add weapon skin & command for it

This commit is contained in:
Naruse
2026-04-28 23:48:14 +08:00
parent 5f0de1a9f0
commit 8d6e0d7638
9 changed files with 138 additions and 3 deletions

View File

@@ -0,0 +1,24 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MikuSB.Data.Excel;
[ResourceEntity("weapon_skin.json")]
public class WeaponSkinExcel : ExcelResource
{
public uint Genre { get; set; }
public uint Detail { get; set; }
public uint Particular { get; set; }
public uint Level { get; set; }
public string I18n { get; set; } = "";
public override uint GetId()
{
return (uint)I18n.GetHashCode();
}
public override void Loaded()
{
GameData.WeaponSkinData.Add(GetId(), this);
}
}

View File

@@ -19,6 +19,7 @@ public static class GameData
public static Dictionary<uint, SpineExcel> SpineData { get; private set; } = []; public static Dictionary<uint, SpineExcel> SpineData { get; private set; } = [];
public static Dictionary<uint, NodeConditionExcel> NodeConditionData { get; private set; } = []; public static Dictionary<uint, NodeConditionExcel> NodeConditionData { get; private set; } = [];
public static List<SupportCardExcel> SupportCardData { get; private set; } = []; public static List<SupportCardExcel> SupportCardData { get; private set; } = [];
public static Dictionary<uint, WeaponSkinExcel> WeaponSkinData { get; private set; } = [];
} }
public static class GameResourceTemplateId public static class GameResourceTemplateId

View File

@@ -23,6 +23,7 @@ public class CharacterInfo
public int Trust { get; set; } public int Trust { get; set; }
public uint WeaponUniqueId { get; set; } public uint WeaponUniqueId { get; set; }
public uint SkinId { get; set; } public uint SkinId { get; set; }
public uint WeaponSkinId { get; set; }
public ItemFlagEnum Flag { get; set; } = ItemFlagEnum.FLAG_READED; public ItemFlagEnum Flag { get; set; } = ItemFlagEnum.FLAG_READED;
public uint Expiration { get; set; } public uint Expiration { get; set; }
[SugarColumn(IsJson = true)] public List<uint> UnlockedSkin { get; set; } = []; [SugarColumn(IsJson = true)] public List<uint> UnlockedSkin { get; set; } = [];
@@ -57,6 +58,7 @@ public class CharacterInfo
proto.Slots[4] = WeaponUniqueId; proto.Slots[4] = WeaponUniqueId;
proto.Slots[5] = SkinId; proto.Slots[5] = SkinId;
proto.Slots[6] = WeaponSkinId;
foreach (var (slot, uid) in SupportSlots) foreach (var (slot, uid) in SupportSlots)
proto.Slots[slot] = uid; proto.Slots[slot] = uid;

View File

@@ -35,6 +35,7 @@ public class ServerTextCHS
/// </summary> /// </summary>
public class WordTextCHS public class WordTextCHS
{ {
public string WeaponSkin => "武器皮肤";
public string SupportCard => "支援卡"; public string SupportCard => "支援卡";
public string Weapon => "武器"; public string Weapon => "武器";
public string Rank => "星魂"; public string Rank => "星魂";
@@ -236,8 +237,9 @@ public class GiveAllTextCHS
{ {
public string Desc => "给予玩家所有物品\n" + public string Desc => "给予玩家所有物品\n" +
"注意:-1 表示全部"; "注意:-1 表示全部";
public string Usage => "用法:/giveall weapon <detail/-1> -p<特定> -l<等>\n" + public string Usage => "用法:/giveall weapon <detail/-1> -p<特定> -l<等>\n" +
"用法:/giveall card <detail/-1> -p<特定> -l<等级>"; "用法:/giveall weaponskin <detail/-1> -p<特定>\n" +
"用法:/giveall card <detail/-1> -p<特定> -l<等級>";
public string NotFound => "未找到 {0}"; public string NotFound => "未找到 {0}";
public string GiveAllItems => "已向玩家添加 {0} 个 {1}"; public string GiveAllItems => "已向玩家添加 {0} 个 {1}";
} }

View File

@@ -35,6 +35,7 @@ public class ServerTextCHT
/// </summary> /// </summary>
public class WordTextCHT public class WordTextCHT
{ {
public string WeaponSkin => "武器外觀";
public string SupportCard => "支援卡"; public string SupportCard => "支援卡";
public string Weapon => "武器"; public string Weapon => "武器";
public string Rank => "星魂"; public string Rank => "星魂";
@@ -237,6 +238,7 @@ public class GiveAllTextCHT
public string Desc => "給予玩家所有物品\n" + public string Desc => "給予玩家所有物品\n" +
"注意:-1 表示全部"; "注意:-1 表示全部";
public string Usage => "用法:/giveall weapon <detail/-1> -p<特定> -l<等級>\n" + public string Usage => "用法:/giveall weapon <detail/-1> -p<特定> -l<等級>\n" +
"用法:/giveall weaponskin <detail/-1> -p<特定>\n" +
"用法:/giveall card <detail/-1> -p<特定> -l<等級>"; "用法:/giveall card <detail/-1> -p<特定> -l<等級>";
public string NotFound => "未找到 {0}"; public string NotFound => "未找到 {0}";
public string GiveAllItems => "已向玩家添加 {0} 個 {1}"; public string GiveAllItems => "已向玩家添加 {0} 個 {1}";

View File

@@ -35,7 +35,7 @@ public class ServerTextEN
/// </summary> /// </summary>
public class WordTextEN public class WordTextEN
{ {
public string Star => "Star"; public string WeaponSkin => "Weapon Skin";
public string Valk => "Valkyrie"; public string Valk => "Valkyrie";
public string Material => "Material"; public string Material => "Material";
public string SupportCard => "Support Card"; public string SupportCard => "Support Card";
@@ -204,6 +204,7 @@ public class GiveAllTextEN
public string Desc => "Give all items to player\n"+ public string Desc => "Give all items to player\n"+
"Note: -1 means all"; "Note: -1 means all";
public string Usage => "Usage: /giveall weapon <detail/-1> -p<particular> -l<level>\n" + public string Usage => "Usage: /giveall weapon <detail/-1> -p<particular> -l<level>\n" +
"Usage: /giveall weaponskin <detail/-1> -p<particular>\n" +
"Usage: /giveall card <detail/-1> -p<particular> -l<level>"; "Usage: /giveall card <detail/-1> -p<particular> -l<level>";
public string NotFound => "{0} not found!"; public string NotFound => "{0} not found!";
public string GiveAllItems => "Added {0} {1} to player!"; public string GiveAllItems => "Added {0} {1} to player!";

View File

@@ -80,4 +80,38 @@ public class CommandGiveAll : ICommands
await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems", await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems",
I18NManager.Translate("Word.SupportCard"), supportCards.Count.ToString())); I18NManager.Translate("Word.SupportCard"), supportCards.Count.ToString()));
} }
[CommandMethod("weaponskin")]
public async ValueTask GiveAllWeaponSkin(CommandArg arg)
{
if (!await arg.CheckOnlineTarget()) return;
if (await arg.GetOption('p') is not int particular) return;
var detail = arg.GetInt(0);
var player = arg.Target!.Player!;
List<BaseGameItemInfo> weaponSkins = [];
if (detail == -1)
{
// add all
foreach (var config in GameData.WeaponSkinData.Values)
{
var weaponSkin = await player.InventoryManager!
.AddWeaponSkinItem((ItemTypeEnum)config.Genre, config.Detail, config.Particular, 1, false);
if (weaponSkin != null) weaponSkins.Add(weaponSkin);
}
}
else
{
var weaponSkin = await player.InventoryManager!.AddWeaponSkinItem(ItemTypeEnum.TYPE_WEAPON, (uint)detail, (uint)particular, 1, false);
if (weaponSkin == null)
{
await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.NotFound", I18NManager.Translate("Word.WeaponSkin")));
return;
}
weaponSkins.Add(weaponSkin);
}
if (weaponSkins.Count > 0) await player.SendPacket(new PacketNtfCallScript(weaponSkins));
await arg.SendMsg(I18NManager.Translate("Game.Command.GiveAll.GiveAllItems",
I18NManager.Translate("Word.WeaponSkin"), weaponSkins.Count.ToString()));
}
} }

View File

@@ -216,4 +216,26 @@ public class InventoryManager(PlayerInstance player) : BasePlayerManager(player)
return itemInfo; return itemInfo;
} }
public async ValueTask<BaseGameItemInfo?> AddWeaponSkinItem(ItemTypeEnum genre, uint detail, uint particular, uint level = 1, bool sendPacket = true)
{
if (genre != ItemTypeEnum.TYPE_WEAPON_SKIN) return null;
var skinData = GameData.WeaponSkinData.Values.FirstOrDefault(x => x.Genre == (int)genre && x.Detail == detail && x.Particular == particular && x.Level == level);
if (skinData == null) return null;
var templateId = GameResourceTemplateId.FromGdpl((uint)genre, detail, particular, level);
if (InventoryData.Items.Values.Any(x => x.TemplateId == templateId)) return null;
var skinInfo = new BaseGameItemInfo
{
TemplateId = templateId,
UniqueId = InventoryData.NextUniqueUid++,
ItemType = ItemTypeEnum.TYPE_WEAPON_SKIN,
ItemCount = 1
};
InventoryData.Items[skinInfo.UniqueId] = skinInfo;
if (sendPacket) await Player.SendPacket(new PacketNtfCallScript([skinInfo]));
return skinInfo;
}
} }

View File

@@ -0,0 +1,47 @@
using MikuSB.Proto;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace MikuSB.GameServer.Server.CallGS.Handlers.Girl;
[CallGSApi("GirlWeaponSkin_Change")]
public class GirlWeaponSkin_Change : ICallGSHandler
{
public async Task Handle(Connection connection, string param, ushort seqNo)
{
var req = JsonSerializer.Deserialize<GirlWeaponSkinParam>(param);
if (req == null)
{
await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}");
return;
}
var player = connection.Player!;
var cardData = player.CharacterManager.GetCharacterByGUID(req.CardId);
if (cardData == null) return;
var skinData = player.InventoryManager.GetNormalItem(req.SkinId);
if (skinData == null)
{
await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{\"err\":\"error.BadParam\"}");
return;
}
cardData.WeaponSkinId = req.SkinId;
var sync = new NtfSyncPlayer
{
Items = { cardData.ToProto() }
};
await CallGSRouter.SendScript(connection, "GirlWeaponSkin_Change", "{}", sync);
}
}
internal sealed class GirlWeaponSkinParam
{
[JsonPropertyName("nCardId")]
public uint CardId { get; set; }
[JsonPropertyName("nSkinId")]
public uint SkinId { get; set; }
}