migrate to metadata2

This commit is contained in:
Lightczx
2023-06-05 11:17:47 +08:00
parent 5b35167908
commit 2d9165599b
164 changed files with 1476 additions and 834 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
namespace Snap.Hutao.SourceGeneration.Identity;
@@ -32,8 +33,6 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
if (identities.Any())
{
GenerateIdentityConverter(context);
foreach (IdentityStructMetadata identityStruct in identities)
{
GenerateIdentityStruct(context, identityStruct);
@@ -41,137 +40,157 @@ internal sealed class IdentityGenerator : IIncrementalGenerator
}
}
private static void GenerateIdentityConverter(SourceProductionContext context)
private static void GenerateIdentityStruct(SourceProductionContext context, IdentityStructMetadata metadata)
{
string source = $$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
string name = metadata.Name;
namespace Snap.Hutao.Model.Primitive.Converter;
/// <summary>
/// Id 转换器
/// </summary>
/// <typeparam name="TWrapper">包装类型</typeparam>
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(IdentityGenerator)}}", "1.0.0.0")]
internal unsafe sealed class IdentityConverter<TWrapper> : JsonConverter<TWrapper>
where TWrapper : unmanaged
{
/// <inheritdoc/>
public override TWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
{
int value = reader.GetInt32();
return *(TWrapper*)&value;
}
throw new JsonException();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options)
{
writer.WriteNumberValue(*(int*)&value);
}
}
""";
context.AddSource("IdentityConverter.g.cs", source);
}
private static void GenerateIdentityStruct(SourceProductionContext context, IdentityStructMetadata identityStruct)
{
string name = identityStruct.Name;
string type = identityStruct.Type;
string source = $$"""
StringBuilder sourceBuilder = new StringBuilder().AppendLine($$"""
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive.Converter;
using System.Numerics;
namespace Snap.Hutao.Model.Primitive;
/// <summary>
/// {{identityStruct.Documentation}}
/// {{metadata.Documentation}}
/// </summary>
[JsonConverter(typeof(IdentityConverter<{{name}}>))]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(IdentityGenerator)}}","1.0.0.0")]
internal readonly struct {{name}} : IEquatable<{{name}}>
internal readonly partial struct {{name}}
{
/// <summary>
/// 值
/// </summary>
public readonly {{type}} Value;
public readonly uint Value;
/// <summary>
/// Initializes a new instance of the <see cref="{{name}}"/> struct.
/// </summary>
/// <param name="value">value</param>
public {{name}}({{type}} value)
public {{name}}(uint value)
{
Value = value;
}
public static implicit operator {{type}}({{name}} value)
public static implicit operator uint({{name}} value)
{
return value.Value;
}
public static implicit operator {{name}}({{type}} value)
public static implicit operator {{name}}(uint value)
{
return new(value);
}
public static bool operator ==({{name}} left, {{name}} right)
{
return left.Value == right.Value;
}
public static bool operator !=({{name}} left, {{name}} right)
{
return !(left == right);
}
/// <inheritdoc/>
public bool Equals({{name}} other)
{
return Value == other.Value;
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is {{name}} other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Value.GetHashCode();
}
}
""";
""");
context.AddSource($"{name}.g.cs", source);
if (metadata.Equatable)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IEquatable<{{name}}>
{
/// <inheritdoc/>
public override bool Equals(object obj)
{
return obj is {{name}} other && Equals(other);
}
/// <inheritdoc/>
public bool Equals({{name}} other)
{
return Value == other.Value;
}
}
""");
}
if (metadata.EqualityOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IEqualityOperators<{{name}}, {{name}}, bool>, IEqualityOperators<{{name}}, uint, bool>
{
public static bool operator ==({{name}} left, {{name}} right)
{
return left.Value == right.Value;
}
public static bool operator ==({{name}} left, uint right)
{
return left.Value == right;
}
public static bool operator !=({{name}} left, {{name}} right)
{
return !(left == right);
}
public static bool operator !=({{name}} left, uint right)
{
return !(left == right);
}
}
""");
}
if (metadata.AdditionOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IAdditionOperators<{{name}}, {{name}}, {{name}}>, IAdditionOperators<{{name}}, uint, {{name}}>
{
public static {{name}} operator +({{name}} left, {{name}} right)
{
return left.Value + right.Value;
}
public static {{name}} operator +({{name}} left, uint right)
{
return left.Value + right;
}
}
""");
}
if (metadata.IncrementOperators)
{
sourceBuilder.AppendLine($$"""
internal readonly partial struct {{name}} : IIncrementOperators<{{name}}>
{
public static unsafe {{name}} operator ++({{name}} value)
{
++*(uint*)&value;
return value;
}
}
""");
}
context.AddSource($"{name}.g.cs", sourceBuilder.ToString());
}
private sealed class IdentityStructMetadata
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 基底类型
/// </summary>
public string Type { get; set; } = default!;
/// <summary>
/// 文档
/// </summary>
public string? Documentation { get; set; }
public bool Equatable { get; set; }
public bool EqualityOperators { get; set; }
public bool AdditionOperators { get; set; }
public bool IncrementOperators { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Snap.Hutao.Test;
@@ -18,6 +19,13 @@ public class JsonSerializeTest
}
""";
private const string SmapleNumberDictionaryJson = """
{
"111" : "12",
"222" : "34"
}
""";
[TestMethod]
public void DelegatePropertyCanSerialize()
{
@@ -26,11 +34,33 @@ public class JsonSerializeTest
}
[TestMethod]
public void EmptyStringCanSerializeAsNumber()
public void EmptyStringCannotSerializeAsNumber()
{
// Throw
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleNumberObjectJson)!;
Assert.AreEqual(sample.A, 0);
bool caught = false;
try
{
// Throw
StringNumberSample sample = JsonSerializer.Deserialize<StringNumberSample>(SmapleNumberObjectJson)!;
Assert.AreEqual(sample.A, 0);
}
catch
{
caught = true;
}
Assert.IsTrue(caught);
}
[TestMethod]
public void NumberStringKeyCanSerializeAsKey()
{
JsonSerializerOptions options = new()
{
NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
Dictionary<int,string> sample = JsonSerializer.Deserialize<Dictionary<int, string>>(SmapleNumberDictionaryJson, options)!;
Assert.AreEqual(sample[111], "12");
}
private class Sample

View File

@@ -14,8 +14,8 @@ internal sealed class UserdataCorruptedException : Exception
/// </summary>
/// <param name="message">消息</param>
/// <param name="innerException">内部错误</param>
public UserdataCorruptedException(string message, Exception innerException)
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, $"{message}\n{innerException.Message}"), innerException)
public UserdataCorruptedException(string message, Exception? innerException)
: base(string.Format(SH.CoreExceptionServiceUserdataCorruptedMessage, $"{message}\n{innerException?.Message}"), innerException)
{
}
}

View File

@@ -2,39 +2,38 @@
// Licensed under the MIT license.
using System.IO;
using System.Security.Cryptography;
namespace Snap.Hutao.Core.IO;
namespace Snap.Hutao.Core.IO.Hashing;
/// <summary>
/// 摘要
/// </summary>
[HighQuality]
internal static class Digest
internal static class MD5
{
/// <summary>
/// 异步获取文件 Md5 摘要
/// 异步获取文件 MD5 摘要
/// </summary>
/// <param name="filePath">文件路径</param>
/// <param name="token">取消令牌</param>
/// <returns>文件 Md5 摘要</returns>
public static async Task<string> GetFileMD5Async(string filePath, CancellationToken token = default)
public static async Task<string> HashFileAsync(string filePath, CancellationToken token = default)
{
using (FileStream stream = File.OpenRead(filePath))
{
return await GetStreamMD5Async(stream, token).ConfigureAwait(false);
return await HashAsync(stream, token).ConfigureAwait(false);
}
}
/// <summary>
/// 获取流的 Md5 摘要
/// 获取流的 MD5 摘要
/// </summary>
/// <param name="stream">流</param>
/// <param name="token">取消令牌</param>
/// <returns>流 Md5 摘要</returns>
public static async Task<string> GetStreamMD5Async(Stream stream, CancellationToken token = default)
public static async Task<string> HashAsync(Stream stream, CancellationToken token = default)
{
using (MD5 md5 = MD5.Create())
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
{
byte[] bytes = await md5.ComputeHashAsync(stream, token).ConfigureAwait(false);
return System.Convert.ToHexString(bytes);

View File

@@ -0,0 +1,41 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.IO;
using System.IO.Hashing;
namespace Snap.Hutao.Core.IO.Hashing;
/// <summary>
/// XXH64 摘要
/// </summary>
internal static class XXH64
{
/// <summary>
/// 获取流的 XXH64 摘要
/// </summary>
/// <param name="stream">流</param>
/// <param name="token">取消令牌</param>
/// <returns>摘要</returns>
public static async Task<string> HashAsync(Stream stream, CancellationToken token = default)
{
XxHash64 xxHash64 = new();
await xxHash64.AppendAsync(stream, token).ConfigureAwait(false);
byte[] bytes = xxHash64.GetHashAndReset();
return System.Convert.ToHexString(bytes);
}
/// <summary>
/// 获取文件的 XXH64 摘要
/// </summary>
/// <param name="path">路径</param>
/// <param name="token">取消令牌</param>
/// <returns>摘要</returns>
public static async Task<string> HashFileAsync(string path, CancellationToken token = default)
{
using (FileStream stream = File.OpenRead(path))
{
return await HashAsync(stream, token).ConfigureAwait(false);
}
}
}

View File

@@ -20,6 +20,7 @@ internal static class JsonOptions
ReadCommentHandling = JsonCommentHandling.Skip,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
NumberHandling = JsonNumberHandling.AllowReadingFromString,
PropertyNameCaseInsensitive = true,
WriteIndented = true,
TypeInfoResolver = new DefaultJsonTypeInfoResolver()

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -15,10 +16,12 @@ internal static partial class EnumerableExtension
/// 增加计数
/// </summary>
/// <typeparam name="TKey">键类型</typeparam>
/// <typeparam name="TValue">值类型</typeparam>
/// <param name="dict">字典</param>
/// <param name="key">键</param>
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key)
public static void IncreaseOne<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key)
where TKey : notnull
where TValue : struct, IIncrementOperators<TValue>
{
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
}
@@ -27,14 +30,16 @@ internal static partial class EnumerableExtension
/// 增加计数
/// </summary>
/// <typeparam name="TKey">键类型</typeparam>
/// <typeparam name="TValue">值类型</typeparam>
/// <param name="dict">字典</param>
/// <param name="key">键</param>
/// <param name="value">增加的值</param>
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
public static void IncreaseValue<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue value)
where TKey : notnull
where TValue : struct, IAdditionOperators<TValue, TValue, TValue>
{
// ref the value, so that we can manipulate it outside the dict.
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
ref TValue current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
current += value;
}
@@ -42,13 +47,15 @@ internal static partial class EnumerableExtension
/// 增加计数
/// </summary>
/// <typeparam name="TKey">键类型</typeparam>
/// <typeparam name="TValue">值类型</typeparam>
/// <param name="dict">字典</param>
/// <param name="key">键</param>
/// <returns>是否存在键值</returns>
public static bool TryIncrease<TKey>(this Dictionary<TKey, int> dict, TKey key)
public static bool TryIncreaseOne<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key)
where TKey : notnull
where TValue : struct, IIncrementOperators<TValue>
{
ref int value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
ref TValue value = ref CollectionsMarshal.GetValueRefOrNullRef(dict, key);
if (!Unsafe.IsNullRef(ref value))
{
++value;

View File

@@ -17,9 +17,9 @@ internal static class NumberExtension
/// <param name="x">给定的整数</param>
/// <returns>位数</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Place(in this int x)
public static uint Place(in this uint x)
{
// Benchmarked and compared as a most optimized solution
return (int)(MathF.Log10(x) + 1);
return (uint)(MathF.Log10(x) + 1);
}
}

View File

@@ -1,82 +1,166 @@
[
{
"Name": "AchievementGoalId",
"Type": "int",
"Documentation": "1-2位 成就分类Id"
"Documentation": "成就分类 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "AchievementId",
"Type": "int",
"Documentation": "5位 成就Id"
"Documentation": "成就 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "AvatarId",
"Type": "int",
"Documentation": "8位 角色Id"
"Documentation": "角色 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "CostumeId",
"Type": "int",
"Documentation": "6位 角色装扮Id"
"Documentation": "角色装扮 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "EquipAffixId",
"Type": "int",
"Documentation": "6位 装备属性Id"
"Documentation": "装备属性 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ExtendedEquipAffixId",
"Type": "int",
"Documentation": "7位 装备属性Id"
"Documentation": "装备属性 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "FetterLevel",
"Documentation": "好感等级 1 - 10",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "Level",
"Documentation": "等级 1 - 90",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "MaterialId",
"Type": "int",
"Documentation": "3-6位 材料Id"
"Documentation": "材料 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "MonsterDescribeId",
"Documentation": "怪物描述 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "MonsterId",
"Type": "int",
"Documentation": "8位 怪物Id"
"Documentation": "怪物 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "PromoteId",
"Type": "int",
"Documentation": "1-5位 角色突破提升Id"
"Documentation": "角色突破提升 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquaryAffixId",
"Type": "int",
"Documentation": "6位 圣遗物副词条Id"
"Name": "PromoteLevel",
"Documentation": "突破等级 0 - 6",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquaryLevel",
"Documentation": "圣遗物等级 1 - 21",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquarySubAffixId",
"Documentation": "圣遗物副词条 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquaryId",
"Type": "int",
"Documentation": "5位 圣遗物Id"
"Documentation": "圣遗物 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquaryMainAffixId",
"Type": "int",
"Documentation": "5位 圣遗物主属性Id"
"Documentation": "圣遗物主属性 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "ReliquarySetId",
"Type": "int",
"Documentation": "5位 圣遗物套装Id"
"Documentation": "圣遗物套装 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "SkillGroupId",
"Type": "int",
"Documentation": "3-4位 技能组Id"
"Documentation": "技能组 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "SkillId",
"Type": "int",
"Documentation": "5-6位 技能Id"
"Documentation": "技能 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "SkillLevel",
"Documentation": "技能等级 1 - 15",
"Equatable": true,
"EqualityOperators": true,
"AdditionOperators": true,
"IncrementOperators": true
},
{
"Name": "TowerLevelId",
"Documentation": "深渊间 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "TowerLevelGroupId",
"Documentation": "深渊间分组 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "TowerFloorId",
"Documentation": "深渊层 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "TowerScheduleId",
"Documentation": "深渊计划 Id",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "WeaponAffixLevel",
"Documentation": "武器精炼等级 0 - 4",
"Equatable": true,
"EqualityOperators": true
},
{
"Name": "WeaponId",
"Type": "int",
"Documentation": "5位 武器Id"
"Documentation": "武器 Id",
"Equatable": true,
"EqualityOperators": true
}
]

View File

@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("achievements");
b.ToTable("achievements", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("achievement_archives");
b.ToTable("achievement_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
@@ -79,7 +79,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("avatar_infos");
b.ToTable("avatar_infos", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
@@ -101,7 +101,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
b.ToTable("cultivate_entries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
@@ -126,7 +126,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
b.ToTable("cultivate_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
@@ -147,7 +147,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
b.ToTable("cultivate_projects", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
@@ -200,7 +200,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("UserId");
b.ToTable("daily_notes");
b.ToTable("daily_notes", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
@@ -218,7 +218,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("gacha_archives");
b.ToTable("gacha_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
@@ -249,7 +249,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
b.ToTable("gacha_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
@@ -274,7 +274,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("game_accounts");
b.ToTable("game_accounts", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
@@ -296,7 +296,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
b.ToTable("inventory_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
@@ -325,7 +325,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
b.ToTable("inventory_reliquaries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
@@ -350,7 +350,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
b.ToTable("inventory_weapons", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
@@ -366,7 +366,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("object_cache");
b.ToTable("object_cache", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
@@ -379,7 +379,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("settings");
b.ToTable("settings", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
@@ -401,7 +401,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("spiral_abysses");
b.ToTable("spiral_abysses", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
@@ -435,7 +435,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("users");
b.ToTable("users", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>

View File

@@ -15,8 +15,8 @@ namespace Snap.Hutao.Model.Calculable;
[HighQuality]
internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
{
private int levelCurrent;
private int levelTarget;
private uint levelCurrent;
private uint levelTarget;
/// <summary>
/// 构造一个新的可计算角色
@@ -58,10 +58,10 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
public AvatarId AvatarId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
public uint LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
public uint LevelMax { get; }
/// <inheritdoc/>
public List<ICalculableSkill> Skills { get; }
@@ -73,11 +73,11 @@ internal sealed class CalculableAvatar : ObservableObject, ICalculableAvatar
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
public QualityType Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Model.Calculable;
@@ -15,8 +16,8 @@ namespace Snap.Hutao.Model.Calculable;
[HighQuality]
internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
{
private int levelCurrent;
private int levelTarget;
private uint levelCurrent;
private uint levelTarget;
/// <summary>
/// 构造一个新的可计算的技能
@@ -29,7 +30,7 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
LevelMax = 10; // hard coded 10 here
Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = ItemQuality.QUALITY_NONE;
Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
@@ -46,20 +47,20 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
LevelMax = 10; // hard coded 10 here
Name = skill.Name;
Icon = skill.Icon;
Quality = ItemQuality.QUALITY_NONE;
Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
public int GruopId { get; }
public SkillGroupId GruopId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
public uint LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
public uint LevelMax { get; }
/// <inheritdoc/>
public string Name { get; }
@@ -68,11 +69,11 @@ internal sealed class CalculableSkill : ObservableObject, ICalculableSkill
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
public QualityType Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -15,8 +15,8 @@ namespace Snap.Hutao.Model.Calculable;
[HighQuality]
internal class CalculableWeapon : ObservableObject, ICalculableWeapon
{
private int levelCurrent;
private int levelTarget;
private uint levelCurrent;
private uint levelTarget;
/// <summary>
/// 构造一个新的可计算武器
@@ -56,10 +56,10 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
public WeaponId WeaponId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
public uint LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
public uint LevelMax { get; }
/// <inheritdoc/>
public string Name { get; }
@@ -68,11 +68,11 @@ internal class CalculableWeapon : ObservableObject, ICalculableWeapon
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
public QualityType Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -14,15 +14,15 @@ internal interface ICalculable : INameIcon
/// <summary>
/// 星级
/// </summary>
ItemQuality Quality { get; }
QualityType Quality { get; }
/// <summary>
/// 当前等级
/// </summary>
int LevelCurrent { get; set; }
uint LevelCurrent { get; set; }
/// <summary>
/// 目标等级
/// </summary>
int LevelTarget { get; set; }
uint LevelTarget { get; set; }
}

View File

@@ -19,12 +19,12 @@ internal interface ICalculableAvatar : ICalculable
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
uint LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
uint LevelMax { get; }
/// <summary>
/// 技能组

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Calculable;
/// <summary>
@@ -12,15 +14,15 @@ internal interface ICalculableSkill : ICalculable
/// <summary>
/// 技能组Id
/// </summary>
int GruopId { get; }
SkillGroupId GruopId { get; }
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
uint LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
uint LevelMax { get; }
}

View File

@@ -19,10 +19,10 @@ internal interface ICalculableWeapon : ICalculable
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
uint LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
uint LevelMax { get; }
}

View File

@@ -37,12 +37,12 @@ internal sealed class Achievement : IEquatable<Achievement>
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
public uint Id { get; set; }
/// <summary>
/// 当前进度
/// </summary>
public int Current { get; set; }
public uint Current { get; set; }
/// <summary>
/// 完成时间

View File

@@ -40,7 +40,7 @@ internal sealed class CultivateEntry
/// <summary>
/// 角色/武器/家具 Id
/// </summary>
public int Id { get; set; }
public uint Id { get; set; }
/// <summary>
/// 创建一个新的养成入口点
@@ -49,7 +49,7 @@ internal sealed class CultivateEntry
/// <param name="type">类型</param>
/// <param name="id">主Id</param>
/// <returns>养成入口点</returns>
public static CultivateEntry Create(in Guid projectId, CultivateType type, int id)
public static CultivateEntry Create(in Guid projectId, CultivateType type, uint id)
{
return new()
{

View File

@@ -49,7 +49,7 @@ internal sealed class GachaItem
/// <summary>
/// 物品Id
/// </summary>
public int ItemId { get; set; }
public uint ItemId { get; set; }
/// <summary>
/// 获取时间
@@ -66,12 +66,12 @@ internal sealed class GachaItem
/// </summary>
/// <param name="itemId">物品Id</param>
/// <returns>物品类型字符串</returns>
public static string GetItemTypeStringByItemId(int itemId)
public static string GetItemTypeStringByItemId(uint itemId)
{
return itemId.Place() switch
{
8 => "角色",
5 => "武器",
8U => "角色",
5U => "武器",
_ => "未知",
};
}
@@ -83,7 +83,7 @@ internal sealed class GachaItem
/// <param name="item">祈愿物品</param>
/// <param name="itemId">物品Id</param>
/// <returns>新的祈愿物品</returns>
public static GachaItem Create(in Guid archiveId, GachaLogItem item, int itemId)
public static GachaItem Create(in Guid archiveId, GachaLogItem item, uint itemId)
{
return new()
{
@@ -103,7 +103,7 @@ internal sealed class GachaItem
/// <param name="item">祈愿物品</param>
/// <param name="itemId">物品Id</param>
/// <returns>新的祈愿物品</returns>
public static GachaItem CreateForMajor2Minor2OrLower(in Guid archiveId, UIGFItem item, int itemId)
public static GachaItem CreateForMajor2Minor2OrLower(in Guid archiveId, UIGFItem item, uint itemId)
{
return new()
{
@@ -129,7 +129,7 @@ internal sealed class GachaItem
ArchiveId = archiveId,
GachaType = item.GachaType,
QueryType = item.UIGFGachaType,
ItemId = int.Parse(item.ItemId),
ItemId = uint.Parse(item.ItemId),
Time = item.Time,
Id = item.Id,
};

View File

@@ -34,7 +34,7 @@ internal sealed class InventoryItem
/// <summary>
/// 物品Id
/// </summary>
public int ItemId { get; set; }
public uint ItemId { get; set; }
/// <summary>
/// 个数 4294967295
@@ -47,7 +47,7 @@ internal sealed class InventoryItem
/// <param name="projectId">项目Id</param>
/// <param name="itemId">物品Id</param>
/// <returns>新的个数为0的物品</returns>
public static InventoryItem Create(in Guid projectId, int itemId)
public static InventoryItem Create(in Guid projectId, uint itemId)
{
return new()
{

View File

@@ -14,7 +14,7 @@ internal sealed class UIAFItem
/// 成就Id
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }
public uint Id { get; set; }
/// <summary>
/// 完成时间
@@ -27,7 +27,7 @@ internal sealed class UIAFItem
/// 对于progress为1的项该属性始终为0
/// </summary>
[JsonPropertyName("current")]
public int Current { get; set; }
public uint Current { get; set; }
/// <summary>
/// 完成状态

View File

@@ -27,7 +27,6 @@ internal sealed class UIGFInfo
/// 导出的时间戳
/// </summary>
[JsonPropertyName("export_timestamp")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long? ExportTimestamp { get; set; }
/// <summary>

View File

@@ -27,7 +27,6 @@ internal sealed class UIIFInfo
/// 导出的时间戳
/// </summary>
[JsonPropertyName("export_timestamp")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long? ExportTimestamp { get; set; }
/// <summary>

View File

@@ -36,37 +36,45 @@ internal enum GrowCurveType
GROW_CURVE_HP_2 = 23,
GROW_CURVE_ATTACK_2 = 24,
GROW_CURVE_HP_ENVIRONMENT = 25,
GROW_CURVE_ATTACK_S5 = 31,
GROW_CURVE_ATTACK_S4 = 32,
GROW_CURVE_ATTACK_S3 = 33,
GROW_CURVE_STRIKE_S5 = 34,
GROW_CURVE_DEFENSE_S5 = 41,
GROW_CURVE_DEFENSE_S4 = 42,
GROW_CURVE_ATTACK_101 = 1101,
GROW_CURVE_ATTACK_102 = 1102,
GROW_CURVE_ATTACK_103 = 1103,
GROW_CURVE_ATTACK_104 = 1104,
GROW_CURVE_ATTACK_105 = 1105,
GROW_CURVE_ATTACK_201 = 1201,
GROW_CURVE_ATTACK_202 = 1202,
GROW_CURVE_ATTACK_203 = 1203,
GROW_CURVE_ATTACK_204 = 1204,
GROW_CURVE_ATTACK_205 = 1205,
GROW_CURVE_ATTACK_301 = 1301,
GROW_CURVE_ATTACK_302 = 1302,
GROW_CURVE_ATTACK_303 = 1303,
GROW_CURVE_ATTACK_304 = 1304,
GROW_CURVE_ATTACK_305 = 1305,
GROW_CURVE_CRITICAL_101 = 2101,
GROW_CURVE_CRITICAL_102 = 2102,
GROW_CURVE_CRITICAL_103 = 2103,
GROW_CURVE_CRITICAL_104 = 2104,
GROW_CURVE_CRITICAL_105 = 2105,
GROW_CURVE_CRITICAL_201 = 2201,
GROW_CURVE_CRITICAL_202 = 2202,
GROW_CURVE_CRITICAL_203 = 2203,
GROW_CURVE_CRITICAL_204 = 2204,
GROW_CURVE_CRITICAL_205 = 2205,
GROW_CURVE_CRITICAL_301 = 2301,
GROW_CURVE_CRITICAL_302 = 2302,
GROW_CURVE_CRITICAL_303 = 2303,

View File

@@ -24,7 +24,7 @@ internal static class IntrinsicImmutables
/// <summary>
/// 物品类型
/// </summary>
public static readonly ImmutableHashSet<string> ItemQualities = Enum.GetValues<ItemQuality>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToImmutableHashSet();
public static readonly ImmutableHashSet<string> ItemQualities = Enum.GetValues<QualityType>().Select(e => e.GetLocalizedDescriptionOrDefault()).OfType<string>().ToImmutableHashSet();
/// <summary>
/// 身材类型

View File

@@ -12,6 +12,7 @@ internal enum MaterialType
MATERIAL_NONE = 0,
MATERIAL_FOOD = 1,
MATERIAL_QUEST = 2,
MATERIAL_EXCHANGE = 4,
MATERIAL_CONSUME = 5,
MATERIAL_EXP_FRUIT = 6,
@@ -34,6 +35,7 @@ internal enum MaterialType
MATERIAL_FAKE_ABSORBATE = 23,
MATERIAL_CONSUME_BATCH_USE = 24,
MATERIAL_WOOD = 25,
MATERIAL_FURNITURE_FORMULA = 27,
MATERIAL_CHANNELLER_SLAB_BUFF = 28,
MATERIAL_FURNITURE_SUITE_FORMULA = 29,
@@ -41,7 +43,7 @@ internal enum MaterialType
MATERIAL_HOME_SEED = 31,
MATERIAL_FISH_BAIT = 32,
MATERIAL_FISH_ROD = 33,
MATERIAL_SUMO_BUFF = 34, // never appear
MATERIAL_SUMO_BUFF = 34, // sumo 活动道具,never appear
MATERIAL_FIREWORKS = 35,
MATERIAL_BGM = 36,
MATERIAL_SPICE_FOOD = 37,

View File

@@ -8,7 +8,7 @@ namespace Snap.Hutao.Model.Intrinsic;
/// </summary>
[HighQuality]
[Localization]
internal enum ItemQuality
internal enum QualityType
{
/// <summary>
/// 无

View File

@@ -29,5 +29,5 @@ internal class Item : INameIcon
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
public QualityType Quality { get; set; }
}

View File

@@ -19,5 +19,5 @@ internal interface INameQuality
/// <summary>
/// 星级
/// </summary>
ItemQuality Quality { get; }
QualityType Quality { get; }
}

View File

@@ -15,7 +15,7 @@ internal interface ISummaryItemSource
/// <summary>
/// 星级
/// </summary>
ItemQuality Quality { get; }
QualityType Quality { get; }
/// <summary>
/// 转换到简述统计物品

View File

@@ -24,7 +24,7 @@ internal sealed class Achievement
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; set; }
public uint Order { get; set; }
/// <summary>
/// 标题
@@ -44,7 +44,7 @@ internal sealed class Achievement
/// <summary>
/// 总进度
/// </summary>
public int Progress { get; set; }
public uint Progress { get; set; }
/// <summary>
/// 图标

View File

@@ -19,7 +19,7 @@ internal sealed class AchievementGoal
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; set; }
public uint Order { get; set; }
/// <summary>
/// 名称

View File

@@ -18,5 +18,5 @@ internal sealed class Reward
/// <summary>
/// 数量
/// </summary>
public int Count { get; set; }
public uint Count { get; set; }
}

View File

@@ -18,6 +18,7 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IName
{
/// <summary>
/// [非元数据] 搭配数据
/// TODO:Add View suffix.
/// </summary>
[JsonIgnore]
public AvatarCollocationView? Collocation { get; set; }
@@ -37,7 +38,7 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IName
/// 最大等级
/// </summary>
[SuppressMessage("", "CA1822")]
public int MaxLevel { get => 90; }
public uint MaxLevel { get => 90U; }
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()

View File

@@ -26,12 +26,11 @@ internal partial class Avatar
/// <summary>
/// 排序号
/// </summary>
public int Sort { get; set; }
public uint Sort { get; set; }
/// <summary>
/// 体型
/// </summary>
[JsonEnum(JsonSerializeType.String)]
public BodyType Body { get; set; } = default!;
/// <summary>
@@ -62,7 +61,7 @@ internal partial class Avatar
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
public QualityType Quality { get; set; }
/// <summary>
/// 武器类型
@@ -72,12 +71,12 @@ internal partial class Avatar
/// <summary>
/// 基础数值
/// </summary>
public BaseValue BaseValue { get; set; } = default!;
public AvatarBaseValue BaseValue { get; set; } = default!;
/// <summary>
/// 生长曲线
/// </summary>
public Dictionary<FightProperty, GrowCurveType> GrowCurves { get; set; } = default!;
public List<TypeValue<FightProperty, GrowCurveType>> GrowCurves { get; set; } = default!;
/// <summary>
/// 技能

View File

@@ -0,0 +1,21 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Wiki;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色基础数值
/// </summary>
internal sealed class AvatarBaseValue : BaseValue
{
public PropertyCurveValue GetPropertyCurveValue(FightProperty fightProperty)
{
// TODO: impl
return null;
}
}

View File

@@ -34,7 +34,7 @@ internal sealed class Costume
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
public string FrontIcon { get; set; } = default!;
/// <summary>
/// 侧面图标

View File

@@ -25,7 +25,6 @@ internal sealed class FetterInfo
/// <summary>
/// 地区
/// </summary>
[JsonEnum(JsonSerializeType.String)]
public AssociationType Association { get; set; } = default!;
/// <summary>
@@ -36,12 +35,12 @@ internal sealed class FetterInfo
/// <summary>
/// 生月
/// </summary>
public int BirthMonth { get; set; }
public uint BirthMonth { get; set; }
/// <summary>
/// 生日
/// </summary>
public int BirthDay { get; set; }
public uint BirthDay { get; set; }
/// <summary>
/// 格式化的生日日期

View File

@@ -12,6 +12,7 @@ namespace Snap.Hutao.Model.Metadata;
[SuppressMessage("", "SA1600")]
internal static class AvatarIds
{
// 此处的变量名称以 UI_AvatarIcon 为准
public static readonly AvatarId Ayaka = 10000002;
public static readonly AvatarId Qin = 10000003;
@@ -106,7 +107,7 @@ internal static class AvatarIds
Name = "旅行者",
Icon = "UI_AvatarIcon_PlayerBoy",
SideIcon = "UI_AvatarIcon_Side_PlayerBoy",
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
Quality = Intrinsic.QualityType.QUALITY_ORANGE,
},
[PlayerGirl] = new()
@@ -114,7 +115,7 @@ internal static class AvatarIds
Name = "旅行者",
Icon = "UI_AvatarIcon_PlayerGirl",
SideIcon = "UI_AvatarIcon_Side_PlayerGirl",
Quality = Intrinsic.ItemQuality.QUALITY_ORANGE,
Quality = Intrinsic.QualityType.QUALITY_ORANGE,
},
};
}

View File

@@ -30,7 +30,7 @@ internal class BaseValue
/// </summary>
/// <param name="fightProperty">战斗属性</param>
/// <returns>值</returns>
public float GetValue(FightProperty fightProperty)
public virtual float GetValue(FightProperty fightProperty)
{
return fightProperty switch
{

View File

@@ -20,7 +20,7 @@ internal sealed partial class DescriptionsParametersDescriptor : ValueConverter<
/// <param name="from">源</param>
/// <param name="level">等级</param>
/// <returns>特定等级的解释</returns>
public static LevelParameters<string, ParameterDescription> Convert(DescriptionsParameters from, int level)
public static LevelParameters<string, ParameterDescription> Convert(DescriptionsParameters from, uint level)
{
LevelParameters<int, float> param = from.Parameters.Single(param => param.Level == level);
return new LevelParameters<string, ParameterDescription>($"Lv.{param.Level}", GetParameterDescription(from.Descriptions, param.Parameters));

View File

@@ -13,18 +13,18 @@ namespace Snap.Hutao.Model.Metadata.Converter;
/// 品质颜色转换器
/// </summary>
[HighQuality]
internal sealed class QualityColorConverter : ValueConverter<ItemQuality, Color>
internal sealed class QualityColorConverter : ValueConverter<QualityType, Color>
{
/// <inheritdoc/>
public override Color Convert(ItemQuality from)
public override Color Convert(QualityType from)
{
return from switch
{
ItemQuality.QUALITY_WHITE => StructMarshal.Color(0xFF72778B),
ItemQuality.QUALITY_GREEN => StructMarshal.Color(0xFF2A8F72),
ItemQuality.QUALITY_BLUE => StructMarshal.Color(0xFF5180CB),
ItemQuality.QUALITY_PURPLE => StructMarshal.Color(0xFFA156E0),
ItemQuality.QUALITY_ORANGE or ItemQuality.QUALITY_ORANGE_SP => StructMarshal.Color(0xFFBC6932),
QualityType.QUALITY_WHITE => StructMarshal.Color(0xFF72778B),
QualityType.QUALITY_GREEN => StructMarshal.Color(0xFF2A8F72),
QualityType.QUALITY_BLUE => StructMarshal.Color(0xFF5180CB),
QualityType.QUALITY_PURPLE => StructMarshal.Color(0xFFA156E0),
QualityType.QUALITY_ORANGE or QualityType.QUALITY_ORANGE_SP => StructMarshal.Color(0xFFBC6932),
_ => Colors.Transparent,
};
}

View File

@@ -10,13 +10,13 @@ namespace Snap.Hutao.Model.Metadata.Converter;
/// 物品等级转换器
/// </summary>
[HighQuality]
internal sealed class QualityConverter : ValueConverter<ItemQuality, Uri>
internal sealed class QualityConverter : ValueConverter<QualityType, Uri>
{
/// <inheritdoc/>
public override Uri Convert(ItemQuality from)
public override Uri Convert(QualityType from)
{
string name = Enum.GetName(from) ?? from.ToString();
if (name == nameof(ItemQuality.QUALITY_ORANGE_SP))
if (name == nameof(QualityType.QUALITY_ORANGE_SP))
{
name = "QUALITY_RED";
}

View File

@@ -21,11 +21,21 @@ internal sealed class GachaEvent
/// </summary>
public string Version { get; set; } = default!;
/// <summary>
/// 顺序
/// </summary>
public uint Order { get; set; }
/// <summary>
/// 卡池图
/// </summary>
public Uri Banner { get; set; } = default!;
/// <summary>
/// 卡池图2
/// </summary>
public Uri Banner2 { get; set; } = default!;
/// <summary>
/// 开始时间
/// </summary>
@@ -44,10 +54,10 @@ internal sealed class GachaEvent
/// <summary>
/// 五星列表
/// </summary>
public List<int> UpOrangeList { get; set; } = default!;
public List<uint> UpOrangeList { get; set; } = default!;
/// <summary>
/// 四星列表
/// </summary>
public List<int> UpPurpleList { get; set; } = default!;
public List<uint> UpPurpleList { get; set; } = default!;
}

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata;
@@ -11,13 +12,23 @@ namespace Snap.Hutao.Model.Metadata;
[HighQuality]
internal sealed class GrowCurve
{
private Dictionary<GrowCurveType, float>? curveMap;
/// <summary>
/// 等级
/// </summary>
public int Level { get; set; }
public Level Level { get; set; }
/// <summary>
/// 曲线 值相乘
/// </summary>
public Dictionary<GrowCurveType, float> Curves { get; set; } = default!;
public List<TypeValue<GrowCurveType, float>> Curves { get; set; } = default!;
/// <summary>
/// 曲线映射
/// </summary>
public Dictionary<GrowCurveType, float> CurveMap
{
get => curveMap ??= Curves.ToDictionary(v => v.Type, v => v.Value);
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Model.Metadata.Item;
/// 展示物品
/// </summary>
[HighQuality]
internal class Display
internal class DisplayItem
{
/// <summary>
/// 物品Id
@@ -20,7 +20,7 @@ internal class Display
/// <summary>
/// 等级
/// </summary>
public ItemQuality RankLevel { get; set; }
public QualityType RankLevel { get; set; }
/// <summary>
/// 物品类型

View File

@@ -11,44 +11,8 @@ namespace Snap.Hutao.Model.Metadata.Item;
/// <summary>
/// 材料
/// </summary>
internal sealed class Material : Display
internal sealed class Material : DisplayItem
{
private static readonly ImmutableHashSet<MaterialId> MondayThursdayItems = new HashSet<MaterialId>
{
104301, 104302, 104303, // 「自由」
104310, 104311, 104312, // 「繁荣」
104320, 104321, 104322, // 「浮世」
104329, 104330, 104331, // 「诤言」
114001, 114002, 114003, 114004, // 高塔孤王
114013, 114014, 114015, 114016, // 孤云寒林
114025, 114026, 114027, 114028, // 远海夷地
114037, 114038, 114039, 114040, // 谧林涓露
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> TuesdayFridayItems = new HashSet<MaterialId>
{
104304, 104305, 104306, // 「抗争」
104313, 104314, 104315, // 「勤劳」
104323, 104324, 104325, // 「风雅」
104332, 104333, 104334, // 「巧思」
114005, 114006, 114007, 114008, // 凛风奔狼
114017, 114018, 114019, 114020, // 雾海云间
114029, 114030, 114031, 114032, // 鸣神御灵
114041, 114042, 114043, 114044, // 绿洲花园
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> WednesdaySaturdayItems = new HashSet<MaterialId>
{
104307, 104308, 104309, // 「诗文」
104316, 104317, 104318, // 「黄金」
104326, 104327, 104328, // 「天光」
104335, 104336, 104337, // 「笃行」
114009, 114010, 114011, 114012, // 狮牙斗士
114021, 114022, 114023, 114024, // 漆黑陨铁
114033, 114034, 114035, 114036, // 今昔剧画
114045, 114046, 114047, 114048, // 谧林涓露
}.ToImmutableHashSet();
/// <summary>
/// 材料类型
/// </summary>
@@ -66,23 +30,24 @@ internal sealed class Material : Display
public bool IsInventoryItem()
{
// 原质
if (Id == 112001)
if (Id == 112001U)
{
return false;
}
// 摩拉
if (Id == 202)
if (Id == 202U)
{
return true;
}
// TODO: support non-CHS
if (TypeDescription.EndsWith("区域特产"))
{
return true;
}
// TODO: Currently only support CN
// TODO: support non-CHS
return TypeDescription switch
{
"角色与武器培养素材" => true, // 怪物掉落
@@ -106,9 +71,9 @@ internal sealed class Material : Display
{
return DateTimeOffset.UtcNow.AddHours(4).DayOfWeek switch
{
DayOfWeek.Monday or DayOfWeek.Thursday => MondayThursdayItems.Contains(Id),
DayOfWeek.Tuesday or DayOfWeek.Friday => TuesdayFridayItems.Contains(Id),
DayOfWeek.Wednesday or DayOfWeek.Saturday => WednesdaySaturdayItems.Contains(Id),
DayOfWeek.Monday or DayOfWeek.Thursday => Materials.MondayThursdayItems.Contains(Id),
DayOfWeek.Tuesday or DayOfWeek.Friday => Materials.TuesdayFridayItems.Contains(Id),
DayOfWeek.Wednesday or DayOfWeek.Saturday => Materials.WednesdaySaturdayItems.Contains(Id),
_ => false,
};
}
@@ -119,17 +84,17 @@ internal sealed class Material : Display
/// <returns>DaysOfWeek</returns>
public DaysOfWeek GetDaysOfWeek()
{
if (MondayThursdayItems.Contains(Id))
if (Materials.MondayThursdayItems.Contains(Id))
{
return DaysOfWeek.MondayAndThursday;
}
if (TuesdayFridayItems.Contains(Id))
if (Materials.TuesdayFridayItems.Contains(Id))
{
return DaysOfWeek.TuesdayAndFriday;
}
if (WednesdaySaturdayItems.Contains(Id))
if (Materials.WednesdaySaturdayItems.Contains(Id))
{
return DaysOfWeek.WednesdayAndSaturday;
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.Cultivation;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Metadata.Item;
/// <summary>
/// 材料表
/// </summary>
internal static class Materials
{
private static readonly ImmutableHashSet<MaterialId> MondayThursdayItemsInner = new HashSet<MaterialId>
{
104301, 104302, 104303, // 「自由」
104310, 104311, 104312, // 「繁荣」
104320, 104321, 104322, // 「浮世」
104329, 104330, 104331, // 「诤言」
114001, 114002, 114003, 114004, // 高塔孤王
114013, 114014, 114015, 114016, // 孤云寒林
114025, 114026, 114027, 114028, // 远海夷地
114037, 114038, 114039, 114040, // 谧林涓露
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> TuesdayFridayItemsInner = new HashSet<MaterialId>
{
104304, 104305, 104306, // 「抗争」
104313, 104314, 104315, // 「勤劳」
104323, 104324, 104325, // 「风雅」
104332, 104333, 104334, // 「巧思」
114005, 114006, 114007, 114008, // 凛风奔狼
114017, 114018, 114019, 114020, // 雾海云间
114029, 114030, 114031, 114032, // 鸣神御灵
114041, 114042, 114043, 114044, // 绿洲花园
}.ToImmutableHashSet();
private static readonly ImmutableHashSet<MaterialId> WednesdaySaturdayItemsInner = new HashSet<MaterialId>
{
104307, 104308, 104309, // 「诗文」
104316, 104317, 104318, // 「黄金」
104326, 104327, 104328, // 「天光」
104335, 104336, 104337, // 「笃行」
114009, 114010, 114011, 114012, // 狮牙斗士
114021, 114022, 114023, 114024, // 漆黑陨铁
114033, 114034, 114035, 114036, // 今昔剧画
114045, 114046, 114047, 114048, // 谧林涓露
}.ToImmutableHashSet();
/// <summary>
/// 周一/周四
/// </summary>
public static ImmutableHashSet<MaterialId> MondayThursdayItems { get => MondayThursdayItemsInner; }
/// <summary>
/// 周二/周五
/// </summary>
public static ImmutableHashSet<MaterialId> TuesdayFridayItems { get => TuesdayFridayItemsInner; }
/// <summary>
/// 周三/周六
/// </summary>
public static ImmutableHashSet<MaterialId> WednesdaySaturdayItems { get => WednesdaySaturdayItemsInner; }
}

View File

@@ -17,6 +17,11 @@ internal sealed class Monster
/// </summary>
public MonsterId Id { get; set; }
/// <summary>
/// 描述 Id
/// </summary>
public MonsterDescribeId DescribeId { get; set; }
/// <summary>
/// 内部代号
/// </summary>
@@ -60,10 +65,11 @@ internal sealed class Monster
/// <summary>
/// 生长曲线
/// </summary>
public Dictionary<FightProperty, GrowCurveType> GrowCurves { get; set; } = default!;
public List<TypeValue<FightProperty, GrowCurveType>> GrowCurves { get; set; } = default!;
/// <summary>
/// 养成物品视图
/// </summary>
public List<Display>? DropsView { get; set; }
[JsonIgnore]
public List<DisplayItem>? DropsView { get; set; }
}

View File

@@ -12,6 +12,8 @@ namespace Snap.Hutao.Model.Metadata;
[HighQuality]
internal sealed class Promote
{
private Dictionary<FightProperty, float>? addPropertyMap;
/// <summary>
/// Id
/// </summary>
@@ -20,10 +22,18 @@ internal sealed class Promote
/// <summary>
/// 突破等级
/// </summary>
public int Level { get; set; }
public PromoteLevel Level { get; set; }
/// <summary>
/// 增加的属性
/// </summary>
public Dictionary<FightProperty, float> AddProperties { get; set; } = default!;
public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!;
/// <summary>
/// 属性映射
/// </summary>
public Dictionary<FightProperty, float> AddPropertyMap
{
get => addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value);
}
}

View File

@@ -20,7 +20,7 @@ internal sealed class Reliquary
/// <summary>
/// 允许出现的等级
/// </summary>
public ItemQuality RankLevel { get; set; } = default!;
public QualityType RankLevel { get; set; } = default!;
/// <summary>
/// 套装Id

View File

@@ -0,0 +1,109 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 圣遗物词条权重
/// </summary>
internal sealed class ReliquaryAffixWeight
{
/// <summary>
/// 角色 Id
/// </summary>
public AvatarId AvatarId { get; set; }
/// <summary>
/// 生命值
/// </summary>
public float HpPercent { get; set; }
/// <summary>
/// 攻击力
/// </summary>
public float AttackPercent { get; set; }
/// <summary>
/// 防御力
/// </summary>
public float DefensePercent { get; set; }
/// <summary>
/// 暴击率
/// </summary>
public float Critical { get; set; }
/// <summary>
/// 暴击伤害
/// </summary>
public float CriticalHurt { get; set; }
/// <summary>
/// 元素精通
/// </summary>
public float ElementMastery { get; set; }
/// <summary>
/// 元素充能效率
/// </summary>
public float ChargeEfficiency { get; set; }
/// <summary>
/// 治疗加成
/// </summary>
public float HealAdd { get; set; }
/// <summary>
/// 物理伤害加成
/// </summary>
public float PhysicalAddHurt { get; set; }
/// <summary>
/// 伤害加成
/// </summary>
public float AddHurt { get; set; }
/// <summary>
/// 元素类型
/// </summary>
public ElementType ElementType { get; set; }
/// <summary>
/// 获取权重
/// </summary>
/// <param name="fightProperty">属性</param>
/// <returns>权重</returns>
public float this[FightProperty fightProperty]
{
get
{
if ((ElementType == ElementType.Fire && fightProperty == FightProperty.FIGHT_PROP_FIRE_ADD_HURT) ||
(ElementType == ElementType.Water && fightProperty == FightProperty.FIGHT_PROP_WATER_ADD_HURT) ||
(ElementType == ElementType.Grass && fightProperty == FightProperty.FIGHT_PROP_GRASS_ADD_HURT) ||
(ElementType == ElementType.Electric && fightProperty == FightProperty.FIGHT_PROP_ELEC_ADD_HURT) ||
(ElementType == ElementType.Ice && fightProperty == FightProperty.FIGHT_PROP_ICE_ADD_HURT) ||
(ElementType == ElementType.Wind && fightProperty == FightProperty.FIGHT_PROP_WIND_ADD_HURT) ||
(ElementType == ElementType.Rock && fightProperty == FightProperty.FIGHT_PROP_ROCK_ADD_HURT))
{
return AddHurt;
}
return fightProperty switch
{
FightProperty.FIGHT_PROP_HP_PERCENT => HpPercent,
FightProperty.FIGHT_PROP_ATTACK_PERCENT => AttackPercent,
FightProperty.FIGHT_PROP_DEFENSE_PERCENT => DefensePercent,
FightProperty.FIGHT_PROP_CRITICAL => Critical,
FightProperty.FIGHT_PROP_CRITICAL_HURT => CriticalHurt,
FightProperty.FIGHT_PROP_ELEMENT_MASTERY => ElementMastery,
FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY => ChargeEfficiency,
FightProperty.FIGHT_PROP_HEALED_ADD => HealAdd,
FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT => PhysicalAddHurt,
_ => 0,
};
}
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Reliquary;
/// <summary>
/// 属性映射
/// </summary>
internal sealed partial class ReliquaryMainAffixLevel
{
private Dictionary<FightProperty, float>? propertyMap;
/// <summary>
/// 属性映射
/// </summary>
public Dictionary<FightProperty, float> PropertyMap
{
get => propertyMap ??= Properties.ToDictionary(t => t.Type, t => t.Value);
}
}

View File

@@ -9,20 +9,20 @@ namespace Snap.Hutao.Model.Metadata.Reliquary;
/// 圣遗物等级
/// </summary>
[HighQuality]
internal sealed class ReliquaryLevel
internal sealed partial class ReliquaryMainAffixLevel
{
/// <summary>
/// 品质
/// </summary>
public ItemQuality Quality { get; set; }
public QualityType Rank { get; set; }
/// <summary>
/// 等级 1-21
/// </summary>
public int Level { get; set; }
public uint Level { get; set; }
/// <summary>
/// 属性
/// </summary>
public Dictionary<FightProperty, float> Properties { get; set; } = default!;
public List<TypeValue<FightProperty, float>> Properties { get; set; } = default!;
}

View File

@@ -14,7 +14,7 @@ internal sealed class ReliquarySet
/// <summary>
/// 套装Id
/// </summary>
public int SetId { get; set; }
public ReliquarySetId SetId { get; set; }
/// <summary>
/// 装备被动Id

View File

@@ -9,12 +9,12 @@ namespace Snap.Hutao.Model.Metadata.Reliquary;
/// 圣遗物突破属性
/// </summary>
[HighQuality]
internal sealed class ReliquaryAffix : ReliquaryMainAffix
internal sealed class ReliquarySubAffix : ReliquaryMainAffix
{
/// <summary>
/// Id
/// </summary>
public new ReliquaryAffixId Id { get; set; }
public new ReliquarySubAffixId Id { get; set; }
/// <summary>
/// 值

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Tower;
/// <summary>
/// 深渊 层
/// </summary>
internal sealed class TowerFloor
{
/// <summary>
/// Id
/// </summary>
public TowerFloorId Id { get; set; }
/// <summary>
/// 编号 [1-12]
/// </summary>
public uint Index { get; set; }
/// <summary>
/// 深渊间分组编号
/// </summary>
public TowerLevelGroupId LevelGroupId { get; set; }
/// <summary>
/// 背景图片
/// </summary>
public string Background { get; set; } = default!;
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Tower;
/// <summary>
/// 深渊 间
/// </summary>
internal sealed class TowerLevel
{
/// <summary>
/// Id
/// </summary>
public TowerLevelId Id { get; set; }
/// <summary>
/// 深渊间分组编号
/// </summary>
public TowerLevelGroupId GroupId { get; set; }
/// <summary>
/// 编号
/// </summary>
public uint Index { get; set; }
/// <summary>
/// 怪物等级
/// </summary>
public uint MonsterLevel { get; set; }
/// <summary>
/// 上半怪物预览
/// </summary>
public List<MonsterId> FirstMonsters { get; set; } = default!;
/// <summary>
/// 下半怪物预览
/// </summary>
public List<MonsterId> SecondMonsters { get; set; } = default!;
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Tower;
/// <summary>
/// 深渊 计划
/// </summary>
internal sealed class TowerSchedule
{
/// <summary>
/// 计划 Id
/// </summary>
public TowerScheduleId Id { get; set; }
/// <summary>
/// 层 Id 表
/// </summary>
public List<TowerFloorId> FloorIds { get; set; } = default!;
/// <summary>
/// 开始时间
/// </summary>
public DateTimeOffset Open { get; set; }
/// <summary>
/// 结束时间
/// </summary>
public DateTimeOffset Close { get; set; }
/// <summary>
/// 渊月祝福
/// </summary>
public string BuffName { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public List<string> Descriptions { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
}

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 类型与值
/// </summary>
/// <typeparam name="TType">类型</typeparam>
/// <typeparam name="TValue">值</typeparam>
internal class TypeValue<TType, TValue>
{
/// <summary>
/// 构造一个新的类型与值
/// </summary>
/// <param name="type">类型</param>
/// <param name="value">值</param>
public TypeValue(TType type, TValue value)
{
Type = type;
Value = value;
}
/// <summary>
/// 类型
/// </summary>
public TType Type { get; set; }
/// <summary>
/// 值
/// </summary>
public TValue Value { get; set; }
}

View File

@@ -1,23 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 生长曲线与值
/// </summary>
[HighQuality]
internal sealed class GrowCurveTypeValue
{
/// <summary>
/// 类型
/// </summary>
public GrowCurveType Type { get; set; }
/// <summary>
/// 值
/// </summary>
public float Value { get; set; }
}

View File

@@ -23,7 +23,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
/// <inheritdoc/>
[JsonIgnore]
public ItemQuality Quality
public QualityType Quality
{
get => RankLevel;
}
@@ -31,7 +31,7 @@ internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource
/// <summary>
/// 最大等级
/// </summary>
internal int MaxLevel { get => ((int)Quality) >= 3 ? 90 : 70; }
internal uint MaxLevel { get => ((int)Quality) >= 3 ? 90U : 70U; }
/// <inheritdoc/>
public ICalculableWeapon ToCalculable()

View File

@@ -30,7 +30,7 @@ internal sealed partial class Weapon
/// <summary>
/// 等级
/// </summary>
public ItemQuality RankLevel { get; set; }
public QualityType RankLevel { get; set; }
/// <summary>
/// 名称
@@ -55,7 +55,7 @@ internal sealed partial class Weapon
/// <summary>
/// 生长曲线
/// </summary>
public Dictionary<FightProperty, GrowCurveTypeValue> GrowCurves { get; set; } = default!;
public List<WeaponTypeValue> GrowCurves { get; set; } = default!;
/// <summary>
/// 被动信息, 无被动的武器为 <see langword="null"/>

View File

@@ -0,0 +1,29 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 武器曲线值
/// </summary>
internal sealed class WeaponTypeValue : TypeValue<FightProperty, GrowCurveType>
{
/// <summary>
/// 构造一个新的武器曲线值
/// </summary>
/// <param name="type">属性类型</param>
/// <param name="value">曲线类型</param>
/// <param name="initValue">初始值</param>
public WeaponTypeValue(FightProperty type, GrowCurveType value, float initValue)
: base(type, value)
{
InitValue = initValue;
}
/// <summary>
/// 初始值
/// </summary>
public float InitValue { get; set; }
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Model.Primitive.Converter;
/// <summary>
/// Id 转换器
/// </summary>
/// <typeparam name="TWrapper">包装类型</typeparam>
internal unsafe sealed class IdentityConverter<TWrapper> : JsonConverter<TWrapper>
where TWrapper : unmanaged
{
/// <inheritdoc/>
public override TWrapper Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
uint value = JsonSerializer.Deserialize<uint>(ref reader, options);
return *(TWrapper*)&value;
}
/// <inheritdoc/>
public override TWrapper ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
string? value = reader.GetString();
_ = uint.TryParse(value,out uint result);
return *(TWrapper*)&result;
}
throw new JsonException();
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, *(uint*)&value, options);
}
/// <inheritdoc/>
public override void WriteAsPropertyName(Utf8JsonWriter writer, TWrapper value, JsonSerializerOptions options)
{
writer.WritePropertyName((*(uint*)&value).ToString());
}
}

View File

@@ -34,7 +34,7 @@ internal sealed partial class AchievementService : IAchievementService
/// <inheritdoc/>
public List<AchievementView> GetAchievements(AchievementArchive archive, List<MetadataAchievement> metadata)
{
Dictionary<int, EntityAchievement> entityMap;
Dictionary<AchievementId, EntityAchievement> entityMap;
try
{
using (IServiceScope scope = serviceProvider.CreateScope())
@@ -43,7 +43,7 @@ internal sealed partial class AchievementService : IAchievementService
entityMap = appDbContext.Achievements
.Where(a => a.ArchiveId == archive.InnerId)
.AsEnumerable()
.ToDictionary(a => a.Id);
.ToDictionary(a => (AchievementId)a.Id);
}
}
catch (ArgumentException ex)

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Entity.Database;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.AvatarInfo.Transformer;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.ViewModel.User;
@@ -202,7 +203,7 @@ internal sealed partial class AvatarInfoDbBulkOperation
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddOrUpdateAvatarInfo<TSource>(ModelAvatarInfo? entity, int avatarId, string uid, AppDbContext appDbContext, IAvatarInfoTransformer<TSource> transformer, TSource source)
private static void AddOrUpdateAvatarInfo<TSource>(ModelAvatarInfo? entity, AvatarId avatarId, string uid, AppDbContext appDbContext, IAvatarInfoTransformer<TSource> transformer, TSource source)
{
if (entity == null)
{

View File

@@ -26,7 +26,7 @@ internal sealed class AffixWeight : Dictionary<FightProperty, float>
/// <param name="heal">治疗加成</param>
/// <param name="name">名称</param>
public AffixWeight(
int avatarId,
uint avatarId,
float hp,
float atk,
float def,

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model;
@@ -79,7 +80,7 @@ internal sealed class SummaryAvatarFactory
Model.Metadata.Avatar.Costume costume = avatar.Costumes.Single(c => c.Id == avatarInfo.CostumeId.Value);
// Set to costume icon
propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(costume.Icon);
propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon);
propertyAvatar.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon);
}
else
@@ -116,9 +117,9 @@ internal sealed class SummaryAvatarFactory
{
MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId];
// AffixMap can be empty when it's a white weapon.
KeyValuePair<string, int>? idLevel = equip.Weapon!.AffixMap?.Single();
int affixLevel = idLevel?.Value ?? 0;
// AffixMap can be null when it's a white weapon.
KeyValuePair<EquipAffixId, WeaponAffixLevel>? idLevel = equip.Weapon!.AffixMap?.Single();
uint affixLevel = idLevel?.Value ?? 0U;
WeaponStat? mainStat = equip.Flat.WeaponStats?.ElementAtOrDefault(0);
WeaponStat? subStat = equip.Flat.WeaponStats?.ElementAtOrDefault(1);
@@ -145,7 +146,7 @@ internal sealed class SummaryAvatarFactory
Description = weapon.Description,
// EquipBase
Level = $"Lv.{equip.Weapon!.Level}",
Level = $"Lv.{equip.Weapon!.Level.Value}",
Quality = weapon.Quality,
MainProperty = mainStat != null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : default!,

View File

@@ -26,7 +26,7 @@ internal sealed partial class SummaryFactory : ISummaryFactory
IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false),
IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false),
IdRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false),
IdReliquaryAffixMap = await metadataService.GetIdToReliquaryAffixMapAsync(token).ConfigureAwait(false),
IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false),
ReliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false),
};

View File

@@ -39,29 +39,26 @@ internal static class SummaryHelper
/// <param name="proudSkillExtraLevelMap">额外提升等级映射</param>
/// <param name="proudSkills">技能列表</param>
/// <returns>技能</returns>
public static List<SkillView> CreateSkills(Dictionary<string, int> skillLevelMap, Dictionary<string, int>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills)
public static List<SkillView> CreateSkills(Dictionary<SkillId, SkillLevel> skillLevelMap, Dictionary<SkillGroupId, SkillLevel>? proudSkillExtraLevelMap, List<ProudableSkill> proudSkills)
{
if (skillLevelMap == null)
{
return new();
}
Dictionary<string, int> skillExtraLeveledMap = new(skillLevelMap);
Dictionary<SkillId, SkillLevel> skillExtraLeveledMap = new(skillLevelMap);
if (proudSkillExtraLevelMap != null)
{
foreach ((string skillGroupIdString, int extraLevel) in proudSkillExtraLevelMap)
foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap)
{
SkillGroupId skillGroupId = int.Parse(skillGroupIdString);
SkillId skillId = proudSkills.Single(p => p.GroupId == skillGroupId).Id;
skillExtraLeveledMap.Increase($"{skillId.Value}", extraLevel);
skillExtraLeveledMap.IncreaseValue(proudSkills.Single(p => p.GroupId == groupId).Id, extraLevel);
}
}
return proudSkills.SelectList(proudableSkill =>
{
string skillId = $"{proudableSkill.Id.Value}";
SkillId skillId = proudableSkill.Id;
return new SkillView()
{
@@ -81,10 +78,10 @@ internal static class SummaryHelper
/// </summary>
/// <param name="appendId">属性Id</param>
/// <returns>最大属性Id</returns>
public static int GetAffixMaxId(int appendId)
public static ReliquarySubAffixId GetAffixMaxId(in ReliquarySubAffixId appendId)
{
int value = appendId / 100000;
int max = value switch
uint value = (uint)appendId / 100000U;
uint max = value switch
{
1 => 2,
2 => 3,
@@ -100,10 +97,10 @@ internal static class SummaryHelper
/// </summary>
/// <param name="appendId">id</param>
/// <returns>分数</returns>
public static float GetPercentSubAffixScore(int appendId)
public static float GetPercentSubAffixScore(ReliquarySubAffixId appendId)
{
int maxId = GetAffixMaxId(appendId);
int delta = maxId - appendId;
uint maxId = GetAffixMaxId(appendId);
uint delta = maxId - appendId;
return (maxId / 100000, delta) switch
{

View File

@@ -34,12 +34,12 @@ internal sealed class SummaryMetadataContext
/// <summary>
/// 圣遗物副属性映射
/// </summary>
public Dictionary<ReliquaryAffixId, ReliquaryAffix> IdReliquaryAffixMap { get; set; } = default!;
public Dictionary<ReliquarySubAffixId, ReliquarySubAffix> IdReliquarySubAffixMap { get; set; } = default!;
/// <summary>
/// 圣遗物等级
/// </summary>
public List<ReliquaryLevel> ReliqueryLevels { get; set; } = default!;
public List<ReliquaryMainAffixLevel> ReliqueryLevels { get; set; } = default!;
/// <summary>
/// 圣遗物

View File

@@ -5,11 +5,12 @@ using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Intrinsic.Format;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Enka.Model;
using System.Runtime.InteropServices;
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
using MetadataReliquaryAffix = Snap.Hutao.Model.Metadata.Reliquary.ReliquaryAffix;
using MetadataReliquaryAffix = Snap.Hutao.Model.Metadata.Reliquary.ReliquarySubAffix;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using PropertyReliquary = Snap.Hutao.ViewModel.AvatarProperty.ReliquaryView;
@@ -67,11 +68,10 @@ internal sealed class SummaryReliquaryFactory
result.PrimarySubProperties = new(span[..^affixCount].ToArray());
result.SecondarySubProperties = new(span[^affixCount..].ToArray());
result.ComposedSubProperties = equip.Flat.ReliquarySubstats!.SelectList(CreateComposedSubProperty);
ReliquaryLevel relicLevel = metadataContext.ReliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel);
ReliquaryMainAffixLevel relicLevel = metadataContext.ReliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Rank == reliquary.RankLevel);
FightProperty property = metadataContext.IdRelicMainPropMap[equip.Reliquary.MainPropId];
result.MainProperty = FightPropertyFormat.ToNameValue(property, relicLevel.Properties[property]);
result.MainProperty = FightPropertyFormat.ToNameValue(property, relicLevel.PropertyMap[property]);
result.Score = ScoreReliquary(property, reliquary, relicLevel, subProperty);
}
@@ -81,46 +81,46 @@ internal sealed class SummaryReliquaryFactory
private int GetSecondaryAffixCount(MetadataReliquary reliquary)
{
// 强化词条个数
return (reliquary.RankLevel, equip.Reliquary!.Level) switch
return (reliquary.RankLevel, equip.Reliquary!.Level.Value) switch
{
(ItemQuality.QUALITY_ORANGE, > 20) => 5,
(ItemQuality.QUALITY_ORANGE, > 16) => 4,
(ItemQuality.QUALITY_ORANGE, > 12) => 3,
(ItemQuality.QUALITY_ORANGE, > 8) => 2,
(ItemQuality.QUALITY_ORANGE, > 4) => 1,
(ItemQuality.QUALITY_ORANGE, _) => 0,
(QualityType.QUALITY_ORANGE, > 20U) => 5,
(QualityType.QUALITY_ORANGE, > 16U) => 4,
(QualityType.QUALITY_ORANGE, > 12U) => 3,
(QualityType.QUALITY_ORANGE, > 8U) => 2,
(QualityType.QUALITY_ORANGE, > 4U) => 1,
(QualityType.QUALITY_ORANGE, _) => 0,
(ItemQuality.QUALITY_PURPLE, > 16) => 4,
(ItemQuality.QUALITY_PURPLE, > 12) => 3,
(ItemQuality.QUALITY_PURPLE, > 8) => 2,
(ItemQuality.QUALITY_PURPLE, > 4) => 1,
(ItemQuality.QUALITY_PURPLE, _) => 0,
(QualityType.QUALITY_PURPLE, > 16U) => 4,
(QualityType.QUALITY_PURPLE, > 12U) => 3,
(QualityType.QUALITY_PURPLE, > 8U) => 2,
(QualityType.QUALITY_PURPLE, > 4U) => 1,
(QualityType.QUALITY_PURPLE, _) => 0,
(ItemQuality.QUALITY_BLUE, > 12) => 3,
(ItemQuality.QUALITY_BLUE, > 8) => 2,
(ItemQuality.QUALITY_BLUE, > 4) => 1,
(ItemQuality.QUALITY_BLUE, _) => 0,
(QualityType.QUALITY_BLUE, > 12U) => 3,
(QualityType.QUALITY_BLUE, > 8U) => 2,
(QualityType.QUALITY_BLUE, > 4U) => 1,
(QualityType.QUALITY_BLUE, _) => 0,
(ItemQuality.QUALITY_GREEN, > 4) => 1,
(ItemQuality.QUALITY_GREEN, _) => 0,
(QualityType.QUALITY_GREEN, > 4U) => 1,
(QualityType.QUALITY_GREEN, _) => 0,
(ItemQuality.QUALITY_WHITE, > 4) => 1,
(ItemQuality.QUALITY_WHITE, _) => 0,
(QualityType.QUALITY_WHITE, > 4U) => 1,
(QualityType.QUALITY_WHITE, _) => 0,
_ => 0,
};
}
private float ScoreReliquary(FightProperty property, MetadataReliquary reliquary, ReliquaryLevel relicLevel, List<ReliquarySubProperty> subProperties)
private float ScoreReliquary(FightProperty property, MetadataReliquary reliquary, ReliquaryMainAffixLevel relicLevel, List<ReliquarySubProperty> subProperties)
{
// 沙 杯 头
// equip.Flat.EquipType is EquipType.EQUIP_SHOES or EquipType.EQUIP_RING or EquipType.EQUIP_DRESS
if ((int)equip.Flat.EquipType > 3)
{
AffixWeight weightConfig = GetAffixWeightForAvatarId();
ReliquaryLevel maxRelicLevel = metadataContext.ReliqueryLevels.Where(r => r.Quality == reliquary.RankLevel).MaxBy(r => r.Level)!;
ReliquaryMainAffixLevel maxRelicLevel = metadataContext.ReliqueryLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level)!;
float percent = relicLevel.Properties[property] / maxRelicLevel.Properties[property];
float percent = relicLevel.PropertyMap[property] / maxRelicLevel.PropertyMap[property];
float baseScore = 8 * percent * weightConfig.GetValueOrDefault(property);
float score = subProperties.Sum(p => p.Score);
@@ -151,9 +151,10 @@ internal sealed class SummaryReliquaryFactory
return new(substat.AppendPropId.GetLocalizedDescription(), valueFormatted, 0);
}
private ReliquarySubProperty CreateSubProperty(int appendPropId)
[SuppressMessage("", "SH002")]
private ReliquarySubProperty CreateSubProperty(ReliquarySubAffixId appendPropId)
{
MetadataReliquaryAffix affix = metadataContext.IdReliquaryAffixMap[appendPropId];
MetadataReliquaryAffix affix = metadataContext.IdReliquarySubAffixMap[appendPropId];
FightProperty property = affix.Type;
return new(
@@ -162,9 +163,9 @@ internal sealed class SummaryReliquaryFactory
ScoreSubAffix(appendPropId));
}
private float ScoreSubAffix(int appendId)
private float ScoreSubAffix(in ReliquarySubAffixId appendId)
{
MetadataReliquaryAffix affix = metadataContext.IdReliquaryAffixMap[appendId];
MetadataReliquaryAffix affix = metadataContext.IdReliquarySubAffixMap[appendId];
AffixWeight weightConfig = GetAffixWeightForAvatarId();
float weight = weightConfig.GetValueOrDefault(affix.Type) / 100F;
@@ -179,7 +180,7 @@ internal sealed class SummaryReliquaryFactory
weight = weightConfig.GetValueOrDefault(affix.Type + 1) / 100F;
// 最大同属性百分比数值 最大同属性百分比Id 第四五位是战斗属性位
MetadataReliquaryAffix maxPercentAffix = metadataContext.IdReliquaryAffixMap[SummaryHelper.GetAffixMaxId(appendId + 10)];
MetadataReliquaryAffix maxPercentAffix = metadataContext.IdReliquarySubAffixMap[SummaryHelper.GetAffixMaxId(appendId + 10U)];
float equalScore = equalPercent / maxPercentAffix.Value;
return weight * equalScore * 100;

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Service.AvatarInfo.Transformer;
@@ -16,6 +17,6 @@ internal sealed class CalculateAvatarDetailAvatarInfoTransformer : IAvatarInfoTr
public void Transform(ref Web.Enka.Model.AvatarInfo avatarInfo, AvatarDetail source)
{
// update skills
avatarInfo.SkillLevelMap = source.SkillList.ToDictionary(s => $"{s.Id}", s => s.LevelCurrent);
avatarInfo.SkillLevelMap = source.SkillList.ToDictionary(s => (SkillId)s.Id, s => (SkillLevel)s.LevelCurrent);
}
}

View File

@@ -57,7 +57,10 @@ internal sealed class GameRecordCharacterAvatarInfoTransformer : IAvatarInfoTran
equip.Weapon = new()
{
Level = source.Weapon.Level,
AffixMap = new() { { $"1{source.Weapon.Id}", source.Weapon.AffixLevel - 1 }, },
AffixMap = new()
{
[100000 + source.Weapon.Id] = source.Weapon.AffixLevel - 1,
},
};
// Special case here, don't set EQUIP_WEAPON

View File

@@ -186,7 +186,7 @@ internal sealed partial class CultivationService : ICultivationService
}
/// <inheritdoc/>
public async Task<bool> SaveConsumptionAsync(CultivateType type, int itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
public async Task<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
{
if (items.Count == 0)
{

View File

@@ -71,7 +71,7 @@ internal interface ICultivationService
/// <param name="itemId">主Id</param>
/// <param name="items">待存物品</param>
/// <returns>是否保存成功</returns>
Task<bool> SaveConsumptionAsync(CultivateType type, int itemId, List<Item> items);
Task<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items);
/// <summary>
/// 保存养成物品状态

View File

@@ -60,19 +60,19 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
// It's an avatar
switch (item.ItemId.Place())
{
case 8:
case 8U:
{
Avatar avatar = context.IdAvatarMap[item.ItemId];
bool isUp = false;
switch (avatar.Quality)
{
case ItemQuality.QUALITY_ORANGE:
orangeAvatarCounter.Increase(avatar);
case QualityType.QUALITY_ORANGE:
orangeAvatarCounter.IncreaseOne(avatar);
isUp = targetHistoryWishBuilder?.IncreaseOrange(avatar) ?? false;
break;
case ItemQuality.QUALITY_PURPLE:
purpleAvatarCounter.Increase(avatar);
case QualityType.QUALITY_PURPLE:
purpleAvatarCounter.IncreaseOne(avatar);
targetHistoryWishBuilder?.IncreasePurple(avatar);
break;
}
@@ -83,24 +83,24 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
break;
}
case 5:
case 5U:
{
Weapon weapon = context.IdWeaponMap[item.ItemId];
bool isUp = false;
switch (weapon.RankLevel)
{
case ItemQuality.QUALITY_ORANGE:
case QualityType.QUALITY_ORANGE:
isUp = targetHistoryWishBuilder?.IncreaseOrange(weapon) ?? false;
orangeWeaponCounter.Increase(weapon);
orangeWeaponCounter.IncreaseOne(weapon);
break;
case ItemQuality.QUALITY_PURPLE:
case QualityType.QUALITY_PURPLE:
targetHistoryWishBuilder?.IncreasePurple(weapon);
purpleWeaponCounter.Increase(weapon);
purpleWeaponCounter.IncreaseOne(weapon);
break;
case ItemQuality.QUALITY_BLUE:
case QualityType.QUALITY_BLUE:
targetHistoryWishBuilder?.IncreaseBlue(weapon);
blueWeaponCounter.Increase(weapon);
blueWeaponCounter.IncreaseOne(weapon);
break;
}

View File

@@ -73,15 +73,15 @@ internal sealed partial class GachaStatisticsSlimFactory : IGachaStatisticsSlimF
{
switch (nameQuality.Quality)
{
case ItemQuality.QUALITY_ORANGE:
case QualityType.QUALITY_ORANGE:
orangeTracker = 0;
++purpleTracker;
break;
case ItemQuality.QUALITY_PURPLE:
case QualityType.QUALITY_PURPLE:
++orangeTracker;
purpleTracker = 0;
break;
case ItemQuality.QUALITY_BLUE:
case QualityType.QUALITY_BLUE:
++orangeTracker;
++purpleTracker;
break;

View File

@@ -71,10 +71,10 @@ internal sealed class HistoryWishBuilder
/// <returns>是否为Up物品</returns>
public bool IncreaseOrange(IStatisticsItemSource item)
{
orangeCounter.Increase(item);
orangeCounter.IncreaseOne(item);
++totalCountTracker;
return orangeUpCounter.TryIncrease(item);
return orangeUpCounter.TryIncreaseOne(item);
}
/// <summary>
@@ -83,8 +83,8 @@ internal sealed class HistoryWishBuilder
/// <param name="item">物品</param>
public void IncreasePurple(IStatisticsItemSource item)
{
purpleUpCounter.TryIncrease(item);
purpleCounter.Increase(item);
purpleUpCounter.TryIncreaseOne(item);
purpleCounter.IncreaseOne(item);
++totalCountTracker;
}
@@ -94,7 +94,7 @@ internal sealed class HistoryWishBuilder
/// <param name="item">武器</param>
public void IncreaseBlue(IStatisticsItemSource item)
{
blueCounter.Increase(item);
blueCounter.IncreaseOne(item);
++totalCountTracker;
}

View File

@@ -87,7 +87,7 @@ internal sealed class TypedWishSummaryBuilder
switch (source.Quality)
{
case ItemQuality.QUALITY_ORANGE:
case QualityType.QUALITY_ORANGE:
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
@@ -105,14 +105,14 @@ internal sealed class TypedWishSummaryBuilder
break;
}
case ItemQuality.QUALITY_PURPLE:
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
case ItemQuality.QUALITY_BLUE:
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;

View File

@@ -104,13 +104,13 @@ internal readonly struct GachaLogServiceContext
/// </summary>
/// <param name="id">Id</param>
/// <returns>名称星级</returns>
public INameQuality GetNameQualityByItemId(int id)
public INameQuality GetNameQualityByItemId(uint id)
{
int place = id.Place();
uint place = id.Place();
return place switch
{
8 => IdAvatarMap![id],
5 => IdWeaponMap![id],
8U => IdAvatarMap![id],
5U => IdWeaponMap![id],
_ => throw Must.NeverHappen($"Id places: {place}"),
};
}
@@ -121,13 +121,13 @@ internal readonly struct GachaLogServiceContext
/// </summary>
/// <param name="item">祈愿物品</param>
/// <returns>物品 Id</returns>
public int GetItemId(GachaLogItem item)
public uint GetItemId(GachaLogItem item)
{
return item.ItemType switch
{
"角色" => NameAvatarMap!.GetValueOrDefault(item.Name)?.Id ?? 0,
"武器" => NameWeaponMap!.GetValueOrDefault(item.Name)?.Id ?? 0,
_ => 0,
_ => 0U,
};
}
}

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Web.Hoyolab.SdkStatic.Hk4e.Launcher;
using System.IO;
@@ -240,7 +241,7 @@ internal sealed partial class PackageConverter
{
if (File.Exists(cacheFilePath))
{
string remoteMd5 = await Digest.GetFileMD5Async(cacheFilePath).ConfigureAwait(false);
string remoteMd5 = await MD5.HashFileAsync(cacheFilePath).ConfigureAwait(false);
if (info.Md5 == remoteMd5.ToLowerInvariant() && new FileInfo(cacheFilePath).Length == info.TotalBytes)
{
// Valid, move it to target path
@@ -270,7 +271,7 @@ internal sealed partial class PackageConverter
StreamCopyWorker<PackageReplaceStatus> streamCopyWorker = new(webStream, fileStream, bytesRead => new(info.Target, bytesRead, totalBytes));
await streamCopyWorker.CopyAsync(progress).ConfigureAwait(false);
fileStream.Seek(0, SeekOrigin.Begin);
string remoteMd5 = await Digest.GetStreamMD5Async(fileStream).ConfigureAwait(false);
string remoteMd5 = await MD5.HashAsync(fileStream).ConfigureAwait(false);
if (string.Equals(info.Md5, remoteMd5, StringComparison.OrdinalIgnoreCase))
{
return;

View File

@@ -58,7 +58,7 @@ internal static class ProcessInterop
public static Task UnlockFpsAsync(IServiceProvider serviceProvider, Process game)
{
IGameFpsUnlocker unlocker = serviceProvider.CreateInstance<GameFpsUnlocker>(game);
UnlockTimingOptions options = new(100, 10000, 3000);
UnlockTimingOptions options = new(100, 20000, 3000);
return unlocker.UnlockAsync(options);
}

View File

@@ -90,14 +90,14 @@ internal interface IMetadataService : ICastableService
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物强化属性列表</returns>
ValueTask<List<ReliquaryAffix>> GetReliquaryAffixesAsync(CancellationToken token = default);
ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物等级数据
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>圣遗物等级数据</returns>
ValueTask<List<ReliquaryLevel>> GetReliquaryLevelsAsync(CancellationToken token = default);
ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主属性强化属性列表
@@ -156,7 +156,7 @@ internal interface IMetadataService : ICastableService
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>显示与材料映射</returns>
ValueTask<Dictionary<MaterialId, Display>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到材料的字典
@@ -170,7 +170,7 @@ internal interface IMetadataService : ICastableService
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>字典</returns>
ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdToReliquaryAffixMapAsync(CancellationToken token = default);
ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取圣遗物主词条Id与属性的字典
@@ -211,20 +211,20 @@ internal interface IMetadataService : ICastableService
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级角色曲线映射</returns>
ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default);
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取等级怪物曲线映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级怪物曲线映射</returns>
ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default);
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取等级武器曲线映射
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>等级武器曲线映射</returns>
ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default);
ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default);
#endregion
}

View File

@@ -84,7 +84,7 @@ internal sealed partial class MetadataOptions : IOptions<MetadataOptions>
/// <returns>服务器上的本地化元数据文件</returns>
public string GetLocalizedRemoteFile(string fileNameWithExtension)
{
return Web.HutaoEndpoints.HutaoMetadataFile(LocaleName, fileNameWithExtension);
return Web.HutaoEndpoints.RawGithubUserContentMetadataFile(LocaleName, fileNameWithExtension);
}
private string GetLocaleName()

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata;
/// <summary>
/// 名称常量
/// </summary>
internal partial class MetadataService
{
private const string FileNameAchievement = "Achievement";
private const string FileNameAchievementGoal = "AchievementGoal";
private const string FileNameAvatar = "Avatar";
private const string FileNameAvatarCurve = "AvatarCurve";
private const string FileNameAvatarPromote = "AvatarPromote";
private const string FileNameDisplayItem = "DisplayItem";
private const string FileNameGachaEvent = "GachaEvent";
private const string FileNameMaterial = "Material";
private const string FileNameMonster = "Monster";
private const string FileNameMonsterCurve = "MonsterCurve";
private const string FileNameReliquary = "Reliquary";
private const string FileNameReliquaryAffixWeight = "ReliquaryAffixWeight";
private const string FileNameReliquaryMainAffix = "ReliquaryMainAffix";
private const string FileNameReliquaryMainAffixLevel = "ReliquaryMainAffixLevel";
private const string FileNameReliquarySet = "ReliquarySet";
private const string FileNameReliquarySubAffix = "ReliquarySubAffix";
private const string FileNameTowerFloor = "TowerFloor";
private const string FileNameTowerLevel = "TowerLevel";
private const string FileNameTowerSchedule = "TowerSchedule";
private const string FileNameWeapon = "Weapon";
private const string FileNameWeaponCurve = "WeaponCurve";
private const string FileNameWeaponPromote = "WeaponPromote";
}

View File

@@ -19,84 +19,84 @@ internal sealed partial class MetadataService
/// <inheritdoc/>
public ValueTask<List<AchievementGoal>> GetAchievementGoalsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<AchievementGoal>>("AchievementGoal", token);
return FromCacheOrFileAsync<List<AchievementGoal>>(FileNameAchievementGoal, token);
}
/// <inheritdoc/>
public ValueTask<List<Model.Metadata.Achievement.Achievement>> GetAchievementsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>("Achievement", token);
return FromCacheOrFileAsync<List<Model.Metadata.Achievement.Achievement>>(FileNameAchievement, token);
}
/// <inheritdoc/>
public ValueTask<List<Avatar>> GetAvatarsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Avatar>>("Avatar", token);
return FromCacheOrFileAsync<List<Avatar>>(FileNameAvatar, token);
}
/// <inheritdoc/>
public ValueTask<List<Promote>> GetAvatarPromotesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Promote>>("AvatarPromote", token);
}
/// <inheritdoc/>
public ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Promote>>("WeaponPromote", token);
return FromCacheOrFileAsync<List<Promote>>(FileNameAvatarPromote, token);
}
/// <inheritdoc/>
public ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<GachaEvent>>("GachaEvent2", token);
return FromCacheOrFileAsync<List<GachaEvent>>(FileNameGachaEvent, token);
}
/// <inheritdoc/>
public ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Material>>("Material", token);
return FromCacheOrFileAsync<List<Material>>(FileNameMaterial, token);
}
/// <inheritdoc/>
public ValueTask<List<Monster>> GetMonstersAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Monster>>("Monster", token);
return FromCacheOrFileAsync<List<Monster>>(FileNameMonster, token);
}
/// <inheritdoc/>
public ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Reliquary>>("Reliquary", token);
return FromCacheOrFileAsync<List<Reliquary>>(FileNameReliquary, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquaryAffix>> GetReliquaryAffixesAsync(CancellationToken token = default)
public ValueTask<List<ReliquaryMainAffixLevel>> GetReliquaryLevelsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquaryAffix>>("ReliquaryAffix", token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquaryLevel>> GetReliquaryLevelsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquaryLevel>>("ReliquaryMainAffixLevel", token);
return FromCacheOrFileAsync<List<ReliquaryMainAffixLevel>>(FileNameReliquaryMainAffixLevel, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquaryMainAffix>> GetReliquaryMainAffixesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquaryMainAffix>>("ReliquaryMainAffix", token);
return FromCacheOrFileAsync<List<ReliquaryMainAffix>>(FileNameReliquaryMainAffix, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquarySet>>("ReliquarySet", token);
return FromCacheOrFileAsync<List<ReliquarySet>>(FileNameReliquarySet, token);
}
/// <inheritdoc/>
public ValueTask<List<ReliquarySubAffix>> GetReliquarySubAffixesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<ReliquarySubAffix>>(FileNameReliquarySubAffix, token);
}
/// <inheritdoc/>
public ValueTask<List<Weapon>> GetWeaponsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Weapon>>("Weapon", token);
return FromCacheOrFileAsync<List<Weapon>>(FileNameWeapon, token);
}
/// <inheritdoc/>
public ValueTask<List<Promote>> GetWeaponPromotesAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Promote>>(FileNameWeaponPromote, token);
}
}

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Model.Metadata.Item;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using System.Runtime.CompilerServices;
namespace Snap.Hutao.Service.Metadata;
@@ -20,38 +21,38 @@ internal sealed partial class MetadataService
/// <inheritdoc/>
public ValueTask<Dictionary<EquipAffixId, ReliquarySet>> GetEquipAffixIdToReliquarySetMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>("ReliquarySet", r => r.EquipAffixId, token);
return FromCacheAsDictionaryAsync<EquipAffixId, ReliquarySet>(FileNameReliquarySet, r => r.EquipAffixId, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>("Achievement", a => a.Id, token);
return FromCacheAsDictionaryAsync<AchievementId, Model.Metadata.Achievement.Achievement>(FileNameAchievement, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<AvatarId, Avatar>("Avatar", a => a.Id, token);
return FromCacheAsDictionaryAsync<AvatarId, Avatar>(FileNameAvatar, a => a.Id, token);
}
/// <inheritdoc/>
public async ValueTask<Dictionary<MaterialId, Display>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default)
public async ValueTask<Dictionary<MaterialId, DisplayItem>> GetIdToDisplayAndMaterialMapAsync(CancellationToken token = default)
{
string cacheKey = $"{nameof(MetadataService)}.Cache.DisplayAndMaterial.Map.{typeof(MaterialId).Name}";
if (memoryCache.TryGetValue(cacheKey, out object? value))
{
return Must.NotNull((Dictionary<MaterialId, Display>)value!);
return Must.NotNull((Dictionary<MaterialId, DisplayItem>)value!);
}
Dictionary<MaterialId, Display> displays = await FromCacheAsDictionaryAsync<MaterialId, Display>("Display", a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, DisplayItem> displays = await FromCacheAsDictionaryAsync<MaterialId, DisplayItem>(FileNameDisplayItem, a => a.Id, token).ConfigureAwait(false);
Dictionary<MaterialId, Material> materials = await GetIdToMaterialMapAsync(token).ConfigureAwait(false);
// TODO: Cache this
Dictionary<MaterialId, Display> results = new(displays);
Dictionary<MaterialId, DisplayItem> results = new(displays);
foreach ((MaterialId id, Display material) in materials)
foreach ((MaterialId id, DisplayItem material) in materials)
{
results[id] = material;
}
@@ -62,54 +63,54 @@ internal sealed partial class MetadataService
/// <inheritdoc/>
public ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MaterialId, Material>("Material", a => a.Id, token);
return FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdToReliquaryAffixMapAsync(CancellationToken token = default)
public ValueTask<Dictionary<ReliquarySubAffixId, ReliquarySubAffix>> GetIdToReliquarySubAffixMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<ReliquaryAffixId, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
return FromCacheAsDictionaryAsync<ReliquarySubAffixId, ReliquarySubAffix>(FileNameReliquarySubAffix, a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquaryMainAffixId, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
return FromCacheAsDictionaryAsync<ReliquaryMainAffixId, FightProperty, ReliquaryMainAffix>(FileNameReliquaryMainAffix, r => r.Id, r => r.Type, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<WeaponId, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<WeaponId, Weapon>("Weapon", w => w.Id, token);
return FromCacheAsDictionaryAsync<WeaponId, Weapon>(FileNameWeapon, w => w.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default)
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToAvatarCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<int, Dictionary<GrowCurveType, float>, GrowCurve>("AvatarCurve", a => a.Level, a => a.Curves, token);
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameAvatarCurve, a => a.Level, a => a.CurveMap, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default)
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToMonsterCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<int, Dictionary<GrowCurveType, float>, GrowCurve>("MonsterCurve", m => m.Level, m => m.Curves, token);
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameMonsterCurve, m => m.Level, m => m.CurveMap, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<int, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default)
public ValueTask<Dictionary<Level, Dictionary<GrowCurveType, float>>> GetLevelToWeaponCurveMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<int, Dictionary<GrowCurveType, float>, GrowCurve>("WeaponCurve", w => w.Level, w => w.Curves, token);
return FromCacheAsDictionaryAsync<Level, Dictionary<GrowCurveType, float>, GrowCurve>(FileNameWeaponCurve, w => w.Level, w => w.CurveMap, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<string, Avatar>> GetNameToAvatarMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<string, Avatar>("Avatar", a => a.Name, token);
return FromCacheAsDictionaryAsync<string, Avatar>(FileNameAvatar, a => a.Name, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<string, Weapon>> GetNameToWeaponMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<string, Weapon>("Weapon", w => w.Name, token);
return FromCacheAsDictionaryAsync<string, Weapon>(FileNameWeapon, w => w.Name, token);
}
}

View File

@@ -6,7 +6,7 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.IO;
using Snap.Hutao.Core.IO.Hashing;
using Snap.Hutao.Service.Notification;
using System.IO;
using System.Net.Http;
@@ -122,12 +122,12 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
string fileFullPath = metadataOptions.GetLocalizedLocalFile(fileFullName);
if (File.Exists(fileFullPath))
{
skip = md5 == await Digest.GetFileMD5Async(fileFullPath, token).ConfigureAwait(false);
skip = md5 == await XXH64.HashFileAsync(fileFullPath, token).ConfigureAwait(false);
}
if (!skip)
{
logger.LogInformation("MD5 of {file} not matched, begin downloading", fileFullName);
logger.LogInformation("{hash} of {file} not matched, begin downloading", nameof(XXH64), fileFullName);
await DownloadMetadataAsync(fileFullName, token).ConfigureAwait(false);
}
@@ -182,6 +182,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
}
else
{
FileNotFoundException exception = new(SH.ServiceMetadataFileNotFound, fileName);
throw ThrowHelper.UserdataCorrupted(SH.ServiceMetadataFileNotFound, null!);
}
}

View File

@@ -253,7 +253,7 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.5.22">
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.6.40">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -268,6 +268,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IO.Hashing" Version="7.0.0" />
<PackageReference Include="TaskScheduler" Version="2.10.1" />
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.View.Control;
[HighQuality]
internal sealed partial class ItemIcon : UserControl
{
private static readonly DependencyProperty QualityProperty = Property<ItemIcon>.Depend(nameof(Quality), ItemQuality.QUALITY_NONE);
private static readonly DependencyProperty QualityProperty = Property<ItemIcon>.Depend(nameof(Quality), QualityType.QUALITY_NONE);
private static readonly DependencyProperty IconProperty = Property<ItemIcon>.Depend<Uri>(nameof(Icon));
private static readonly DependencyProperty BadgeProperty = Property<ItemIcon>.Depend<Uri>(nameof(Badge));
@@ -29,9 +29,9 @@ internal sealed partial class ItemIcon : UserControl
/// <summary>
/// 等阶
/// </summary>
public ItemQuality Quality
public QualityType Quality
{
get => (ItemQuality)GetValue(QualityProperty);
get => (QualityType)GetValue(QualityProperty);
set => SetValue(QualityProperty, value);
}

View File

@@ -521,7 +521,7 @@
Width="80"
Height="80"
Margin="0,0,16,0"
Source="{Binding Icon, Converter={StaticResource AvatarCardConverter}}"/>
Source="{Binding FrontIcon, Converter={StaticResource AvatarCardConverter}}"/>
<StackPanel Grid.Column="1" Margin="0,0,16,-4">
<TextBlock Text="{Binding Name}"/>
<TextBlock

View File

@@ -38,7 +38,7 @@ internal sealed class AchievementGoalView : ObservableObject, INameIcon
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; }
public uint Order { get; }
/// <summary>
/// 名称

View File

@@ -36,7 +36,7 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; set; }
public QualityType Quality { get; set; }
/// <summary>
/// 元素类型
@@ -91,7 +91,7 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <summary>
/// 好感度等级
/// </summary>
public int FetterLevel { get; set; }
public uint FetterLevel { get; set; }
/// <summary>
/// Id
@@ -101,7 +101,7 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <summary>
/// 等级数字
/// </summary>
internal int LevelNumber { get; set; }
internal uint LevelNumber { get; set; }
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()

View File

@@ -20,7 +20,7 @@ internal abstract class Equip : NameIconDescription
/// <summary>
/// 品质
/// </summary>
public ItemQuality Quality { get; set; }
public QualityType Quality { get; set; }
/// <summary>
/// 主属性

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.ViewModel.AvatarProperty;
@@ -20,7 +21,7 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
/// <summary>
/// 不计算命座的技能等级
/// </summary>
public int LevelNumber { get; set; }
public uint LevelNumber { get; set; }
/// <summary>
/// 不计算命座的技能等级字符串
@@ -30,7 +31,7 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
/// <summary>
/// 技能组Id
/// </summary>
internal int GroupId { get; set; }
internal SkillGroupId GroupId { get; set; }
/// <inheritdoc/>
public ICalculableSkill ToCalculable()

View File

@@ -20,7 +20,7 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
/// <summary>
/// 精炼等级
/// </summary>
public int AffixLevelNumber { get; set; }
public uint AffixLevelNumber { get; set; }
/// <summary>
/// 精炼属性
@@ -45,12 +45,12 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
/// <summary>
/// 等级数字
/// </summary>
internal int LevelNumber { get; set; }
internal uint LevelNumber { get; set; }
/// <summary>
/// 最大等级
/// </summary>
internal int MaxLevel { get => ((int)Quality) >= 3 ? 90 : 70; }
internal uint MaxLevel { get => ((int)Quality) >= 3 ? 90U : 70U; }
/// <inheritdoc/>
public ICalculableWeapon ToCalculable()

View File

@@ -46,7 +46,7 @@ internal class AvatarView : INameIconSide
/// <summary>
/// 星级
/// </summary>
public ItemQuality Quality { get; }
public QualityType Quality { get; }
/// <summary>
/// 比率

Some files were not shown because too many files have changed in this diff Show More