mirror of
https://github.com/MikuLeaks/MikuSB.git
synced 2026-06-04 04:03:58 +00:00
Implement Weapon_Upgrade
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -370,3 +370,4 @@ FodyWeavers.xsd
|
|||||||
*.rar
|
*.rar
|
||||||
/Lua_Script
|
/Lua_Script
|
||||||
/.note
|
/.note
|
||||||
|
/Snowbreak_Data
|
||||||
|
|||||||
24
Common/Data/Excel/BreakLevelLimitExcel.cs
Normal file
24
Common/Data/Excel/BreakLevelLimitExcel.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace MikuSB.Data.Excel;
|
||||||
|
|
||||||
|
[ResourceEntity("break_level_limit.json")]
|
||||||
|
public class BreakLevelLimitExcel : ExcelResource
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public uint Break0 { get; set; }
|
||||||
|
public uint Break1 { get; set; }
|
||||||
|
public uint Break2 { get; set; }
|
||||||
|
public uint Break3 { get; set; }
|
||||||
|
public uint Break4 { get; set; }
|
||||||
|
public uint Break5 { get; set; }
|
||||||
|
public uint Break6 { get; set; }
|
||||||
|
|
||||||
|
public override uint GetId()
|
||||||
|
{
|
||||||
|
return (uint)ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Loaded()
|
||||||
|
{
|
||||||
|
GameData.BreakLevelLimitData[ID] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Common/Data/Excel/RecycleExcel.cs
Normal file
21
Common/Data/Excel/RecycleExcel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace MikuSB.Data.Excel;
|
||||||
|
|
||||||
|
[ResourceEntity("recycle.json")]
|
||||||
|
public class RecycleExcel : ExcelResource
|
||||||
|
{
|
||||||
|
public int ID { get; set; }
|
||||||
|
public JToken? RecycleBase { get; set; }
|
||||||
|
public JToken? RecycleRatio { get; set; }
|
||||||
|
|
||||||
|
public override uint GetId()
|
||||||
|
{
|
||||||
|
return (uint)ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Loaded()
|
||||||
|
{
|
||||||
|
GameData.RecycleData[ID] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Common/Data/Excel/SuppliesExcel.cs
Normal file
52
Common/Data/Excel/SuppliesExcel.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace MikuSB.Data.Excel;
|
||||||
|
|
||||||
|
[ResourceEntity("suplies.json")]
|
||||||
|
public class SuppliesExcel : ExcelResource
|
||||||
|
{
|
||||||
|
public uint Genre { get; set; }
|
||||||
|
public uint Detail { get; set; }
|
||||||
|
public uint Particular { get; set; }
|
||||||
|
public uint Level { get; set; }
|
||||||
|
[JsonProperty("Color")] public JToken? ColorRaw { get; set; }
|
||||||
|
[JsonProperty("ProvideExp")] public JToken? ProvideExpRaw { get; set; }
|
||||||
|
[JsonProperty("ConsumeGold")] public JToken? ConsumeGoldRaw { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public int Color => ReadInt(ColorRaw);
|
||||||
|
[JsonIgnore] public uint ProvideExp => ReadUInt(ProvideExpRaw);
|
||||||
|
[JsonIgnore] public uint ConsumeGold => ReadUInt(ConsumeGoldRaw);
|
||||||
|
|
||||||
|
public override uint GetId()
|
||||||
|
{
|
||||||
|
return (uint)GameResourceTemplateId.FromGdpl(Genre, Detail, Particular, Level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Loaded()
|
||||||
|
{
|
||||||
|
GameData.SuppliesData[GetId()] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ReadInt(JToken? token)
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Type switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => token.Value<int>(),
|
||||||
|
JTokenType.Float => (int)token.Value<decimal>(),
|
||||||
|
JTokenType.String when int.TryParse(token.Value<string>(), out var value) => value,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint ReadUInt(JToken? token)
|
||||||
|
{
|
||||||
|
var value = ReadInt(token);
|
||||||
|
return value > 0 ? (uint)value : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Common/Data/Excel/UpgradeExpExcel.cs
Normal file
49
Common/Data/Excel/UpgradeExpExcel.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace MikuSB.Data.Excel;
|
||||||
|
|
||||||
|
[ResourceEntity("upgrade_exp.json")]
|
||||||
|
public class UpgradeExpExcel : ExcelResource
|
||||||
|
{
|
||||||
|
public int Lv { get; set; }
|
||||||
|
[JsonProperty("CardNeedExp")] public JToken? CardNeedExpRaw { get; set; }
|
||||||
|
[JsonProperty("SSRCardNeedExp")] public JToken? SsrCardNeedExpRaw { get; set; }
|
||||||
|
[JsonProperty("SusNeedExp")] public JToken? SusNeedExpRaw { get; set; }
|
||||||
|
[JsonProperty("SSRSusNeedExp")] public JToken? SsrSusNeedExpRaw { get; set; }
|
||||||
|
[JsonProperty("WeaponNeedExp")] public JToken? WeaponNeedExpRaw { get; set; }
|
||||||
|
[JsonProperty("SSRWeaponNeedExp")] public JToken? SsrWeaponNeedExpRaw { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore] public uint CardNeedExp => ReadUInt(CardNeedExpRaw);
|
||||||
|
[JsonIgnore] public uint SSRCardNeedExp => ReadUInt(SsrCardNeedExpRaw);
|
||||||
|
[JsonIgnore] public uint SusNeedExp => ReadUInt(SusNeedExpRaw);
|
||||||
|
[JsonIgnore] public uint SSRSusNeedExp => ReadUInt(SsrSusNeedExpRaw);
|
||||||
|
[JsonIgnore] public uint WeaponNeedExp => ReadUInt(WeaponNeedExpRaw);
|
||||||
|
[JsonIgnore] public uint SSRWeaponNeedExp => ReadUInt(SsrWeaponNeedExpRaw);
|
||||||
|
|
||||||
|
public override uint GetId()
|
||||||
|
{
|
||||||
|
return (uint)Lv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Loaded()
|
||||||
|
{
|
||||||
|
GameData.UpgradeExpData[Lv] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint ReadUInt(JToken? token)
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Type switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => token.Value<uint>(),
|
||||||
|
JTokenType.Float => (uint)Math.Max(0, token.Value<decimal>()),
|
||||||
|
JTokenType.String when uint.TryParse(token.Value<string>(), out var value) => value,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
namespace MikuSB.Data.Excel;
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace MikuSB.Data.Excel;
|
||||||
|
|
||||||
[ResourceEntity("weapon.json")]
|
[ResourceEntity("weapon.json")]
|
||||||
public class WeaponExcel : ExcelResource
|
public class WeaponExcel : ExcelResource
|
||||||
@@ -7,8 +10,13 @@ public class WeaponExcel : ExcelResource
|
|||||||
public uint Detail { get; set; }
|
public uint Detail { get; set; }
|
||||||
public uint Particular { get; set; }
|
public uint Particular { get; set; }
|
||||||
public uint Level { get; set; }
|
public uint Level { get; set; }
|
||||||
|
[JsonProperty("Color")] public JToken? ColorRaw { get; set; }
|
||||||
|
[JsonProperty("InitBreak")] public JToken? InitBreakRaw { get; set; }
|
||||||
public int Class { get; set; }
|
public int Class { get; set; }
|
||||||
public uint Icon { get; set; }
|
public uint Icon { get; set; }
|
||||||
|
[JsonProperty("ProvideExp")] public JToken? ProvideExpRaw { get; set; }
|
||||||
|
[JsonProperty("ConsumeGold")] public JToken? ConsumeGoldRaw { get; set; }
|
||||||
|
[JsonProperty("RecycleID")] public JToken? RecycleIdRaw { get; set; }
|
||||||
public int EvolutionMatID { get; set; }
|
public int EvolutionMatID { get; set; }
|
||||||
public int BreakMatID { get; set; }
|
public int BreakMatID { get; set; }
|
||||||
public int LevelLimitID { get; set; }
|
public int LevelLimitID { get; set; }
|
||||||
@@ -17,6 +25,13 @@ public class WeaponExcel : ExcelResource
|
|||||||
public List<int> DefaultSkillID { get; set; } = [];
|
public List<int> DefaultSkillID { get; set; } = [];
|
||||||
public List<int> WeaponPartsLimit { get; set; } = [];
|
public List<int> WeaponPartsLimit { get; set; } = [];
|
||||||
public string I18n { get; set; } = "";
|
public string I18n { get; set; } = "";
|
||||||
|
|
||||||
|
[JsonIgnore] public int Color => ReadInt(ColorRaw);
|
||||||
|
[JsonIgnore] public uint InitBreak => ReadUInt(InitBreakRaw);
|
||||||
|
[JsonIgnore] public uint ProvideExp => ReadUInt(ProvideExpRaw);
|
||||||
|
[JsonIgnore] public uint ConsumeGold => ReadUInt(ConsumeGoldRaw);
|
||||||
|
[JsonIgnore] public int RecycleID => ReadInt(RecycleIdRaw);
|
||||||
|
|
||||||
public override uint GetId()
|
public override uint GetId()
|
||||||
{
|
{
|
||||||
return (uint)I18n.GetHashCode();
|
return (uint)I18n.GetHashCode();
|
||||||
@@ -26,4 +41,26 @@ public class WeaponExcel : ExcelResource
|
|||||||
{
|
{
|
||||||
GameData.WeaponData.Add(GetId(), this);
|
GameData.WeaponData.Add(GetId(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int ReadInt(JToken? token)
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Type switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => token.Value<int>(),
|
||||||
|
JTokenType.Float => (int)token.Value<decimal>(),
|
||||||
|
JTokenType.String when int.TryParse(token.Value<string>(), out var value) => value,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint ReadUInt(JToken? token)
|
||||||
|
{
|
||||||
|
var value = ReadInt(token);
|
||||||
|
return value > 0 ? (uint)value : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,10 @@ public static class GameData
|
|||||||
public static Dictionary<uint, CardExcel> CardData { get; private set; } = [];
|
public static Dictionary<uint, CardExcel> CardData { get; private set; } = [];
|
||||||
public static Dictionary<uint, WeaponExcel> WeaponData { get; private set; } = [];
|
public static Dictionary<uint, WeaponExcel> WeaponData { get; private set; } = [];
|
||||||
public static Dictionary<uint, CardSkinExcel> CardSkinData { get; private set; } = [];
|
public static Dictionary<uint, CardSkinExcel> CardSkinData { get; private set; } = [];
|
||||||
|
public static Dictionary<uint, SuppliesExcel> SuppliesData { get; private set; } = [];
|
||||||
|
public static Dictionary<int, UpgradeExpExcel> UpgradeExpData { get; private set; } = [];
|
||||||
|
public static Dictionary<int, BreakLevelLimitExcel> BreakLevelLimitData { get; private set; } = [];
|
||||||
|
public static Dictionary<int, RecycleExcel> RecycleData { get; private set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class GameResourceTemplateId
|
public static class GameResourceTemplateId
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ public abstract class BaseGameItemInfo
|
|||||||
public uint UniqueId { get; set; }
|
public uint UniqueId { get; set; }
|
||||||
public ulong TemplateId { get; set; }
|
public ulong TemplateId { get; set; }
|
||||||
public uint ItemCount { get; set; }
|
public uint ItemCount { get; set; }
|
||||||
|
|
||||||
|
public virtual Item ToProto()
|
||||||
|
{
|
||||||
|
return new Item
|
||||||
|
{
|
||||||
|
Id = UniqueId,
|
||||||
|
Template = TemplateId,
|
||||||
|
Count = ItemCount
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class GrowableItemInfo : BaseGameItemInfo
|
public abstract class GrowableItemInfo : BaseGameItemInfo
|
||||||
@@ -30,12 +40,13 @@ public abstract class GrowableItemInfo : BaseGameItemInfo
|
|||||||
public bool IsLocked { get; set; }
|
public bool IsLocked { get; set; }
|
||||||
public uint Level { get; set; }
|
public uint Level { get; set; }
|
||||||
public uint Exp { get; set; }
|
public uint Exp { get; set; }
|
||||||
|
public uint Break { get; set; }
|
||||||
public uint EquipAvatarId { get; set; }
|
public uint EquipAvatarId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameWeaponInfo : GrowableItemInfo
|
public class GameWeaponInfo : GrowableItemInfo
|
||||||
{
|
{
|
||||||
public Item ToProto()
|
public override Item ToProto()
|
||||||
{
|
{
|
||||||
var proto = new Item
|
var proto = new Item
|
||||||
{
|
{
|
||||||
@@ -44,17 +55,17 @@ public class GameWeaponInfo : GrowableItemInfo
|
|||||||
Count = ItemCount,
|
Count = ItemCount,
|
||||||
Enhance = new Enhance
|
Enhance = new Enhance
|
||||||
{
|
{
|
||||||
Level = Level
|
Level = Level,
|
||||||
|
Exp = Exp,
|
||||||
|
Break = Break
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
}
|
}public class GameSkinInfo : BaseGameItemInfo
|
||||||
|
|
||||||
public class GameSkinInfo : BaseGameItemInfo
|
|
||||||
{
|
{
|
||||||
public uint Level { get; set; }
|
public uint Level { get; set; }
|
||||||
public Item ToProto()
|
public override Item ToProto()
|
||||||
{
|
{
|
||||||
var proto = new Item
|
var proto = new Item
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -57,4 +57,59 @@ public class CharacterManager(PlayerInstance player) : BasePlayerManager(player)
|
|||||||
var templateId = GameResourceTemplateId.FromGdpl((uint)genre,(uint)detail,(uint)particular,1);
|
var templateId = GameResourceTemplateId.FromGdpl((uint)genre,(uint)detail,(uint)particular,1);
|
||||||
return CharacterData.Characters.Find(Character => Character.TemplateId == templateId);
|
return CharacterData.Characters.Find(Character => Character.TemplateId == templateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask RepairCharacterWeapons()
|
||||||
|
{
|
||||||
|
var changed = false;
|
||||||
|
var equippedWeaponIds = new HashSet<uint>();
|
||||||
|
|
||||||
|
foreach (var character in CharacterData.Characters)
|
||||||
|
{
|
||||||
|
var weapon = Player.InventoryManager.GetWeaponItem(character.WeaponUniqueId);
|
||||||
|
if (weapon == null)
|
||||||
|
{
|
||||||
|
var cardData = GameData.CardData.Values.FirstOrDefault(x =>
|
||||||
|
GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level) == character.TemplateId);
|
||||||
|
if (cardData?.DefaultWeaponGPDL.Count >= 4)
|
||||||
|
{
|
||||||
|
weapon = await Player.InventoryManager.AddWeaponItem(
|
||||||
|
(ItemTypeEnum)cardData.DefaultWeaponGPDL[0],
|
||||||
|
cardData.DefaultWeaponGPDL[1],
|
||||||
|
cardData.DefaultWeaponGPDL[2],
|
||||||
|
cardData.DefaultWeaponGPDL[3]);
|
||||||
|
if (weapon != null)
|
||||||
|
{
|
||||||
|
character.WeaponUniqueId = weapon.UniqueId;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weapon == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (weapon.EquipAvatarId != character.Guid)
|
||||||
|
{
|
||||||
|
weapon.EquipAvatarId = character.Guid;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
equippedWeaponIds.Add(weapon.UniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var weapon in Player.InventoryManager.InventoryData.Weapons.Values)
|
||||||
|
{
|
||||||
|
if (!equippedWeaponIds.Contains(weapon.UniqueId) && weapon.EquipAvatarId != 0)
|
||||||
|
{
|
||||||
|
weapon.EquipAvatarId = 0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DatabaseHelper.SaveDatabaseType(CharacterData);
|
||||||
|
DatabaseHelper.SaveDatabaseType(Player.InventoryManager.InventoryData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
|
|||||||
TemplateId = templateId,
|
TemplateId = templateId,
|
||||||
UniqueId = InventoryData.NextUniqueUid++,
|
UniqueId = InventoryData.NextUniqueUid++,
|
||||||
Level = level,
|
Level = level,
|
||||||
|
Break = weaponData.InitBreak,
|
||||||
ItemCount = 1
|
ItemCount = 1
|
||||||
};
|
};
|
||||||
InventoryData.Weapons[weaponInfo.UniqueId] = weaponInfo;
|
InventoryData.Weapons[weaponInfo.UniqueId] = weaponInfo;
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ public class PlayerInstance(PlayerGameData data)
|
|||||||
public async ValueTask OnEnterGame()
|
public async ValueTask OnEnterGame()
|
||||||
{
|
{
|
||||||
if (!Initialized) await InitialPlayerManager();
|
if (!Initialized) await InitialPlayerManager();
|
||||||
|
await CharacterManager.RepairCharacterWeapons();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask OnLogin()
|
public async ValueTask OnLogin()
|
||||||
@@ -218,6 +219,11 @@ public class PlayerInstance(PlayerGameData data)
|
|||||||
yield return (178, 1, 1_700_000_000);
|
yield return (178, 1, 1_700_000_000);
|
||||||
yield return (187, 1, 2);
|
yield return (187, 1, 2);
|
||||||
|
|
||||||
|
// Cash.GetMoneyCount uses group 1 with sid = moneyId * 2 + 1 for most currencies.
|
||||||
|
// Fill a wide currency id range so every in-game currency starts effectively unlimited.
|
||||||
|
for (uint moneyId = 1; moneyId <= 200; moneyId++)
|
||||||
|
yield return (1, moneyId * 2 + 1, 999_999_999);
|
||||||
|
|
||||||
for (uint guideId = 1; guideId <= 150; guideId++)
|
for (uint guideId = 1; guideId <= 150; guideId++)
|
||||||
yield return (4, guideId, 999);
|
yield return (4, guideId, 999);
|
||||||
|
|
||||||
|
|||||||
384
GameServer/Server/CallGS/Handlers/Weapon/Weapon_Upgrade.cs
Normal file
384
GameServer/Server/CallGS/Handlers/Weapon/Weapon_Upgrade.cs
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
using MikuSB.Data;
|
||||||
|
using MikuSB.Data.Excel;
|
||||||
|
using MikuSB.Database;
|
||||||
|
using MikuSB.Database.Inventory;
|
||||||
|
using MikuSB.Proto;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MikuSB.GameServer.Server.CallGS.Handlers.Weapon;
|
||||||
|
|
||||||
|
[CallGSApi("Weapon_Upgrade")]
|
||||||
|
public class Weapon_Upgrade : ICallGSHandler
|
||||||
|
{
|
||||||
|
public async Task Handle(Connection connection, string param, ushort seqNo)
|
||||||
|
{
|
||||||
|
var player = connection.Player!;
|
||||||
|
var req = JsonSerializer.Deserialize<WeaponUpgradeParam>(param);
|
||||||
|
if (req == null || req.Id <= 0 || req.TbMat == null || req.TbMat.Count == 0)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetWeapon = player.InventoryManager.GetWeaponItem((uint)req.Id);
|
||||||
|
if (targetWeapon == null)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = WeaponUpgradeConfig.Load();
|
||||||
|
if (!config.TryGetWeaponTemplate(targetWeapon.TemplateId, out var targetTemplate))
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requestedMaterials = new Dictionary<uint, uint>();
|
||||||
|
foreach (var row in req.TbMat)
|
||||||
|
{
|
||||||
|
if (row == null || row.Count < 2) continue;
|
||||||
|
var itemId = (uint)Math.Max(0, row[0]);
|
||||||
|
var count = (uint)Math.Max(0, row[1]);
|
||||||
|
if (itemId == 0 || count == 0) continue;
|
||||||
|
requestedMaterials[itemId] = requestedMaterials.GetValueOrDefault(itemId) + count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestedMaterials.Count == 0)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong totalExp = 0;
|
||||||
|
var syncItems = new List<Item>();
|
||||||
|
var equippedWeaponIds = player.CharacterManager.CharacterData.Characters
|
||||||
|
.Select(x => x.WeaponUniqueId)
|
||||||
|
.Where(x => x != 0)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
foreach (var (itemId, count) in requestedMaterials)
|
||||||
|
{
|
||||||
|
if (itemId == targetWeapon.UniqueId)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var material = FindInventoryItem(player.InventoryManager.InventoryData, itemId);
|
||||||
|
if (material == null || material.ItemCount < count)
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (material is GameWeaponInfo materialWeapon &&
|
||||||
|
(materialWeapon.EquipAvatarId != 0 || equippedWeaponIds.Contains(materialWeapon.UniqueId)))
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"tip.material_not_enough\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetMaterialGain(config, material, out var gainExp))
|
||||||
|
{
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", "{\"sErr\":\"error.BadParam\"}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalExp += gainExp * count;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (itemId, count) in requestedMaterials)
|
||||||
|
{
|
||||||
|
var material = FindInventoryItem(player.InventoryManager.InventoryData, itemId)!;
|
||||||
|
material.ItemCount -= count;
|
||||||
|
|
||||||
|
if (material.ItemCount == 0)
|
||||||
|
{
|
||||||
|
RemoveInventoryItem(player.InventoryManager.InventoryData, itemId);
|
||||||
|
syncItems.Add(BuildRemovedProto(material));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
syncItems.Add(material.ToProto());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxLevel = config.GetWeaponMaxLevel(targetTemplate.BreakLimitId, targetWeapon.Break);
|
||||||
|
var oldLevel = targetWeapon.Level == 0 ? 1u : targetWeapon.Level;
|
||||||
|
targetWeapon.Level = oldLevel;
|
||||||
|
var (newLevel, newExp) = config.ApplyWeaponExp(targetWeapon.Level, targetWeapon.Exp, totalExp, targetTemplate.Color, maxLevel);
|
||||||
|
targetWeapon.Level = newLevel;
|
||||||
|
targetWeapon.Exp = newExp;
|
||||||
|
|
||||||
|
syncItems.Add(targetWeapon.ToProto());
|
||||||
|
|
||||||
|
DatabaseHelper.SaveDatabaseType(player.InventoryManager.InventoryData);
|
||||||
|
|
||||||
|
var sync = new NtfSyncPlayer();
|
||||||
|
sync.Items.AddRange(syncItems);
|
||||||
|
|
||||||
|
var bMaxUnlock = maxLevel > 0 && targetWeapon.Level >= maxLevel;
|
||||||
|
var arg = $"{{\"bMaxUnLock\":{(bMaxUnlock ? "true" : "false")}}}";
|
||||||
|
await CallGSRouter.SendScript(connection, "Weapon_Upgrade", arg, sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BaseGameItemInfo? FindInventoryItem(InventoryData inventory, uint itemId)
|
||||||
|
{
|
||||||
|
if (inventory.Weapons.TryGetValue(itemId, out var weapon))
|
||||||
|
{
|
||||||
|
return weapon;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory.Skins.TryGetValue(itemId, out var skin))
|
||||||
|
{
|
||||||
|
return skin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory.Items.TryGetValue(itemId, out var item))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveInventoryItem(InventoryData inventory, uint itemId)
|
||||||
|
{
|
||||||
|
inventory.Weapons.Remove(itemId);
|
||||||
|
inventory.Skins.Remove(itemId);
|
||||||
|
inventory.Items.Remove(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Item BuildRemovedProto(BaseGameItemInfo item)
|
||||||
|
{
|
||||||
|
var proto = item.ToProto();
|
||||||
|
proto.Count = 0;
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetMaterialGain(WeaponUpgradeConfig config, BaseGameItemInfo item, out ulong exp)
|
||||||
|
{
|
||||||
|
exp = 0;
|
||||||
|
if (config.TryGetWeaponTemplate(item.TemplateId, out var weaponTemplate))
|
||||||
|
{
|
||||||
|
exp = weaponTemplate.ProvideExp;
|
||||||
|
if (item is GameWeaponInfo weapon && weapon.Level > 1)
|
||||||
|
{
|
||||||
|
exp += config.GetWeaponRecycleExp(weaponTemplate, weapon.Level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.TryGetSuppliesTemplate(item.TemplateId, out var suppliesTemplate))
|
||||||
|
{
|
||||||
|
exp = suppliesTemplate.ProvideExp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WeaponUpgradeParam
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("tbMat")]
|
||||||
|
public List<List<int>> TbMat { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class WeaponUpgradeConfig
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, uint> _weaponNeedExpNormal;
|
||||||
|
private readonly Dictionary<int, uint> _weaponNeedExpSsr;
|
||||||
|
private readonly Dictionary<int, uint[]> _breakLimits;
|
||||||
|
private readonly Dictionary<int, RecycleEntry> _recycleById;
|
||||||
|
private readonly Dictionary<ulong, MaterialTemplate> _weaponTemplates;
|
||||||
|
private readonly Dictionary<ulong, MaterialTemplate> _suppliesTemplates;
|
||||||
|
private readonly Dictionary<int, ulong> _weaponRecycleExpNormal = [];
|
||||||
|
private readonly Dictionary<int, ulong> _weaponRecycleExpSsr = [];
|
||||||
|
|
||||||
|
public WeaponUpgradeConfig(
|
||||||
|
Dictionary<int, uint> weaponNeedExpNormal,
|
||||||
|
Dictionary<int, uint> weaponNeedExpSsr,
|
||||||
|
Dictionary<int, uint[]> breakLimits,
|
||||||
|
Dictionary<int, RecycleEntry> recycleById,
|
||||||
|
Dictionary<ulong, MaterialTemplate> weaponTemplates,
|
||||||
|
Dictionary<ulong, MaterialTemplate> suppliesTemplates)
|
||||||
|
{
|
||||||
|
_weaponNeedExpNormal = weaponNeedExpNormal;
|
||||||
|
_weaponNeedExpSsr = weaponNeedExpSsr;
|
||||||
|
_breakLimits = breakLimits;
|
||||||
|
_recycleById = recycleById;
|
||||||
|
_weaponTemplates = weaponTemplates;
|
||||||
|
_suppliesTemplates = suppliesTemplates;
|
||||||
|
|
||||||
|
BuildRecycleExpTable(_weaponNeedExpNormal, _weaponRecycleExpNormal);
|
||||||
|
BuildRecycleExpTable(_weaponNeedExpSsr, _weaponRecycleExpSsr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeaponUpgradeConfig Load()
|
||||||
|
{
|
||||||
|
var normalExp = new Dictionary<int, uint>();
|
||||||
|
var ssrExp = new Dictionary<int, uint>();
|
||||||
|
foreach (var row in GameData.UpgradeExpData.Values)
|
||||||
|
{
|
||||||
|
normalExp[row.Lv] = row.WeaponNeedExp;
|
||||||
|
ssrExp[row.Lv] = row.SSRWeaponNeedExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
var breakLimits = new Dictionary<int, uint[]>();
|
||||||
|
foreach (var row in GameData.BreakLevelLimitData.Values)
|
||||||
|
{
|
||||||
|
breakLimits[row.ID] =
|
||||||
|
[
|
||||||
|
row.Break0,
|
||||||
|
row.Break1,
|
||||||
|
row.Break2,
|
||||||
|
row.Break3,
|
||||||
|
row.Break4,
|
||||||
|
row.Break5,
|
||||||
|
row.Break6
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
var recycleById = new Dictionary<int, RecycleEntry>();
|
||||||
|
foreach (var row in GameData.RecycleData.Values)
|
||||||
|
{
|
||||||
|
recycleById[row.ID] = new RecycleEntry(
|
||||||
|
GetUIntFlexible(row.RecycleBase),
|
||||||
|
GetDecimalFlexible(row.RecycleRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
var weaponTemplates = GameData.WeaponData.Values.ToDictionary(
|
||||||
|
x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level),
|
||||||
|
x => new MaterialTemplate(x.Color, x.ProvideExp, x.ConsumeGold, x.RecycleID, x.BreakLimitID));
|
||||||
|
var suppliesTemplates = GameData.SuppliesData.Values.ToDictionary(
|
||||||
|
x => GameResourceTemplateId.FromGdpl(x.Genre, x.Detail, x.Particular, x.Level),
|
||||||
|
x => new MaterialTemplate(x.Color, x.ProvideExp, x.ConsumeGold, 0, 0));
|
||||||
|
|
||||||
|
return new WeaponUpgradeConfig(normalExp, ssrExp, breakLimits, recycleById, weaponTemplates, suppliesTemplates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetWeaponTemplate(ulong templateId, out MaterialTemplate template) =>
|
||||||
|
_weaponTemplates.TryGetValue(templateId, out template!);
|
||||||
|
|
||||||
|
public bool TryGetSuppliesTemplate(ulong templateId, out MaterialTemplate template) =>
|
||||||
|
_suppliesTemplates.TryGetValue(templateId, out template!);
|
||||||
|
|
||||||
|
public ulong GetWeaponRecycleExp(MaterialTemplate template, uint level)
|
||||||
|
{
|
||||||
|
if (template.RecycleId <= 0 || !_recycleById.TryGetValue(template.RecycleId, out var recycle))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelExp = template.Color == 5 ? _weaponRecycleExpSsr : _weaponRecycleExpNormal;
|
||||||
|
var baseExp = levelExp.GetValueOrDefault((int)level);
|
||||||
|
return (ulong)Math.Floor((recycle.RecycleBase + baseExp) * recycle.RecycleRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetWeaponMaxLevel(int breakLimitId, uint currentBreak)
|
||||||
|
{
|
||||||
|
if (!_breakLimits.TryGetValue(breakLimitId, out var limits) || limits.Length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = (int)Math.Min(currentBreak, (uint)(limits.Length - 1));
|
||||||
|
return limits[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public (uint Level, uint Exp) ApplyWeaponExp(uint level, uint exp, ulong addExp, int color, uint maxLevel)
|
||||||
|
{
|
||||||
|
if (addExp == 0)
|
||||||
|
{
|
||||||
|
return (level, exp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxLevel > 0 && level >= maxLevel)
|
||||||
|
{
|
||||||
|
return (maxLevel, checked((uint)(exp + addExp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var destLevel = level;
|
||||||
|
var destExp = exp + addExp;
|
||||||
|
var needExp = GetWeaponNeedExp(color, destLevel);
|
||||||
|
|
||||||
|
while (needExp > 0 && destExp >= needExp)
|
||||||
|
{
|
||||||
|
destExp -= needExp;
|
||||||
|
destLevel++;
|
||||||
|
|
||||||
|
if (maxLevel > 0 && destLevel >= maxLevel)
|
||||||
|
{
|
||||||
|
return (maxLevel, checked((uint)destExp));
|
||||||
|
}
|
||||||
|
|
||||||
|
needExp = GetWeaponNeedExp(color, destLevel);
|
||||||
|
if (needExp == 0)
|
||||||
|
{
|
||||||
|
return (destLevel, checked((uint)destExp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (destLevel, checked((uint)destExp));
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint GetWeaponNeedExp(int color, uint level)
|
||||||
|
{
|
||||||
|
return color == 5
|
||||||
|
? _weaponNeedExpSsr.GetValueOrDefault((int)level)
|
||||||
|
: _weaponNeedExpNormal.GetValueOrDefault((int)level);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildRecycleExpTable(Dictionary<int, uint> needExp, Dictionary<int, ulong> recycleExp)
|
||||||
|
{
|
||||||
|
ulong current = 0;
|
||||||
|
foreach (var level in needExp.Keys.OrderBy(x => x))
|
||||||
|
{
|
||||||
|
recycleExp[level] = current;
|
||||||
|
current += needExp[level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetUIntFlexible(JToken? token)
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Type switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => token.Value<uint>(),
|
||||||
|
JTokenType.Float => (uint)Math.Max(0, token.Value<decimal>()),
|
||||||
|
JTokenType.String when uint.TryParse(token.Value<string>(), out var result) => result,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static decimal GetDecimalFlexible(JToken? token)
|
||||||
|
{
|
||||||
|
if (token == null)
|
||||||
|
{
|
||||||
|
return 0m;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.Type switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => token.Value<decimal>(),
|
||||||
|
JTokenType.Float => token.Value<decimal>(),
|
||||||
|
JTokenType.String when decimal.TryParse(token.Value<string>(), out var result) => result,
|
||||||
|
_ => 0m
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly record struct MaterialTemplate(int Color, uint ProvideExp, uint ConsumeGold, int RecycleId, int BreakLimitId);
|
||||||
|
internal readonly record struct RecycleEntry(uint RecycleBase, decimal RecycleRatio);
|
||||||
Reference in New Issue
Block a user