From 7c1075a40c81f6166efb5eb55621751663ec4ec6 Mon Sep 17 00:00:00 2001 From: SeungGeol Song Date: Fri, 18 Sep 2020 16:58:37 +0900 Subject: [PATCH 1/4] Fix bug in ring notification condition. --- Lib9c/Model/Item/Inventory.cs | 116 +++++--------------------------- Lib9c/TableData/UnlockHelper.cs | 43 ++++++++++++ 2 files changed, 58 insertions(+), 101 deletions(-) create mode 100644 Lib9c/TableData/UnlockHelper.cs diff --git a/Lib9c/Model/Item/Inventory.cs b/Lib9c/Model/Item/Inventory.cs index 119dd96a2c..e6f085baa3 100644 --- a/Lib9c/Model/Item/Inventory.cs +++ b/Lib9c/Model/Item/Inventory.cs @@ -88,8 +88,6 @@ public IValue Serialize() } } - private const int MaxRingEquippedCount = 2; - private readonly List _items = new List(); public IReadOnlyList Items => _items; @@ -425,119 +423,35 @@ public bool HasItem(HashDigest id, int count = 1) #endregion - public bool HasNotification(int currentLevel) + public bool HasNotification(int level) { - foreach (var subType in new [] {ItemSubType.Weapon, ItemSubType.Armor, ItemSubType.Belt, ItemSubType.Necklace}) - { - var requiredLevel = GetRequiredLevelOfSlot(subType); - if (currentLevel < requiredLevel) - { - continue; - } + var availableSlots = UnlockHelper.GetAvailableEquipmentSlots(level); - var equipments = Equipments.Where(e => e.ItemSubType == subType).ToList(); - var current = equipments.FirstOrDefault(e => e.equipped); + foreach (var (type, slotCount) in availableSlots) + { + var equipments = Equipments.Where(e => e.ItemSubType == type).ToList(); + var current = equipments.Where(e => e.equipped); // When an equipment slot is empty. - if (current is null && equipments.Any()) + if (current.Count() < slotCount) { return true; } - var hasNotification = equipments.Any(e => CPHelper.GetCP(e) > CPHelper.GetCP(current)); // When any other equipments are stronger than current one. - if (hasNotification) - { - return true; - } - } - - // Seperate case because rings can be equipped more than one. - var rings = Equipments.Where(e => e.ItemSubType == ItemSubType.Ring).ToList(); - var currentRings = rings.Where(e => e.equipped); - if (!currentRings.Any() && rings.Any()) - { - return true; - } - - if (rings.Count >= MaxRingEquippedCount && - currentRings.Count() < MaxRingEquippedCount) - { - return true; - } - - int index = 1; - foreach (var ring in currentRings) - { - var requiredLevel = GetRequiredLevelOfSlot(ItemSubType.Ring, index++); - if (currentLevel < requiredLevel) + foreach (var equipment in equipments) { - continue; - } - - var hasNotification = - rings.Any(e => !e.equipped && CPHelper.GetCP(e) > CPHelper.GetCP(ring)); + if (equipment.equipped) + continue; - if (hasNotification) - { - return true; + var cp = CPHelper.GetCP(equipment); + if (current.Any(i => CPHelper.GetCP(i) < cp)) + { + return true; + } } } return false; } - - private int GetRequiredLevelOfSlot(ItemSubType itemSubType, int index = 1) - { - switch (itemSubType) - { - case ItemSubType.FullCostume: - return GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot; - case ItemSubType.HairCostume: - return GameConfig.RequireCharacterLevel.CharacterHairCostumeSlot; - case ItemSubType.EarCostume: - return GameConfig.RequireCharacterLevel.CharacterEarCostumeSlot; - case ItemSubType.EyeCostume: - return GameConfig.RequireCharacterLevel.CharacterEyeCostumeSlot; - case ItemSubType.TailCostume: - return GameConfig.RequireCharacterLevel.CharacterTailCostumeSlot; - case ItemSubType.Title: - return GameConfig.RequireCharacterLevel.CharacterTitleSlot; - case ItemSubType.Weapon: - return GameConfig.RequireCharacterLevel.CharacterEquipmentSlotWeapon; - case ItemSubType.Armor: - return GameConfig.RequireCharacterLevel.CharacterEquipmentSlotArmor; - case ItemSubType.Belt: - return GameConfig.RequireCharacterLevel.CharacterEquipmentSlotBelt; - case ItemSubType.Necklace: - return GameConfig.RequireCharacterLevel.CharacterEquipmentSlotNecklace; - case ItemSubType.Ring: - return index == 1 - ? GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing1 - : GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing2; - case ItemSubType.Food: - switch (index) - { - case 1: - return GameConfig.RequireCharacterLevel - .CharacterConsumableSlot1; - case 2: - return GameConfig.RequireCharacterLevel - .CharacterConsumableSlot2; - case 3: - return GameConfig.RequireCharacterLevel - .CharacterConsumableSlot3; - case 4: - return GameConfig.RequireCharacterLevel - .CharacterConsumableSlot4; - case 5: - return GameConfig.RequireCharacterLevel - .CharacterConsumableSlot5; - default: - throw new ArgumentOutOfRangeException(); - } - default: - throw new ArgumentOutOfRangeException(); - } - } } } diff --git a/Lib9c/TableData/UnlockHelper.cs b/Lib9c/TableData/UnlockHelper.cs new file mode 100644 index 0000000000..cc2eed51ed --- /dev/null +++ b/Lib9c/TableData/UnlockHelper.cs @@ -0,0 +1,43 @@ +using Nekoyume.Model.Item; +using System.Collections.Generic; + +namespace Nekoyume +{ + public static class UnlockHelper + { + public static List<(ItemSubType, int)> GetAvailableEquipmentSlots(int level) + { + var availableSlots = new List<(ItemSubType, int)>(); + + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotWeapon) + { + availableSlots.Add((ItemSubType.Weapon, 1)); + } + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotArmor) + { + availableSlots.Add((ItemSubType.Armor, 1)); + } + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotBelt) + { + availableSlots.Add((ItemSubType.Belt, 1)); + } + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotNecklace) + { + availableSlots.Add((ItemSubType.Necklace, 1)); + } + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing1) + { + if (level >= GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing2) + { + availableSlots.Add((ItemSubType.Ring, 2)); + } + else + { + availableSlots.Add((ItemSubType.Ring, 1)); + } + } + + return availableSlots; + } + } +} From 6827775e24237dcc16cb68e331d68110afa7c556 Mon Sep 17 00:00:00 2001 From: SeungGeol Song Date: Fri, 18 Sep 2020 18:13:03 +0900 Subject: [PATCH 2/4] Reflect count of item that player has. --- Lib9c/Model/Item/Inventory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c/Model/Item/Inventory.cs b/Lib9c/Model/Item/Inventory.cs index e6f085baa3..2f5e31d0ce 100644 --- a/Lib9c/Model/Item/Inventory.cs +++ b/Lib9c/Model/Item/Inventory.cs @@ -432,7 +432,7 @@ public bool HasNotification(int level) var equipments = Equipments.Where(e => e.ItemSubType == type).ToList(); var current = equipments.Where(e => e.equipped); // When an equipment slot is empty. - if (current.Count() < slotCount) + if (current.Count() < Math.Min(equipments.Count(), slotCount)) { return true; } From 80db17d57b6eaa4f014388282683664338ff4b26 Mon Sep 17 00:00:00 2001 From: SeungGeol Song Date: Mon, 21 Sep 2020 11:36:14 +0900 Subject: [PATCH 3/4] Fix compile error --- Lib9c/Model/Item/Inventory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c/Model/Item/Inventory.cs b/Lib9c/Model/Item/Inventory.cs index 2f5e31d0ce..4212071ba9 100644 --- a/Lib9c/Model/Item/Inventory.cs +++ b/Lib9c/Model/Item/Inventory.cs @@ -429,7 +429,7 @@ public bool HasNotification(int level) foreach (var (type, slotCount) in availableSlots) { - var equipments = Equipments.Where(e => e.ItemSubType == type).ToList(); + var equipments = Equipments.Where(e => e.ItemSubType == type); var current = equipments.Where(e => e.equipped); // When an equipment slot is empty. if (current.Count() < Math.Min(equipments.Count(), slotCount)) From 7602736c5137195117c9c1fa3eba38473ef94b4f Mon Sep 17 00:00:00 2001 From: SeungGeol Song Date: Mon, 21 Sep 2020 13:30:54 +0900 Subject: [PATCH 4/4] Add test cases. (+2 squashed commit) Squashed commit: [4792c28] Revert "Add tests." This reverts commit 37ec9c467247103771841211fab3029f169a68dc. [7745953] Add tests. --- .Lib9c.Tests/Model/ItemNotificationTest.cs | 151 +++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 .Lib9c.Tests/Model/ItemNotificationTest.cs diff --git a/.Lib9c.Tests/Model/ItemNotificationTest.cs b/.Lib9c.Tests/Model/ItemNotificationTest.cs new file mode 100644 index 0000000000..cbe064cae8 --- /dev/null +++ b/.Lib9c.Tests/Model/ItemNotificationTest.cs @@ -0,0 +1,151 @@ +namespace Lib9c.Tests.Model +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Libplanet; + using Libplanet.Crypto; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Battle; + using Nekoyume.Model.Item; + using Nekoyume.Model.State; + using Xunit; + + public class ItemNotificationTest + { + private readonly Address _agentAddress; + private readonly TableSheets _tableSheets; + private readonly ItemSubType[] _itemSubTypesForEquipments + = new ItemSubType[] + { + ItemSubType.Weapon, + ItemSubType.Armor, + ItemSubType.Belt, + ItemSubType.Necklace, + ItemSubType.Ring, + }; + + public ItemNotificationTest() + { + var sheets = TableSheetsImporter.ImportSheets(); + + var privateKey = new PrivateKey(); + _agentAddress = privateKey.PublicKey.ToAddress(); + _tableSheets = new TableSheets(sheets); + } + + [Fact] + public void EquipItems() + { + var avatarAddress = _agentAddress.Derive("avatar_1"); + var avatarState = new AvatarState( + avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + avatarState.level = GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing1; + var equipmentList = new List(); + avatarState.EquipEquipments(equipmentList); + + var inventory = avatarState.inventory; + var hasNotification = inventory.HasNotification(avatarState.level); + // When inventory is empty. + Assert.False(hasNotification); + + foreach (var type in _itemSubTypesForEquipments) + { + var rows = _tableSheets.EquipmentItemSheet.Values + .Where(r => r.ItemSubType == type && r.Grade == 1); + foreach (var row in rows) + { + var guid = Guid.NewGuid(); + var equipment = (Equipment)ItemFactory.CreateItemUsable(row, guid, 0, 0); + inventory.AddItem(equipment); + } + + hasNotification = inventory.HasNotification(avatarState.level); + // When all of the items are unequipped. + Assert.True(hasNotification); + + var ordered = inventory.Equipments + .Where(i => i.ItemSubType == type) + .OrderBy(i => CPHelper.GetCP(i)); + + var weakest = ordered.First(); + equipmentList.Add(weakest.ItemId); + avatarState.EquipEquipments(equipmentList); + hasNotification = inventory.HasNotification(avatarState.level); + // When weakest item is equipped. + Assert.True(hasNotification); + + equipmentList.Remove(weakest.ItemId); + + var strongest = ordered.Last(); + equipmentList.Add(strongest.ItemId); + avatarState.EquipEquipments(equipmentList); + hasNotification = inventory.HasNotification(avatarState.level); + // When strongest item is equipped. + Assert.False(hasNotification); + } + } + + [Fact] + public void EquipTwoRings() + { + var avatarAddress = _agentAddress.Derive("avatar_2"); + var avatarState = new AvatarState( + avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + avatarState.level = GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing2; + var equipmentList = new List(); + avatarState.EquipEquipments(equipmentList); + + var inventory = avatarState.inventory; + var hasNotification = inventory.HasNotification(avatarState.level); + // When inventory is empty. + Assert.False(hasNotification); + + var rows = _tableSheets.EquipmentItemSheet.Values + .Where(r => r.ItemSubType == ItemSubType.Ring && r.Grade == 1); + foreach (var row in rows) + { + var guid = Guid.NewGuid(); + var equipment = (Equipment)ItemFactory.CreateItemUsable(row, guid, 0, 0); + inventory.AddItem(equipment); + } + + hasNotification = inventory.HasNotification(avatarState.level); + // When all of the items are unequipped. + Assert.True(hasNotification); + + var ordered = inventory.Equipments.OrderBy(i => CPHelper.GetCP(i)); + + var strongest = ordered.Last(); + equipmentList.Add(strongest.ItemId); + avatarState.EquipEquipments(equipmentList); + hasNotification = inventory.HasNotification(avatarState.level); + // When one strongest ring is equipped. + Assert.True(hasNotification); + + equipmentList.Clear(); + + var strongests = ordered.TakeLast(2).Select(i => i.ItemId); + equipmentList.AddRange(strongests); + avatarState.EquipEquipments(equipmentList); + hasNotification = inventory.HasNotification(avatarState.level); + // When the 1st strongest, the 2nd strongest items are equipped. + Assert.False(hasNotification); + } + } +}