Drops now land on the ground as gadgets and are picked up with F instead of appearing in the bag out of nowhere. Chests, ore, crystals and plants all spawn and can be interacted with. Monsters load by scene group rather than by a radius around each one, so a camp shows up as a camp and nothing pops into existence mid fight. The group anchors come out of the scene Lua, which also killed four invented tuning constants. The gateway was creating a whole new session for every handshake datagram, and the client retransmits those until it gets an answer. That is where the piles of duplicated monsters and the chests coming back from the dead were coming from. Also: monster level scales to the party now, path queries return an actual two point path instead of an empty one, NpcTalk closes properly, and the ability and combat invocations are no longer echoed back to the client that sent them.
480 lines
18 KiB
C#
480 lines
18 KiB
C#
using Yuanshen.Common.Data.Excel;
|
|
using Yuanshen.Gameserver.Database;
|
|
|
|
namespace Yuanshen.Gameserver.Game;
|
|
|
|
internal sealed class Player
|
|
{
|
|
public uint Uid { get; private set; } = 1;
|
|
public const uint PeerId = 1;
|
|
public const uint TeamEntityId = 0x09000001;
|
|
private const uint StarterReliquaryRank = 5;
|
|
public const uint MpLevelEntityId = 0x0B000001;
|
|
|
|
public static readonly uint[] Flycloaks = { 140001, 140002, 140003, 140004, 140005 };
|
|
|
|
public string Nickname { get; } = "Traveler";
|
|
|
|
public List<GameAvatar> Avatars { get; } = new();
|
|
public List<GameWeapon> Weapons { get; } = new();
|
|
public List<GameReliquary> Reliquaries { get; } = new();
|
|
public List<GameMonster> Monsters { get; } = new();
|
|
public List<GameGadget> Gadgets { get; } = new();
|
|
public HashSet<uint> KilledNodeIds { get; } = new();
|
|
public HashSet<uint> OpenedGadgetIds { get; } = new();
|
|
public Dictionary<uint, uint> Materials { get; } = new();
|
|
|
|
public bool IsInitialized { get; private set; }
|
|
|
|
public uint CurrentTeamId { get; set; } = 1;
|
|
public List<ulong> TeamAvatarGuids { get; } = new();
|
|
public ulong CurrentAvatarGuid { get; set; }
|
|
|
|
private readonly ExcelTableCollection _excel;
|
|
private uint _guidSeq;
|
|
private uint _defaultGuidCeiling;
|
|
private uint _avatarEntitySeq;
|
|
private uint _weaponEntitySeq;
|
|
private uint _monsterEntitySeq;
|
|
private uint _gadgetEntitySeq;
|
|
|
|
public Player(ExcelTableCollection excel) => _excel = excel;
|
|
|
|
public void AssignUid(uint uid) => Uid = uid;
|
|
|
|
public const uint StartingHcoin = 1_600_000;
|
|
public const uint StartingScoin = 9_999_999;
|
|
|
|
public const uint CurrentSpawnDataVersion = 2;
|
|
|
|
public ulong NextGuid() => ((ulong)Uid << 32) | ++_guidSeq;
|
|
|
|
public GameAvatar? ActiveAvatar => FindAvatar(CurrentAvatarGuid);
|
|
public GameAvatar? FindAvatar(ulong guid) => Avatars.FirstOrDefault(avatar => avatar.Guid == guid);
|
|
public GameWeapon? FindWeapon(ulong guid) => Weapons.FirstOrDefault(weapon => weapon.Guid == guid);
|
|
public GameReliquary? FindReliquary(ulong guid) => Reliquaries.FirstOrDefault(item => item.Guid == guid);
|
|
|
|
public GameReliquary? FindReliquaryByItemId(uint itemId) => Reliquaries.FirstOrDefault(item => item.ItemId == itemId);
|
|
|
|
public IEnumerable<GameAvatar> TeamAvatars
|
|
=> TeamAvatarGuids.Select(FindAvatar).OfType<GameAvatar>();
|
|
|
|
public void InitDefault()
|
|
{
|
|
if (IsInitialized)
|
|
return;
|
|
|
|
List<AvatarSkillDepotExcel> depots = _excel.GetTable(ExcelType.AvatarSkillDepot)
|
|
.Items.OfType<AvatarSkillDepotExcel>().ToList();
|
|
List<AvatarAbilitiesExcel> abilities = _excel.GetTable(ExcelType.AvatarAbilities)
|
|
.Items.OfType<AvatarAbilitiesExcel>().ToList();
|
|
|
|
foreach (AvatarExcel excel in _excel.GetTable(ExcelType.Avatar).Items.OfType<AvatarExcel>())
|
|
{
|
|
AvatarAbilitiesExcel? avatarAbilities = abilities.FirstOrDefault(a => a.Id == excel.Id);
|
|
if (avatarAbilities is null)
|
|
continue;
|
|
|
|
AvatarSkillDepotExcel? depot = depots.FirstOrDefault(d => d.Id == excel.SkillDepotId);
|
|
GameAvatar avatar = new(excel, NextGuid(), 0x01000000 + ++_avatarEntitySeq, 0x06000000 + ++_weaponEntitySeq, depot,
|
|
avatarAbilities);
|
|
GameWeapon weapon = CreateWeapon(excel.InitialWeapon);
|
|
avatar.WeaponGuid = weapon.Guid;
|
|
avatar.OwnerUid = Uid;
|
|
Avatars.Add(avatar);
|
|
}
|
|
|
|
foreach (WeaponExcel excel in _excel.GetTable(ExcelType.Weapon).Items.OfType<WeaponExcel>())
|
|
CreateWeapon(excel.Id);
|
|
|
|
CreateReliquaries();
|
|
|
|
CurrentAvatarGuid = Avatars[0].Guid;
|
|
TeamAvatarGuids.Add(Avatars[0].Guid);
|
|
_defaultGuidCeiling = _guidSeq;
|
|
IsInitialized = true;
|
|
}
|
|
|
|
public GameAvatar? GrantAvatar(uint avatarId)
|
|
{
|
|
if (Avatars.Any(existing => existing.AvatarId == avatarId))
|
|
return null;
|
|
|
|
AvatarExcel? excel = _excel.GetTable(ExcelType.Avatar).Items.OfType<AvatarExcel>()
|
|
.FirstOrDefault(candidate => candidate.Id == avatarId);
|
|
AvatarAbilitiesExcel? abilities = _excel.GetTable(ExcelType.AvatarAbilities).Items.OfType<AvatarAbilitiesExcel>()
|
|
.FirstOrDefault(candidate => candidate.Id == avatarId);
|
|
|
|
if (excel is null)
|
|
return null;
|
|
|
|
AvatarSkillDepotExcel? depot = _excel.GetTable(ExcelType.AvatarSkillDepot).Items.OfType<AvatarSkillDepotExcel>()
|
|
.FirstOrDefault(candidate => candidate.Id == excel.SkillDepotId);
|
|
|
|
GameAvatar avatar = new(excel, NextGuid(), 0x01000000 + ++_avatarEntitySeq, 0x06000000 + ++_weaponEntitySeq, depot, abilities);
|
|
GameWeapon weapon = CreateWeapon(excel.InitialWeapon);
|
|
avatar.WeaponGuid = weapon.Guid;
|
|
avatar.OwnerUid = Uid;
|
|
Avatars.Add(avatar);
|
|
|
|
return avatar;
|
|
}
|
|
|
|
public bool HasAvatarData(uint avatarId)
|
|
=> _excel.GetTable(ExcelType.Avatar).Items.OfType<AvatarExcel>().Any(candidate => candidate.Id == avatarId);
|
|
|
|
public bool HasAbilities(uint avatarId)
|
|
=> _excel.GetTable(ExcelType.AvatarAbilities).Items.OfType<AvatarAbilitiesExcel>().Any(candidate => candidate.Id == avatarId);
|
|
|
|
public void ResetTeam()
|
|
{
|
|
TeamAvatarGuids.Clear();
|
|
TeamAvatarGuids.Add(Avatars[0].Guid);
|
|
CurrentAvatarGuid = Avatars[0].Guid;
|
|
}
|
|
|
|
public ulong MaterialGuid(uint itemId) => ((ulong)Uid << 32) | (0x80000000u | itemId);
|
|
|
|
public void MarkNodeKilled(uint nodeId) => KilledNodeIds.Add(nodeId);
|
|
|
|
public Dictionary<uint, uint> GachaPity { get; } = new();
|
|
public Dictionary<uint, uint> GachaGuarantee { get; } = new();
|
|
public List<GachaLogEntry> GachaLog { get; } = new();
|
|
public Dictionary<uint, uint> BoughtGoods { get; } = new();
|
|
public Dictionary<uint, uint> ClientGadgets { get; } = new();
|
|
|
|
public uint Hcoin { get; set; } = StartingHcoin;
|
|
public uint Scoin { get; set; } = StartingScoin;
|
|
|
|
public Yuanshen.Protocol.PlayerPropNotify BuildCurrencyProps()
|
|
{
|
|
Yuanshen.Protocol.PlayerPropNotify notify = new();
|
|
notify.PropMap.Add(10015u, new Yuanshen.Protocol.PropValue { Type = 10015, Ival = Hcoin, Val = Hcoin });
|
|
notify.PropMap.Add(10016u, new Yuanshen.Protocol.PropValue { Type = 10016, Ival = Scoin, Val = Scoin });
|
|
return notify;
|
|
}
|
|
|
|
public void TakeMaterial(uint itemId, uint count)
|
|
{
|
|
uint left = Materials.GetValueOrDefault(itemId);
|
|
Materials[itemId] = left > count ? left - count : 0;
|
|
}
|
|
|
|
public void GiveMaterial(uint itemId, uint count)
|
|
=> Materials[itemId] = Materials.GetValueOrDefault(itemId) + count;
|
|
|
|
public GameMonster SpawnMonster(uint monsterId, Yuanshen.Protocol.Vector pos, uint level, float maxHp, float attack, float defense, uint nodeId = 0, uint configId = 0, uint groupId = 0)
|
|
{
|
|
GameMonster monster = new(monsterId, 0x02000000 + ++_monsterEntitySeq, pos, level, maxHp, attack, defense)
|
|
{
|
|
NodeId = nodeId,
|
|
ConfigId = configId,
|
|
GroupId = groupId
|
|
};
|
|
Monsters.Add(monster);
|
|
return monster;
|
|
}
|
|
|
|
public GameGadget SpawnDrop(uint gadgetId, uint itemId, uint count, Yuanshen.Protocol.Vector pos)
|
|
{
|
|
GadgetNode node = new(0, 0, 0, gadgetId, 1, pos, new Yuanshen.Protocol.Vector(), "drop", 0, 0, 0, 0, 0, 0);
|
|
|
|
GameGadget drop = new(node, 0x04000000 + ++_gadgetEntitySeq)
|
|
{
|
|
DropItemId = itemId,
|
|
DropCount = count,
|
|
DropGuid = NextGuid()
|
|
};
|
|
|
|
Gadgets.Add(drop);
|
|
return drop;
|
|
}
|
|
|
|
public GameGadget SpawnGadget(GadgetNode node)
|
|
{
|
|
GameGadget gadget = new(node, 0x04000000 + ++_gadgetEntitySeq);
|
|
Gadgets.Add(gadget);
|
|
return gadget;
|
|
}
|
|
|
|
public PlayerRecord ToRecord(Yuanshen.Protocol.Vector pos) => new()
|
|
{
|
|
Uid = Uid,
|
|
Nickname = Nickname,
|
|
CurrentTeamId = CurrentTeamId,
|
|
CurrentAvatarGuid = CurrentAvatarGuid,
|
|
TeamAvatarGuids = TeamAvatarGuids.ToList(),
|
|
TeamAvatarIds = TeamAvatars.Select(avatar => avatar.AvatarId).ToList(),
|
|
CurrentAvatarId = ActiveAvatar?.AvatarId ?? 0,
|
|
EquippedWeapons = Avatars
|
|
.GroupBy(a => a.AvatarId)
|
|
.ToDictionary(group => group.Key, group => FindWeapon(group.First().WeaponGuid)?.ItemId ?? 0UL),
|
|
EquippedReliquaries = Avatars
|
|
.Where(a => a.ReliquaryGuids.Count != 0)
|
|
.GroupBy(a => a.AvatarId)
|
|
.ToDictionary(group => group.Key, group => group.First().ReliquaryGuids
|
|
.ToDictionary(slot => slot.Key, slot => (ulong)(FindReliquary(slot.Value)?.ItemId ?? 0))),
|
|
AvatarProgress = Avatars
|
|
.GroupBy(a => a.AvatarId)
|
|
.ToDictionary(group => group.Key, group => BuildProgress(group.First())),
|
|
Materials = new Dictionary<uint, uint>(Materials),
|
|
KilledNodes = KilledNodeIds.ToList(),
|
|
OpenedGadgets = OpenedGadgetIds.ToList(),
|
|
DroppedWeapons = Weapons.Where(IsDropped).Select(weapon => new DroppedWeapon
|
|
{
|
|
ItemId = weapon.ItemId,
|
|
Level = weapon.Level,
|
|
PromoteLevel = weapon.PromoteLevel,
|
|
Refinement = weapon.Refinement
|
|
}).ToList(),
|
|
DroppedReliquaries = Reliquaries.Where(IsDropped).Select(reliquary => new DroppedReliquary
|
|
{
|
|
ItemId = reliquary.ItemId,
|
|
EquipType = reliquary.EquipType,
|
|
MainPropId = reliquary.MainPropId,
|
|
Level = reliquary.Level,
|
|
PromoteLevel = reliquary.PromoteLevel,
|
|
AppendPropIds = reliquary.AppendPropIds.ToList()
|
|
}).ToList(),
|
|
SpawnDataVersion = CurrentSpawnDataVersion,
|
|
GachaPity = GachaPity.ToDictionary(pair => pair.Key, pair => pair.Value),
|
|
GachaGuarantee = GachaGuarantee.ToDictionary(pair => pair.Key, pair => pair.Value),
|
|
GachaLog = GachaLog.ToList(),
|
|
BoughtGoods = BoughtGoods.ToDictionary(pair => pair.Key, pair => pair.Value),
|
|
Hcoin = Hcoin,
|
|
Scoin = Scoin,
|
|
PosX = pos.X,
|
|
PosY = pos.Y,
|
|
PosZ = pos.Z
|
|
};
|
|
|
|
public void ApplyRecord(PlayerRecord record)
|
|
{
|
|
CurrentTeamId = record.CurrentTeamId;
|
|
|
|
RestoreDropped(record);
|
|
|
|
Hcoin = record.Hcoin != 0 || record.Scoin != 0 ? record.Hcoin : StartingHcoin;
|
|
Scoin = record.Hcoin != 0 || record.Scoin != 0 ? record.Scoin : StartingScoin;
|
|
|
|
BoughtGoods.Clear();
|
|
foreach ((uint goodsId, uint bought) in record.BoughtGoods)
|
|
BoughtGoods[goodsId] = bought;
|
|
|
|
GachaPity.Clear();
|
|
foreach ((uint gachaType, uint counter) in record.GachaPity)
|
|
GachaPity[gachaType] = counter;
|
|
|
|
GachaGuarantee.Clear();
|
|
foreach ((uint gachaType, uint flag) in record.GachaGuarantee)
|
|
GachaGuarantee[gachaType] = flag;
|
|
|
|
GachaLog.Clear();
|
|
GachaLog.AddRange(record.GachaLog);
|
|
|
|
KilledNodeIds.Clear();
|
|
foreach (uint nodeId in record.SpawnDataVersion == CurrentSpawnDataVersion ? record.KilledNodes : new List<uint>())
|
|
KilledNodeIds.Add(nodeId);
|
|
|
|
OpenedGadgetIds.Clear();
|
|
foreach (uint gadgetNodeId in record.OpenedGadgets)
|
|
OpenedGadgetIds.Add(gadgetNodeId);
|
|
|
|
HashSet<ulong> takenWeapons = new();
|
|
foreach ((uint avatarId, ulong itemId) in record.EquippedWeapons)
|
|
{
|
|
GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId);
|
|
GameWeapon? weapon = Weapons.FirstOrDefault(w => w.ItemId == itemId && !takenWeapons.Contains(w.Guid));
|
|
if (avatar is null || weapon is null)
|
|
continue;
|
|
|
|
avatar.WeaponGuid = weapon.Guid;
|
|
takenWeapons.Add(weapon.Guid);
|
|
}
|
|
|
|
HashSet<ulong> takenReliquaries = new();
|
|
foreach ((uint avatarId, Dictionary<uint, ulong> slots) in record.EquippedReliquaries)
|
|
{
|
|
GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId);
|
|
if (avatar is null)
|
|
continue;
|
|
|
|
foreach ((uint slot, ulong itemId) in slots)
|
|
{
|
|
GameReliquary? reliquary = Reliquaries
|
|
.FirstOrDefault(r => r.ItemId == itemId && !takenReliquaries.Contains(r.Guid));
|
|
if (reliquary is null)
|
|
continue;
|
|
|
|
avatar.ReliquaryGuids[slot] = reliquary.Guid;
|
|
takenReliquaries.Add(reliquary.Guid);
|
|
}
|
|
}
|
|
|
|
foreach ((uint avatarId, AvatarProgress progress) in record.AvatarProgress)
|
|
{
|
|
GameAvatar? avatar = Avatars.FirstOrDefault(a => a.AvatarId == avatarId);
|
|
if (avatar is null)
|
|
continue;
|
|
|
|
avatar.Level = progress.Level;
|
|
avatar.PromoteLevel = progress.PromoteLevel;
|
|
avatar.TalentLevel = progress.TalentLevel;
|
|
avatar.Constellation = progress.Constellation;
|
|
|
|
GameWeapon? weapon = FindWeapon(avatar.WeaponGuid);
|
|
if (weapon is not null)
|
|
{
|
|
weapon.Level = progress.WeaponLevel;
|
|
weapon.PromoteLevel = progress.WeaponPromote;
|
|
weapon.Refinement = progress.WeaponRefinement;
|
|
}
|
|
}
|
|
|
|
Materials.Clear();
|
|
foreach ((uint itemId, uint count) in record.Materials)
|
|
Materials[itemId] = count;
|
|
|
|
List<ulong> team = record.TeamAvatarIds
|
|
.Select(avatarId => Avatars.FirstOrDefault(a => a.AvatarId == avatarId)?.Guid ?? 0)
|
|
.Where(guid => guid != 0)
|
|
.ToList();
|
|
|
|
if (team.Count != 0)
|
|
{
|
|
TeamAvatarGuids.Clear();
|
|
TeamAvatarGuids.AddRange(team);
|
|
|
|
ulong active = Avatars.FirstOrDefault(a => a.AvatarId == record.CurrentAvatarId)?.Guid ?? 0;
|
|
CurrentAvatarGuid = team.Contains(active) ? active : team[0];
|
|
}
|
|
}
|
|
|
|
private AvatarProgress BuildProgress(GameAvatar avatar)
|
|
{
|
|
GameWeapon? weapon = FindWeapon(avatar.WeaponGuid);
|
|
return new AvatarProgress
|
|
{
|
|
Level = avatar.Level,
|
|
PromoteLevel = avatar.PromoteLevel,
|
|
TalentLevel = avatar.TalentLevel,
|
|
Constellation = avatar.Constellation,
|
|
WeaponLevel = weapon?.Level ?? 90,
|
|
WeaponPromote = weapon?.PromoteLevel ?? 6,
|
|
WeaponRefinement = weapon?.Refinement ?? 0
|
|
};
|
|
}
|
|
|
|
private bool IsDropped(GameWeapon weapon) => (uint)(weapon.Guid & 0xFFFFFFFF) > _defaultGuidCeiling;
|
|
|
|
private bool IsDropped(GameReliquary reliquary) => (uint)(reliquary.Guid & 0xFFFFFFFF) > _defaultGuidCeiling;
|
|
|
|
private void RestoreDropped(PlayerRecord record)
|
|
{
|
|
foreach (DroppedWeapon saved in record.DroppedWeapons)
|
|
{
|
|
GameWeapon weapon = CreateWeapon(saved.ItemId);
|
|
weapon.Level = saved.Level;
|
|
weapon.PromoteLevel = saved.PromoteLevel;
|
|
weapon.Refinement = saved.Refinement;
|
|
}
|
|
|
|
foreach (DroppedReliquary saved in record.DroppedReliquaries)
|
|
{
|
|
GameReliquary reliquary = new(saved.ItemId, NextGuid(), saved.EquipType, saved.MainPropId)
|
|
{
|
|
Level = saved.Level,
|
|
PromoteLevel = saved.PromoteLevel
|
|
};
|
|
|
|
foreach (uint appendPropId in saved.AppendPropIds)
|
|
reliquary.AppendPropIds.Add(appendPropId);
|
|
|
|
Reliquaries.Add(reliquary);
|
|
}
|
|
}
|
|
|
|
public GameWeapon CreateWeapon(uint itemId)
|
|
{
|
|
GameWeapon weapon = new(itemId, NextGuid());
|
|
Weapons.Add(weapon);
|
|
return weapon;
|
|
}
|
|
|
|
public GameReliquary? CreateReliquary(uint itemId, Random random)
|
|
{
|
|
ReliquaryExcel? excel = _excel.GetTable(ExcelType.Reliquary).Items.OfType<ReliquaryExcel>()
|
|
.FirstOrDefault(candidate => candidate.Id == itemId);
|
|
|
|
if (excel is null)
|
|
return null;
|
|
|
|
List<ReliquaryMainPropExcel> pool = _excel.GetTable(ExcelType.ReliquaryMainProp)
|
|
.Items.OfType<ReliquaryMainPropExcel>()
|
|
.Where(candidate => candidate.PropDepotId == excel.MainPropDepotId)
|
|
.ToList();
|
|
|
|
if (pool.Count == 0)
|
|
return null;
|
|
|
|
ReliquaryMainPropExcel main = pool[random.Next(pool.Count)];
|
|
GameReliquary reliquary = new(excel.Id, NextGuid(), EquipTypeId(excel.EquipType), main.Id)
|
|
{
|
|
Level = 1,
|
|
PromoteLevel = 0
|
|
};
|
|
|
|
List<IGrouping<uint, ReliquaryAffixExcel>> groups = _excel.GetTable(ExcelType.ReliquaryAffix)
|
|
.Items.OfType<ReliquaryAffixExcel>()
|
|
.Where(candidate => candidate.DepotId == excel.AppendPropDepotId && candidate.PropType != main.PropType)
|
|
.GroupBy(candidate => candidate.GroupId)
|
|
.ToList();
|
|
|
|
int wanted = (int)Math.Clamp(excel.AppendPropNum, 1u, (uint)groups.Count);
|
|
|
|
for (int taken = 0; taken < wanted && groups.Count != 0; taken++)
|
|
{
|
|
int index = random.Next(groups.Count);
|
|
List<ReliquaryAffixExcel> tiers = groups[index].ToList();
|
|
groups.RemoveAt(index);
|
|
|
|
reliquary.AppendPropIds.Add(tiers[random.Next(tiers.Count)].Id);
|
|
}
|
|
|
|
Reliquaries.Add(reliquary);
|
|
return reliquary;
|
|
}
|
|
|
|
private void CreateReliquaries()
|
|
{
|
|
List<ReliquaryMainPropExcel> mainProps = _excel.GetTable(ExcelType.ReliquaryMainProp)
|
|
.Items.OfType<ReliquaryMainPropExcel>().ToList();
|
|
List<ReliquaryAffixExcel> affixes = _excel.GetTable(ExcelType.ReliquaryAffix)
|
|
.Items.OfType<ReliquaryAffixExcel>().ToList();
|
|
|
|
foreach (ReliquaryExcel excel in _excel.GetTable(ExcelType.Reliquary).Items.OfType<ReliquaryExcel>()
|
|
.Where(candidate => candidate.RankLevel == StarterReliquaryRank))
|
|
{
|
|
ReliquaryMainPropExcel? main = mainProps.FirstOrDefault(p => p.PropDepotId == excel.MainPropDepotId);
|
|
if (main is null)
|
|
continue;
|
|
|
|
GameReliquary reliquary = new(excel.Id, NextGuid(), EquipTypeId(excel.EquipType), main.Id);
|
|
|
|
foreach (ReliquaryAffixExcel affix in affixes.Where(a => a.DepotId == excel.AppendPropDepotId).Take(4))
|
|
reliquary.AppendPropIds.Add(affix.Id);
|
|
|
|
Reliquaries.Add(reliquary);
|
|
}
|
|
}
|
|
|
|
public static uint EquipTypeId(string equipType) => equipType switch
|
|
{
|
|
"EQUIP_BRACER" => 1,
|
|
"EQUIP_NECKLACE" => 2,
|
|
"EQUIP_SHOES" => 3,
|
|
"EQUIP_RING" => 4,
|
|
"EQUIP_DRESS" => 5,
|
|
_ => 0
|
|
};
|
|
}
|