Skip to content

Commit

Permalink
Merge pull request planetarium#79 from Unengine/bugfix/ring-inventory…
Browse files Browse the repository at this point in the history
…-notification

Fix bug in ring notification condition.
  • Loading branch information
longfin committed Sep 21, 2020
2 parents 4d4f37c + 7602736 commit a66f5be
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 101 deletions.
151 changes: 151 additions & 0 deletions .Lib9c.Tests/Model/ItemNotificationTest.cs
Original file line number Diff line number Diff line change
@@ -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<Guid>();
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<Guid>();
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);
}
}
}
116 changes: 15 additions & 101 deletions Lib9c/Model/Item/Inventory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,6 @@ public IValue Serialize()
}
}

private const int MaxRingEquippedCount = 2;

private readonly List<Item> _items = new List<Item>();

public IReadOnlyList<Item> Items => _items;
Expand Down Expand Up @@ -425,119 +423,35 @@ public bool HasItem(HashDigest<SHA256> 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);
var current = equipments.Where(e => e.equipped);
// When an equipment slot is empty.
if (current is null && equipments.Any())
if (current.Count() < Math.Min(equipments.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();
}
}
}
}
43 changes: 43 additions & 0 deletions Lib9c/TableData/UnlockHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

0 comments on commit a66f5be

Please sign in to comment.