From 7298f4b8b739e3dbe1da2f094c2f4f1d69eaf681 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 14 Mar 2024 12:29:17 +0900 Subject: [PATCH 01/10] Restore mistakenly removed tests since release 1.6.0 --- .../Action/CombinationConsumable8Test.cs | 147 +++ .Lib9c.Tests/Action/CreateAvatar10Test.cs | 307 ++++++ .../Action/EventDungeonBattleV5Test.cs | 499 +++++++++ .Lib9c.Tests/Action/HackAndSlashSweep9Test.cs | 985 ++++++++++++++++++ .Lib9c.Tests/Action/ItemEnhancement13Test.cs | 381 +++++++ .Lib9c.Tests/Action/Raid6Test.cs | 636 +++++++++++ .Lib9c.Tests/Action/RapidCombination9Test.cs | 684 ++++++++++++ 7 files changed, 3639 insertions(+) create mode 100644 .Lib9c.Tests/Action/CombinationConsumable8Test.cs create mode 100644 .Lib9c.Tests/Action/CreateAvatar10Test.cs create mode 100644 .Lib9c.Tests/Action/EventDungeonBattleV5Test.cs create mode 100644 .Lib9c.Tests/Action/HackAndSlashSweep9Test.cs create mode 100644 .Lib9c.Tests/Action/ItemEnhancement13Test.cs create mode 100644 .Lib9c.Tests/Action/Raid6Test.cs create mode 100644 .Lib9c.Tests/Action/RapidCombination9Test.cs diff --git a/.Lib9c.Tests/Action/CombinationConsumable8Test.cs b/.Lib9c.Tests/Action/CombinationConsumable8Test.cs new file mode 100644 index 0000000000..90fc3bb85d --- /dev/null +++ b/.Lib9c.Tests/Action/CombinationConsumable8Test.cs @@ -0,0 +1,147 @@ +namespace Lib9c.Tests.Action +{ + using System.Globalization; + using System.Linq; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Model; + using Nekoyume.Model.Item; + using Nekoyume.Model.Mail; + using Nekoyume.Model.State; + using Xunit; + using static Lib9c.SerializeKeys; + + public class CombinationConsumable8Test + { + private readonly Address _agentAddress; + private readonly Address _avatarAddress; + private readonly IRandom _random; + private readonly TableSheets _tableSheets; + private IAccount _initialState; + + public CombinationConsumable8Test() + { + _agentAddress = new PrivateKey().Address; + _avatarAddress = _agentAddress.Derive("avatar"); + var slotAddress = _avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0 + ) + ); + var sheets = TableSheetsImporter.ImportSheets(); + _random = new TestRandom(); + _tableSheets = new TableSheets(sheets); + + var agentState = new AgentState(_agentAddress); + agentState.avatarAddresses[0] = _avatarAddress; + + var gameConfigState = new GameConfigState(); + + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 1, + _tableSheets.GetAvatarSheets(), + gameConfigState, + default + ); + +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + var gold = new GoldCurrencyState(Currency.Legacy("NCG", 2, null)); +#pragma warning restore CS0618 + + _initialState = new Account(MockState.Empty) + .SetState(_agentAddress, agentState.Serialize()) + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState( + slotAddress, + new CombinationSlotState( + slotAddress, + GameConfig.RequireClearedStageLevel.CombinationConsumableAction).Serialize()) + .SetState(GameConfigState.Address, gold.Serialize()); + + foreach (var (key, value) in sheets) + { + _initialState = + _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute(bool backward) + { + var avatarState = _initialState.GetAvatarState(_avatarAddress); + var row = _tableSheets.ConsumableItemRecipeSheet.Values.First(); + var costActionPoint = row.RequiredActionPoint; + foreach (var materialInfo in row.Materials) + { + var materialRow = _tableSheets.MaterialItemSheet[materialInfo.Id]; + var material = ItemFactory.CreateItem(materialRow, _random); + avatarState.inventory.AddItem(material, materialInfo.Count); + } + + var previousActionPoint = avatarState.actionPoint; + var previousResultConsumableCount = + avatarState.inventory.Equipments.Count(e => e.Id == row.ResultConsumableItemId); + var previousMailCount = avatarState.mailBox.Count; + + avatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + GameConfig.RequireClearedStageLevel.CombinationConsumableAction); + + IAccount previousState; + if (backward) + { + previousState = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + previousState = _initialState + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(_avatarAddress, avatarState.SerializeV2()); + } + + var action = new CombinationConsumable8 + { + avatarAddress = _avatarAddress, + recipeId = row.Id, + slotIndex = 0, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = previousState, + Signer = _agentAddress, + BlockIndex = 1, + RandomSeed = _random.Seed, + }); + + var slotState = nextState.GetCombinationSlotState(_avatarAddress, 0); + Assert.NotNull(slotState.Result); + Assert.NotNull(slotState.Result.itemUsable); + + var consumable = (Consumable)slotState.Result.itemUsable; + Assert.NotNull(consumable); + + var nextAvatarState = nextState.GetAvatarStateV2(_avatarAddress); + Assert.Equal(previousActionPoint - costActionPoint, nextAvatarState.actionPoint); + Assert.Equal(previousMailCount + 1, nextAvatarState.mailBox.Count); + Assert.IsType(nextAvatarState.mailBox.First()); + Assert.Equal( + previousResultConsumableCount + 1, + nextAvatarState.inventory.Consumables.Count(e => e.Id == row.ResultConsumableItemId)); + } + } +} diff --git a/.Lib9c.Tests/Action/CreateAvatar10Test.cs b/.Lib9c.Tests/Action/CreateAvatar10Test.cs new file mode 100644 index 0000000000..71a257306d --- /dev/null +++ b/.Lib9c.Tests/Action/CreateAvatar10Test.cs @@ -0,0 +1,307 @@ +namespace Lib9c.Tests.Action +{ + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.Serialization.Formatters.Binary; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Helper; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Xunit; + using static Lib9c.SerializeKeys; + + public class CreateAvatar10Test + { + private readonly Address _agentAddress; + private readonly TableSheets _tableSheets; + + public CreateAvatar10Test() + { + _agentAddress = default; + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + } + + [Theory] + [InlineData(0L)] + [InlineData(7_210_000L)] + [InlineData(7_210_001L)] + public void Execute(long blockIndex) + { + var action = new CreateAvatar10() + { + index = 0, + hair = 0, + ear = 0, + lens = 0, + tail = 0, + name = "test", + }; + + var sheets = TableSheetsImporter.ImportSheets(); + var state = new Account(MockState.Empty) + .SetState( + Addresses.GameConfig, + new GameConfigState(sheets[nameof(GameConfigSheet)]).Serialize() + ); + + foreach (var (key, value) in sheets) + { + state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + Assert.Equal(0 * CrystalCalculator.CRYSTAL, state.GetBalance(_agentAddress, CrystalCalculator.CRYSTAL)); + + var nextState = action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + BlockIndex = blockIndex, + RandomSeed = 0, + }); + + var avatarAddress = _agentAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CreateAvatar2.DeriveFormat, + 0 + ) + ); + Assert.True(nextState.TryGetAgentAvatarStatesV2( + default, + avatarAddress, + out var agentState, + out var nextAvatarState, + out _) + ); + Assert.True(agentState.avatarAddresses.Any()); + Assert.Equal("test", nextAvatarState.name); + Assert.Equal(200_000 * CrystalCalculator.CRYSTAL, nextState.GetBalance(_agentAddress, CrystalCalculator.CRYSTAL)); + var avatarItemSheet = nextState.GetSheet(); + foreach (var row in avatarItemSheet.Values) + { + Assert.True(nextAvatarState.inventory.HasItem(row.ItemId, row.Count)); + } + + var avatarFavSheet = nextState.GetSheet(); + foreach (var row in avatarFavSheet.Values) + { + var targetAddress = row.Target == CreateAvatarFavSheet.Target.Agent + ? _agentAddress + : avatarAddress; + Assert.Equal(row.Currency * row.Quantity, nextState.GetBalance(targetAddress, row.Currency)); + } + } + + [Theory] + [InlineData("홍길동")] + [InlineData("山田太郎")] + public void ExecuteThrowInvalidNamePatterException(string nickName) + { + var agentAddress = default(Address); + + var action = new CreateAvatar10() + { + index = 0, + hair = 0, + ear = 0, + lens = 0, + tail = 0, + name = nickName, + }; + + var state = new Account(MockState.Empty); + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = agentAddress, + BlockIndex = 0, + }) + ); + } + + [Fact] + public void ExecuteThrowInvalidAddressException() + { + var avatarAddress = _agentAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CreateAvatar2.DeriveFormat, + 0 + ) + ); + + var avatarState = new AvatarState( + avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + var action = new CreateAvatar10() + { + index = 0, + hair = 0, + ear = 0, + lens = 0, + tail = 0, + name = "test", + }; + + var state = new Account(MockState.Empty).SetState(avatarAddress, avatarState.Serialize()); + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + BlockIndex = 0, + }) + ); + } + + [Theory] + [InlineData(-1)] + [InlineData(3)] + public void ExecuteThrowAvatarIndexOutOfRangeException(int index) + { + var agentState = new AgentState(_agentAddress); + var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); + var action = new CreateAvatar10() + { + index = index, + hair = 0, + ear = 0, + lens = 0, + tail = 0, + name = "test", + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + BlockIndex = 0, + }) + ); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) + { + var agentState = new AgentState(_agentAddress); + var avatarAddress = _agentAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CreateAvatar2.DeriveFormat, + 0 + ) + ); + agentState.avatarAddresses[index] = avatarAddress; + var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); + + var action = new CreateAvatar10() + { + index = index, + hair = 0, + ear = 0, + lens = 0, + tail = 0, + name = "test", + }; + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + BlockIndex = 0, + }) + ); + } + + [Fact] + public void Serialize_With_DotnetAPI() + { + var formatter = new BinaryFormatter(); + var action = new CreateAvatar10() + { + index = 2, + hair = 1, + ear = 4, + lens = 5, + tail = 7, + name = "test", + }; + + using var ms = new MemoryStream(); + formatter.Serialize(ms, action); + + ms.Seek(0, SeekOrigin.Begin); + var deserialized = (CreateAvatar10)formatter.Deserialize(ms); + + Assert.Equal(2, deserialized.index); + Assert.Equal(1, deserialized.hair); + Assert.Equal(4, deserialized.ear); + Assert.Equal(5, deserialized.lens); + Assert.Equal(7, deserialized.tail); + Assert.Equal("test", deserialized.name); + } + + [Fact] + public void AddItem() + { + var itemSheet = _tableSheets.ItemSheet; + var createAvatarItemSheet = new CreateAvatarItemSheet(); + createAvatarItemSheet.Set(@"item_id,count +10112000,2 +10512000,2 +600201,2 +"); + var avatarState = new AvatarState(default, default, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); + CreateAvatar10.AddItem(itemSheet, createAvatarItemSheet, avatarState, new TestRandom()); + foreach (var row in createAvatarItemSheet.Values) + { + Assert.True(avatarState.inventory.HasItem(row.ItemId, row.Count)); + } + + Assert.Equal(4, avatarState.inventory.Equipments.Count()); + foreach (var equipment in avatarState.inventory.Equipments) + { + var equipmentRow = _tableSheets.EquipmentItemSheet[equipment.Id]; + Assert.Equal(equipmentRow.Stat, equipment.Stat); + } + } + + [Fact] + public void MintAsset() + { + var createAvatarFavSheet = new CreateAvatarFavSheet(); + createAvatarFavSheet.Set(@"currency,quantity,target +CRYSTAL,200000,Agent +RUNE_GOLDENLEAF,200000,Avatar +"); + var avatarAddress = new PrivateKey().Address; + var agentAddress = new PrivateKey().Address; + var avatarState = new AvatarState(avatarAddress, agentAddress, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); + var nextState = CreateAvatar10.MintAsset(createAvatarFavSheet, avatarState, new Account(MockState.Empty), new ActionContext()); + foreach (var row in createAvatarFavSheet.Values) + { + var targetAddress = row.Target == CreateAvatarFavSheet.Target.Agent + ? agentAddress + : avatarAddress; + Assert.Equal(row.Currency * row.Quantity, nextState.GetBalance(targetAddress, row.Currency)); + } + } + } +} diff --git a/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs b/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs new file mode 100644 index 0000000000..07197699ad --- /dev/null +++ b/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs @@ -0,0 +1,499 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Blockchain.Policy; + using Nekoyume.Exceptions; + using Nekoyume.Extensions; + using Nekoyume.Model.Event; + using Nekoyume.Model.Rune; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Nekoyume.TableData.Event; + using Xunit; + using static Lib9c.SerializeKeys; + + public class EventDungeonBattleV5Test + { + private readonly Currency _ncgCurrency; + private readonly TableSheets _tableSheets; + + private readonly Address _agentAddress; + private readonly Address _avatarAddress; + private IAccount _initialStates; + + public EventDungeonBattleV5Test() + { + _initialStates = new Account(MockState.Empty); + +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + _ncgCurrency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + _initialStates = _initialStates.SetState( + GoldCurrencyState.Address, + new GoldCurrencyState(_ncgCurrency).Serialize()); + var sheets = TableSheetsImporter.ImportSheets(); + foreach (var (key, value) in sheets) + { + _initialStates = _initialStates + .SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + _tableSheets = new TableSheets(sheets); + + _agentAddress = new PrivateKey().Address; + _avatarAddress = _agentAddress.Derive("avatar"); + var inventoryAddr = _avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddr = _avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddr = _avatarAddress.Derive(LegacyQuestListKey); + + var agentState = new AgentState(_agentAddress); + agentState.avatarAddresses.Add(0, _avatarAddress); + + var gameConfigState = new GameConfigState(sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + new PrivateKey().Address + ) + { + level = 100, + }; + + _initialStates = _initialStates + .SetState(_agentAddress, agentState.Serialize()) + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddr, avatarState.inventory.Serialize()) + .SetState(worldInformationAddr, avatarState.worldInformation.Serialize()) + .SetState(questListAddr, avatarState.questList.Serialize()) + .SetState(gameConfigState.address, gameConfigState.Serialize()); + } + + [Theory] + [InlineData(1001, 10010001, 10010001)] + public void Execute_Success_Within_Event_Period( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + var contextBlockIndex = scheduleRow.StartBlockIndex; + var nextStates = Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex); + var eventDungeonInfoAddr = + EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); + var eventDungeonInfo = + new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + Assert.Equal( + scheduleRow.DungeonTicketsMax - 1, + eventDungeonInfo.RemainingTickets); + + contextBlockIndex = scheduleRow.DungeonEndBlockIndex; + nextStates = Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex); + eventDungeonInfo = + new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + Assert.Equal( + scheduleRow.DungeonTicketsMax - 1, + eventDungeonInfo.RemainingTickets); + } + + [Theory] + [InlineData(1001, 10010001, 10010001, 0, 0, 0)] + [InlineData(1001, 10010001, 10010001, 1, 1, 1)] + [InlineData(1001, 10010001, 10010001, int.MaxValue, int.MaxValue, int.MaxValue - 1)] + public void Execute_Success_With_Ticket_Purchase( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId, + int dungeonTicketPrice, + int dungeonTicketAdditionalPrice, + int numberOfTicketPurchases) + { + var context = new ActionContext(); + var previousStates = _initialStates; + var scheduleSheet = _tableSheets.EventScheduleSheet; + Assert.True(scheduleSheet.TryGetValue(eventScheduleId, out var scheduleRow)); + var sb = new StringBuilder(); + sb.AppendLine( + "id,_name,start_block_index,dungeon_end_block_index,dungeon_tickets_max,dungeon_tickets_reset_interval_block_range,dungeon_exp_seed_value,recipe_end_block_index,dungeon_ticket_price,dungeon_ticket_additional_price"); + sb.AppendLine( + $"{eventScheduleId}" + + $",\"2022 Summer Event\"" + + $",{scheduleRow.StartBlockIndex}" + + $",{scheduleRow.DungeonEndBlockIndex}" + + $",{scheduleRow.DungeonTicketsMax}" + + $",{scheduleRow.DungeonTicketsResetIntervalBlockRange}" + + $",{dungeonTicketPrice}" + + $",{dungeonTicketAdditionalPrice}" + + $",{scheduleRow.DungeonExpSeedValue}" + + $",{scheduleRow.RecipeEndBlockIndex}"); + previousStates = previousStates.SetState( + Addresses.GetSheetAddress(), + sb.ToString().Serialize()); + + var eventDungeonInfoAddr = + EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); + var eventDungeonInfo = new EventDungeonInfo( + remainingTickets: 0, + numberOfTicketPurchases: numberOfTicketPurchases); + previousStates = previousStates.SetState( + eventDungeonInfoAddr, + eventDungeonInfo.Serialize()); + + Assert.True(previousStates.GetSheet() + .TryGetValue(eventScheduleId, out var newScheduleRow)); + var ncgHas = newScheduleRow.GetDungeonTicketCost( + numberOfTicketPurchases, + _ncgCurrency); + if (ncgHas.Sign > 0) + { + previousStates = previousStates.MintAsset(context, _agentAddress, ncgHas); + } + + var nextStates = Execute( + previousStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + buyTicketIfNeeded: true, + blockIndex: scheduleRow.StartBlockIndex); + var nextEventDungeonInfoList = + (Bencodex.Types.List)nextStates.GetState(eventDungeonInfoAddr)!; + Assert.Equal( + numberOfTicketPurchases + 1, + nextEventDungeonInfoList[2].ToInteger()); + Assert.True( + nextStates.TryGetGoldBalance( + _agentAddress, + _ncgCurrency, + out FungibleAssetValue balance + ) + ); + Assert.Equal(0 * _ncgCurrency, balance); + } + + [Theory] + [InlineData(10000001, 10010001, 10010001)] + [InlineData(10010001, 10010001, 10010001)] + public void Execute_Throw_InvalidActionFieldException_By_EventScheduleId( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) => + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId)); + + [Theory] + [InlineData(1001, 10010001, 10010001)] + public void Execute_Throw_InvalidActionFieldException_By_ContextBlockIndex( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + var contextBlockIndex = scheduleRow.StartBlockIndex - 1; + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex)); + contextBlockIndex = scheduleRow.DungeonEndBlockIndex + 1; + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex)); + } + + [Theory] + [InlineData(1001, 10020001, 10010001)] + [InlineData(1001, 1001, 10010001)] + public void Execute_Throw_InvalidActionFieldException_By_EventDungeonId( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: scheduleRow.StartBlockIndex)); + } + + [Theory] + [InlineData(1001, 10010001, 10020001)] + [InlineData(1001, 10010001, 1001)] + public void Execute_Throw_InvalidActionFieldException_By_EventDungeonStageId( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: scheduleRow.StartBlockIndex)); + } + + [Theory] + [InlineData(1001, 10010001, 10010001)] + public void Execute_Throw_NotEnoughEventDungeonTicketsException( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + var previousStates = _initialStates; + var eventDungeonInfoAddr = + EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); + var eventDungeonInfo = new EventDungeonInfo(); + previousStates = previousStates + .SetState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + Assert.Throws(() => + Execute( + previousStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: scheduleRow.StartBlockIndex)); + } + + [Theory] + [InlineData(1001, 10010001, 10010001, 0)] + [InlineData(1001, 10010001, 10010001, int.MaxValue - 1)] + public void Execute_Throw_InsufficientBalanceException( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId, + int numberOfTicketPurchases) + { + var context = new ActionContext(); + var previousStates = _initialStates; + var eventDungeonInfoAddr = + EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); + var eventDungeonInfo = new EventDungeonInfo( + remainingTickets: 0, + numberOfTicketPurchases: numberOfTicketPurchases); + previousStates = previousStates + .SetState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + var ncgHas = scheduleRow.GetDungeonTicketCost( + numberOfTicketPurchases, + _ncgCurrency) - 1 * _ncgCurrency; + if (ncgHas.Sign > 0) + { + previousStates = previousStates.MintAsset(context, _agentAddress, ncgHas); + } + + Assert.Throws(() => + Execute( + previousStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + buyTicketIfNeeded: true, + blockIndex: scheduleRow.StartBlockIndex)); + } + + [Theory] + [InlineData(1001, 10010001, 10010002)] + public void Execute_Throw_StageNotClearedException( + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(eventScheduleId, out var scheduleRow)); + Assert.Throws(() => + Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: scheduleRow.StartBlockIndex)); + } + + [Theory] + [InlineData(0, 30001, 1, 30001, typeof(DuplicatedRuneIdException))] + [InlineData(1, 10002, 1, 30001, typeof(DuplicatedRuneSlotIndexException))] + public void Execute_DuplicatedException(int slotIndex, int runeId, int slotIndex2, int runeId2, Type exception) + { + Assert.True(_tableSheets.EventScheduleSheet + .TryGetValue(1001, out var scheduleRow)); + + var context = new ActionContext(); + _initialStates = _initialStates.MintAsset(context, _agentAddress, 99999 * _ncgCurrency); + + var unlockRuneSlot = new UnlockRuneSlot() + { + AvatarAddress = _avatarAddress, + SlotIndex = 1, + }; + + _initialStates = unlockRuneSlot.Execute(new ActionContext + { + BlockIndex = 1, + PreviousState = _initialStates, + Signer = _agentAddress, + RandomSeed = 0, + }); + + Assert.Throws(exception, () => + Execute( + _initialStates, + 1001, + 10010001, + 10010001, + false, + scheduleRow.StartBlockIndex, + slotIndex, + runeId, + slotIndex2, + runeId2)); + } + + [Fact] + public void Execute_V100301() + { + int eventScheduleId = 1001; + int eventDungeonId = 10010001; + int eventDungeonStageId = 10010001; + var csv = $@"id,_name,start_block_index,dungeon_end_block_index,dungeon_tickets_max,dungeon_tickets_reset_interval_block_range,dungeon_ticket_price,dungeon_ticket_additional_price,dungeon_exp_seed_value,recipe_end_block_index + 1001,2022 Summer Event,{ActionObsoleteConfig.V100301ExecutedBlockIndex},{ActionObsoleteConfig.V100301ExecutedBlockIndex + 100},5,7200,5,2,1,5018000"; + _initialStates = + _initialStates.SetState( + Addresses.GetSheetAddress(), + csv.Serialize()); + var sheet = new EventScheduleSheet(); + sheet.Set(csv); + Assert.True(sheet.TryGetValue(eventScheduleId, out var scheduleRow)); + var contextBlockIndex = scheduleRow.StartBlockIndex; + var nextStates = Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex); + var eventDungeonInfoAddr = + EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); + var eventDungeonInfo = + new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + Assert.Equal( + scheduleRow.DungeonTicketsMax - 1, + eventDungeonInfo.RemainingTickets); + + contextBlockIndex = scheduleRow.DungeonEndBlockIndex; + nextStates = Execute( + _initialStates, + eventScheduleId, + eventDungeonId, + eventDungeonStageId, + blockIndex: contextBlockIndex); + eventDungeonInfo = + new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + Assert.Equal( + scheduleRow.DungeonTicketsMax - 1, + eventDungeonInfo.RemainingTickets); + } + + private IAccount Execute( + IAccount previousStates, + int eventScheduleId, + int eventDungeonId, + int eventDungeonStageId, + bool buyTicketIfNeeded = false, + long blockIndex = 0, + int slotIndex = 0, + int runeId = 10002, + int slotIndex2 = 1, + int runeId2 = 30001) + { + var previousAvatarState = previousStates.GetAvatarStateV2(_avatarAddress); + var equipments = + Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + previousAvatarState.inventory.AddItem(equipment, iLock: null); + } + + var action = new EventDungeonBattleV5 + { + AvatarAddress = _avatarAddress, + EventScheduleId = eventScheduleId, + EventDungeonId = eventDungeonId, + EventDungeonStageId = eventDungeonStageId, + Equipments = equipments + .Select(e => e.NonFungibleId) + .ToList(), + Costumes = new List(), + Foods = new List(), + RuneInfos = new List() + { + new RuneSlotInfo(slotIndex, runeId), + new RuneSlotInfo(slotIndex2, runeId2), + }, + BuyTicketIfNeeded = buyTicketIfNeeded, + }; + + var nextStates = action.Execute(new ActionContext + { + PreviousState = previousStates, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = blockIndex, + }); + + Assert.True(nextStates.GetSheet().TryGetValue( + eventScheduleId, + out var scheduleRow)); + var nextAvatarState = nextStates.GetAvatarStateV2(_avatarAddress); + var expectExp = scheduleRow.GetStageExp( + eventDungeonStageId.ToEventDungeonStageNumber()); + Assert.Equal( + previousAvatarState.exp + expectExp, + nextAvatarState.exp); + + return nextStates; + } + } +} diff --git a/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs b/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs new file mode 100644 index 0000000000..53a7dafbed --- /dev/null +++ b/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs @@ -0,0 +1,985 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Bencodex.Types; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Extensions; + using Nekoyume.Helper; + using Nekoyume.Model; + using Nekoyume.Model.Item; + using Nekoyume.Model.Rune; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Xunit; + using static Lib9c.SerializeKeys; + + public class HackAndSlashSweep9Test + { + private readonly Dictionary _sheets; + private readonly TableSheets _tableSheets; + + private readonly Address _agentAddress; + + private readonly Address _avatarAddress; + private readonly AvatarState _avatarState; + + private readonly Address _inventoryAddress; + private readonly Address _worldInformationAddress; + private readonly Address _questListAddress; + + private readonly Address _rankingMapAddress; + + private readonly WeeklyArenaState _weeklyArenaState; + private readonly IAccount _initialState; + private readonly IRandom _random; + + public HackAndSlashSweep9Test() + { + _random = new TestRandom(); + _sheets = TableSheetsImporter.ImportSheets(); + _tableSheets = new TableSheets(_sheets); + + var privateKey = new PrivateKey(); + _agentAddress = privateKey.PublicKey.Address; + var agentState = new AgentState(_agentAddress); + + _avatarAddress = _agentAddress.Derive("avatar"); + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + _rankingMapAddress = _avatarAddress.Derive("ranking_map"); + _avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress + ) + { + level = 100, + }; + _inventoryAddress = _avatarAddress.Derive(LegacyInventoryKey); + _worldInformationAddress = _avatarAddress.Derive(LegacyWorldInformationKey); + _questListAddress = _avatarAddress.Derive(LegacyQuestListKey); + agentState.avatarAddresses.Add(0, _avatarAddress); + +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + var currency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + var goldCurrencyState = new GoldCurrencyState(currency); + _weeklyArenaState = new WeeklyArenaState(0); + _initialState = new Account(MockState.Empty) + .SetState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) + .SetState(_agentAddress, agentState.SerializeV2()) + .SetState(_avatarAddress, _avatarState.SerializeV2()) + .SetState(_inventoryAddress, _avatarState.inventory.Serialize()) + .SetState(_worldInformationAddress, _avatarState.worldInformation.Serialize()) + .SetState(_questListAddress, _avatarState.questList.Serialize()) + .SetState(gameConfigState.address, gameConfigState.Serialize()) + .SetState(Addresses.GoldCurrency, goldCurrencyState.Serialize()); + + foreach (var (key, value) in _sheets) + { + _initialState = _initialState + .SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + foreach (var address in _avatarState.combinationSlotAddresses) + { + var slotState = new CombinationSlotState( + address, + GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + _initialState = _initialState.SetState(address, slotState.Serialize()); + } + } + + public (List Equipments, List Costumes) GetDummyItems(AvatarState avatarState) + { + var equipments = Doomfist.GetAllParts(_tableSheets, avatarState.level) + .Select(e => e.NonFungibleId).ToList(); + var random = new TestRandom(); + var costumes = new List(); + if (avatarState.level >= GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot) + { + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + avatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + } + + return (equipments, costumes); + } + + [Theory] + [InlineData(1, 1, 1, false, true)] + [InlineData(1, 1, 1, false, false)] + [InlineData(2, 1, 2, false, true)] + [InlineData(2, 1, 2, false, false)] + [InlineData(2, 2, 51, false, true)] + [InlineData(2, 2, 51, false, false)] + [InlineData(2, 2, 52, false, true)] + [InlineData(2, 2, 52, false, false)] + [InlineData(2, 1, 1, true, true)] + [InlineData(2, 1, 1, true, false)] + [InlineData(2, 1, 2, true, true)] + [InlineData(2, 1, 2, true, false)] + [InlineData(2, 2, 51, true, true)] + [InlineData(2, 2, 51, true, false)] + [InlineData(2, 2, 52, true, true)] + [InlineData(2, 2, 52, true, false)] + public void Execute(int apStoneCount, int worldId, int stageId, bool challenge, bool backward) + { + var gameConfigState = _initialState.GetGameConfigState(); + var prevStageId = stageId - 1; + var worldInformation = new WorldInformation( + 0, _initialState.GetSheet(), challenge ? prevStageId : stageId); + + if (challenge) + { + worldInformation.UnlockWorld(worldId, 0, _tableSheets.WorldSheet); + } + + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = worldInformation, + level = 400, + }; + + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(row); + avatarState.inventory.AddItem(apStone, apStoneCount); + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + state = state.SetState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + var stageSheet = _initialState.GetSheet(); + var (expectedLevel, expectedExp) = (0, 0L); + if (stageSheet.TryGetValue(stageId, out var stageRow)) + { + var itemPlayCount = gameConfigState.ActionPointMax / stageRow.CostAP * apStoneCount; + var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var playCount = apPlayCount + itemPlayCount; + (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _tableSheets.CharacterLevelSheet, + stageId, + playCount); + + var random = new TestRandom(_random.Seed); + var expectedRewardItems = HackAndSlashSweep6.GetRewardItems( + random, + playCount, + stageRow, + _tableSheets.MaterialItemSheet); + + var (equipments, costumes) = GetDummyItems(avatarState); + var action = new HackAndSlashSweep9 + { + actionPoint = avatarState.actionPoint, + costumes = costumes, + equipments = equipments, + runeInfos = new List(), + avatarAddress = _avatarAddress, + apStoneCount = apStoneCount, + worldId = worldId, + stageId = stageId, + }; + + state = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = _random.Seed, + }); + + var nextAvatarState = state.GetAvatarStateV2(_avatarAddress); + + Assert.Equal(expectedLevel, nextAvatarState.level); + Assert.Equal(expectedExp, nextAvatarState.exp); + Assert.Equal( + expectedRewardItems.Count(), + nextAvatarState.inventory.Items.Sum(x => x.count)); + foreach (var i in nextAvatarState.inventory.Items) + { + nextAvatarState.inventory.TryGetItem(i.item.Id, out var item); + Assert.Equal(expectedRewardItems.Count(x => x.Id == i.item.Id), item.count); + } + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute_FailedLoadStateException(bool backward) + { + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = 1, + avatarAddress = _avatarAddress, + worldId = 1, + stageId = 1, + }; + + var state = backward ? new Account(MockState.Empty) : _initialState; + if (!backward) + { + state = _initialState + .SetState(_avatarAddress, _avatarState.SerializeV2()) + .SetNull(_avatarAddress.Derive(LegacyInventoryKey)) + .SetNull(_avatarAddress.Derive(LegacyWorldInformationKey)) + .SetNull(_avatarAddress.Derive(LegacyQuestListKey)); + } + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(100, 1)] + public void Execute_SheetRowNotFoundException(int worldId, int stageId) + { + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = 1, + avatarAddress = _avatarAddress, + worldId = worldId, + stageId = stageId, + }; + + var state = _initialState.SetState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(1, 999)] + [InlineData(2, 50)] + public void Execute_SheetRowColumnException(int worldId, int stageId) + { + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = 1, + avatarAddress = _avatarAddress, + worldId = worldId, + stageId = stageId, + }; + + var state = _initialState.SetState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(1, 48, 1, 50, true)] + [InlineData(1, 48, 1, 50, false)] + [InlineData(1, 49, 2, 51, true)] + [InlineData(1, 49, 2, 51, false)] + public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId, int worldId, int stageId, bool backward) + { + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = 1, + avatarAddress = _avatarAddress, + worldId = worldId, + stageId = stageId, + }; + var worldSheet = _initialState.GetSheet(); + var worldUnlockSheet = _initialState.GetSheet(); + + _avatarState.worldInformation.ClearStage(clearedWorldId, clearedStageId, 1, worldSheet, worldUnlockSheet); + + var state = _initialState.SetState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + if (backward) + { + state = state.SetState(_avatarAddress, _avatarState.Serialize()); + } + else + { + state = state + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + _avatarState.worldInformation.Serialize()); + } + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(GameConfig.MimisbrunnrWorldId, true, 10000001, false)] + [InlineData(GameConfig.MimisbrunnrWorldId, false, 10000001, true)] + // Unlock CRYSTAL first. + [InlineData(2, false, 51, false)] + [InlineData(2, true, 51, false)] + public void Execute_InvalidWorldException(int worldId, bool backward, int stageId, bool unlockedIdsExist) + { + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 10000001), + }; + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + if (unlockedIdsExist) + { + state = state.SetState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + } + + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = 1, + avatarAddress = _avatarAddress, + worldId = worldId, + stageId = stageId, + }; + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(99, true)] + [InlineData(99, false)] + public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) + { + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + }; + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + apStoneCount = apStoneCount, + avatarAddress = _avatarAddress, + worldId = 1, + stageId = 2, + }; + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + [Theory] + [InlineData(3, 2, true)] + [InlineData(7, 5, false)] + public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingApStoneCount, bool backward) + { + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + level = 400, + }; + + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(row); + avatarState.inventory.AddItem(apStone, holdingApStoneCount); + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var stageSheet = _initialState.GetSheet(); + var (expectedLevel, expectedExp) = (0, 0L); + if (stageSheet.TryGetValue(2, out var stageRow)) + { + var itemPlayCount = + gameConfigState.ActionPointMax / stageRow.CostAP * useApStoneCount; + var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var playCount = apPlayCount + itemPlayCount; + (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _tableSheets.CharacterLevelSheet, + 2, + playCount); + + var (equipments, costumes) = GetDummyItems(avatarState); + + var action = new HackAndSlashSweep9 + { + equipments = equipments, + costumes = costumes, + runeInfos = new List(), + avatarAddress = _avatarAddress, + actionPoint = avatarState.actionPoint, + apStoneCount = useApStoneCount, + worldId = 1, + stageId = 2, + }; + + Assert.Throws(() => action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute_NotEnoughActionPointException(bool backward) + { + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + level = 400, + actionPoint = 0, + }; + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var stageSheet = _initialState.GetSheet(); + var (expectedLevel, expectedExp) = (0, 0L); + if (stageSheet.TryGetValue(2, out var stageRow)) + { + var itemPlayCount = + gameConfigState.ActionPointMax / stageRow.CostAP * 1; + var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var playCount = apPlayCount + itemPlayCount; + (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _tableSheets.CharacterLevelSheet, + 2, + playCount); + + var (equipments, costumes) = GetDummyItems(avatarState); + var action = new HackAndSlashSweep9 + { + runeInfos = new List(), + costumes = costumes, + equipments = equipments, + avatarAddress = _avatarAddress, + actionPoint = 999999, + apStoneCount = 0, + worldId = 1, + stageId = 2, + }; + + Assert.Throws(() => + action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute_PlayCountIsZeroException(bool backward) + { + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + level = 400, + actionPoint = 0, + }; + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var stageSheet = _initialState.GetSheet(); + var (expectedLevel, expectedExp) = (0, 0L); + if (stageSheet.TryGetValue(2, out var stageRow)) + { + var itemPlayCount = + gameConfigState.ActionPointMax / stageRow.CostAP * 1; + var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var playCount = apPlayCount + itemPlayCount; + (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _tableSheets.CharacterLevelSheet, + 2, + playCount); + + var (equipments, costumes) = GetDummyItems(avatarState); + var action = new HackAndSlashSweep9 + { + costumes = costumes, + equipments = equipments, + runeInfos = new List(), + avatarAddress = _avatarAddress, + actionPoint = 0, + apStoneCount = 0, + worldId = 1, + stageId = 2, + }; + + Assert.Throws(() => + action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + } + + [Theory] + [InlineData(1, 24, true)] + [InlineData(1, 24, false)] + public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool backward) + { + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + actionPoint = 0, + level = 1, + }; + + IAccount state; + if (backward) + { + state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + state = _initialState + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var stageSheet = _initialState.GetSheet(); + var (expectedLevel, expectedExp) = (0, 0L); + if (stageSheet.TryGetValue(stageId, out var stageRow)) + { + var itemPlayCount = + gameConfigState.ActionPointMax / stageRow.CostAP * 1; + var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var playCount = apPlayCount + itemPlayCount; + (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _tableSheets.CharacterLevelSheet, + stageId, + playCount); + + var action = new HackAndSlashSweep9 + { + costumes = new List(), + equipments = new List(), + runeInfos = new List(), + avatarAddress = _avatarAddress, + actionPoint = avatarState.actionPoint, + apStoneCount = 1, + worldId = worldId, + stageId = stageId, + }; + + Assert.Throws(() => + action.Execute(new ActionContext() + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + public void ExecuteWithStake(int stakingLevel) + { + const int worldId = 1; + const int stageId = 1; + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + actionPoint = 120, + level = 3, + }; + var itemRow = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(itemRow); + avatarState.inventory.AddItem(apStone); + + var stakeStateAddress = StakeState.DeriveAddress(_agentAddress); + var stakeState = new StakeState(stakeStateAddress, 1); + var requiredGold = _tableSheets.StakeRegularRewardSheet.OrderedRows + .FirstOrDefault(r => r.Level == stakingLevel)?.RequiredGold ?? 0; + var context = new ActionContext(); + var state = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(stakeStateAddress, stakeState.Serialize()) + .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); + var stageSheet = _initialState.GetSheet(); + if (stageSheet.TryGetValue(stageId, out var stageRow)) + { + var apSheet = _initialState.GetSheet(); + var costAp = apSheet.GetActionPointByStaking(stageRow.CostAP, 1, stakingLevel); + var itemPlayCount = + gameConfigState.ActionPointMax / costAp * 1; + var apPlayCount = avatarState.actionPoint / costAp; + var playCount = apPlayCount + itemPlayCount; + var (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _initialState.GetSheet(), + stageId, + playCount); + + var action = new HackAndSlashSweep9 + { + costumes = new List(), + equipments = new List(), + runeInfos = new List(), + avatarAddress = _avatarAddress, + actionPoint = avatarState.actionPoint, + apStoneCount = 1, + worldId = worldId, + stageId = stageId, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + var nextAvatar = nextState.GetAvatarStateV2(_avatarAddress); + Assert.Equal(expectedLevel, nextAvatar.level); + Assert.Equal(expectedExp, nextAvatar.exp); + } + else + { + throw new SheetRowNotFoundException(nameof(StageSheet), stageId); + } + } + + [Theory] + [InlineData(0, 30001, 1, 30001, typeof(DuplicatedRuneIdException))] + [InlineData(1, 10002, 1, 30001, typeof(DuplicatedRuneSlotIndexException))] + public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2, int runeId2, Type exception) + { + var stakingLevel = 1; + const int worldId = 1; + const int stageId = 1; + var gameConfigState = _initialState.GetGameConfigState(); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _initialState.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress) + { + worldInformation = + new WorldInformation(0, _initialState.GetSheet(), 25), + actionPoint = 120, + level = 3, + }; + var itemRow = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(itemRow); + avatarState.inventory.AddItem(apStone); + + var stakeStateAddress = StakeState.DeriveAddress(_agentAddress); + var stakeState = new StakeState(stakeStateAddress, 1); + var requiredGold = _tableSheets.StakeRegularRewardSheet.OrderedRows + .FirstOrDefault(r => r.Level == stakingLevel)?.RequiredGold ?? 0; + var context = new ActionContext(); + var state = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(stakeStateAddress, stakeState.Serialize()) + .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); + var stageSheet = _initialState.GetSheet(); + if (stageSheet.TryGetValue(stageId, out var stageRow)) + { + var apSheet = _initialState.GetSheet(); + var costAp = apSheet.GetActionPointByStaking(stageRow.CostAP, 1, stakingLevel); + var itemPlayCount = + gameConfigState.ActionPointMax / costAp * 1; + var apPlayCount = avatarState.actionPoint / costAp; + var playCount = apPlayCount + itemPlayCount; + var (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( + _initialState.GetSheet(), + stageId, + playCount); + + var ncgCurrency = state.GetGoldCurrency(); + state = state.MintAsset(context, _agentAddress, 99999 * ncgCurrency); + + var unlockRuneSlot = new UnlockRuneSlot() + { + AvatarAddress = _avatarAddress, + SlotIndex = 1, + }; + + state = unlockRuneSlot.Execute(new ActionContext + { + BlockIndex = 1, + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + + var action = new HackAndSlashSweep9 + { + costumes = new List(), + equipments = new List(), + runeInfos = new List() + { + new RuneSlotInfo(slotIndex, runeId), + new RuneSlotInfo(slotIndex2, runeId2), + }, + avatarAddress = _avatarAddress, + actionPoint = avatarState.actionPoint, + apStoneCount = 1, + worldId = worldId, + stageId = stageId, + }; + + Assert.Throws(exception, () => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + else + { + throw new SheetRowNotFoundException(nameof(StageSheet), stageId); + } + } + } +} diff --git a/.Lib9c.Tests/Action/ItemEnhancement13Test.cs b/.Lib9c.Tests/Action/ItemEnhancement13Test.cs new file mode 100644 index 0000000000..9c280c1eed --- /dev/null +++ b/.Lib9c.Tests/Action/ItemEnhancement13Test.cs @@ -0,0 +1,381 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using Bencodex.Types; + using Lib9c.Tests.Fixtures.TableCSV; + using Lib9c.Tests.Fixtures.TableCSV.Cost; + using Lib9c.Tests.Fixtures.TableCSV.Item; + using Lib9c.Tests.Util; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Extensions; + using Nekoyume.Model.Item; + using Nekoyume.Model.Mail; + using Nekoyume.Model.State; + using Xunit; + using static SerializeKeys; + + public class ItemEnhancement13Test + { + private readonly TableSheets _tableSheets; + private readonly Address _agentAddress; + private readonly Address _avatarAddress; + private readonly AvatarState _avatarState; + private readonly Currency _currency; + private IAccount _initialState; + + public ItemEnhancement13Test() + { + _initialState = new Account(MockState.Empty); + Dictionary sheets; + (_initialState, sheets) = InitializeUtil.InitializeTableSheets( + _initialState, + sheetsOverride: new Dictionary + { + { + "EnhancementCostSheetV3", + EnhancementCostSheetFixtures.V3 + }, + }); + _tableSheets = new TableSheets(sheets); + foreach (var (key, value) in sheets) + { + _initialState = + _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + var privateKey = new PrivateKey(); + _agentAddress = privateKey.PublicKey.Address; + var agentState = new AgentState(_agentAddress); + + _avatarAddress = _agentAddress.Derive("avatar"); + _avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + agentState.avatarAddresses.Add(0, _avatarAddress); + +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + _currency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + var gold = new GoldCurrencyState(_currency); + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0 + )); + + var context = new ActionContext(); + _initialState = _initialState + .SetState(_agentAddress, agentState.Serialize()) + .SetState(_avatarAddress, _avatarState.Serialize()) + .SetState(slotAddress, new CombinationSlotState(slotAddress, 0).Serialize()) + .SetState(GoldCurrencyState.Address, gold.Serialize()) + .MintAsset(context, GoldCurrencyState.Address, gold.Currency * 100_000_000_000) + .TransferAsset( + context, + Addresses.GoldCurrency, + _agentAddress, + gold.Currency * 3_000_000 + ); + + Assert.Equal( + gold.Currency * 99_997_000_000, + _initialState.GetBalance(Addresses.GoldCurrency, gold.Currency) + ); + Assert.Equal( + gold.Currency * 3_000_000, + _initialState.GetBalance(_agentAddress, gold.Currency) + ); + } + + [Theory] + // from 0 to 0 using one level 0 material + [InlineData(0, false, 0, false, 1)] + [InlineData(0, false, 0, true, 1)] + [InlineData(0, true, 0, false, 1)] + [InlineData(0, true, 0, true, 1)] + // from 0 to 1 using two level 0 material + [InlineData(0, false, 0, false, 3)] + [InlineData(0, false, 0, true, 3)] + [InlineData(0, true, 0, false, 3)] + [InlineData(0, true, 0, true, 3)] + // // Duplicated > from 0 to 0 + [InlineData(0, false, 0, false, 3, true)] + [InlineData(0, false, 0, true, 3, true)] + [InlineData(0, true, 0, false, 3, true)] + [InlineData(0, true, 0, true, 3, true)] + // from 0 to N using multiple level 0 materials + [InlineData(0, false, 0, false, 7)] + [InlineData(0, false, 0, false, 31)] + [InlineData(0, false, 0, true, 7)] + [InlineData(0, false, 0, true, 31)] + [InlineData(0, true, 0, false, 7)] + [InlineData(0, true, 0, false, 31)] + [InlineData(0, true, 0, true, 7)] + [InlineData(0, true, 0, true, 31)] + // // Duplicated > from 0 to 0 + [InlineData(0, false, 0, false, 7, true)] + [InlineData(0, false, 0, false, 31, true)] + [InlineData(0, false, 0, true, 7, true)] + [InlineData(0, false, 0, true, 31, true)] + [InlineData(0, true, 0, false, 7, true)] + [InlineData(0, true, 0, false, 31, true)] + [InlineData(0, true, 0, true, 7, true)] + [InlineData(0, true, 0, true, 31, true)] + // from K to K with material(s). Check requiredBlock == 0 + [InlineData(10, false, 0, false, 1)] + [InlineData(10, false, 0, true, 1)] + [InlineData(10, true, 0, false, 1)] + [InlineData(10, true, 0, true, 1)] + // from K to N using one level X material + [InlineData(5, false, 6, false, 1)] + [InlineData(5, false, 6, true, 1)] + [InlineData(5, true, 6, false, 1)] + [InlineData(5, true, 6, true, 1)] + // from K to N using multiple materials + [InlineData(5, false, 4, false, 6)] + [InlineData(5, false, 7, false, 5)] + [InlineData(5, false, 4, true, 6)] + [InlineData(5, false, 7, true, 5)] + [InlineData(5, true, 4, false, 6)] + [InlineData(5, true, 7, false, 5)] + [InlineData(5, true, 4, true, 6)] + [InlineData(5, true, 7, true, 5)] + // // Duplicated: from K to K + [InlineData(5, true, 4, true, 6, true)] + [InlineData(5, true, 7, true, 5, true)] + [InlineData(5, true, 4, false, 6, true)] + [InlineData(5, true, 7, false, 5, true)] + [InlineData(5, false, 4, true, 6, true)] + [InlineData(5, false, 7, true, 5, true)] + [InlineData(5, false, 4, false, 6, true)] + [InlineData(5, false, 7, false, 5, true)] + // from 20 to 21 (just to reach level 21 exp) + [InlineData(20, false, 20, false, 1)] + [InlineData(20, false, 20, true, 1)] + [InlineData(20, true, 20, false, 1)] + [InlineData(20, true, 20, true, 1)] + // from 20 to 21 (over level 21) + [InlineData(20, false, 20, false, 2)] + [InlineData(20, false, 20, true, 2)] + [InlineData(20, true, 20, false, 2)] + [InlineData(20, true, 20, true, 2)] + // from 21 to 21 (no level up) + [InlineData(21, false, 1, false, 1)] + [InlineData(21, false, 21, false, 1)] + [InlineData(21, false, 1, true, 1)] + [InlineData(21, false, 21, true, 1)] + [InlineData(21, true, 1, false, 1)] + [InlineData(21, true, 21, false, 1)] + [InlineData(21, true, 1, true, 1)] + [InlineData(21, true, 21, true, 1)] + public void Execute( + int startLevel, + bool oldStart, + int materialLevel, + bool oldMaterial, + int materialCount, + bool duplicated = false + ) + { + var row = _tableSheets.EquipmentItemSheet.Values.First(r => r.Id == 10110000); + var equipment = (Equipment)ItemFactory.CreateItemUsable(row, default, 0, startLevel); + if (startLevel == 0) + { + equipment.Exp = (long)row.Exp!; + } + else + { + equipment.Exp = _tableSheets.EnhancementCostSheetV3.OrderedList.First(r => + r.ItemSubType == equipment.ItemSubType && r.Grade == equipment.Grade && + r.Level == equipment.level).Exp; + } + + var startExp = equipment.Exp; + if (oldStart) + { + equipment.Exp = 0L; + } + + _avatarState.inventory.AddItem(equipment, count: 1); + + var startRow = _tableSheets.EnhancementCostSheetV3.OrderedList.FirstOrDefault(r => + r.Grade == equipment.Grade && r.ItemSubType == equipment.ItemSubType && + r.Level == startLevel); + var expectedExpIncrement = 0L; + var materialIds = new List(); + var duplicatedGuid = Guid.NewGuid(); + for (var i = 0; i < materialCount; i++) + { + var materialId = duplicated ? duplicatedGuid : Guid.NewGuid(); + materialIds.Add(materialId); + var material = + (Equipment)ItemFactory.CreateItemUsable(row, materialId, 0, materialLevel); + if (materialLevel == 0) + { + material.Exp = (long)row.Exp!; + } + else + { + material.Exp = _tableSheets.EnhancementCostSheetV3.OrderedList.First(r => + r.ItemSubType == material.ItemSubType && r.Grade == material.Grade && + r.Level == material.level).Exp; + } + + if (!(duplicated && i > 0)) + { + expectedExpIncrement += material.Exp; + } + + if (oldMaterial) + { + material.Exp = 0L; + } + + _avatarState.inventory.AddItem(material, count: 1); + } + + var result = new CombinationConsumable5.ResultModel() + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = equipment, + }; + var preItemUsable = new Equipment((Dictionary)equipment.Serialize()); + + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + _avatarState.Update(mail); + } + + _avatarState.worldInformation.ClearStage( + 1, + 1, + 1, + _tableSheets.WorldSheet, + _tableSheets.WorldUnlockSheet + ); + + var slotAddress = + _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0 + )); + + Assert.Equal(startLevel, equipment.level); + + _initialState = _initialState + .SetState( + _avatarAddress.Derive(LegacyInventoryKey), + _avatarState.inventory.Serialize() + ) + .SetState( + _avatarAddress.Derive(LegacyWorldInformationKey), + _avatarState.worldInformation.Serialize() + ) + .SetState( + _avatarAddress.Derive(LegacyQuestListKey), + _avatarState.questList.Serialize() + ) + .SetState(_avatarAddress, _avatarState.SerializeV2()); + + var action = new ItemEnhancement13 + { + itemId = default, + materialIds = materialIds, + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + var nextState = action.Execute(new ActionContext() + { + PreviousState = _initialState, + Signer = _agentAddress, + BlockIndex = 1, + RandomSeed = 0, + }); + + var slotState = nextState.GetCombinationSlotState(_avatarAddress, 0); + var resultEquipment = (Equipment)slotState.Result.itemUsable; + var level = resultEquipment.level; + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); + var expectedTargetRow = _tableSheets.EnhancementCostSheetV3.OrderedList.FirstOrDefault( + r => r.Grade == equipment.Grade && r.ItemSubType == equipment.ItemSubType && + r.Level == level); + var expectedCost = (expectedTargetRow?.Cost ?? 0) - (startRow?.Cost ?? 0); + var expectedBlockIndex = + (expectedTargetRow?.RequiredBlockIndex ?? 0) - (startRow?.RequiredBlockIndex ?? 0); + Assert.Equal(default, resultEquipment.ItemId); + Assert.Equal(startExp + expectedExpIncrement, resultEquipment.Exp); + Assert.Equal( + (3_000_000 - expectedCost) * _currency, + nextState.GetBalance(_agentAddress, _currency) + ); + + var arenaSheet = _tableSheets.ArenaSheet; + var arenaData = arenaSheet.GetRoundByBlockIndex(1); + var feeStoreAddress = + Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); + Assert.Equal( + expectedCost * _currency, + nextState.GetBalance(feeStoreAddress, _currency) + ); + Assert.Equal(30, nextAvatarState.mailBox.Count); + + var stateDict = (Dictionary)nextState.GetState(slotAddress); + var slot = new CombinationSlotState(stateDict); + var slotResult = (ItemEnhancement13.ResultModel)slot.Result; + if (startLevel != level) + { + var baseMinAtk = (decimal)preItemUsable.StatsMap.BaseATK; + var baseMaxAtk = (decimal)preItemUsable.StatsMap.BaseATK; + var extraMinAtk = (decimal)preItemUsable.StatsMap.AdditionalATK; + var extraMaxAtk = (decimal)preItemUsable.StatsMap.AdditionalATK; + + for (var i = startLevel + 1; i <= level; i++) + { + var currentRow = _tableSheets.EnhancementCostSheetV3.OrderedList + .First(x => + x.Grade == 1 && x.ItemSubType == equipment.ItemSubType && x.Level == i); + + baseMinAtk *= currentRow.BaseStatGrowthMin.NormalizeFromTenThousandths() + 1; + baseMaxAtk *= currentRow.BaseStatGrowthMax.NormalizeFromTenThousandths() + 1; + extraMinAtk *= currentRow.ExtraStatGrowthMin.NormalizeFromTenThousandths() + 1; + extraMaxAtk *= currentRow.ExtraStatGrowthMax.NormalizeFromTenThousandths() + 1; + } + + Assert.InRange( + resultEquipment.StatsMap.ATK, + baseMinAtk + extraMinAtk, + baseMaxAtk + extraMaxAtk + 1 + ); + } + + Assert.Equal( + expectedBlockIndex + 1, // +1 for execution + resultEquipment.RequiredBlockIndex + ); + Assert.Equal(preItemUsable.ItemId, slotResult.preItemUsable.ItemId); + Assert.Equal(preItemUsable.ItemId, resultEquipment.ItemId); + Assert.Equal(expectedCost, slotResult.gold); + } + } +} diff --git a/.Lib9c.Tests/Action/Raid6Test.cs b/.Lib9c.Tests/Action/Raid6Test.cs new file mode 100644 index 0000000000..b377efedec --- /dev/null +++ b/.Lib9c.Tests/Action/Raid6Test.cs @@ -0,0 +1,636 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Bencodex.Types; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Battle; + using Nekoyume.Extensions; + using Nekoyume.Helper; + using Nekoyume.Model.Arena; + using Nekoyume.Model.Rune; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Xunit; + using static SerializeKeys; + + public class Raid6Test + { + private readonly Dictionary _sheets; + private readonly Address _agentAddress; + private readonly Address _avatarAddress; + private readonly TableSheets _tableSheets; + private readonly Currency _goldCurrency; + + public Raid6Test() + { + _sheets = TableSheetsImporter.ImportSheets(); + _tableSheets = new TableSheets(_sheets); + _agentAddress = new PrivateKey().Address; + _avatarAddress = new PrivateKey().Address; +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + _goldCurrency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + } + + [Theory] + // Join new raid. + [InlineData(null, true, true, true, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + [InlineData(null, true, true, true, false, 0, 0L, false, false, 0, false, false, false, 5, true, 0, 10002, 1, 30001)] + // Refill by interval. + [InlineData(null, true, true, false, true, 0, -10368, false, false, 0, false, false, false, 5, true, 0, 10002, 1, 30001)] + // Refill by NCG. + [InlineData(null, true, true, false, true, 0, 200L, true, true, 0, false, false, false, 5, true, 0, 10002, 1, 30001)] + [InlineData(null, true, true, false, true, 0, 200L, true, true, 1, false, false, false, 5, true, 0, 10002, 1, 30001)] + // Boss level up. + [InlineData(null, true, true, false, true, 3, 100L, false, false, 0, true, true, false, 5, true, 0, 10002, 1, 30001)] + // Update RaidRewardInfo. + [InlineData(null, true, true, false, true, 3, 100L, false, false, 0, true, true, true, 5, true, 0, 10002, 1, 30001)] + // Boss skip level up. + [InlineData(null, true, true, false, true, 3, 100L, false, false, 0, true, false, false, 5, true, 0, 10002, 1, 30001)] + // AvatarState null. + [InlineData(typeof(FailedLoadStateException), false, false, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + // Stage not cleared. + [InlineData(typeof(NotEnoughClearedStageLevelException), true, false, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + // Insufficient CRYSTAL. + [InlineData(typeof(InsufficientBalanceException), true, true, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + // Insufficient NCG. + [InlineData(typeof(InsufficientBalanceException), true, true, false, true, 0, 0L, true, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + // Wait interval. + [InlineData(typeof(RequiredBlockIntervalException), true, true, false, true, 3, 10L, false, false, 0, false, false, false, 1, false, 0, 10002, 1, 30001)] + // Exceed purchase limit. + [InlineData(typeof(ExceedTicketPurchaseLimitException), true, true, false, true, 0, 100L, true, false, 1_000, false, false, false, 5, false, 0, 10002, 1, 30001)] + // Exceed challenge count. + [InlineData(typeof(ExceedPlayCountException), true, true, false, true, 0, 100L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] + [InlineData(typeof(DuplicatedRuneIdException), true, true, false, true, 3, 100L, true, false, 0, false, false, false, 5, false, 0, 30001, 1, 30001)] + [InlineData(typeof(DuplicatedRuneSlotIndexException), true, true, false, true, 3, 100L, true, false, 0, false, false, false, 5, false, 1, 10002, 1, 30001)] + public void Execute( + Type exc, + bool avatarExist, + bool stageCleared, + bool crystalExist, + bool raiderStateExist, + int remainChallengeCount, + long refillBlockIndexOffset, + bool payNcg, + bool ncgExist, + int purchaseCount, + bool kill, + bool levelUp, + bool rewardRecordExist, + long executeOffset, + bool raiderListExist, + int slotIndex, + int runeId, + int slotIndex2, + int runeId2 + ) + { + var blockIndex = _tableSheets.WorldBossListSheet.Values + .OrderBy(x => x.StartedBlockIndex) + .First(x => + { + if (exc == typeof(InsufficientBalanceException)) + { + return ncgExist ? x.TicketPrice > 0 : x.EntranceFee > 0; + } + + return true; + }) + .StartedBlockIndex; + + var action = new Raid6 + { + AvatarAddress = _avatarAddress, + EquipmentIds = new List(), + CostumeIds = new List(), + FoodIds = new List(), + RuneInfos = new List() + { + new RuneSlotInfo(slotIndex, runeId), + new RuneSlotInfo(slotIndex2, runeId2), + }, + PayNcg = payNcg, + }; + Currency crystal = CrystalCalculator.CRYSTAL; + int raidId = _tableSheets.WorldBossListSheet.FindRaidIdByBlockIndex(blockIndex); + Address raiderAddress = Addresses.GetRaiderAddress(_avatarAddress, raidId); + var goldCurrencyState = new GoldCurrencyState(_goldCurrency); + WorldBossListSheet.Row worldBossRow = _tableSheets.WorldBossListSheet.FindRowByBlockIndex(blockIndex); + var hpSheet = _tableSheets.WorldBossGlobalHpSheet; + Address bossAddress = Addresses.GetWorldBossAddress(raidId); + Address worldBossKillRewardRecordAddress = Addresses.GetWorldBossKillRewardRecordAddress(_avatarAddress, raidId); + Address raiderListAddress = Addresses.GetRaiderListAddress(raidId); + int level = 1; + if (kill & !levelUp) + { + level = hpSheet.OrderedList.Last().Level; + } + + var fee = _tableSheets.WorldBossListSheet[raidId].EntranceFee; + + var context = new ActionContext(); + IAccount state = new Account(MockState.Empty) + .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + + foreach (var (key, value) in _sheets) + { + state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + default + ); + + if (avatarExist) + { + var equipments = Doomfist.GetAllParts(_tableSheets, avatarState.level); + foreach (var equipment in equipments) + { + avatarState.inventory.AddItem(equipment); + } + + if (stageCleared) + { + for (int i = 0; i < 50; i++) + { + avatarState.worldInformation.ClearStage(1, i + 1, 0, _tableSheets.WorldSheet, _tableSheets.WorldUnlockSheet); + } + } + + if (crystalExist) + { + var price = _tableSheets.WorldBossListSheet[raidId].EntranceFee; + state = state.MintAsset(context, _agentAddress, price * crystal); + } + + if (raiderStateExist) + { + var raiderState = new RaiderState(); + raiderState.RefillBlockIndex = blockIndex + refillBlockIndexOffset; + raiderState.RemainChallengeCount = remainChallengeCount; + raiderState.TotalScore = 1_000; + raiderState.HighScore = 0; + raiderState.TotalChallengeCount = 1; + raiderState.PurchaseCount = purchaseCount; + raiderState.Cp = 0; + raiderState.Level = 0; + raiderState.IconId = 0; + raiderState.AvatarName = "hash"; + raiderState.AvatarAddress = _avatarAddress; + raiderState.UpdatedBlockIndex = blockIndex; + + state = state.SetState(raiderAddress, raiderState.Serialize()); + + var raiderList = new List().Add(raiderAddress.Serialize()); + + if (raiderListExist) + { + raiderList = raiderList.Add(new PrivateKey().Address.Serialize()); + } + + state = state.SetState(raiderListAddress, raiderList); + } + + if (rewardRecordExist) + { + var rewardRecord = new WorldBossKillRewardRecord + { + [0] = false, + }; + state = state.SetState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); + } + + if (ncgExist) + { + var row = _tableSheets.WorldBossListSheet.FindRowByBlockIndex(blockIndex); + state = state.MintAsset(context, _agentAddress, (row.TicketPrice + row.AdditionalTicketPrice * purchaseCount) * _goldCurrency); + } + + state = state + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(gameConfigState.address, gameConfigState.Serialize()); + } + + if (kill) + { + var bossState = + new WorldBossState(worldBossRow, _tableSheets.WorldBossGlobalHpSheet[level]) + { + CurrentHp = 0, + Level = level, + }; + state = state.SetState(bossAddress, bossState.Serialize()); + } + + if (exc is null) + { + var randomSeed = 0; + var ctx = new ActionContext + { + BlockIndex = blockIndex + executeOffset, + PreviousState = state, + RandomSeed = randomSeed, + Signer = _agentAddress, + }; + + var nextState = action.Execute(ctx); + + var random = new TestRandom(randomSeed); + var bossListRow = _tableSheets.WorldBossListSheet.FindRowByBlockIndex(ctx.BlockIndex); + var raidSimulatorSheets = _tableSheets.GetRaidSimulatorSheets(); + var simulator = new RaidSimulator( + bossListRow.BossId, + random, + avatarState, + action.FoodIds, + null, + raidSimulatorSheets, + _tableSheets.CostumeStatSheet); + simulator.Simulate(); + var score = simulator.DamageDealt; + + Dictionary rewardMap + = new Dictionary(); + foreach (var reward in simulator.AssetReward) + { + rewardMap[reward.Currency] = reward; + } + + if (rewardRecordExist) + { + var bossRow = raidSimulatorSheets.WorldBossCharacterSheet[bossListRow.BossId]; + Assert.True(state.TryGetState(bossAddress, out List prevRawBoss)); + var prevBossState = new WorldBossState(prevRawBoss); + int rank = WorldBossHelper.CalculateRank(bossRow, raiderStateExist ? 1_000 : 0); + var rewards = RuneHelper.CalculateReward( + rank, + prevBossState.Id, + _tableSheets.RuneWeightSheet, + _tableSheets.WorldBossKillRewardSheet, + _tableSheets.RuneSheet, + random + ); + + foreach (var reward in rewards) + { + if (!rewardMap.ContainsKey(reward.Currency)) + { + rewardMap[reward.Currency] = reward; + } + else + { + rewardMap[reward.Currency] += reward; + } + } + + foreach (var reward in rewardMap) + { + if (reward.Key.Equals(CrystalCalculator.CRYSTAL)) + { + Assert.Equal(reward.Value, nextState.GetBalance(_agentAddress, reward.Key)); + } + else + { + Assert.Equal(reward.Value, nextState.GetBalance(_avatarAddress, reward.Key)); + } + } + } + + if (rewardMap.ContainsKey(crystal)) + { + Assert.Equal(rewardMap[crystal], nextState.GetBalance(_agentAddress, crystal)); + } + + if (crystalExist) + { + Assert.Equal(fee * crystal, nextState.GetBalance(bossAddress, crystal)); + } + + Assert.True(nextState.TryGetState(raiderAddress, out List rawRaider)); + var raiderState = new RaiderState(rawRaider); + int expectedTotalScore = raiderStateExist ? 1_000 + score : score; + int expectedRemainChallenge = payNcg ? 0 : 2; + int expectedTotalChallenge = raiderStateExist ? 2 : 1; + + Assert.Equal(score, raiderState.HighScore); + Assert.Equal(expectedTotalScore, raiderState.TotalScore); + Assert.Equal(expectedRemainChallenge, raiderState.RemainChallengeCount); + Assert.Equal(expectedTotalChallenge, raiderState.TotalChallengeCount); + Assert.Equal(1, raiderState.Level); + Assert.Equal(GameConfig.DefaultAvatarArmorId, raiderState.IconId); + Assert.True(raiderState.Cp > 0); + + Assert.True(nextState.TryGetState(bossAddress, out List rawBoss)); + var bossState = new WorldBossState(rawBoss); + int expectedLevel = level; + if (kill & levelUp) + { + expectedLevel++; + } + + Assert.Equal(expectedLevel, bossState.Level); + Assert.Equal(expectedLevel, raiderState.LatestBossLevel); + if (kill) + { + Assert.Equal(hpSheet[expectedLevel].Hp, bossState.CurrentHp); + } + + if (payNcg) + { + Assert.Equal(0 * _goldCurrency, nextState.GetBalance(_agentAddress, _goldCurrency)); + Assert.Equal(purchaseCount + 1, nextState.GetRaiderState(raiderAddress).PurchaseCount); + } + + Assert.True(nextState.TryGetState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); + var rewardRecord = new WorldBossKillRewardRecord(rawRewardInfo); + Assert.Contains(expectedLevel, rewardRecord.Keys); + if (rewardRecordExist) + { + Assert.True(rewardRecord[0]); + } + else + { + if (expectedLevel == 1) + { + Assert.False(rewardRecord[1]); + } + else + { + Assert.DoesNotContain(1, rewardRecord.Keys); + } + } + + Assert.True(nextState.TryGetState(raiderListAddress, out List rawRaiderList)); + List
raiderList = rawRaiderList.ToList(StateExtensions.ToAddress); + + Assert.Contains(raiderAddress, raiderList); + } + else + { + if (exc == typeof(DuplicatedRuneIdException) || exc == typeof(DuplicatedRuneSlotIndexException)) + { + var ncgCurrency = state.GetGoldCurrency(); + state = state.MintAsset(context, _agentAddress, 99999 * ncgCurrency); + + var unlockRuneSlot = new UnlockRuneSlot() + { + AvatarAddress = _avatarAddress, + SlotIndex = 1, + }; + + state = unlockRuneSlot.Execute(new ActionContext + { + BlockIndex = 1, + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + } + + Assert.Throws(exc, () => action.Execute(new ActionContext + { + BlockIndex = blockIndex + executeOffset, + PreviousState = state, + RandomSeed = 0, + Signer = _agentAddress, + })); + } + } + + [Fact] + public void Execute_With_Reward() + { + var action = new Raid6 + { + AvatarAddress = _avatarAddress, + EquipmentIds = new List(), + CostumeIds = new List(), + FoodIds = new List(), + RuneInfos = new List(), + PayNcg = false, + }; + + var worldBossRow = _tableSheets.WorldBossListSheet.First().Value; + int raidId = worldBossRow.Id; + Address raiderAddress = Addresses.GetRaiderAddress(_avatarAddress, raidId); + var goldCurrencyState = new GoldCurrencyState(_goldCurrency); + Address bossAddress = Addresses.GetWorldBossAddress(raidId); + Address worldBossKillRewardRecordAddress = Addresses.GetWorldBossKillRewardRecordAddress(_avatarAddress, raidId); + + IAccount state = new Account(MockState.Empty) + .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + + foreach (var (key, value) in _sheets) + { + state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + default + ); + + for (int i = 0; i < 50; i++) + { + avatarState.worldInformation.ClearStage(1, i + 1, 0, _tableSheets.WorldSheet, _tableSheets.WorldUnlockSheet); + } + + var raiderState = new RaiderState(); + raiderState.RefillBlockIndex = 0; + raiderState.RemainChallengeCount = WorldBossHelper.MaxChallengeCount; + raiderState.TotalScore = 1_000; + raiderState.TotalChallengeCount = 1; + raiderState.PurchaseCount = 0; + raiderState.Cp = 0; + raiderState.Level = 0; + raiderState.IconId = 0; + raiderState.AvatarName = "hash"; + raiderState.AvatarAddress = _avatarAddress; + state = state.SetState(raiderAddress, raiderState.Serialize()); + + var rewardRecord = new WorldBossKillRewardRecord + { + [1] = false, + }; + state = state.SetState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); + + state = state + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(gameConfigState.address, gameConfigState.Serialize()); + + var bossState = + new WorldBossState(worldBossRow, _tableSheets.WorldBossGlobalHpSheet[2]) + { + CurrentHp = 0, + Level = 2, + }; + state = state.SetState(bossAddress, bossState.Serialize()); + var randomSeed = 0; + var random = new TestRandom(randomSeed); + + var simulator = new RaidSimulator( + worldBossRow.BossId, + random, + avatarState, + action.FoodIds, + null, + _tableSheets.GetRaidSimulatorSheets(), + _tableSheets.CostumeStatSheet); + simulator.Simulate(); + + Dictionary rewardMap + = new Dictionary(); + foreach (var reward in simulator.AssetReward) + { + rewardMap[reward.Currency] = reward; + } + + List killRewards = RuneHelper.CalculateReward( + 0, + bossState.Id, + _tableSheets.RuneWeightSheet, + _tableSheets.WorldBossKillRewardSheet, + _tableSheets.RuneSheet, + random + ); + + var nextState = action.Execute(new ActionContext + { + BlockIndex = worldBossRow.StartedBlockIndex + gameConfigState.WorldBossRequiredInterval, + PreviousState = state, + RandomSeed = randomSeed, + Signer = _agentAddress, + }); + + Assert.True(nextState.TryGetState(raiderAddress, out List rawRaider)); + var nextRaiderState = new RaiderState(rawRaider); + Assert.Equal(simulator.DamageDealt, nextRaiderState.HighScore); + + foreach (var reward in killRewards) + { + if (!rewardMap.ContainsKey(reward.Currency)) + { + rewardMap[reward.Currency] = reward; + } + else + { + rewardMap[reward.Currency] += reward; + } + } + + foreach (var reward in rewardMap) + { + if (reward.Key.Equals(CrystalCalculator.CRYSTAL)) + { + Assert.Equal(reward.Value, nextState.GetBalance(_agentAddress, reward.Key)); + } + else + { + Assert.Equal(reward.Value, nextState.GetBalance(_avatarAddress, reward.Key)); + } + } + + Assert.Equal(1, nextRaiderState.Level); + Assert.Equal(GameConfig.DefaultAvatarArmorId, nextRaiderState.IconId); + Assert.True(nextRaiderState.Cp > 0); + Assert.Equal(3, nextRaiderState.LatestBossLevel); + Assert.True(nextState.TryGetState(bossAddress, out List rawBoss)); + var nextBossState = new WorldBossState(rawBoss); + Assert.Equal(3, nextBossState.Level); + Assert.True(nextState.TryGetState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); + var nextRewardInfo = new WorldBossKillRewardRecord(rawRewardInfo); + Assert.True(nextRewardInfo[1]); + } + + [Fact] + public void Execute_With_Free_Crystal_Fee() + { + var action = new Raid6 + { + AvatarAddress = _avatarAddress, + EquipmentIds = new List(), + CostumeIds = new List(), + FoodIds = new List(), + RuneInfos = new List(), + PayNcg = false, + }; + Currency crystal = CrystalCalculator.CRYSTAL; + + _sheets[nameof(WorldBossListSheet)] = + "id,boss_id,started_block_index,ended_block_index,fee,ticket_price,additional_ticket_price,max_purchase_count\r\n" + + "1,900002,0,100,0,1,1,40"; + + var goldCurrencyState = new GoldCurrencyState(_goldCurrency); + IAccount state = new Account(MockState.Empty) + .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + + foreach (var (key, value) in _sheets) + { + state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + default + ); + + for (int i = 0; i < 50; i++) + { + avatarState.worldInformation.ClearStage(1, i + 1, 0, _tableSheets.WorldSheet, _tableSheets.WorldUnlockSheet); + } + + state = state + .SetState(_avatarAddress, avatarState.SerializeV2()) + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(gameConfigState.address, gameConfigState.Serialize()); + + var blockIndex = gameConfigState.WorldBossRequiredInterval; + var randomSeed = 0; + var ctx = new ActionContext + { + BlockIndex = blockIndex, + PreviousState = state, + RandomSeed = randomSeed, + Signer = _agentAddress, + }; + + IAccount nextState; + var exception = Record.Exception(() => nextState = action.Execute(ctx)); + Assert.Null(exception); + } + } +} diff --git a/.Lib9c.Tests/Action/RapidCombination9Test.cs b/.Lib9c.Tests/Action/RapidCombination9Test.cs new file mode 100644 index 0000000000..25833ae3d5 --- /dev/null +++ b/.Lib9c.Tests/Action/RapidCombination9Test.cs @@ -0,0 +1,684 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Globalization; + using System.Linq; + using Bencodex.Types; + using Lib9c.Tests.Fixtures.TableCSV; + using Lib9c.Tests.Fixtures.TableCSV.Item; + using Lib9c.Tests.Util; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Helper; + using Nekoyume.Model; + using Nekoyume.Model.Item; + using Nekoyume.Model.Mail; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Xunit; + using static Lib9c.SerializeKeys; + + public class RapidCombination9Test + { + private readonly IAccount _initialState; + + private readonly TableSheets _tableSheets; + + private readonly Address _agentAddress; + private readonly Address _avatarAddress; + + public RapidCombination9Test() + { + _initialState = new Account(MockState.Empty); + Dictionary sheets; + (_initialState, sheets) = InitializeUtil.InitializeTableSheets( + _initialState, + sheetsOverride: new Dictionary + { + { + "EquipmentItemRecipeSheet", + EquipmentItemRecipeSheetFixtures.Default + }, + { + "EquipmentItemSubRecipeSheet", + EquipmentItemSubRecipeSheetFixtures.V1 + }, + { + "GameConfigSheet", + GameConfigSheetFixtures.Default + }, + }); + _tableSheets = new TableSheets(sheets); + foreach (var (key, value) in sheets) + { + _initialState = + _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + _agentAddress = new PrivateKey().Address; + var agentState = new AgentState(_agentAddress); + + _avatarAddress = new PrivateKey().Address; + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + agentState.avatarAddresses[0] = _avatarAddress; + + _initialState = _initialState + .SetState(Addresses.GameConfig, new GameConfigState(sheets[nameof(GameConfigSheet)]).Serialize()) + .SetState(_agentAddress, agentState.Serialize()) + .SetState(_avatarAddress, avatarState.Serialize()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute(bool backward) + { + const int slotStateUnlockStage = 1; + + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + slotStateUnlockStage); + + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.Hourglass); + avatarState.inventory.AddItem(ItemFactory.CreateMaterial(row), 83); + avatarState.inventory.AddItem(ItemFactory.CreateTradableMaterial(row), 100); + Assert.True(avatarState.inventory.HasFungibleItem(row.ItemId, 0, 183)); + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; + Assert.NotNull(firstEquipmentRow); + + var gameConfigState = _initialState.GetGameConfigState(); + var requiredBlockIndex = gameConfigState.HourglassPerBlock * 200; + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + requiredBlockIndex); + avatarState.inventory.AddItem(equipment); + + var result = new CombinationConsumable5.ResultModel + { + actionPoint = 0, + gold = 0, + materials = new Dictionary(), + itemUsable = equipment, + recipeId = 0, + itemType = ItemType.Equipment, + }; + + var mail = new CombinationMail(result, 0, default, requiredBlockIndex); + result.id = mail.id; + avatarState.Update2(mail); + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); + slotState.Update(result, 0, requiredBlockIndex); + + var tempState = _initialState.SetState(slotAddress, slotState.Serialize()); + + if (backward) + { + tempState = tempState.SetState(_avatarAddress, avatarState.Serialize()); + } + else + { + tempState = tempState + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(_avatarAddress, avatarState.SerializeV2()); + } + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 51, + }); + + var nextAvatarState = nextState.GetAvatarStateV2(_avatarAddress); + var item = nextAvatarState.inventory.Equipments.First(); + + Assert.Empty(nextAvatarState.inventory.Materials.Select(r => r.ItemSubType == ItemSubType.Hourglass)); + Assert.Equal(equipment.ItemId, item.ItemId); + Assert.Equal(51, item.RequiredBlockIndex); + } + + [Fact] + public void Execute_Throw_CombinationSlotResultNullException() + { + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, 0); + slotState.Update(null, 0, 0); + + var tempState = _initialState + .SetState(slotAddress, slotState.Serialize()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 1, + })); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(1, 2)] + public void Execute_Throw_NotEnoughClearedStageLevelException(int avatarClearedStage, int slotStateUnlockStage) + { + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + avatarClearedStage); + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; + Assert.NotNull(firstEquipmentRow); + + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + 100); + + var result = new CombinationConsumable5.ResultModel + { + actionPoint = 0, + gold = 0, + materials = new Dictionary(), + itemUsable = equipment, + recipeId = 0, + itemType = ItemType.Equipment, + }; + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); + slotState.Update(result, 0, 0); + + var tempState = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 1, + })); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(10, 100)] + public void Execute_Throw_RequiredBlockIndexException(int itemRequiredBlockIndex, int contextBlockIndex) + { + const int avatarClearedStage = 1; + + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + avatarClearedStage); + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; + Assert.NotNull(firstEquipmentRow); + + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + itemRequiredBlockIndex); + + var result = new CombinationConsumable5.ResultModel + { + actionPoint = 0, + gold = 0, + materials = new Dictionary(), + itemUsable = equipment, + recipeId = 0, + itemType = ItemType.Equipment, + }; + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, avatarClearedStage); + slotState.Update(result, 0, 0); + + var tempState = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = contextBlockIndex, + })); + } + + [Theory] + [InlineData(0, 0, 0, 40)] + [InlineData(0, 1, 2, 40)] + [InlineData(22, 0, 0, 40)] + [InlineData(0, 22, 0, 40)] + [InlineData(0, 22, 2, 40)] + [InlineData(2, 10, 2, 40)] + public void Execute_Throw_NotEnoughMaterialException(int materialCount, int tradableCount, long blockIndex, int requiredCount) + { + const int slotStateUnlockStage = 1; + + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + slotStateUnlockStage); + + var row = _tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.Hourglass); + avatarState.inventory.AddItem(ItemFactory.CreateMaterial(row), count: materialCount); + if (tradableCount > 0) + { + var material = ItemFactory.CreateTradableMaterial(row); + material.RequiredBlockIndex = blockIndex; + avatarState.inventory.AddItem(material, count: tradableCount); + } + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; + Assert.NotNull(firstEquipmentRow); + + var gameConfigState = _initialState.GetGameConfigState(); + var requiredBlockIndex = gameConfigState.HourglassPerBlock * requiredCount; + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + requiredBlockIndex); + avatarState.inventory.AddItem(equipment); + + var result = new CombinationConsumable5.ResultModel + { + actionPoint = 0, + gold = 0, + materials = new Dictionary(), + itemUsable = equipment, + recipeId = 0, + itemType = ItemType.Equipment, + }; + + var mail = new CombinationMail(result, 0, default, requiredBlockIndex); + result.id = mail.id; + avatarState.Update2(mail); + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); + slotState.Update(result, 0, 0); + + var tempState = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 51, + })); + } + + [Theory] + [InlineData(null)] + [InlineData(1)] + public void ResultModelDeterministic(int? subRecipeId) + { + var row = _tableSheets.MaterialItemSheet.Values.First(); + var row2 = _tableSheets.MaterialItemSheet.Values.Last(); + + Assert.True(row.Id < row2.Id); + + var material = ItemFactory.CreateMaterial(row); + var material2 = ItemFactory.CreateMaterial(row2); + + var itemUsable = ItemFactory.CreateItemUsable(_tableSheets.EquipmentItemSheet.Values.First(), default, 0); + var r = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + subRecipeId = subRecipeId, + materials = new Dictionary + { + [material] = 1, + [material2] = 1, + }, + itemUsable = itemUsable, + }; + var result = new RapidCombination0.ResultModel((Dictionary)r.Serialize()) + { + cost = new Dictionary + { + [material] = 1, + [material2] = 1, + }, + }; + + var r2 = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + subRecipeId = subRecipeId, + materials = new Dictionary + { + [material2] = 1, + [material] = 1, + }, + itemUsable = itemUsable, + }; + + var result2 = new RapidCombination0.ResultModel((Dictionary)r2.Serialize()) + { + cost = new Dictionary + { + [material2] = 1, + [material] = 1, + }, + }; + + Assert.Equal(result.Serialize(), result2.Serialize()); + } + + [Fact] + public void Execute_Throw_RequiredAppraiseBlockException() + { + const int slotStateUnlockStage = 1; + + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + slotStateUnlockStage); + + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.Hourglass); + avatarState.inventory.AddItem(ItemFactory.CreateMaterial(row), count: 22); + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; + Assert.NotNull(firstEquipmentRow); + + var gameConfigState = _initialState.GetGameConfigState(); + var requiredBlockIndex = gameConfigState.HourglassPerBlock * 40; + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + requiredBlockIndex); + avatarState.inventory.AddItem(equipment); + + var result = new CombinationConsumable5.ResultModel + { + actionPoint = 0, + gold = 0, + materials = new Dictionary(), + itemUsable = equipment, + recipeId = 0, + itemType = ItemType.Equipment, + }; + + var mail = new CombinationMail(result, 0, default, requiredBlockIndex); + result.id = mail.id; + avatarState.Update(mail); + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); + slotState.Update(result, 0, 0); + + var tempState = _initialState + .SetState(_avatarAddress, avatarState.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 1, + })); + } + + [Theory] + [InlineData(7)] + [InlineData(9)] + [InlineData(10)] + [InlineData(11)] + public void Execute_NotThrow_InvalidOperationException_When_TargetSlotCreatedBy( + int itemEnhancementResultModelNumber) + { + const int slotStateUnlockStage = 1; + + var avatarState = _initialState.GetAvatarState(_avatarAddress); + avatarState.worldInformation = new WorldInformation( + 0, + _initialState.GetSheet(), + slotStateUnlockStage); + + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.Hourglass); + avatarState.inventory.AddItem(ItemFactory.CreateMaterial(row), 83); + avatarState.inventory.AddItem(ItemFactory.CreateTradableMaterial(row), 100); + Assert.True(avatarState.inventory.HasFungibleItem(row.ItemId, 0, 183)); + + var firstEquipmentRow = _tableSheets.EquipmentItemSheet + .OrderedList.First(e => e.Grade >= 1); + Assert.NotNull(firstEquipmentRow); + + var gameConfigState = _initialState.GetGameConfigState(); + var requiredBlockIndex = gameConfigState.HourglassPerBlock * 200; + var equipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + requiredBlockIndex); + var materialEquipment = (Equipment)ItemFactory.CreateItemUsable( + firstEquipmentRow, + Guid.NewGuid(), + requiredBlockIndex); + avatarState.inventory.AddItem(equipment); + avatarState.inventory.AddItem(materialEquipment); + + AttachmentActionResult resultModel = null; + var random = new TestRandom(); + var mailId = random.GenerateRandomGuid(); + var preItemUsable = new Equipment((Dictionary)equipment.Serialize()); + switch (itemEnhancementResultModelNumber) + { + case 7: + { + equipment = ItemEnhancement7.UpgradeEquipment(equipment); + resultModel = new ItemEnhancement7.ResultModel + { + id = mailId, + itemUsable = equipment, + materialItemIdList = new[] { materialEquipment.NonFungibleId }, + }; + + break; + } + + case 9: + { + Assert.True(ItemEnhancement9.TryGetRow( + equipment, + _tableSheets.EnhancementCostSheetV2, + out var costRow)); + var equipmentResult = ItemEnhancement9.GetEnhancementResult(costRow, random); + equipment.LevelUp( + random, + costRow, + equipmentResult == ItemEnhancement9.EnhancementResult.GreatSuccess); + resultModel = new ItemEnhancement9.ResultModel + { + id = mailId, + preItemUsable = preItemUsable, + itemUsable = equipment, + materialItemIdList = new[] { materialEquipment.NonFungibleId }, + gold = 0, + actionPoint = 0, + enhancementResult = ItemEnhancement9.EnhancementResult.GreatSuccess, + }; + + break; + } + + case 10: + { + Assert.True(ItemEnhancement10.TryGetRow( + equipment, + _tableSheets.EnhancementCostSheetV2, + out var costRow)); + var equipmentResult = ItemEnhancement10.GetEnhancementResult(costRow, random); + equipment.LevelUp( + random, + costRow, + equipmentResult == ItemEnhancement10.EnhancementResult.GreatSuccess); + resultModel = new ItemEnhancement10.ResultModel + { + id = mailId, + preItemUsable = preItemUsable, + itemUsable = equipment, + materialItemIdList = new[] { materialEquipment.NonFungibleId }, + gold = 0, + actionPoint = 0, + enhancementResult = ItemEnhancement10.EnhancementResult.GreatSuccess, + }; + + break; + } + + case 11: + { + Assert.True(ItemEnhancement11.TryGetRow( + equipment, + _tableSheets.EnhancementCostSheetV2, + out var costRow)); + var equipmentResult = ItemEnhancement11.GetEnhancementResult(costRow, random); + equipment.LevelUp( + random, + costRow, + equipmentResult == ItemEnhancement11.EnhancementResult.GreatSuccess); + resultModel = new ItemEnhancement11.ResultModel + { + id = mailId, + preItemUsable = preItemUsable, + itemUsable = equipment, + materialItemIdList = new[] { materialEquipment.NonFungibleId }, + gold = 0, + actionPoint = 0, + enhancementResult = ItemEnhancement11.EnhancementResult.GreatSuccess, + CRYSTAL = 0 * CrystalCalculator.CRYSTAL, + }; + + break; + } + + default: + break; + } + + // NOTE: Do not update `mail`, because this test assumes that the `mail` was removed. + { + // var mail = new ItemEnhanceMail(resultModel, 0, random.GenerateRandomGuid(), requiredBlockIndex); + // avatarState.Update(mail); + } + + var slotAddress = _avatarAddress.Derive(string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + 0)); + var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); + slotState.Update(resultModel, 0, requiredBlockIndex); + + var tempState = _initialState.SetState(slotAddress, slotState.Serialize()) + .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) + .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(_avatarAddress, avatarState.SerializeV2()); + + var action = new RapidCombination9 + { + avatarAddress = _avatarAddress, + slotIndex = 0, + }; + + action.Execute(new ActionContext + { + PreviousState = tempState, + Signer = _agentAddress, + BlockIndex = 51, + }); + } + } +} From 536e5091ee3256d6ad2fb20d6779db1d9fd50a0a Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 14 Mar 2024 14:26:09 +0900 Subject: [PATCH 02/10] Basic renaming --- ...e8Test.cs => CombinationConsumableTest.cs} | 6 ++-- ...ateAvatar10Test.cs => CreateAvatarTest.cs} | 16 +++++----- ...tleV5Test.cs => EventDungeonBattleTest.cs} | 6 ++-- ...Sweep9Test.cs => HackAndSlashSweepTest.cs} | 30 +++++++++---------- ...cement13Test.cs => ItemEnhancementTest.cs} | 6 ++-- .../Action/{Raid6Test.cs => RaidTest.cs} | 10 +++---- ...nation9Test.cs => RapidCombinationTest.cs} | 18 +++++------ 7 files changed, 46 insertions(+), 46 deletions(-) rename .Lib9c.Tests/Action/{CombinationConsumable8Test.cs => CombinationConsumableTest.cs} (97%) rename .Lib9c.Tests/Action/{CreateAvatar10Test.cs => CreateAvatarTest.cs} (96%) rename .Lib9c.Tests/Action/{EventDungeonBattleV5Test.cs => EventDungeonBattleTest.cs} (99%) rename .Lib9c.Tests/Action/{HackAndSlashSweep9Test.cs => HackAndSlashSweepTest.cs} (98%) rename .Lib9c.Tests/Action/{ItemEnhancement13Test.cs => ItemEnhancementTest.cs} (99%) rename .Lib9c.Tests/Action/{Raid6Test.cs => RaidTest.cs} (99%) rename .Lib9c.Tests/Action/{RapidCombination9Test.cs => RapidCombinationTest.cs} (98%) diff --git a/.Lib9c.Tests/Action/CombinationConsumable8Test.cs b/.Lib9c.Tests/Action/CombinationConsumableTest.cs similarity index 97% rename from .Lib9c.Tests/Action/CombinationConsumable8Test.cs rename to .Lib9c.Tests/Action/CombinationConsumableTest.cs index 90fc3bb85d..bc66bc3981 100644 --- a/.Lib9c.Tests/Action/CombinationConsumable8Test.cs +++ b/.Lib9c.Tests/Action/CombinationConsumableTest.cs @@ -15,7 +15,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class CombinationConsumable8Test + public class CombinationConsumableTest { private readonly Address _agentAddress; private readonly Address _avatarAddress; @@ -23,7 +23,7 @@ public class CombinationConsumable8Test private readonly TableSheets _tableSheets; private IAccount _initialState; - public CombinationConsumable8Test() + public CombinationConsumableTest() { _agentAddress = new PrivateKey().Address; _avatarAddress = _agentAddress.Derive("avatar"); @@ -113,7 +113,7 @@ public void Execute(bool backward) .SetState(_avatarAddress, avatarState.SerializeV2()); } - var action = new CombinationConsumable8 + var action = new CombinationConsumable { avatarAddress = _avatarAddress, recipeId = row.Id, diff --git a/.Lib9c.Tests/Action/CreateAvatar10Test.cs b/.Lib9c.Tests/Action/CreateAvatarTest.cs similarity index 96% rename from .Lib9c.Tests/Action/CreateAvatar10Test.cs rename to .Lib9c.Tests/Action/CreateAvatarTest.cs index 71a257306d..c06f32e5b2 100644 --- a/.Lib9c.Tests/Action/CreateAvatar10Test.cs +++ b/.Lib9c.Tests/Action/CreateAvatarTest.cs @@ -17,12 +17,12 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class CreateAvatar10Test + public class CreateAvatarTest { private readonly Address _agentAddress; private readonly TableSheets _tableSheets; - public CreateAvatar10Test() + public CreateAvatarTest() { _agentAddress = default; _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); @@ -34,7 +34,7 @@ public CreateAvatar10Test() [InlineData(7_210_001L)] public void Execute(long blockIndex) { - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = 0, hair = 0, @@ -106,7 +106,7 @@ public void ExecuteThrowInvalidNamePatterException(string nickName) { var agentAddress = default(Address); - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = 0, hair = 0, @@ -147,7 +147,7 @@ public void ExecuteThrowInvalidAddressException() default ); - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = 0, hair = 0, @@ -175,7 +175,7 @@ public void ExecuteThrowAvatarIndexOutOfRangeException(int index) { var agentState = new AgentState(_agentAddress); var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = index, hair = 0, @@ -211,7 +211,7 @@ public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) agentState.avatarAddresses[index] = avatarAddress; var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = index, hair = 0, @@ -234,7 +234,7 @@ public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) public void Serialize_With_DotnetAPI() { var formatter = new BinaryFormatter(); - var action = new CreateAvatar10() + var action = new CreateAvatar() { index = 2, hair = 1, diff --git a/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs b/.Lib9c.Tests/Action/EventDungeonBattleTest.cs similarity index 99% rename from .Lib9c.Tests/Action/EventDungeonBattleV5Test.cs rename to .Lib9c.Tests/Action/EventDungeonBattleTest.cs index 07197699ad..940acbfa93 100644 --- a/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs +++ b/.Lib9c.Tests/Action/EventDungeonBattleTest.cs @@ -20,7 +20,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class EventDungeonBattleV5Test + public class EventDungeonBattleTest { private readonly Currency _ncgCurrency; private readonly TableSheets _tableSheets; @@ -29,7 +29,7 @@ public class EventDungeonBattleV5Test private readonly Address _avatarAddress; private IAccount _initialStates; - public EventDungeonBattleV5Test() + public EventDungeonBattleTest() { _initialStates = new Account(MockState.Empty); @@ -456,7 +456,7 @@ private IAccount Execute( previousAvatarState.inventory.AddItem(equipment, iLock: null); } - var action = new EventDungeonBattleV5 + var action = new EventDungeonBattle { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs similarity index 98% rename from .Lib9c.Tests/Action/HackAndSlashSweep9Test.cs rename to .Lib9c.Tests/Action/HackAndSlashSweepTest.cs index 53a7dafbed..f5ee35ae51 100644 --- a/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs +++ b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs @@ -20,7 +20,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class HackAndSlashSweep9Test + public class HackAndSlashSweepTest { private readonly Dictionary _sheets; private readonly TableSheets _tableSheets; @@ -40,7 +40,7 @@ public class HackAndSlashSweep9Test private readonly IAccount _initialState; private readonly IRandom _random; - public HackAndSlashSweep9Test() + public HackAndSlashSweepTest() { _random = new TestRandom(); _sheets = TableSheetsImporter.ImportSheets(); @@ -214,7 +214,7 @@ public void Execute(int apStoneCount, int worldId, int stageId, bool challenge, _tableSheets.MaterialItemSheet); var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { actionPoint = avatarState.actionPoint, costumes = costumes, @@ -253,7 +253,7 @@ public void Execute(int apStoneCount, int worldId, int stageId, bool challenge, [InlineData(false)] public void Execute_FailedLoadStateException(bool backward) { - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = 1, @@ -284,7 +284,7 @@ public void Execute_FailedLoadStateException(bool backward) [InlineData(100, 1)] public void Execute_SheetRowNotFoundException(int worldId, int stageId) { - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = 1, @@ -311,7 +311,7 @@ public void Execute_SheetRowNotFoundException(int worldId, int stageId) [InlineData(2, 50)] public void Execute_SheetRowColumnException(int worldId, int stageId) { - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = 1, @@ -340,7 +340,7 @@ public void Execute_SheetRowColumnException(int worldId, int stageId) [InlineData(1, 49, 2, 51, false)] public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId, int worldId, int stageId, bool backward) { - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = 1, @@ -427,7 +427,7 @@ public void Execute_InvalidWorldException(int worldId, bool backward, int stageI ); } - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = 1, @@ -482,7 +482,7 @@ public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) avatarState.questList.Serialize()); } - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), apStoneCount = apStoneCount, @@ -558,7 +558,7 @@ public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingA var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { equipments = equipments, costumes = costumes, @@ -633,7 +633,7 @@ public void Execute_NotEnoughActionPointException(bool backward) playCount); var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { runeInfos = new List(), costumes = costumes, @@ -709,7 +709,7 @@ public void Execute_PlayCountIsZeroException(bool backward) playCount); var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { costumes = costumes, equipments = equipments, @@ -784,7 +784,7 @@ public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool stageId, playCount); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { costumes = new List(), equipments = new List(), @@ -858,7 +858,7 @@ public void ExecuteWithStake(int stakingLevel) stageId, playCount); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { costumes = new List(), equipments = new List(), @@ -953,7 +953,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 RandomSeed = 0, }); - var action = new HackAndSlashSweep9 + var action = new HackAndSlashSweep { costumes = new List(), equipments = new List(), diff --git a/.Lib9c.Tests/Action/ItemEnhancement13Test.cs b/.Lib9c.Tests/Action/ItemEnhancementTest.cs similarity index 99% rename from .Lib9c.Tests/Action/ItemEnhancement13Test.cs rename to .Lib9c.Tests/Action/ItemEnhancementTest.cs index 9c280c1eed..1ed3009a8c 100644 --- a/.Lib9c.Tests/Action/ItemEnhancement13Test.cs +++ b/.Lib9c.Tests/Action/ItemEnhancementTest.cs @@ -21,7 +21,7 @@ namespace Lib9c.Tests.Action using Xunit; using static SerializeKeys; - public class ItemEnhancement13Test + public class ItemEnhancementTest { private readonly TableSheets _tableSheets; private readonly Address _agentAddress; @@ -30,7 +30,7 @@ public class ItemEnhancement13Test private readonly Currency _currency; private IAccount _initialState; - public ItemEnhancement13Test() + public ItemEnhancementTest() { _initialState = new Account(MockState.Empty); Dictionary sheets; @@ -297,7 +297,7 @@ public void Execute( ) .SetState(_avatarAddress, _avatarState.SerializeV2()); - var action = new ItemEnhancement13 + var action = new ItemEnhancement { itemId = default, materialIds = materialIds, diff --git a/.Lib9c.Tests/Action/Raid6Test.cs b/.Lib9c.Tests/Action/RaidTest.cs similarity index 99% rename from .Lib9c.Tests/Action/Raid6Test.cs rename to .Lib9c.Tests/Action/RaidTest.cs index b377efedec..e13531a033 100644 --- a/.Lib9c.Tests/Action/Raid6Test.cs +++ b/.Lib9c.Tests/Action/RaidTest.cs @@ -19,7 +19,7 @@ namespace Lib9c.Tests.Action using Xunit; using static SerializeKeys; - public class Raid6Test + public class RaidTest { private readonly Dictionary _sheets; private readonly Address _agentAddress; @@ -27,7 +27,7 @@ public class Raid6Test private readonly TableSheets _tableSheets; private readonly Currency _goldCurrency; - public Raid6Test() + public RaidTest() { _sheets = TableSheetsImporter.ImportSheets(); _tableSheets = new TableSheets(_sheets); @@ -105,7 +105,7 @@ int runeId2 }) .StartedBlockIndex; - var action = new Raid6 + var action = new Raid { AvatarAddress = _avatarAddress, EquipmentIds = new List(), @@ -417,7 +417,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Reward() { - var action = new Raid6 + var action = new Raid { AvatarAddress = _avatarAddress, EquipmentIds = new List(), @@ -571,7 +571,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Free_Crystal_Fee() { - var action = new Raid6 + var action = new Raid { AvatarAddress = _avatarAddress, EquipmentIds = new List(), diff --git a/.Lib9c.Tests/Action/RapidCombination9Test.cs b/.Lib9c.Tests/Action/RapidCombinationTest.cs similarity index 98% rename from .Lib9c.Tests/Action/RapidCombination9Test.cs rename to .Lib9c.Tests/Action/RapidCombinationTest.cs index 25833ae3d5..a89d5ba078 100644 --- a/.Lib9c.Tests/Action/RapidCombination9Test.cs +++ b/.Lib9c.Tests/Action/RapidCombinationTest.cs @@ -23,7 +23,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class RapidCombination9Test + public class RapidCombinationTest { private readonly IAccount _initialState; @@ -32,7 +32,7 @@ public class RapidCombination9Test private readonly Address _agentAddress; private readonly Address _avatarAddress; - public RapidCombination9Test() + public RapidCombinationTest() { _initialState = new Account(MockState.Empty); Dictionary sheets; @@ -147,7 +147,7 @@ public void Execute(bool backward) .SetState(_avatarAddress, avatarState.SerializeV2()); } - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -181,7 +181,7 @@ public void Execute_Throw_CombinationSlotResultNullException() var tempState = _initialState .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -235,7 +235,7 @@ public void Execute_Throw_NotEnoughClearedStageLevelException(int avatarClearedS .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -291,7 +291,7 @@ public void Execute_Throw_RequiredBlockIndexException(int itemRequiredBlockIndex .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -367,7 +367,7 @@ public void Execute_Throw_NotEnoughMaterialException(int materialCount, int trad .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -496,7 +496,7 @@ public void Execute_Throw_RequiredAppraiseBlockException() .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, @@ -667,7 +667,7 @@ public void Execute_NotThrow_InvalidOperationException_When_TargetSlotCreatedBy( .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) .SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new RapidCombination9 + var action = new RapidCombination { avatarAddress = _avatarAddress, slotIndex = 0, From 31f60f393c31a2369d685f2580179d020c7c9bae Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:12:23 +0900 Subject: [PATCH 03/10] Fix broken test from skill id changed --- .Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs | 9 ++++----- .Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs | 2 +- .Lib9c.Tests/Model/Skill/CombatTest.cs | 8 +++----- .Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs | 2 +- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs index 97307544db..1cee8a57dd 100644 --- a/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs @@ -6,6 +6,7 @@ namespace Lib9c.Tests.Model.Skill.Arena using Nekoyume.Arena; using Nekoyume.Model; using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill; using Nekoyume.Model.Skill.Arena; using Nekoyume.Model.Stat; using Nekoyume.Model.State; @@ -13,8 +14,6 @@ namespace Lib9c.Tests.Model.Skill.Arena public class ArenaCombatTest { - private const int ActionBuffId = 708000; // Dispel with duration - private readonly TableSheets _tableSheets; private readonly AvatarState _avatar1; private readonly AvatarState _avatar2; @@ -48,7 +47,7 @@ public ArenaCombatTest() [Theory] [InlineData(700009, new[] { 600001 })] - [InlineData(700010, new[] { 600001, 704000 })] + [InlineData(700009, new[] { 600001, 704000 })] public void DispelOnUse(int dispelId, int[] debuffIdList) { var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); @@ -122,7 +121,7 @@ public void DispelOnDuration_Block() ); // Use Dispel first - var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == ActionBuffId); + var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.ActionBuffType == ActionBuffType.Dispel); challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, dispel)); Assert.Single(challenger.Buffs); @@ -170,7 +169,7 @@ public void DispelOnDuration_Affect() ); // Use Dispel first - var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == ActionBuffId); + var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.ActionBuffType == ActionBuffType.Dispel); challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, dispel)); Assert.Single(challenger.Buffs); diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs index 4aaec7502b..c616f3e393 100644 --- a/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs @@ -73,7 +73,7 @@ public void Use(int ratioBp) new List() ); - var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == 700011); + var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == 700010); var shatterStrike = new ArenaShatterStrike(skillRow, 0, 0, ratioBp, StatType.NONE); var used = shatterStrike.Use(challenger, enemy, simulator.Turn, new List()); Assert.Single(used.SkillInfos); diff --git a/.Lib9c.Tests/Model/Skill/CombatTest.cs b/.Lib9c.Tests/Model/Skill/CombatTest.cs index 652f03b4b2..b01d7fce99 100644 --- a/.Lib9c.Tests/Model/Skill/CombatTest.cs +++ b/.Lib9c.Tests/Model/Skill/CombatTest.cs @@ -139,7 +139,7 @@ public void Bleed() [Theory] [InlineData(700009, new[] { 600001 })] - [InlineData(700010, new[] { 600001, 704000 })] + [InlineData(700009, new[] { 600001, 704000 })] public void DispelOnUse(int dispelId, int[] debuffIdList) { var actionBuffSheet = _tableSheets.ActionBuffSheet; @@ -182,11 +182,10 @@ public void DispelOnUse(int dispelId, int[] debuffIdList) [Fact] public void DispelOnDuration_Block() { - const int actionBuffId = 708000; // Dispel with duration var actionBuffSheet = _tableSheets.ActionBuffSheet; // Use Dispel first - var dispel = actionBuffSheet.Values.First(bf => bf.Id == actionBuffId); + var dispel = actionBuffSheet.Values.First(bf => bf.ActionBuffType == ActionBuffType.Dispel); _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, dispel)); Assert.Single(_player.Buffs); @@ -217,11 +216,10 @@ public void DispelOnDuration_Block() [Fact] public void DispelOnDuration_Affect() { - const int actionBuffId = 708000; // Dispel with duration var actionBuffSheet = _tableSheets.ActionBuffSheet; // Use Dispel first - var dispel = actionBuffSheet.Values.First(bf => bf.Id == actionBuffId); + var dispel = actionBuffSheet.Values.First(bf => bf.ActionBuffType == ActionBuffType.Dispel); _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, dispel)); Assert.Single(_player.Buffs); diff --git a/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs index 190ce2540c..142a08efbb 100644 --- a/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs +++ b/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs @@ -30,7 +30,7 @@ public class ShatterStrikeTest public void Use(int ratioBp, bool copyCharacter) { Assert.True( - _tableSheets.SkillSheet.TryGetValue(700011, out var skillRow) + _tableSheets.SkillSheet.TryGetValue(700010, out var skillRow) ); // 700011 is ShatterStrike var shatterStrike = new ShatterStrike(skillRow, 0, 0, ratioBp, StatType.NONE); From 6261c0e30e309480dc54557c71cd828544a77c1f Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:12:50 +0900 Subject: [PATCH 04/10] Restore CombinationConsumableTest --- .../Action/CombinationConsumableTest.cs | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/.Lib9c.Tests/Action/CombinationConsumableTest.cs b/.Lib9c.Tests/Action/CombinationConsumableTest.cs index bc66bc3981..44844b56f3 100644 --- a/.Lib9c.Tests/Action/CombinationConsumableTest.cs +++ b/.Lib9c.Tests/Action/CombinationConsumableTest.cs @@ -12,8 +12,8 @@ namespace Lib9c.Tests.Action using Nekoyume.Model.Item; using Nekoyume.Model.Mail; using Nekoyume.Model.State; + using Nekoyume.Module; using Xunit; - using static Lib9c.SerializeKeys; public class CombinationConsumableTest { @@ -21,7 +21,7 @@ public class CombinationConsumableTest private readonly Address _avatarAddress; private readonly IRandom _random; private readonly TableSheets _tableSheets; - private IAccount _initialState; + private IWorld _initialState; public CombinationConsumableTest() { @@ -57,27 +57,25 @@ public CombinationConsumableTest() var gold = new GoldCurrencyState(Currency.Legacy("NCG", 2, null)); #pragma warning restore CS0618 - _initialState = new Account(MockState.Empty) - .SetState(_agentAddress, agentState.Serialize()) - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState( + _initialState = new World(new MockWorldState()) + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState( slotAddress, new CombinationSlotState( slotAddress, GameConfig.RequireClearedStageLevel.CombinationConsumableAction).Serialize()) - .SetState(GameConfigState.Address, gold.Serialize()); + .SetLegacyState(GameConfigState.Address, gold.Serialize()); foreach (var (key, value) in sheets) { _initialState = - _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + _initialState.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute(bool backward) + [Fact] + public void Execute() { var avatarState = _initialState.GetAvatarState(_avatarAddress); var row = _tableSheets.ConsumableItemRecipeSheet.Values.First(); @@ -99,19 +97,7 @@ public void Execute(bool backward) _tableSheets.WorldSheet, GameConfig.RequireClearedStageLevel.CombinationConsumableAction); - IAccount previousState; - if (backward) - { - previousState = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - previousState = _initialState - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(_avatarAddress, avatarState.SerializeV2()); - } + IWorld previousState = _initialState.SetAvatarState(_avatarAddress, avatarState); var action = new CombinationConsumable { @@ -135,7 +121,7 @@ public void Execute(bool backward) var consumable = (Consumable)slotState.Result.itemUsable; Assert.NotNull(consumable); - var nextAvatarState = nextState.GetAvatarStateV2(_avatarAddress); + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); Assert.Equal(previousActionPoint - costActionPoint, nextAvatarState.actionPoint); Assert.Equal(previousMailCount + 1, nextAvatarState.mailBox.Count); Assert.IsType(nextAvatarState.mailBox.First()); From d8296649aa8f23cf4aeecbcd2fba1e1521c6285a Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:05 +0900 Subject: [PATCH 05/10] Restore CreateAvatarTest --- .Lib9c.Tests/Action/CreateAvatarTest.cs | 61 +++++++------------------ 1 file changed, 17 insertions(+), 44 deletions(-) diff --git a/.Lib9c.Tests/Action/CreateAvatarTest.cs b/.Lib9c.Tests/Action/CreateAvatarTest.cs index c06f32e5b2..127a3d172e 100644 --- a/.Lib9c.Tests/Action/CreateAvatarTest.cs +++ b/.Lib9c.Tests/Action/CreateAvatarTest.cs @@ -13,6 +13,7 @@ namespace Lib9c.Tests.Action using Nekoyume.Action; using Nekoyume.Helper; using Nekoyume.Model.State; + using Nekoyume.Module; using Nekoyume.TableData; using Xunit; using static Lib9c.SerializeKeys; @@ -45,15 +46,15 @@ public void Execute(long blockIndex) }; var sheets = TableSheetsImporter.ImportSheets(); - var state = new Account(MockState.Empty) - .SetState( + var state = new World(new MockWorldState()) + .SetLegacyState( Addresses.GameConfig, new GameConfigState(sheets[nameof(GameConfigSheet)]).Serialize() ); foreach (var (key, value) in sheets) { - state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } Assert.Equal(0 * CrystalCalculator.CRYSTAL, state.GetBalance(_agentAddress, CrystalCalculator.CRYSTAL)); @@ -69,17 +70,17 @@ public void Execute(long blockIndex) var avatarAddress = _agentAddress.Derive( string.Format( CultureInfo.InvariantCulture, - CreateAvatar2.DeriveFormat, + CreateAvatar.DeriveFormat, 0 ) ); - Assert.True(nextState.TryGetAgentAvatarStatesV2( + Assert.True(nextState.TryGetAvatarState( default, avatarAddress, - out var agentState, - out var nextAvatarState, - out _) + out var nextAvatarState) ); + var agentState = nextState.GetAgentState(default); + Assert.NotNull(agentState); Assert.True(agentState.avatarAddresses.Any()); Assert.Equal("test", nextAvatarState.name); Assert.Equal(200_000 * CrystalCalculator.CRYSTAL, nextState.GetBalance(_agentAddress, CrystalCalculator.CRYSTAL)); @@ -116,7 +117,7 @@ public void ExecuteThrowInvalidNamePatterException(string nickName) name = nickName, }; - var state = new Account(MockState.Empty); + var state = new World(new MockWorldState()); Assert.Throws(() => action.Execute(new ActionContext() { @@ -133,7 +134,7 @@ public void ExecuteThrowInvalidAddressException() var avatarAddress = _agentAddress.Derive( string.Format( CultureInfo.InvariantCulture, - CreateAvatar2.DeriveFormat, + CreateAvatar.DeriveFormat, 0 ) ); @@ -157,7 +158,7 @@ public void ExecuteThrowInvalidAddressException() name = "test", }; - var state = new Account(MockState.Empty).SetState(avatarAddress, avatarState.Serialize()); + var state = new World(new MockWorldState()).SetAvatarState(avatarAddress, avatarState); Assert.Throws(() => action.Execute(new ActionContext() { @@ -174,7 +175,7 @@ public void ExecuteThrowInvalidAddressException() public void ExecuteThrowAvatarIndexOutOfRangeException(int index) { var agentState = new AgentState(_agentAddress); - var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); + var state = new World(new MockWorldState()).SetAgentState(_agentAddress, agentState); var action = new CreateAvatar() { index = index, @@ -204,12 +205,12 @@ public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) var avatarAddress = _agentAddress.Derive( string.Format( CultureInfo.InvariantCulture, - CreateAvatar2.DeriveFormat, + CreateAvatar.DeriveFormat, 0 ) ); agentState.avatarAddresses[index] = avatarAddress; - var state = new Account(MockState.Empty).SetState(_agentAddress, agentState.Serialize()); + var state = new World(new MockWorldState()).SetAgentState(_agentAddress, agentState); var action = new CreateAvatar() { @@ -230,34 +231,6 @@ public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) ); } - [Fact] - public void Serialize_With_DotnetAPI() - { - var formatter = new BinaryFormatter(); - var action = new CreateAvatar() - { - index = 2, - hair = 1, - ear = 4, - lens = 5, - tail = 7, - name = "test", - }; - - using var ms = new MemoryStream(); - formatter.Serialize(ms, action); - - ms.Seek(0, SeekOrigin.Begin); - var deserialized = (CreateAvatar10)formatter.Deserialize(ms); - - Assert.Equal(2, deserialized.index); - Assert.Equal(1, deserialized.hair); - Assert.Equal(4, deserialized.ear); - Assert.Equal(5, deserialized.lens); - Assert.Equal(7, deserialized.tail); - Assert.Equal("test", deserialized.name); - } - [Fact] public void AddItem() { @@ -269,7 +242,7 @@ public void AddItem() 600201,2 "); var avatarState = new AvatarState(default, default, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); - CreateAvatar10.AddItem(itemSheet, createAvatarItemSheet, avatarState, new TestRandom()); + CreateAvatar.AddItem(itemSheet, createAvatarItemSheet, avatarState, new TestRandom()); foreach (var row in createAvatarItemSheet.Values) { Assert.True(avatarState.inventory.HasItem(row.ItemId, row.Count)); @@ -294,7 +267,7 @@ public void MintAsset() var avatarAddress = new PrivateKey().Address; var agentAddress = new PrivateKey().Address; var avatarState = new AvatarState(avatarAddress, agentAddress, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); - var nextState = CreateAvatar10.MintAsset(createAvatarFavSheet, avatarState, new Account(MockState.Empty), new ActionContext()); + var nextState = CreateAvatar.MintAsset(createAvatarFavSheet, avatarState, new World(new MockWorldState()), new ActionContext()); foreach (var row in createAvatarFavSheet.Values) { var targetAddress = row.Target == CreateAvatarFavSheet.Target.Agent From e96956df1500cffb2549aaaa2c8721cc89ebfb3b Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:12 +0900 Subject: [PATCH 06/10] Restore EventDungeonBattleTest --- .Lib9c.Tests/Action/EventDungeonBattleTest.cs | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/.Lib9c.Tests/Action/EventDungeonBattleTest.cs b/.Lib9c.Tests/Action/EventDungeonBattleTest.cs index 940acbfa93..92d23c0ced 100644 --- a/.Lib9c.Tests/Action/EventDungeonBattleTest.cs +++ b/.Lib9c.Tests/Action/EventDungeonBattleTest.cs @@ -9,16 +9,15 @@ namespace Lib9c.Tests.Action using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Action; - using Nekoyume.Blockchain.Policy; using Nekoyume.Exceptions; using Nekoyume.Extensions; using Nekoyume.Model.Event; using Nekoyume.Model.Rune; using Nekoyume.Model.State; + using Nekoyume.Module; using Nekoyume.TableData; using Nekoyume.TableData.Event; using Xunit; - using static Lib9c.SerializeKeys; public class EventDungeonBattleTest { @@ -27,33 +26,30 @@ public class EventDungeonBattleTest private readonly Address _agentAddress; private readonly Address _avatarAddress; - private IAccount _initialStates; + private IWorld _initialStates; public EventDungeonBattleTest() { - _initialStates = new Account(MockState.Empty); + _initialStates = new World(new MockWorldState()); #pragma warning disable CS0618 // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 _ncgCurrency = Currency.Legacy("NCG", 2, null); #pragma warning restore CS0618 - _initialStates = _initialStates.SetState( + _initialStates = _initialStates.SetLegacyState( GoldCurrencyState.Address, new GoldCurrencyState(_ncgCurrency).Serialize()); var sheets = TableSheetsImporter.ImportSheets(); foreach (var (key, value) in sheets) { _initialStates = _initialStates - .SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + .SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } _tableSheets = new TableSheets(sheets); _agentAddress = new PrivateKey().Address; _avatarAddress = _agentAddress.Derive("avatar"); - var inventoryAddr = _avatarAddress.Derive(LegacyInventoryKey); - var worldInformationAddr = _avatarAddress.Derive(LegacyWorldInformationKey); - var questListAddr = _avatarAddress.Derive(LegacyQuestListKey); var agentState = new AgentState(_agentAddress); agentState.avatarAddresses.Add(0, _avatarAddress); @@ -72,12 +68,9 @@ public EventDungeonBattleTest() }; _initialStates = _initialStates - .SetState(_agentAddress, agentState.Serialize()) - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState(inventoryAddr, avatarState.inventory.Serialize()) - .SetState(worldInformationAddr, avatarState.worldInformation.Serialize()) - .SetState(questListAddr, avatarState.questList.Serialize()) - .SetState(gameConfigState.address, gameConfigState.Serialize()); + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); } [Theory] @@ -99,7 +92,7 @@ public void Execute_Success_Within_Event_Period( var eventDungeonInfoAddr = EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); var eventDungeonInfo = - new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + new EventDungeonInfo(nextStates.GetLegacyState(eventDungeonInfoAddr)); Assert.Equal( scheduleRow.DungeonTicketsMax - 1, eventDungeonInfo.RemainingTickets); @@ -112,7 +105,7 @@ public void Execute_Success_Within_Event_Period( eventDungeonStageId, blockIndex: contextBlockIndex); eventDungeonInfo = - new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + new EventDungeonInfo(nextStates.GetLegacyState(eventDungeonInfoAddr)); Assert.Equal( scheduleRow.DungeonTicketsMax - 1, eventDungeonInfo.RemainingTickets); @@ -148,7 +141,7 @@ public void Execute_Success_With_Ticket_Purchase( $",{dungeonTicketAdditionalPrice}" + $",{scheduleRow.DungeonExpSeedValue}" + $",{scheduleRow.RecipeEndBlockIndex}"); - previousStates = previousStates.SetState( + previousStates = previousStates.SetLegacyState( Addresses.GetSheetAddress(), sb.ToString().Serialize()); @@ -157,7 +150,7 @@ public void Execute_Success_With_Ticket_Purchase( var eventDungeonInfo = new EventDungeonInfo( remainingTickets: 0, numberOfTicketPurchases: numberOfTicketPurchases); - previousStates = previousStates.SetState( + previousStates = previousStates.SetLegacyState( eventDungeonInfoAddr, eventDungeonInfo.Serialize()); @@ -179,7 +172,7 @@ public void Execute_Success_With_Ticket_Purchase( buyTicketIfNeeded: true, blockIndex: scheduleRow.StartBlockIndex); var nextEventDungeonInfoList = - (Bencodex.Types.List)nextStates.GetState(eventDungeonInfoAddr)!; + (Bencodex.Types.List)nextStates.GetLegacyState(eventDungeonInfoAddr)!; Assert.Equal( numberOfTicketPurchases + 1, nextEventDungeonInfoList[2].ToInteger()); @@ -284,7 +277,7 @@ public void Execute_Throw_NotEnoughEventDungeonTicketsException( EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); var eventDungeonInfo = new EventDungeonInfo(); previousStates = previousStates - .SetState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + .SetLegacyState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); Assert.True(_tableSheets.EventScheduleSheet .TryGetValue(eventScheduleId, out var scheduleRow)); Assert.Throws(() => @@ -313,7 +306,7 @@ public void Execute_Throw_InsufficientBalanceException( remainingTickets: 0, numberOfTicketPurchases: numberOfTicketPurchases); previousStates = previousStates - .SetState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + .SetLegacyState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); Assert.True(_tableSheets.EventScheduleSheet .TryGetValue(eventScheduleId, out var scheduleRow)); @@ -401,7 +394,7 @@ public void Execute_V100301() var csv = $@"id,_name,start_block_index,dungeon_end_block_index,dungeon_tickets_max,dungeon_tickets_reset_interval_block_range,dungeon_ticket_price,dungeon_ticket_additional_price,dungeon_exp_seed_value,recipe_end_block_index 1001,2022 Summer Event,{ActionObsoleteConfig.V100301ExecutedBlockIndex},{ActionObsoleteConfig.V100301ExecutedBlockIndex + 100},5,7200,5,2,1,5018000"; _initialStates = - _initialStates.SetState( + _initialStates.SetLegacyState( Addresses.GetSheetAddress(), csv.Serialize()); var sheet = new EventScheduleSheet(); @@ -417,7 +410,7 @@ public void Execute_V100301() var eventDungeonInfoAddr = EventDungeonInfo.DeriveAddress(_avatarAddress, eventDungeonId); var eventDungeonInfo = - new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + new EventDungeonInfo(nextStates.GetLegacyState(eventDungeonInfoAddr)); Assert.Equal( scheduleRow.DungeonTicketsMax - 1, eventDungeonInfo.RemainingTickets); @@ -430,14 +423,14 @@ public void Execute_V100301() eventDungeonStageId, blockIndex: contextBlockIndex); eventDungeonInfo = - new EventDungeonInfo(nextStates.GetState(eventDungeonInfoAddr)); + new EventDungeonInfo(nextStates.GetLegacyState(eventDungeonInfoAddr)); Assert.Equal( scheduleRow.DungeonTicketsMax - 1, eventDungeonInfo.RemainingTickets); } - private IAccount Execute( - IAccount previousStates, + private IWorld Execute( + IWorld previousStates, int eventScheduleId, int eventDungeonId, int eventDungeonStageId, @@ -448,7 +441,7 @@ private IAccount Execute( int slotIndex2 = 1, int runeId2 = 30001) { - var previousAvatarState = previousStates.GetAvatarStateV2(_avatarAddress); + var previousAvatarState = previousStates.GetAvatarState(_avatarAddress); var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); foreach (var equipment in equipments) @@ -486,7 +479,7 @@ private IAccount Execute( Assert.True(nextStates.GetSheet().TryGetValue( eventScheduleId, out var scheduleRow)); - var nextAvatarState = nextStates.GetAvatarStateV2(_avatarAddress); + var nextAvatarState = nextStates.GetAvatarState(_avatarAddress); var expectExp = scheduleRow.GetStageExp( eventDungeonStageId.ToEventDungeonStageNumber()); Assert.Equal( From e524d48c3042ea58bcb868db304b4e07b466aec2 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:22 +0900 Subject: [PATCH 07/10] Restore HackAndSlashSweepTest --- .Lib9c.Tests/Action/HackAndSlashSweepTest.cs | 377 +++---------------- 1 file changed, 52 insertions(+), 325 deletions(-) diff --git a/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs index f5ee35ae51..f14fe1e177 100644 --- a/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs +++ b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs @@ -16,9 +16,9 @@ namespace Lib9c.Tests.Action using Nekoyume.Model.Item; using Nekoyume.Model.Rune; using Nekoyume.Model.State; + using Nekoyume.Module; using Nekoyume.TableData; using Xunit; - using static Lib9c.SerializeKeys; public class HackAndSlashSweepTest { @@ -30,14 +30,10 @@ public class HackAndSlashSweepTest private readonly Address _avatarAddress; private readonly AvatarState _avatarState; - private readonly Address _inventoryAddress; - private readonly Address _worldInformationAddress; - private readonly Address _questListAddress; - private readonly Address _rankingMapAddress; private readonly WeeklyArenaState _weeklyArenaState; - private readonly IAccount _initialState; + private readonly IWorld _initialState; private readonly IRandom _random; public HackAndSlashSweepTest() @@ -64,9 +60,6 @@ public HackAndSlashSweepTest() { level = 100, }; - _inventoryAddress = _avatarAddress.Derive(LegacyInventoryKey); - _worldInformationAddress = _avatarAddress.Derive(LegacyWorldInformationKey); - _questListAddress = _avatarAddress.Derive(LegacyQuestListKey); agentState.avatarAddresses.Add(0, _avatarAddress); #pragma warning disable CS0618 @@ -75,20 +68,17 @@ public HackAndSlashSweepTest() #pragma warning restore CS0618 var goldCurrencyState = new GoldCurrencyState(currency); _weeklyArenaState = new WeeklyArenaState(0); - _initialState = new Account(MockState.Empty) - .SetState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) - .SetState(_agentAddress, agentState.SerializeV2()) - .SetState(_avatarAddress, _avatarState.SerializeV2()) - .SetState(_inventoryAddress, _avatarState.inventory.Serialize()) - .SetState(_worldInformationAddress, _avatarState.worldInformation.Serialize()) - .SetState(_questListAddress, _avatarState.questList.Serialize()) - .SetState(gameConfigState.address, gameConfigState.Serialize()) - .SetState(Addresses.GoldCurrency, goldCurrencyState.Serialize()); + _initialState = new World(new MockWorldState()) + .SetLegacyState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, _avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()) + .SetLegacyState(Addresses.GoldCurrency, goldCurrencyState.Serialize()); foreach (var (key, value) in _sheets) { _initialState = _initialState - .SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + .SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } foreach (var address in _avatarState.combinationSlotAddresses) @@ -96,7 +86,7 @@ public HackAndSlashSweepTest() var slotState = new CombinationSlotState( address, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); - _initialState = _initialState.SetState(address, slotState.Serialize()); + _initialState = _initialState.SetLegacyState(address, slotState.Serialize()); } } @@ -123,135 +113,8 @@ public HackAndSlashSweepTest() return (equipments, costumes); } - [Theory] - [InlineData(1, 1, 1, false, true)] - [InlineData(1, 1, 1, false, false)] - [InlineData(2, 1, 2, false, true)] - [InlineData(2, 1, 2, false, false)] - [InlineData(2, 2, 51, false, true)] - [InlineData(2, 2, 51, false, false)] - [InlineData(2, 2, 52, false, true)] - [InlineData(2, 2, 52, false, false)] - [InlineData(2, 1, 1, true, true)] - [InlineData(2, 1, 1, true, false)] - [InlineData(2, 1, 2, true, true)] - [InlineData(2, 1, 2, true, false)] - [InlineData(2, 2, 51, true, true)] - [InlineData(2, 2, 51, true, false)] - [InlineData(2, 2, 52, true, true)] - [InlineData(2, 2, 52, true, false)] - public void Execute(int apStoneCount, int worldId, int stageId, bool challenge, bool backward) - { - var gameConfigState = _initialState.GetGameConfigState(); - var prevStageId = stageId - 1; - var worldInformation = new WorldInformation( - 0, _initialState.GetSheet(), challenge ? prevStageId : stageId); - - if (challenge) - { - worldInformation.UnlockWorld(worldId, 0, _tableSheets.WorldSheet); - } - - var avatarState = new AvatarState( - _avatarAddress, - _agentAddress, - 0, - _initialState.GetAvatarSheets(), - gameConfigState, - _rankingMapAddress) - { - worldInformation = worldInformation, - level = 400, - }; - - var row = _tableSheets.MaterialItemSheet.Values.First(r => - r.ItemSubType == ItemSubType.ApStone); - var apStone = ItemFactory.CreateTradableMaterial(row); - avatarState.inventory.AddItem(apStone, apStoneCount); - - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } - - state = state.SetState( - _avatarAddress.Derive("world_ids"), - List.Empty.Add(worldId.Serialize()) - ); - - var stageSheet = _initialState.GetSheet(); - var (expectedLevel, expectedExp) = (0, 0L); - if (stageSheet.TryGetValue(stageId, out var stageRow)) - { - var itemPlayCount = gameConfigState.ActionPointMax / stageRow.CostAP * apStoneCount; - var apPlayCount = avatarState.actionPoint / stageRow.CostAP; - var playCount = apPlayCount + itemPlayCount; - (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( - _tableSheets.CharacterLevelSheet, - stageId, - playCount); - - var random = new TestRandom(_random.Seed); - var expectedRewardItems = HackAndSlashSweep6.GetRewardItems( - random, - playCount, - stageRow, - _tableSheets.MaterialItemSheet); - - var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep - { - actionPoint = avatarState.actionPoint, - costumes = costumes, - equipments = equipments, - runeInfos = new List(), - avatarAddress = _avatarAddress, - apStoneCount = apStoneCount, - worldId = worldId, - stageId = stageId, - }; - - state = action.Execute(new ActionContext - { - PreviousState = state, - Signer = _agentAddress, - RandomSeed = _random.Seed, - }); - - var nextAvatarState = state.GetAvatarStateV2(_avatarAddress); - - Assert.Equal(expectedLevel, nextAvatarState.level); - Assert.Equal(expectedExp, nextAvatarState.exp); - Assert.Equal( - expectedRewardItems.Count(), - nextAvatarState.inventory.Items.Sum(x => x.count)); - foreach (var i in nextAvatarState.inventory.Items) - { - nextAvatarState.inventory.TryGetItem(i.item.Id, out var item); - Assert.Equal(expectedRewardItems.Count(x => x.Id == i.item.Id), item.count); - } - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute_FailedLoadStateException(bool backward) + [Fact] + public void Execute_FailedLoadStateException() { var action = new HackAndSlashSweep { @@ -262,15 +125,7 @@ public void Execute_FailedLoadStateException(bool backward) stageId = 1, }; - var state = backward ? new Account(MockState.Empty) : _initialState; - if (!backward) - { - state = _initialState - .SetState(_avatarAddress, _avatarState.SerializeV2()) - .SetNull(_avatarAddress.Derive(LegacyInventoryKey)) - .SetNull(_avatarAddress.Derive(LegacyWorldInformationKey)) - .SetNull(_avatarAddress.Derive(LegacyQuestListKey)); - } + IWorld state = new World(new MockWorldState()); Assert.Throws(() => action.Execute(new ActionContext() { @@ -293,7 +148,7 @@ public void Execute_SheetRowNotFoundException(int worldId, int stageId) stageId = stageId, }; - var state = _initialState.SetState( + var state = _initialState.SetLegacyState( _avatarAddress.Derive("world_ids"), List.Empty.Add(worldId.Serialize()) ); @@ -320,7 +175,7 @@ public void Execute_SheetRowColumnException(int worldId, int stageId) stageId = stageId, }; - var state = _initialState.SetState( + var state = _initialState.SetLegacyState( _avatarAddress.Derive("world_ids"), List.Empty.Add(worldId.Serialize()) ); @@ -334,11 +189,9 @@ public void Execute_SheetRowColumnException(int worldId, int stageId) } [Theory] - [InlineData(1, 48, 1, 50, true)] - [InlineData(1, 48, 1, 50, false)] - [InlineData(1, 49, 2, 51, true)] - [InlineData(1, 49, 2, 51, false)] - public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId, int worldId, int stageId, bool backward) + [InlineData(1, 48, 1, 50)] + [InlineData(1, 49, 2, 51)] + public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId, int worldId, int stageId) { var action = new HackAndSlashSweep { @@ -353,22 +206,12 @@ public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId _avatarState.worldInformation.ClearStage(clearedWorldId, clearedStageId, 1, worldSheet, worldUnlockSheet); - var state = _initialState.SetState( + var state = _initialState + .SetLegacyState( _avatarAddress.Derive("world_ids"), List.Empty.Add(worldId.Serialize()) - ); - - if (backward) - { - state = state.SetState(_avatarAddress, _avatarState.Serialize()); - } - else - { - state = state - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - _avatarState.worldInformation.Serialize()); - } + ) + .SetAvatarState(_avatarAddress, _avatarState); Assert.Throws(() => action.Execute(new ActionContext() { @@ -379,12 +222,11 @@ public void Execute_InvalidStageException(int clearedWorldId, int clearedStageId } [Theory] - [InlineData(GameConfig.MimisbrunnrWorldId, true, 10000001, false)] - [InlineData(GameConfig.MimisbrunnrWorldId, false, 10000001, true)] + [InlineData(GameConfig.MimisbrunnrWorldId, 10000001, false)] + [InlineData(GameConfig.MimisbrunnrWorldId, 10000001, true)] // Unlock CRYSTAL first. - [InlineData(2, false, 51, false)] - [InlineData(2, true, 51, false)] - public void Execute_InvalidWorldException(int worldId, bool backward, int stageId, bool unlockedIdsExist) + [InlineData(2, 51, false)] + public void Execute_InvalidWorldException(int worldId, int stageId, bool unlockedIdsExist) { var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); var avatarState = new AvatarState( @@ -399,29 +241,11 @@ public void Execute_InvalidWorldException(int worldId, bool backward, int stageI new WorldInformation(0, _initialState.GetSheet(), 10000001), }; - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); if (unlockedIdsExist) { - state = state.SetState( + state = state.SetLegacyState( _avatarAddress.Derive("world_ids"), List.Empty.Add(worldId.Serialize()) ); @@ -444,10 +268,8 @@ public void Execute_InvalidWorldException(int worldId, bool backward, int stageI })); } - [Theory] - [InlineData(99, true)] - [InlineData(99, false)] - public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) + [Fact] + public void Execute_UsageLimitExceedException() { var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); var avatarState = new AvatarState( @@ -462,30 +284,12 @@ public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) new WorldInformation(0, _initialState.GetSheet(), 25), }; - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); var action = new HackAndSlashSweep { runeInfos = new List(), - apStoneCount = apStoneCount, + apStoneCount = 99, avatarAddress = _avatarAddress, worldId = 1, stageId = 2, @@ -500,9 +304,9 @@ public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) } [Theory] - [InlineData(3, 2, true)] - [InlineData(7, 5, false)] - public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingApStoneCount, bool backward) + [InlineData(3, 2)] + [InlineData(7, 5)] + public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingApStoneCount) { var gameConfigState = _initialState.GetGameConfigState(); var avatarState = new AvatarState( @@ -523,25 +327,7 @@ public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingA var apStone = ItemFactory.CreateTradableMaterial(row); avatarState.inventory.AddItem(apStone, holdingApStoneCount); - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); @@ -579,10 +365,8 @@ public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingA } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute_NotEnoughActionPointException(bool backward) + [Fact] + public void Execute_NotEnoughActionPointException() { var gameConfigState = _initialState.GetGameConfigState(); var avatarState = new AvatarState( @@ -599,25 +383,7 @@ public void Execute_NotEnoughActionPointException(bool backward) actionPoint = 0, }; - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); @@ -655,10 +421,8 @@ public void Execute_NotEnoughActionPointException(bool backward) } } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute_PlayCountIsZeroException(bool backward) + [Fact] + public void Execute_PlayCountIsZeroException() { var gameConfigState = _initialState.GetGameConfigState(); var avatarState = new AvatarState( @@ -675,25 +439,7 @@ public void Execute_PlayCountIsZeroException(bool backward) actionPoint = 0, }; - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); @@ -731,10 +477,8 @@ public void Execute_PlayCountIsZeroException(bool backward) } } - [Theory] - [InlineData(1, 24, true)] - [InlineData(1, 24, false)] - public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool backward) + [Fact] + public void Execute_NotEnoughCombatPointException() { var gameConfigState = _initialState.GetGameConfigState(); var avatarState = new AvatarState( @@ -751,28 +495,11 @@ public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool level = 1, }; - IAccount state; - if (backward) - { - state = _initialState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - state = _initialState - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - avatarState.inventory.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - avatarState.worldInformation.Serialize()) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - avatarState.questList.Serialize()); - } + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); + int stageId = 24; if (stageSheet.TryGetValue(stageId, out var stageRow)) { var itemPlayCount = @@ -792,7 +519,7 @@ public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool avatarAddress = _avatarAddress, actionPoint = avatarState.actionPoint, apStoneCount = 1, - worldId = worldId, + worldId = 1, stageId = stageId, }; @@ -841,8 +568,8 @@ public void ExecuteWithStake(int stakingLevel) .FirstOrDefault(r => r.Level == stakingLevel)?.RequiredGold ?? 0; var context = new ActionContext(); var state = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(stakeStateAddress, stakeState.Serialize()) + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(stakeStateAddress, stakeState.Serialize()) .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); var stageSheet = _initialState.GetSheet(); if (stageSheet.TryGetValue(stageId, out var stageRow)) @@ -876,7 +603,7 @@ public void ExecuteWithStake(int stakingLevel) Signer = _agentAddress, RandomSeed = 0, }); - var nextAvatar = nextState.GetAvatarStateV2(_avatarAddress); + var nextAvatar = nextState.GetAvatarState(_avatarAddress); Assert.Equal(expectedLevel, nextAvatar.level); Assert.Equal(expectedExp, nextAvatar.exp); } @@ -919,8 +646,8 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 .FirstOrDefault(r => r.Level == stakingLevel)?.RequiredGold ?? 0; var context = new ActionContext(); var state = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(stakeStateAddress, stakeState.Serialize()) + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(stakeStateAddress, stakeState.Serialize()) .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); var stageSheet = _initialState.GetSheet(); if (stageSheet.TryGetValue(stageId, out var stageRow)) From 72b478c645af08930f2eef0b8451d54575b26c90 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:35 +0900 Subject: [PATCH 08/10] Restore ItemEnhancementTest --- .Lib9c.Tests/Action/ItemEnhancementTest.cs | 35 +++++++--------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/.Lib9c.Tests/Action/ItemEnhancementTest.cs b/.Lib9c.Tests/Action/ItemEnhancementTest.cs index 1ed3009a8c..901db08f20 100644 --- a/.Lib9c.Tests/Action/ItemEnhancementTest.cs +++ b/.Lib9c.Tests/Action/ItemEnhancementTest.cs @@ -5,9 +5,7 @@ namespace Lib9c.Tests.Action using System.Globalization; using System.Linq; using Bencodex.Types; - using Lib9c.Tests.Fixtures.TableCSV; using Lib9c.Tests.Fixtures.TableCSV.Cost; - using Lib9c.Tests.Fixtures.TableCSV.Item; using Lib9c.Tests.Util; using Libplanet.Action.State; using Libplanet.Crypto; @@ -18,8 +16,8 @@ namespace Lib9c.Tests.Action using Nekoyume.Model.Item; using Nekoyume.Model.Mail; using Nekoyume.Model.State; + using Nekoyume.Module; using Xunit; - using static SerializeKeys; public class ItemEnhancementTest { @@ -28,11 +26,11 @@ public class ItemEnhancementTest private readonly Address _avatarAddress; private readonly AvatarState _avatarState; private readonly Currency _currency; - private IAccount _initialState; + private IWorld _initialState; public ItemEnhancementTest() { - _initialState = new Account(MockState.Empty); + _initialState = new World(new MockWorldState()); Dictionary sheets; (_initialState, sheets) = InitializeUtil.InitializeTableSheets( _initialState, @@ -47,7 +45,7 @@ public ItemEnhancementTest() foreach (var (key, value) in sheets) { _initialState = - _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + _initialState.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } var privateKey = new PrivateKey(); @@ -79,10 +77,10 @@ public ItemEnhancementTest() var context = new ActionContext(); _initialState = _initialState - .SetState(_agentAddress, agentState.Serialize()) - .SetState(_avatarAddress, _avatarState.Serialize()) - .SetState(slotAddress, new CombinationSlotState(slotAddress, 0).Serialize()) - .SetState(GoldCurrencyState.Address, gold.Serialize()) + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, _avatarState) + .SetLegacyState(slotAddress, new CombinationSlotState(slotAddress, 0).Serialize()) + .SetLegacyState(GoldCurrencyState.Address, gold.Serialize()) .MintAsset(context, GoldCurrencyState.Address, gold.Currency * 100_000_000_000) .TransferAsset( context, @@ -282,20 +280,7 @@ public void Execute( Assert.Equal(startLevel, equipment.level); - _initialState = _initialState - .SetState( - _avatarAddress.Derive(LegacyInventoryKey), - _avatarState.inventory.Serialize() - ) - .SetState( - _avatarAddress.Derive(LegacyWorldInformationKey), - _avatarState.worldInformation.Serialize() - ) - .SetState( - _avatarAddress.Derive(LegacyQuestListKey), - _avatarState.questList.Serialize() - ) - .SetState(_avatarAddress, _avatarState.SerializeV2()); + _initialState = _initialState.SetAvatarState(_avatarAddress, _avatarState); var action = new ItemEnhancement { @@ -340,7 +325,7 @@ public void Execute( ); Assert.Equal(30, nextAvatarState.mailBox.Count); - var stateDict = (Dictionary)nextState.GetState(slotAddress); + var stateDict = (Dictionary)nextState.GetLegacyState(slotAddress); var slot = new CombinationSlotState(stateDict); var slotResult = (ItemEnhancement13.ResultModel)slot.Result; if (startLevel != level) From 5c03b5ccf3cca055f88026a230b180d556b91e83 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:44 +0900 Subject: [PATCH 09/10] Restore RaidTest --- .Lib9c.Tests/Action/RaidTest.cs | 92 +++++++++++++++------------------ 1 file changed, 41 insertions(+), 51 deletions(-) diff --git a/.Lib9c.Tests/Action/RaidTest.cs b/.Lib9c.Tests/Action/RaidTest.cs index e13531a033..3a383361a0 100644 --- a/.Lib9c.Tests/Action/RaidTest.cs +++ b/.Lib9c.Tests/Action/RaidTest.cs @@ -14,7 +14,9 @@ namespace Lib9c.Tests.Action using Nekoyume.Helper; using Nekoyume.Model.Arena; using Nekoyume.Model.Rune; + using Nekoyume.Model.Stat; using Nekoyume.Model.State; + using Nekoyume.Module; using Nekoyume.TableData; using Xunit; using static SerializeKeys; @@ -56,8 +58,6 @@ public RaidTest() [InlineData(null, true, true, false, true, 3, 100L, false, false, 0, true, false, false, 5, true, 0, 10002, 1, 30001)] // AvatarState null. [InlineData(typeof(FailedLoadStateException), false, false, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] - // Stage not cleared. - [InlineData(typeof(NotEnoughClearedStageLevelException), true, false, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] // Insufficient CRYSTAL. [InlineData(typeof(InsufficientBalanceException), true, true, false, false, 0, 0L, false, false, 0, false, false, false, 5, false, 0, 10002, 1, 30001)] // Insufficient NCG. @@ -136,13 +136,13 @@ int runeId2 var fee = _tableSheets.WorldBossListSheet[raidId].EntranceFee; var context = new ActionContext(); - IAccount state = new Account(MockState.Empty) - .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) - .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + IWorld state = new World(new MockWorldState()) + .SetLegacyState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetAgentState(_agentAddress, new AgentState(_agentAddress)); foreach (var (key, value) in _sheets) { - state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); @@ -193,7 +193,7 @@ int runeId2 raiderState.AvatarAddress = _avatarAddress; raiderState.UpdatedBlockIndex = blockIndex; - state = state.SetState(raiderAddress, raiderState.Serialize()); + state = state.SetLegacyState(raiderAddress, raiderState.Serialize()); var raiderList = new List().Add(raiderAddress.Serialize()); @@ -202,7 +202,7 @@ int runeId2 raiderList = raiderList.Add(new PrivateKey().Address.Serialize()); } - state = state.SetState(raiderListAddress, raiderList); + state = state.SetLegacyState(raiderListAddress, raiderList); } if (rewardRecordExist) @@ -211,7 +211,7 @@ int runeId2 { [0] = false, }; - state = state.SetState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); + state = state.SetLegacyState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); } if (ncgExist) @@ -221,11 +221,8 @@ int runeId2 } state = state - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(gameConfigState.address, gameConfigState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); } if (kill) @@ -236,7 +233,7 @@ int runeId2 CurrentHp = 0, Level = level, }; - state = state.SetState(bossAddress, bossState.Serialize()); + state = state.SetLegacyState(bossAddress, bossState.Serialize()); } if (exc is null) @@ -262,7 +259,8 @@ int runeId2 action.FoodIds, null, raidSimulatorSheets, - _tableSheets.CostumeStatSheet); + _tableSheets.CostumeStatSheet, + new List()); simulator.Simulate(); var score = simulator.DamageDealt; @@ -276,7 +274,7 @@ Dictionary rewardMap if (rewardRecordExist) { var bossRow = raidSimulatorSheets.WorldBossCharacterSheet[bossListRow.BossId]; - Assert.True(state.TryGetState(bossAddress, out List prevRawBoss)); + Assert.True(state.TryGetLegacyState(bossAddress, out List prevRawBoss)); var prevBossState = new WorldBossState(prevRawBoss); int rank = WorldBossHelper.CalculateRank(bossRow, raiderStateExist ? 1_000 : 0); var rewards = RuneHelper.CalculateReward( @@ -323,9 +321,9 @@ Dictionary rewardMap Assert.Equal(fee * crystal, nextState.GetBalance(bossAddress, crystal)); } - Assert.True(nextState.TryGetState(raiderAddress, out List rawRaider)); + Assert.True(nextState.TryGetLegacyState(raiderAddress, out List rawRaider)); var raiderState = new RaiderState(rawRaider); - int expectedTotalScore = raiderStateExist ? 1_000 + score : score; + long expectedTotalScore = raiderStateExist ? 1_000 + score : score; int expectedRemainChallenge = payNcg ? 0 : 2; int expectedTotalChallenge = raiderStateExist ? 2 : 1; @@ -337,7 +335,7 @@ Dictionary rewardMap Assert.Equal(GameConfig.DefaultAvatarArmorId, raiderState.IconId); Assert.True(raiderState.Cp > 0); - Assert.True(nextState.TryGetState(bossAddress, out List rawBoss)); + Assert.True(nextState.TryGetLegacyState(bossAddress, out List rawBoss)); var bossState = new WorldBossState(rawBoss); int expectedLevel = level; if (kill & levelUp) @@ -358,7 +356,7 @@ Dictionary rewardMap Assert.Equal(purchaseCount + 1, nextState.GetRaiderState(raiderAddress).PurchaseCount); } - Assert.True(nextState.TryGetState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); + Assert.True(nextState.TryGetLegacyState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); var rewardRecord = new WorldBossKillRewardRecord(rawRewardInfo); Assert.Contains(expectedLevel, rewardRecord.Keys); if (rewardRecordExist) @@ -377,7 +375,7 @@ Dictionary rewardMap } } - Assert.True(nextState.TryGetState(raiderListAddress, out List rawRaiderList)); + Assert.True(nextState.TryGetLegacyState(raiderListAddress, out List rawRaiderList)); List
raiderList = rawRaiderList.ToList(StateExtensions.ToAddress); Assert.Contains(raiderAddress, raiderList); @@ -434,13 +432,13 @@ public void Execute_With_Reward() Address bossAddress = Addresses.GetWorldBossAddress(raidId); Address worldBossKillRewardRecordAddress = Addresses.GetWorldBossKillRewardRecordAddress(_avatarAddress, raidId); - IAccount state = new Account(MockState.Empty) - .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) - .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + IWorld state = new World(new MockWorldState()) + .SetLegacyState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetAgentState(_agentAddress, new AgentState(_agentAddress)); foreach (var (key, value) in _sheets) { - state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); @@ -469,20 +467,17 @@ public void Execute_With_Reward() raiderState.IconId = 0; raiderState.AvatarName = "hash"; raiderState.AvatarAddress = _avatarAddress; - state = state.SetState(raiderAddress, raiderState.Serialize()); + state = state.SetLegacyState(raiderAddress, raiderState.Serialize()); var rewardRecord = new WorldBossKillRewardRecord { [1] = false, }; - state = state.SetState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); + state = state.SetLegacyState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); state = state - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(gameConfigState.address, gameConfigState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); var bossState = new WorldBossState(worldBossRow, _tableSheets.WorldBossGlobalHpSheet[2]) @@ -490,7 +485,7 @@ public void Execute_With_Reward() CurrentHp = 0, Level = 2, }; - state = state.SetState(bossAddress, bossState.Serialize()); + state = state.SetLegacyState(bossAddress, bossState.Serialize()); var randomSeed = 0; var random = new TestRandom(randomSeed); @@ -501,7 +496,8 @@ public void Execute_With_Reward() action.FoodIds, null, _tableSheets.GetRaidSimulatorSheets(), - _tableSheets.CostumeStatSheet); + _tableSheets.CostumeStatSheet, + new List()); simulator.Simulate(); Dictionary rewardMap @@ -528,7 +524,7 @@ Dictionary rewardMap Signer = _agentAddress, }); - Assert.True(nextState.TryGetState(raiderAddress, out List rawRaider)); + Assert.True(nextState.TryGetLegacyState(raiderAddress, out List rawRaider)); var nextRaiderState = new RaiderState(rawRaider); Assert.Equal(simulator.DamageDealt, nextRaiderState.HighScore); @@ -560,10 +556,10 @@ Dictionary rewardMap Assert.Equal(GameConfig.DefaultAvatarArmorId, nextRaiderState.IconId); Assert.True(nextRaiderState.Cp > 0); Assert.Equal(3, nextRaiderState.LatestBossLevel); - Assert.True(nextState.TryGetState(bossAddress, out List rawBoss)); + Assert.True(nextState.TryGetLegacyState(bossAddress, out List rawBoss)); var nextBossState = new WorldBossState(rawBoss); Assert.Equal(3, nextBossState.Level); - Assert.True(nextState.TryGetState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); + Assert.True(nextState.TryGetLegacyState(worldBossKillRewardRecordAddress, out List rawRewardInfo)); var nextRewardInfo = new WorldBossKillRewardRecord(rawRewardInfo); Assert.True(nextRewardInfo[1]); } @@ -587,13 +583,13 @@ public void Execute_With_Free_Crystal_Fee() "1,900002,0,100,0,1,1,40"; var goldCurrencyState = new GoldCurrencyState(_goldCurrency); - IAccount state = new Account(MockState.Empty) - .SetState(goldCurrencyState.address, goldCurrencyState.Serialize()) - .SetState(_agentAddress, new AgentState(_agentAddress).Serialize()); + IWorld state = new World(new MockWorldState()) + .SetLegacyState(goldCurrencyState.address, goldCurrencyState.Serialize()) + .SetAgentState(_agentAddress, new AgentState(_agentAddress)); foreach (var (key, value) in _sheets) { - state = state.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); @@ -612,11 +608,8 @@ public void Execute_With_Free_Crystal_Fee() } state = state - .SetState(_avatarAddress, avatarState.SerializeV2()) - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(gameConfigState.address, gameConfigState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); var blockIndex = gameConfigState.WorldBossRequiredInterval; var randomSeed = 0; @@ -627,10 +620,7 @@ public void Execute_With_Free_Crystal_Fee() RandomSeed = randomSeed, Signer = _agentAddress, }; - - IAccount nextState; - var exception = Record.Exception(() => nextState = action.Execute(ctx)); - Assert.Null(exception); + action.Execute(ctx); } } } From c5a21416f839bfacd6616017d4484d2936192c00 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 21 Mar 2024 15:13:49 +0900 Subject: [PATCH 10/10] Restore RapidCombinationTest --- .Lib9c.Tests/Action/RapidCombinationTest.cs | 113 ++++---------------- 1 file changed, 22 insertions(+), 91 deletions(-) diff --git a/.Lib9c.Tests/Action/RapidCombinationTest.cs b/.Lib9c.Tests/Action/RapidCombinationTest.cs index a89d5ba078..fa6976dbcf 100644 --- a/.Lib9c.Tests/Action/RapidCombinationTest.cs +++ b/.Lib9c.Tests/Action/RapidCombinationTest.cs @@ -19,13 +19,14 @@ namespace Lib9c.Tests.Action using Nekoyume.Model.Item; using Nekoyume.Model.Mail; using Nekoyume.Model.State; + using Nekoyume.Module; using Nekoyume.TableData; using Xunit; using static Lib9c.SerializeKeys; public class RapidCombinationTest { - private readonly IAccount _initialState; + private readonly IWorld _initialState; private readonly TableSheets _tableSheets; @@ -34,7 +35,7 @@ public class RapidCombinationTest public RapidCombinationTest() { - _initialState = new Account(MockState.Empty); + _initialState = new World(new MockWorldState()); Dictionary sheets; (_initialState, sheets) = InitializeUtil.InitializeTableSheets( _initialState, @@ -57,7 +58,7 @@ public RapidCombinationTest() foreach (var (key, value) in sheets) { _initialState = - _initialState.SetState(Addresses.TableSheet.Derive(key), value.Serialize()); + _initialState.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); } _agentAddress = new PrivateKey().Address; @@ -76,15 +77,13 @@ public RapidCombinationTest() agentState.avatarAddresses[0] = _avatarAddress; _initialState = _initialState - .SetState(Addresses.GameConfig, new GameConfigState(sheets[nameof(GameConfigSheet)]).Serialize()) - .SetState(_agentAddress, agentState.Serialize()) - .SetState(_avatarAddress, avatarState.Serialize()); + .SetLegacyState(Addresses.GameConfig, new GameConfigState(sheets[nameof(GameConfigSheet)]).Serialize()) + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, avatarState); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute(bool backward) + [Fact] + public void Execute() { const int slotStateUnlockStage = 1; @@ -132,20 +131,9 @@ public void Execute(bool backward) var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); slotState.Update(result, 0, requiredBlockIndex); - var tempState = _initialState.SetState(slotAddress, slotState.Serialize()); - - if (backward) - { - tempState = tempState.SetState(_avatarAddress, avatarState.Serialize()); - } - else - { - tempState = tempState - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(_avatarAddress, avatarState.SerializeV2()); - } + var tempState = _initialState + .SetLegacyState(slotAddress, slotState.Serialize()) + .SetAvatarState(_avatarAddress, avatarState); var action = new RapidCombination { @@ -160,7 +148,7 @@ public void Execute(bool backward) BlockIndex = 51, }); - var nextAvatarState = nextState.GetAvatarStateV2(_avatarAddress); + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); var item = nextAvatarState.inventory.Equipments.First(); Assert.Empty(nextAvatarState.inventory.Materials.Select(r => r.ItemSubType == ItemSubType.Hourglass)); @@ -179,7 +167,7 @@ public void Execute_Throw_CombinationSlotResultNullException() slotState.Update(null, 0, 0); var tempState = _initialState - .SetState(slotAddress, slotState.Serialize()); + .SetLegacyState(slotAddress, slotState.Serialize()); var action = new RapidCombination { @@ -195,60 +183,6 @@ public void Execute_Throw_CombinationSlotResultNullException() })); } - [Theory] - [InlineData(0, 1)] - [InlineData(1, 2)] - public void Execute_Throw_NotEnoughClearedStageLevelException(int avatarClearedStage, int slotStateUnlockStage) - { - var avatarState = _initialState.GetAvatarState(_avatarAddress); - avatarState.worldInformation = new WorldInformation( - 0, - _initialState.GetSheet(), - avatarClearedStage); - - var firstEquipmentRow = _tableSheets.EquipmentItemSheet.First; - Assert.NotNull(firstEquipmentRow); - - var equipment = (Equipment)ItemFactory.CreateItemUsable( - firstEquipmentRow, - Guid.NewGuid(), - 100); - - var result = new CombinationConsumable5.ResultModel - { - actionPoint = 0, - gold = 0, - materials = new Dictionary(), - itemUsable = equipment, - recipeId = 0, - itemType = ItemType.Equipment, - }; - - var slotAddress = _avatarAddress.Derive(string.Format( - CultureInfo.InvariantCulture, - CombinationSlotState.DeriveFormat, - 0)); - var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); - slotState.Update(result, 0, 0); - - var tempState = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(slotAddress, slotState.Serialize()); - - var action = new RapidCombination - { - avatarAddress = _avatarAddress, - slotIndex = 0, - }; - - Assert.Throws(() => action.Execute(new ActionContext - { - PreviousState = tempState, - Signer = _agentAddress, - BlockIndex = 1, - })); - } - [Theory] [InlineData(0, 0)] [InlineData(10, 100)] @@ -288,8 +222,8 @@ public void Execute_Throw_RequiredBlockIndexException(int itemRequiredBlockIndex slotState.Update(result, 0, 0); var tempState = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(slotAddress, slotState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(slotAddress, slotState.Serialize()); var action = new RapidCombination { @@ -364,8 +298,8 @@ public void Execute_Throw_NotEnoughMaterialException(int materialCount, int trad slotState.Update(result, 0, 0); var tempState = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(slotAddress, slotState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(slotAddress, slotState.Serialize()); var action = new RapidCombination { @@ -493,8 +427,8 @@ public void Execute_Throw_RequiredAppraiseBlockException() slotState.Update(result, 0, 0); var tempState = _initialState - .SetState(_avatarAddress, avatarState.Serialize()) - .SetState(slotAddress, slotState.Serialize()); + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState(slotAddress, slotState.Serialize()); var action = new RapidCombination { @@ -661,11 +595,8 @@ public void Execute_NotThrow_InvalidOperationException_When_TargetSlotCreatedBy( var slotState = new CombinationSlotState(slotAddress, slotStateUnlockStage); slotState.Update(resultModel, 0, requiredBlockIndex); - var tempState = _initialState.SetState(slotAddress, slotState.Serialize()) - .SetState(_avatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) - .SetState(_avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()) - .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) - .SetState(_avatarAddress, avatarState.SerializeV2()); + var tempState = _initialState.SetLegacyState(slotAddress, slotState.Serialize()) + .SetAvatarState(_avatarAddress, avatarState); var action = new RapidCombination {