From 5da8d310d402f8eff9ad6d306942d2c0425388c4 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Mon, 30 Oct 2023 18:55:06 +0900 Subject: [PATCH 01/13] Update GameConfig - delete ui valuables --- Lib9c/GameConfig.cs | 60 ++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index a911571a87..44d79efb4f 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -56,11 +56,11 @@ public static class RequireCharacterLevel { #region character costume slot - public const int CharacterFullCostumeSlot = IsEditor ? 1 : 2; - public const int CharacterHairCostumeSlot = IsEditor ? 1 : 2; - public const int CharacterEarCostumeSlot = IsEditor ? 1 : 2; - public const int CharacterEyeCostumeSlot = IsEditor ? 1 : 2; - public const int CharacterTailCostumeSlot = IsEditor ? 1 : 2; + public const int CharacterFullCostumeSlot = 2; + public const int CharacterHairCostumeSlot = 2; + public const int CharacterEarCostumeSlot = 2; + public const int CharacterEyeCostumeSlot = 2; + public const int CharacterTailCostumeSlot = 2; public const int CharacterTitleSlot = 1; #endregion @@ -68,11 +68,11 @@ public static class RequireCharacterLevel #region character equipment slot public const int CharacterEquipmentSlotWeapon = 1; - public const int CharacterEquipmentSlotArmor = IsEditor ? 1 : 3; - public const int CharacterEquipmentSlotBelt = IsEditor ? 1 : 5; - public const int CharacterEquipmentSlotNecklace = IsEditor ? 1 : 8; - public const int CharacterEquipmentSlotRing1 = IsEditor ? 1 : 13; - public const int CharacterEquipmentSlotRing2 = IsEditor ? 1 : 46; + public const int CharacterEquipmentSlotArmor = 3; + public const int CharacterEquipmentSlotBelt = 5; + public const int CharacterEquipmentSlotNecklace = 8; + public const int CharacterEquipmentSlotRing1 = 13; + public const int CharacterEquipmentSlotRing2 = 46; public const int CharacterEquipmentSlotAura = 1; #endregion @@ -80,10 +80,10 @@ public static class RequireCharacterLevel #region character consumable slot public const int CharacterConsumableSlot1 = 1; - public const int CharacterConsumableSlot2 = IsEditor ? 1 : 35; - public const int CharacterConsumableSlot3 = IsEditor ? 1 : 100; - public const int CharacterConsumableSlot4 = IsEditor ? 1 : 200; - public const int CharacterConsumableSlot5 = IsEditor ? 1 : 350; + public const int CharacterConsumableSlot2 = 35; + public const int CharacterConsumableSlot3 = 100; + public const int CharacterConsumableSlot4 = 200; + public const int CharacterConsumableSlot5 = 350; #endregion } @@ -92,31 +92,13 @@ public static class RequireClearedStageLevel { #region action - public const int CombinationEquipmentAction = IsEditor ? 1 : 3; - public const int CombinationConsumableAction = IsEditor ? 1 : 6; - public const int ItemEnhancementAction = IsEditor ? 1 : 9; - public const int ActionsInShop = IsEditor ? 1 : 17; - public const int ActionsInRankingBoard = IsEditor ? 1 : 25; - public const int ActionsInMimisbrunnr = IsEditor ? 1 : 100; - public const int ActionsInRaid = IsEditor ? 1 : 50; - - #endregion - - #region ui - - public const int UIMainMenuStage = 0; - public const int UIMainMenuCombination = CombinationEquipmentAction; - public const int UIMainMenuShop = ActionsInShop; - public const int UIMainMenuRankingBoard = ActionsInRankingBoard; - public const int UIMainMenuMimisbrunnr = ActionsInMimisbrunnr; - - public const int UIBottomMenuInBattle = 1; - public const int UIBottomMenuCharacter = 1; - public const int UIBottomMenuMail = CombinationEquipmentAction; - public const int UIBottomMenuChat = IsEditor ? 1 : 7; - public const int UIBottomMenuQuest = 1; - public const int UIBottomMenuMimisbrunnr = ActionsInMimisbrunnr; - public const int UIBottomMenuRanking = IsEditor ? 1 : 3; + public const int CombinationEquipmentAction = 3; + public const int CombinationConsumableAction = 6; + public const int ItemEnhancementAction = 9; + public const int ActionsInShop = 17; + public const int ActionsInRankingBoard = 25; + public const int ActionsInMimisbrunnr = 100; + public const int ActionsInRaid = 50; #endregion } From 9e7f851703fac313b60f2061eb5bfd9771abc366 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Tue, 31 Oct 2023 16:06:33 +0900 Subject: [PATCH 02/13] HardForked actions add obsolete to old actions - CombinationConsumable 8 -> 9 - CombinationEquipment 16 -> 17 - CreateAvatar 10 -> 11 - EventConsumableItemCrafts 0 -> 1 - EventMaterialItemCrafts 0 -> 1 - ItemEnhancement 13 -> 14 - RapidCombination 9 -> 10 - BuyProduct 2 -> 3 - CancelProductRegistration 0 -> 1 - RegisterProduct 2 -> 3 - BattleArena 13 -> 14 - JoinArena 3 -> 4 - Raid 6 -> 7 - EventDungeonBattle 5 -> 6 - HackAndSlash 21 -> 22 - HackAndSlashSweep 9 -> 10 --- Lib9c/Action/AttachmentActionResult.cs | 2 +- Lib9c/Action/BattleArena.cs | 4 +- Lib9c/Action/BattleArena13.cs | 467 +++++++++++ Lib9c/Action/Buy.cs | 1 + Lib9c/Action/BuyProduct.cs | 2 +- Lib9c/Action/BuyProduct0.cs | 1 + Lib9c/Action/BuyProduct2.cs | 378 +++++++++ Lib9c/Action/CancelProductRegistration.cs | 2 +- Lib9c/Action/CancelProductRegistration0.cs | 212 +++++ Lib9c/Action/CombinationConsumable.cs | 6 +- Lib9c/Action/CombinationConsumable8.cs | 255 ++++++ Lib9c/Action/CombinationEquipment.cs | 4 +- Lib9c/Action/CombinationEquipment16.cs | 769 ++++++++++++++++++ Lib9c/Action/CreateAvatar.cs | 5 +- Lib9c/Action/CreateAvatar10.cs | 317 ++++++++ Lib9c/Action/EventConsumableItemCrafts.cs | 5 +- Lib9c/Action/EventConsumableItemCrafts0.cs | 333 ++++++++ Lib9c/Action/EventDungeonBattle.cs | 4 +- Lib9c/Action/EventDungeonBattleV5.cs | 432 ++++++++++ Lib9c/Action/EventMaterialItemCrafts.cs | 5 +- Lib9c/Action/EventMaterialItemCrafts0.cs | 277 +++++++ Lib9c/Action/HackAndSlash.cs | 4 +- Lib9c/Action/HackAndSlash21.cs | 604 ++++++++++++++ Lib9c/Action/HackAndSlashSweep.cs | 4 +- Lib9c/Action/HackAndSlashSweep9.cs | 348 ++++++++ Lib9c/Action/ItemEnhancement.cs | 63 +- Lib9c/Action/ItemEnhancement13.cs | 468 +++++++++++ Lib9c/Action/JoinArena.cs | 4 +- Lib9c/Action/JoinArena3.cs | 225 +++++ Lib9c/Action/Raid.cs | 4 +- Lib9c/Action/Raid6.cs | 373 +++++++++ Lib9c/Action/RankingBattle.cs | 1 + Lib9c/Action/RapidCombination.cs | 4 +- Lib9c/Action/RapidCombination9.cs | 194 +++++ Lib9c/Action/ReRegisterProduct.cs | 4 +- Lib9c/Action/ReRegisterProduct0.cs | 2 +- Lib9c/Action/RegisterProduct.cs | 2 +- Lib9c/Action/RegisterProduct0.cs | 1 + Lib9c/Action/RegisterProduct2.cs | 242 ++++++ Lib9c/Action/SellCancellation.cs | 1 + Lib9c/ActionObsoleteConfig.cs | 2 + .../CombinationSlotStateExtensions.cs | 8 +- 42 files changed, 5944 insertions(+), 95 deletions(-) create mode 100644 Lib9c/Action/BattleArena13.cs create mode 100644 Lib9c/Action/BuyProduct2.cs create mode 100644 Lib9c/Action/CancelProductRegistration0.cs create mode 100644 Lib9c/Action/CombinationConsumable8.cs create mode 100644 Lib9c/Action/CombinationEquipment16.cs create mode 100644 Lib9c/Action/CreateAvatar10.cs create mode 100644 Lib9c/Action/EventConsumableItemCrafts0.cs create mode 100644 Lib9c/Action/EventDungeonBattleV5.cs create mode 100644 Lib9c/Action/EventMaterialItemCrafts0.cs create mode 100644 Lib9c/Action/HackAndSlash21.cs create mode 100644 Lib9c/Action/HackAndSlashSweep9.cs create mode 100644 Lib9c/Action/ItemEnhancement13.cs create mode 100644 Lib9c/Action/JoinArena3.cs create mode 100644 Lib9c/Action/Raid6.cs create mode 100644 Lib9c/Action/RapidCombination9.cs create mode 100644 Lib9c/Action/RegisterProduct2.cs diff --git a/Lib9c/Action/AttachmentActionResult.cs b/Lib9c/Action/AttachmentActionResult.cs index 40e09375b1..b87162d71e 100644 --- a/Lib9c/Action/AttachmentActionResult.cs +++ b/Lib9c/Action/AttachmentActionResult.cs @@ -23,7 +23,7 @@ private static readonly Dictionary new ItemEnhancement9.ResultModel(d), ["item_enhancement11.result"] = d => new ItemEnhancement11.ResultModel(d), ["item_enhancement12.result"] = d => new ItemEnhancement12.ResultModel(d), - ["item_enhancement13.result"] = d => new ItemEnhancement.ResultModel(d), + ["item_enhancement13.result"] = d => new ItemEnhancement13.ResultModel(d), ["sellCancellation.result"] = d => new SellCancellation.Result(d), ["rapidCombination.result"] = d => new RapidCombination0.ResultModel(d), ["rapid_combination5.result"] = d => new RapidCombination5.ResultModel(d), diff --git a/Lib9c/Action/BattleArena.cs b/Lib9c/Action/BattleArena.cs index 881739756c..2efc5c0891 100644 --- a/Lib9c/Action/BattleArena.cs +++ b/Lib9c/Action/BattleArena.cs @@ -24,10 +24,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/2094 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("battle_arena13")] + [ActionType("battle_arena14")] public class BattleArena : GameAction, IBattleArenaV1 { public const string PurchasedCountKey = "purchased_count_during_interval"; diff --git a/Lib9c/Action/BattleArena13.cs b/Lib9c/Action/BattleArena13.cs new file mode 100644 index 0000000000..ac9af0d1dc --- /dev/null +++ b/Lib9c/Action/BattleArena13.cs @@ -0,0 +1,467 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Arena; +using Nekoyume.Battle; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model; +using Nekoyume.Model.Arena; +using Nekoyume.Model.BattleStatus.Arena; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/2094 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("battle_arena13")] + public class BattleArena13 : GameAction, IBattleArenaV1 + { + public const string PurchasedCountKey = "purchased_count_during_interval"; + public Address myAvatarAddress; + public Address enemyAvatarAddress; + public int championshipId; + public int round; + public int ticket; + + public List costumes; + public List equipments; + public List runeInfos; + + Address IBattleArenaV1.MyAvatarAddress => myAvatarAddress; + + Address IBattleArenaV1.EnemyAvatarAddress => enemyAvatarAddress; + + int IBattleArenaV1.ChampionshipId => championshipId; + + int IBattleArenaV1.Round => round; + + int IBattleArenaV1.Ticket => ticket; + + IEnumerable IBattleArenaV1.Costumes => costumes; + + IEnumerable IBattleArenaV1.Equipments => equipments; + + IEnumerable IBattleArenaV1.RuneSlotInfos => runeInfos + .Select(x => x.Serialize()); + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary() + { + [MyAvatarAddressKey] = myAvatarAddress.Serialize(), + [EnemyAvatarAddressKey] = enemyAvatarAddress.Serialize(), + [ChampionshipIdKey] = championshipId.Serialize(), + [RoundKey] = round.Serialize(), + [TicketKey] = ticket.Serialize(), + [CostumesKey] = new List(costumes + .OrderBy(element => element).Select(e => e.Serialize())), + [EquipmentsKey] = new List(equipments + .OrderBy(element => element).Select(e => e.Serialize())), + [RuneInfos] = runeInfos.OrderBy(x => x.SlotIndex).Select(x=> x.Serialize()).Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + myAvatarAddress = plainValue[MyAvatarAddressKey].ToAddress(); + enemyAvatarAddress = plainValue[EnemyAvatarAddressKey].ToAddress(); + championshipId = plainValue[ChampionshipIdKey].ToInteger(); + round = plainValue[RoundKey].ToInteger(); + ticket = plainValue[TicketKey].ToInteger(); + costumes = ((List)plainValue[CostumesKey]).Select(e => e.ToGuid()).ToList(); + equipments = ((List)plainValue[EquipmentsKey]).Select(e => e.ToGuid()).ToList(); + runeInfos = plainValue[RuneInfos].ToList(x => new RuneSlotInfo((List)x)); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex( + context, + myAvatarAddress, + enemyAvatarAddress); + + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}BattleArena exec started", addressesHex); + if (myAvatarAddress.Equals(enemyAvatarAddress)) + { + throw new InvalidAddressException( + $"{addressesHex}Aborted as the signer tried to battle for themselves."); + } + + if (!states.TryGetAvatarStateV2( + context.Signer, + myAvatarAddress, + out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex( + out var world)) + { + throw new NotEnoughClearedStageLevelException( + $"{addressesHex}Aborted as NotEnoughClearedStageLevelException"); + } + + if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) + { + throw new NotEnoughClearedStageLevelException( + addressesHex, + GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, + world.StageClearedId); + } + + var sheets = states.GetSheets( + containArenaSimulatorSheets: true, + sheetTypes: new[] + { + typeof(ArenaSheet), + typeof(ItemRequirementSheet), + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(MaterialItemSheet), + typeof(RuneListSheet), + }); + + avatarState.ValidEquipmentAndCostume(costumes, equipments, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + context.BlockIndex, addressesHex); + + // update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(myAvatarAddress, BattleType.Arena); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Arena); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(runeInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(myAvatarAddress, BattleType.Arena); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Arena); + itemSlotState.UpdateEquipment(equipments); + itemSlotState.UpdateCostumes(costumes); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + var arenaSheet = sheets.GetSheet(); + if (!arenaSheet.TryGetValue(championshipId, out var arenaRow)) + { + throw new SheetRowNotFoundException(nameof(ArenaSheet), + $"championship Id : {championshipId}"); + } + + if (!arenaRow.TryGetRound(round, out var roundData)) + { + throw new RoundNotFoundException( + $"[{nameof(BattleArena)}] ChampionshipId({arenaRow.ChampionshipId}) - " + + $"round({round})"); + } + + if (!roundData.IsTheRoundOpened(context.BlockIndex)) + { + throw new ThisArenaIsClosedException( + $"{nameof(BattleArena)} : block index({context.BlockIndex}) - " + + $"championshipId({roundData.ChampionshipId}) - round({roundData.Round})"); + } + + var arenaParticipantsAdr = + ArenaParticipants.DeriveAddress(roundData.ChampionshipId, roundData.Round); + if (!states.TryGetArenaParticipants(arenaParticipantsAdr, out var arenaParticipants)) + { + throw new ArenaParticipantsNotFoundException( + $"[{nameof(BattleArena)}] ChampionshipId({roundData.ChampionshipId}) - " + + $"round({roundData.Round})"); + } + + if (!arenaParticipants.AvatarAddresses.Contains(myAvatarAddress)) + { + throw new AddressNotFoundInArenaParticipantsException( + $"[{nameof(BattleArena)}] my avatar address : {myAvatarAddress}"); + } + + if (!arenaParticipants.AvatarAddresses.Contains(enemyAvatarAddress)) + { + throw new AddressNotFoundInArenaParticipantsException( + $"[{nameof(BattleArena)}] enemy avatar address : {enemyAvatarAddress}"); + } + + var myArenaAvatarStateAdr = ArenaAvatarState.DeriveAddress(myAvatarAddress); + if (!states.TryGetArenaAvatarState(myArenaAvatarStateAdr, out var myArenaAvatarState)) + { + throw new ArenaAvatarStateNotFoundException( + $"[{nameof(BattleArena)}] my avatar address : {myAvatarAddress}"); + } + + var gameConfigState = states.GetGameConfigState(); + var battleArenaInterval = roundData.ArenaType == ArenaType.OffSeason + ? 1 + : gameConfigState.BattleArenaInterval; + if (context.BlockIndex - myArenaAvatarState.LastBattleBlockIndex < battleArenaInterval) + { + throw new CoolDownBlockException( + $"[{nameof(BattleArena)}] LastBattleBlockIndex : " + + $"{myArenaAvatarState.LastBattleBlockIndex} " + + $"CurrentBlockIndex : {context.BlockIndex}"); + } + + var enemyArenaAvatarStateAdr = ArenaAvatarState.DeriveAddress(enemyAvatarAddress); + if (!states.TryGetArenaAvatarState( + enemyArenaAvatarStateAdr, + out var enemyArenaAvatarState)) + { + throw new ArenaAvatarStateNotFoundException( + $"[{nameof(BattleArena)}] enemy avatar address : {enemyAvatarAddress}"); + } + + var myArenaScoreAdr = ArenaScore.DeriveAddress( + myAvatarAddress, + roundData.ChampionshipId, + roundData.Round); + if (!states.TryGetArenaScore(myArenaScoreAdr, out var myArenaScore)) + { + throw new ArenaScoreNotFoundException( + $"[{nameof(BattleArena)}] my avatar address : {myAvatarAddress}" + + $" - ChampionshipId({roundData.ChampionshipId}) - round({roundData.Round})"); + } + + var enemyArenaScoreAdr = ArenaScore.DeriveAddress( + enemyAvatarAddress, + roundData.ChampionshipId, + roundData.Round); + if (!states.TryGetArenaScore(enemyArenaScoreAdr, out var enemyArenaScore)) + { + throw new ArenaScoreNotFoundException( + $"[{nameof(BattleArena)}] enemy avatar address : {enemyAvatarAddress}" + + $" - ChampionshipId({roundData.ChampionshipId}) - round({roundData.Round})"); + } + + var arenaInformationAdr = ArenaInformation.DeriveAddress( + myAvatarAddress, + roundData.ChampionshipId, + roundData.Round); + if (!states.TryGetArenaInformation(arenaInformationAdr, out var arenaInformation)) + { + throw new ArenaInformationNotFoundException( + $"[{nameof(BattleArena)}] my avatar address : {myAvatarAddress}" + + $" - ChampionshipId({roundData.ChampionshipId}) - round({roundData.Round})"); + } + + if (!ArenaHelper.ValidateScoreDifference( + ArenaHelper.ScoreLimits, + roundData.ArenaType, + myArenaScore.Score, + enemyArenaScore.Score)) + { + var scoreDiff = enemyArenaScore.Score - myArenaScore.Score; + throw new ValidateScoreDifferenceException( + $"[{nameof(BattleArena)}] Arena Type({roundData.ArenaType}) : " + + $"enemyScore({enemyArenaScore.Score}) - myScore({myArenaScore.Score}) = " + + $"diff({scoreDiff})"); + } + + var dailyArenaInterval = gameConfigState.DailyArenaInterval; + var currentTicketResetCount = ArenaHelper.GetCurrentTicketResetCount( + context.BlockIndex, roundData.StartBlockIndex, dailyArenaInterval); + var purchasedCountAddr = arenaInformation.Address.Derive(PurchasedCountKey); + if (!states.TryGetState(purchasedCountAddr, out Integer purchasedCountDuringInterval)) + { + purchasedCountDuringInterval = 0; + } + + if (arenaInformation.TicketResetCount < currentTicketResetCount) + { + arenaInformation.ResetTicket(currentTicketResetCount); + purchasedCountDuringInterval = 0; + states = states.SetState(purchasedCountAddr, purchasedCountDuringInterval); + } + + if (roundData.ArenaType != ArenaType.OffSeason && ticket > 1) + { + throw new ExceedPlayCountException($"[{nameof(BattleArena)}] " + + $"ticket : {ticket} / arenaType : " + + $"{roundData.ArenaType}"); + } + + if (arenaInformation.Ticket > 0) + { + arenaInformation.UseTicket(ticket); + } + else if (ticket > 1) + { + throw new TicketPurchaseLimitExceedException( + $"[{nameof(ArenaInformation)}] tickets to buy : {ticket}"); + } + else + { + var arenaAdr = + ArenaHelper.DeriveArenaAddress(roundData.ChampionshipId, roundData.Round); + var goldCurrency = states.GetGoldCurrency(); + var ticketBalance = + ArenaHelper.GetTicketPrice(roundData, arenaInformation, goldCurrency); + arenaInformation.BuyTicket(roundData.MaxPurchaseCount); + if (purchasedCountDuringInterval >= roundData.MaxPurchaseCountWithInterval) + { + throw new ExceedTicketPurchaseLimitDuringIntervalException( + $"[{nameof(ArenaInformation)}] PurchasedTicketCount({purchasedCountDuringInterval}) >= MAX({{max}})"); + } + + purchasedCountDuringInterval++; + states = states + .TransferAsset(context, context.Signer, arenaAdr, ticketBalance) + .SetState(purchasedCountAddr, purchasedCountDuringInterval); + } + + // update arena avatar state + myArenaAvatarState.UpdateEquipment(equipments); + myArenaAvatarState.UpdateCostumes(costumes); + myArenaAvatarState.LastBattleBlockIndex = context.BlockIndex; + var runeStates = new List(); + foreach (var address in runeInfos.Select(info => RuneState.DeriveAddress(myAvatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + runeStates.Add(new RuneState(rawRuneState)); + } + } + + // get enemy equipped items + var enemyItemSlotStateAddress = ItemSlotState.DeriveAddress(enemyAvatarAddress, BattleType.Arena); + var enemyItemSlotState = states.TryGetState(enemyItemSlotStateAddress, out List rawEnemyItemSlotState) + ? new ItemSlotState(rawEnemyItemSlotState) + : new ItemSlotState(BattleType.Arena); + var enemyRuneSlotStateAddress = RuneSlotState.DeriveAddress(enemyAvatarAddress, BattleType.Arena); + var enemyRuneSlotState = states.TryGetState(enemyRuneSlotStateAddress, out List enemyRawRuneSlotState) + ? new RuneSlotState(enemyRawRuneSlotState) + : new RuneSlotState(BattleType.Arena); + + var enemyRuneStates = new List(); + var enemyRuneSlotInfos = enemyRuneSlotState.GetEquippedRuneSlotInfos(); + foreach (var address in enemyRuneSlotInfos.Select(info => RuneState.DeriveAddress(enemyAvatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + enemyRuneStates.Add(new RuneState(rawRuneState)); + } + } + + // simulate + var enemyAvatarState = states.GetEnemyAvatarState(enemyAvatarAddress); + var myArenaPlayerDigest = new ArenaPlayerDigest( + avatarState, + equipments, + costumes, + runeStates); + var enemyArenaPlayerDigest = new ArenaPlayerDigest( + enemyAvatarState, + enemyItemSlotState.Equipments, + enemyItemSlotState.Costumes, + enemyRuneStates); + var previousMyScore = myArenaScore.Score; + var arenaSheets = sheets.GetArenaSimulatorSheets(); + var winCount = 0; + var defeatCount = 0; + var rewards = new List(); + for (var i = 0; i < ticket; i++) + { + var simulator = new ArenaSimulator(context.Random); + var log = simulator.Simulate( + myArenaPlayerDigest, + enemyArenaPlayerDigest, + arenaSheets); + if (log.Result.Equals(ArenaLog.ArenaResult.Win)) + { + winCount++; + } + else + { + defeatCount++; + } + + var reward = RewardSelector.Select( + context.Random, + sheets.GetSheet(), + sheets.GetSheet(), + myArenaPlayerDigest.Level, + maxCount: ArenaHelper.GetRewardCount(previousMyScore)); + rewards.AddRange(reward); + } + + // add reward + foreach (var itemBase in rewards.OrderBy(x => x.Id)) + { + avatarState.inventory.AddItem(itemBase); + } + + // add medal + if (roundData.ArenaType != ArenaType.OffSeason && winCount > 0) + { + var materialSheet = sheets.GetSheet(); + var medal = ArenaHelper.GetMedal( + roundData.ChampionshipId, + roundData.Round, + materialSheet); + avatarState.inventory.AddItem(medal, count: winCount); + } + + // update record + var (myWinScore, myDefeatScore, enemyWinScore) = + ArenaHelper.GetScores(previousMyScore, enemyArenaScore.Score); + var myScore = (myWinScore * winCount) + (myDefeatScore * defeatCount); + myArenaScore.AddScore(myScore); + enemyArenaScore.AddScore(enemyWinScore * winCount); + arenaInformation.UpdateRecord(winCount, defeatCount); + + if (migrationRequired) + { + states = states + .SetState(myAvatarAddress, avatarState.SerializeV2()) + .SetState( + myAvatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()) + .SetState( + myAvatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}BattleArena Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(myArenaAvatarStateAdr, myArenaAvatarState.Serialize()) + .SetState(myArenaScoreAdr, myArenaScore.Serialize()) + .SetState(enemyArenaScoreAdr, enemyArenaScore.Serialize()) + .SetState(arenaInformationAdr, arenaInformation.Serialize()) + .SetState( + myAvatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()); + } + } +} diff --git a/Lib9c/Action/Buy.cs b/Lib9c/Action/Buy.cs index 4ad0718d86..74718f5c5e 100644 --- a/Lib9c/Action/Buy.cs +++ b/Lib9c/Action/Buy.cs @@ -23,6 +23,7 @@ namespace Nekoyume.Action /// Updated at https://github.com/planetarium/lib9c/pull/1164 /// [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] [ActionType("buy12")] public class Buy : GameAction, IBuy5, IBuyV2 { diff --git a/Lib9c/Action/BuyProduct.cs b/Lib9c/Action/BuyProduct.cs index e2323c735d..590f2125ce 100644 --- a/Lib9c/Action/BuyProduct.cs +++ b/Lib9c/Action/BuyProduct.cs @@ -21,7 +21,7 @@ namespace Nekoyume.Action { - [ActionType("buy_product2")] + [ActionType("buy_product3")] public class BuyProduct : GameAction { // Capacity from Buy limits in NineChronicles diff --git a/Lib9c/Action/BuyProduct0.cs b/Lib9c/Action/BuyProduct0.cs index cdc6620ab8..45c17f03ab 100644 --- a/Lib9c/Action/BuyProduct0.cs +++ b/Lib9c/Action/BuyProduct0.cs @@ -21,6 +21,7 @@ namespace Nekoyume.Action { + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] [ActionType("buy_product")] public class BuyProduct0 : GameAction { diff --git a/Lib9c/Action/BuyProduct2.cs b/Lib9c/Action/BuyProduct2.cs new file mode 100644 index 0000000000..49fede92b3 --- /dev/null +++ b/Lib9c/Action/BuyProduct2.cs @@ -0,0 +1,378 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Bencodex.Types; +using Lib9c.Model.Order; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.Market; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using static Lib9c.SerializeKeys; +using Log = Serilog.Log; + +namespace Nekoyume.Action +{ + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("buy_product2")] + public class BuyProduct2 : GameAction + { + // Capacity from Buy limits in NineChronicles + // https://github.com/planetarium/NineChronicles/blob/v100372-1/nekoyume/Assets/_Scripts/UI/Shop/BuyView.cs#L127 + public const int Capacity = 20; + public Address AvatarAddress; + public IEnumerable ProductInfos; + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + IAccount states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var sw = new Stopwatch(); + sw.Start(); + var started = DateTimeOffset.UtcNow; + Log.Debug("BuyProduct exec started"); + + if (!ProductInfos.Any()) + { + throw new ListEmptyException("ProductInfos was empty."); + } + + if (ProductInfos.Count() > Capacity) + { + throw new ArgumentOutOfRangeException($"{nameof(ProductInfos)} must be less than or equal {Capacity}."); + + } + + if (ProductInfos.Any(p => p.AgentAddress == context.Signer || + p.AvatarAddress == AvatarAddress)) + { + throw new InvalidAddressException(); + } + + foreach (var productInfo in ProductInfos) + { + productInfo.ValidateType(); + } + + if (!states.TryGetAvatarStateV2(context.Signer, AvatarAddress, out var buyerAvatarState, out var migrationRequired)) + { + throw new FailedLoadStateException("failed load to buyer avatar state."); + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + + if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) + { + buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException(addressesHex, + GameConfig.RequireClearedStageLevel.ActionsInShop, current); + } + + var materialSheet = states.GetSheet(); + foreach (var productInfo in ProductInfos.OrderBy(p => p.ProductId).ThenBy(p =>p.Price)) + { + var sellerAgentAddress = productInfo.AgentAddress; + var sellerAvatarAddress = productInfo.AvatarAddress; + if (!states.TryGetAvatarStateV2(sellerAgentAddress, sellerAvatarAddress, + out var sellerAvatarState, out var sellerMigrationRequired)) + { + throw new FailedLoadStateException($"failed load to seller avatar state."); + } + + if (productInfo is ItemProductInfo {Legacy: true} itemProductInfo) + { + var purchaseInfo = new PurchaseInfo(itemProductInfo.ProductId, itemProductInfo.TradableId, + sellerAgentAddress, sellerAvatarAddress, itemProductInfo.ItemSubType, + productInfo.Price); + states = Buy_Order(purchaseInfo, context, states, buyerAvatarState, materialSheet, sellerAvatarState); + } + else + { + states = Buy(context, productInfo, sellerAvatarAddress, states, sellerAgentAddress, buyerAvatarState, sellerAvatarState, materialSheet, sellerMigrationRequired); + } + } + + if (migrationRequired) + { + states = states + .SetState(AvatarAddress.Derive(LegacyQuestListKey), buyerAvatarState.questList.Serialize()) + .SetState(AvatarAddress.Derive(LegacyWorldInformationKey), buyerAvatarState.worldInformation.Serialize()); + } + + + states = states + .SetState(AvatarAddress, buyerAvatarState.SerializeV2()) + .SetState(AvatarAddress.Derive(LegacyInventoryKey), buyerAvatarState.inventory.Serialize()); + var ended = DateTimeOffset.UtcNow; + Log.Debug("BuyProduct Total Executed Time: {Elapsed}", ended - started); + return states; + } + + private IAccount Buy(IActionContext context, IProductInfo productInfo, Address sellerAvatarAddress, + IAccount states, Address sellerAgentAddress, AvatarState buyerAvatarState, AvatarState sellerAvatarState, + MaterialItemSheet materialSheet, bool sellerMigrationRequired) + { + var productId = productInfo.ProductId; + var productsStateAddress = ProductsState.DeriveAddress(sellerAvatarAddress); + var productsState = new ProductsState((List) states.GetState(productsStateAddress)); + if (!productsState.ProductIds.Contains(productId)) + { + // sold out or canceled product. + throw new ProductNotFoundException($"can't find product {productId}"); + } + + productsState.ProductIds.Remove(productId); + + var productAddress = Product.DeriveAddress(productId); + var product = ProductFactory.DeserializeProduct((List) states.GetState(productAddress)); + product.Validate(productInfo); + + switch (product) + { + case FavProduct favProduct: + states = states.TransferAsset(context, productAddress, AvatarAddress, favProduct.Asset); + break; + case ItemProduct itemProduct: + { + switch (itemProduct.TradableItem) + { + case Costume costume: + // Fix RequiredBlockIndex from RegisterProduct0 + if (costume.RequiredBlockIndex > context.BlockIndex) + { + costume.RequiredBlockIndex = context.BlockIndex; + } + buyerAvatarState.UpdateFromAddCostume(costume, false); + break; + case ItemUsable itemUsable: + // Fix RequiredBlockIndex from RegisterProduct0 + if (itemUsable.RequiredBlockIndex > context.BlockIndex) + { + itemUsable.RequiredBlockIndex = context.BlockIndex; + } + buyerAvatarState.UpdateFromAddItem(itemUsable, false); + break; + case TradableMaterial tradableMaterial: + { + buyerAvatarState.UpdateFromAddItem(tradableMaterial, itemProduct.ItemCount, false); + break; + } + } + } + break; + default: + throw new InvalidProductTypeException($"{product} is not support type."); + } + + var sellerMail = new ProductSellerMail(context.BlockIndex, productId, + context.BlockIndex, productId); + sellerAvatarState.Update(sellerMail); + sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, product.Price); + sellerAvatarState.UpdateQuestRewards(materialSheet); + + var buyerMail = new ProductBuyerMail(context.BlockIndex, productId, + context.BlockIndex, productId + ); + buyerAvatarState.Update(buyerMail); + buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, product.Price); + buyerAvatarState.UpdateQuestRewards(materialSheet); + + // Transfer tax. + var arenaSheet = states.GetSheet(); + var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); + var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); + var tax = product.Price.DivRem(100, out _) * Action.Buy.TaxRate; + var taxedPrice = product.Price - tax; + + // Receipt + var receipt = new ProductReceipt(productId, sellerAvatarAddress, buyerAvatarState.address, product.Price, + context.BlockIndex); + states = states + .SetState(productAddress, Null.Value) + .SetState(productsStateAddress, productsState.Serialize()) + .SetState(sellerAvatarAddress, sellerAvatarState.SerializeV2()) + .SetState(sellerAvatarAddress.Derive(LegacyQuestListKey), sellerAvatarState.questList.Serialize()) + .SetState(ProductReceipt.DeriveAddress(productId), receipt.Serialize()) + .TransferAsset(context, context.Signer, feeStoreAddress, tax) + .TransferAsset(context, context.Signer, sellerAgentAddress, taxedPrice); + + if (sellerMigrationRequired) + { + states = states + .SetState(sellerAvatarAddress.Derive(LegacyInventoryKey), sellerAvatarState.inventory.Serialize()) + .SetState(sellerAvatarAddress.Derive(LegacyWorldInformationKey), + sellerAvatarState.worldInformation.Serialize()); + } + + return states; + } + + + // backward compatibility for order - shared shop state. + // TODO DELETE THIS METHOD AFTER PRODUCT MIGRATION END. + private static IAccount Buy_Order(PurchaseInfo purchaseInfo, IActionContext context, IAccount states, AvatarState buyerAvatarState, MaterialItemSheet materialSheet, AvatarState sellerAvatarState) + { + Address shardedShopAddress = + ShardedShopStateV2.DeriveAddress(purchaseInfo.ItemSubType, purchaseInfo.OrderId); + Address sellerAgentAddress = purchaseInfo.SellerAgentAddress; + Address sellerAvatarAddress = purchaseInfo.SellerAvatarAddress; + Address sellerInventoryAddress = sellerAvatarAddress.Derive(LegacyInventoryKey); + var sellerWorldInformationAddress = sellerAvatarAddress.Derive(LegacyWorldInformationKey); + Address sellerQuestListAddress = sellerAvatarAddress.Derive(LegacyQuestListKey); + Guid orderId = purchaseInfo.OrderId; + Address orderAddress = Order.DeriveAddress(orderId); + Address digestListAddress = OrderDigestListState.DeriveAddress(sellerAvatarAddress); + + if (!states.TryGetState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) + { + throw new FailedLoadStateException("failed to load shop state"); + } + + if (!states.TryGetState(orderAddress, out Dictionary rawOrder)) + { + throw new OrderIdDoesNotExistException($"{orderId}"); + } + + Order order = OrderFactory.Deserialize(rawOrder); + + var shardedShopState = new ShardedShopStateV2(shopStateDict); + shardedShopState.Remove(order, context.BlockIndex); + + if (!states.TryGetState(digestListAddress, out Dictionary rawDigestList)) + { + throw new FailedLoadStateException($"{orderId}"); + } + var digestList = new OrderDigestListState(rawDigestList); + + // migration method + sellerAvatarState.inventory.UnlockInvalidSlot(digestList, sellerAgentAddress, sellerAvatarAddress); + sellerAvatarState.inventory.ReconfigureFungibleItem(digestList, order.TradableId); + sellerAvatarState.inventory.LockByReferringToDigestList(digestList, order.TradableId, context.BlockIndex); + + digestList.Remove(orderId); + + var errorCode = order.ValidateTransfer(sellerAvatarState, purchaseInfo.TradableId, purchaseInfo.Price, context.BlockIndex); + switch (errorCode) + { + case Action.Buy.ErrorCodeInvalidAddress: + throw new InvalidAddressException($"{orderId}"); + case Action.Buy.ErrorCodeInvalidTradableId: + throw new InvalidTradableIdException($"{orderId}"); + case Action.Buy.ErrorCodeInvalidPrice: + throw new InvalidPriceException($"{orderId}"); + case Action.Buy.ErrorCodeShopItemExpired: + throw new ShopItemExpiredException($"{orderId}"); + case Action.Buy.ErrorCodeItemDoesNotExist: + throw new ItemDoesNotExistException($"{orderId}"); + case Action.Buy.ErrorCodeInvalidItemType: + throw new InvalidItemTypeException($"{orderId}"); + } + + // Check Balance. + FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); + if (buyerBalance < order.Price) + { + throw new InsufficientBalanceException($"{orderId}", buyerAvatarState.address, + buyerBalance); + } + + var orderReceipt = order.Transfer(sellerAvatarState, buyerAvatarState, context.BlockIndex); + + Address orderReceiptAddress = OrderReceipt.DeriveAddress(orderId); + if (!(states.GetState(orderReceiptAddress) is null)) + { + throw new DuplicateOrderIdException($"{orderId}"); + } + + var expirationMail = sellerAvatarState.mailBox.OfType() + .FirstOrDefault(m => m.OrderId.Equals(orderId)); + if (!(expirationMail is null)) + { + sellerAvatarState.mailBox.Remove(expirationMail); + } + + var orderSellerMail = new OrderSellerMail( + context.BlockIndex, + orderId, + context.BlockIndex, + orderId + ); + var orderBuyerMail = new OrderBuyerMail( + context.BlockIndex, + orderId, + context.BlockIndex, + orderId + ); + + buyerAvatarState.Update(orderBuyerMail); + sellerAvatarState.Update(orderSellerMail); + + // // Update quest. + buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, order.Price); + sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, order.Price); + + sellerAvatarState.updatedAt = context.BlockIndex; + sellerAvatarState.blockIndex = context.BlockIndex; + + buyerAvatarState.UpdateQuestRewards(materialSheet); + sellerAvatarState.UpdateQuestRewards(materialSheet); + + FungibleAssetValue tax = order.GetTax(); + var taxedPrice = order.Price - tax; + + // Transfer tax. + var arenaSheet = states.GetSheet(); + var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); + var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); + states = states.TransferAsset( + context, + context.Signer, + feeStoreAddress, + tax); + + // Transfer seller. + states = states.TransferAsset( + context, + context.Signer, + sellerAgentAddress, + taxedPrice + ); + + states = states + .SetState(digestListAddress, digestList.Serialize()) + .SetState(orderReceiptAddress, orderReceipt.Serialize()) + .SetState(sellerInventoryAddress, sellerAvatarState.inventory.Serialize()) + .SetState(sellerWorldInformationAddress, sellerAvatarState.worldInformation.Serialize()) + .SetState(sellerQuestListAddress, sellerAvatarState.questList.Serialize()) + .SetState(sellerAvatarAddress, sellerAvatarState.SerializeV2()); + states = states.SetState(shardedShopAddress, shardedShopState.Serialize()); + return states; + } + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + ["a"] = AvatarAddress.Serialize(), + ["p"] = new List(ProductInfos.Select(p => p.Serialize())), + }.ToImmutableDictionary(); + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + AvatarAddress = plainValue["a"].ToAddress(); + var serialized = (List) plainValue["p"]; + ProductInfos = serialized.Cast().Select(ProductFactory.DeserializeProductInfo).ToList(); + } + } +} diff --git a/Lib9c/Action/CancelProductRegistration.cs b/Lib9c/Action/CancelProductRegistration.cs index af66e0363e..2049fb9d9e 100644 --- a/Lib9c/Action/CancelProductRegistration.cs +++ b/Lib9c/Action/CancelProductRegistration.cs @@ -17,7 +17,7 @@ namespace Nekoyume.Action { - [ActionType("cancel_product_registration")] + [ActionType("cancel_product_registration1")] public class CancelProductRegistration : GameAction { public const int CostAp = 5; diff --git a/Lib9c/Action/CancelProductRegistration0.cs b/Lib9c/Action/CancelProductRegistration0.cs new file mode 100644 index 0000000000..65601d2b39 --- /dev/null +++ b/Lib9c/Action/CancelProductRegistration0.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Lib9c.Model.Order; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Battle; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.Market; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("cancel_product_registration")] + public class CancelProductRegistration0 : GameAction + { + public const int CostAp = 5; + public const int Capacity = 100; + public Address AvatarAddress; + public List ProductInfos; + public bool ChargeAp; + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + IAccount states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + if (!ProductInfos.Any()) + { + throw new ListEmptyException("ProductInfos was empty."); + } + + if (ProductInfos.Count > Capacity) + { + throw new ArgumentOutOfRangeException($"{nameof(ProductInfos)} must be less than or equal {Capacity}."); + } + + foreach (var productInfo in ProductInfos) + { + productInfo.ValidateType(); + if (productInfo.AvatarAddress != AvatarAddress || + productInfo.AgentAddress != context.Signer) + { + throw new InvalidAddressException(); + } + } + + if (!states.TryGetAvatarStateV2(context.Signer, AvatarAddress, out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException("failed to load avatar state"); + } + + if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException(AvatarAddress.ToHex(), + GameConfig.RequireClearedStageLevel.ActionsInShop, current); + } + + avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); + ProductsState productsState; + if (states.TryGetState(productsStateAddress, out List rawProductList)) + { + productsState = new ProductsState(rawProductList); + } + else + { + // cancel order before product registered case. + var marketState = states.TryGetState(Addresses.Market, out List rawMarketList) + ? new MarketState(rawMarketList) + : new MarketState(); + productsState = new ProductsState(); + marketState.AvatarAddresses.Add(AvatarAddress); + states = states.SetState(Addresses.Market, marketState.Serialize()); + } + var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + foreach (var productInfo in ProductInfos) + { + if (productInfo is ItemProductInfo {Legacy: true}) + { + var productType = productInfo.Type; + var orderAddress = Order.DeriveAddress(productInfo.ProductId); + if (!states.TryGetState(orderAddress, out Dictionary rawOrder)) + { + throw new FailedLoadStateException( + $"{addressesHex} failed to load {nameof(Order)}({orderAddress})."); + } + + var order = OrderFactory.Deserialize(rawOrder); + switch (order) + { + case FungibleOrder _: + if (productInfo.Type == ProductType.NonFungible) + { + throw new InvalidProductTypeException($"FungibleOrder not support {productType}"); + } + + break; + case NonFungibleOrder _: + if (productInfo.Type == ProductType.Fungible) + { + throw new InvalidProductTypeException($"NoneFungibleOrder not support {productType}"); + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } + + states = SellCancellation.Cancel(context, states, avatarState, addressesHex, + order); + } + else + { + states = Cancel(productsState, productInfo, states, avatarState, context); + } + } + + states = states + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState(AvatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(productsStateAddress, productsState.Serialize()); + + if (migrationRequired) + { + states = states + .SetState(AvatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()) + .SetState(AvatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()); + } + + return states; + } + + public static IAccount Cancel(ProductsState productsState, IProductInfo productInfo, IAccount states, + AvatarState avatarState, IActionContext context) + { + var productId = productInfo.ProductId; + if (!productsState.ProductIds.Contains(productId)) + { + throw new ProductNotFoundException($"can't find product {productId}"); + } + + productsState.ProductIds.Remove(productId); + + var productAddress = Product.DeriveAddress(productId); + var product = ProductFactory.DeserializeProduct((List) states.GetState(productAddress)); + product.Validate(productInfo); + + switch (product) + { + case FavProduct favProduct: + states = states.TransferAsset(context, productAddress, avatarState.address, + favProduct.Asset); + break; + case ItemProduct itemProduct: + switch (itemProduct.TradableItem) + { + case Costume costume: + avatarState.UpdateFromAddCostume(costume, true); + break; + case ItemUsable itemUsable: + avatarState.UpdateFromAddItem(itemUsable, true); + break; + case TradableMaterial tradableMaterial: + { + avatarState.UpdateFromAddItem(tradableMaterial, itemProduct.ItemCount, true); + break; + } + } + + break; + default: + throw new ArgumentOutOfRangeException(nameof(product)); + } + + var mail = new ProductCancelMail(context.BlockIndex, productId, context.BlockIndex, productId); + avatarState.Update(mail); + states = states.SetState(productAddress, Null.Value); + return states; + } + + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + ["a"] = AvatarAddress.Serialize(), + ["p"] = new List(ProductInfos.Select(p => p.Serialize())), + ["c"] = ChargeAp.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + AvatarAddress = plainValue["a"].ToAddress(); + ProductInfos = plainValue["p"].ToList(s => ProductFactory.DeserializeProductInfo((List) s)); + ChargeAp = plainValue["c"].ToBoolean(); + } + } +} diff --git a/Lib9c/Action/CombinationConsumable.cs b/Lib9c/Action/CombinationConsumable.cs index 2db3de1d13..b76ea81f64 100644 --- a/Lib9c/Action/CombinationConsumable.cs +++ b/Lib9c/Action/CombinationConsumable.cs @@ -22,12 +22,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/637 - /// Updated at https://github.com/planetarium/lib9c/pull/861 - /// Updated at https://github.com/planetarium/lib9c/pull/957 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("combination_consumable8")] + [ActionType("combination_consumable9")] public class CombinationConsumable : GameAction, ICombinationConsumableV1 { public const string AvatarAddressKey = "a"; diff --git a/Lib9c/Action/CombinationConsumable8.cs b/Lib9c/Action/CombinationConsumable8.cs new file mode 100644 index 0000000000..39dc5cae37 --- /dev/null +++ b/Lib9c/Action/CombinationConsumable8.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Security.Cryptography; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/637 + /// Updated at https://github.com/planetarium/lib9c/pull/861 + /// Updated at https://github.com/planetarium/lib9c/pull/957 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("combination_consumable8")] + public class CombinationConsumable8 : GameAction, ICombinationConsumableV1 + { + public const string AvatarAddressKey = "a"; + public Address avatarAddress; + + public const string SlotIndexKey = "s"; + public int slotIndex; + + public const string RecipeIdKey = "r"; + public int recipeId; + + Address ICombinationConsumableV1.AvatarAddress => avatarAddress; + int ICombinationConsumableV1.RecipeId => recipeId; + int ICombinationConsumableV1.SlotIndex => slotIndex; + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + [AvatarAddressKey] = avatarAddress.Serialize(), + [SlotIndexKey] = slotIndex.Serialize(), + [RecipeIdKey] = recipeId.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + avatarAddress = plainValue[AvatarAddressKey].ToAddress(); + slotIndex = plainValue[SlotIndexKey].ToInteger(); + recipeId = plainValue[RecipeIdKey].ToInteger(); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + var slotAddress = avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + slotIndex + ) + ); + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + if (context.Rehearsal) + { + return states + .SetState(avatarAddress, MarkChanged) + .SetState(context.Signer, MarkChanged) + .SetState(inventoryAddress, MarkChanged) + .SetState(worldInformationAddress, MarkChanged) + .SetState(questListAddress, MarkChanged) + .SetState(slotAddress, MarkChanged); + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}Combination exec started", addressesHex); + + if (!states.TryGetAvatarStateV2(context.Signer, avatarAddress, out var avatarState, out _)) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + // Validate Required Cleared Stage + if (!avatarState.worldInformation.IsStageCleared( + GameConfig.RequireClearedStageLevel.CombinationConsumableAction)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException( + addressesHex, + GameConfig.RequireClearedStageLevel.CombinationConsumableAction, + current); + } + // ~Validate Required Cleared Stage + + // Validate SlotIndex + var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); + if (slotState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); + } + + if (!slotState.Validate(avatarState, context.BlockIndex)) + { + throw new CombinationSlotUnlockException( + $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); + } + // ~Validate SlotIndex + + // Validate Work + var costActionPoint = 0; + var endBlockIndex = context.BlockIndex; + var requiredFungibleItems = new Dictionary(); + + // Validate RecipeId + var consumableItemRecipeSheet = states.GetSheet(); + if (!consumableItemRecipeSheet.TryGetValue(recipeId, out var recipeRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(ConsumableItemRecipeSheet), + recipeId); + } + // ~Validate RecipeId + + // Validate Recipe ResultEquipmentId + var consumableItemSheet = states.GetSheet(); + if (!consumableItemSheet.TryGetValue(recipeRow.ResultConsumableItemId, out var consumableRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(consumableItemSheet), + recipeRow.ResultConsumableItemId); + } + // ~Validate Recipe ResultEquipmentId + + // Validate Recipe Material + var materialItemSheet = states.GetSheet(); + for (var i = recipeRow.Materials.Count; i > 0; i--) + { + var materialInfo = recipeRow.Materials[i - 1]; + if (!materialItemSheet.TryGetValue(materialInfo.Id, out var materialRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(MaterialItemSheet), + materialInfo.Id); + } + + if (requiredFungibleItems.ContainsKey(materialRow.Id)) + { + requiredFungibleItems[materialRow.Id] += materialInfo.Count; + } + else + { + requiredFungibleItems[materialRow.Id] = materialInfo.Count; + } + } + // ~Validate Recipe Material + + costActionPoint += recipeRow.RequiredActionPoint; + endBlockIndex += recipeRow.RequiredBlockIndex; + // ~Validate Work + + // Remove Required Materials + var inventory = avatarState.inventory; + foreach (var pair in requiredFungibleItems.OrderBy(pair => pair.Key)) + { + if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || + !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); + } + } + // ~Remove Required Materials + + // Subtract Required ActionPoint + if (costActionPoint > 0) + { + if (avatarState.actionPoint < costActionPoint) + { + throw new NotEnoughActionPointException( + $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + ); + } + + avatarState.actionPoint -= costActionPoint; + } + // ~Subtract Required ActionPoint + + // Create Consumable + var consumable = (Consumable) ItemFactory.CreateItemUsable( + consumableRow, + context.Random.GenerateRandomGuid(), + endBlockIndex + ); + // ~Create Consumable + + // Add or Update Consumable + avatarState.blockIndex = context.BlockIndex; + avatarState.updatedAt = context.BlockIndex; + avatarState.UpdateFromCombination(consumable); + avatarState.UpdateQuestRewards(materialItemSheet); + // ~Add or Update Consumable + + // Update Slot + var mailId = context.Random.GenerateRandomGuid(); + var attachmentResult = new CombinationConsumable5.ResultModel + { + id = mailId, + actionPoint = costActionPoint, + materials = requiredFungibleItems.ToDictionary( + e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), + e => e.Value), + itemUsable = consumable, + recipeId = recipeId, + }; + slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex); + // ~Update Slot + + // Create Mail + var mail = new CombinationMail( + attachmentResult, + context.BlockIndex, + mailId, + endBlockIndex); + avatarState.Update(mail); + // ~Create Mail + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}Combination Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(avatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + } + } +} diff --git a/Lib9c/Action/CombinationEquipment.cs b/Lib9c/Action/CombinationEquipment.cs index a7a433d880..8d3780e6a0 100644 --- a/Lib9c/Action/CombinationEquipment.cs +++ b/Lib9c/Action/CombinationEquipment.cs @@ -24,10 +24,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1711 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("combination_equipment16")] + [ActionType("combination_equipment17")] public class CombinationEquipment : GameAction, ICombinationEquipmentV4 { public const string AvatarAddressKey = "a"; diff --git a/Lib9c/Action/CombinationEquipment16.cs b/Lib9c/Action/CombinationEquipment16.cs new file mode 100644 index 0000000000..b380a5d484 --- /dev/null +++ b/Lib9c/Action/CombinationEquipment16.cs @@ -0,0 +1,769 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.Skill; +using Nekoyume.Model.Stat; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Crystal; +using Nekoyume.TableData.Pet; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1711 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("combination_equipment16")] + public class CombinationEquipment16 : GameAction, ICombinationEquipmentV4 + { + public const string AvatarAddressKey = "a"; + public Address avatarAddress; + + public const string SlotIndexKey = "s"; + public int slotIndex; + + public const string RecipeIdKey = "r"; + public int recipeId; + + public const string SubRecipeIdKey = "i"; + public int? subRecipeId; + public const string PayByCrystalKey = "p"; + public bool payByCrystal; + public const string UseHammerPointKey = "h"; + public bool useHammerPoint; + public const string PetIdKey = "pid"; + public int? petId; + + public const int BasicSubRecipeHammerPoint = 1; + public const int SpecialSubRecipeHammerPoint = 2; + + Address ICombinationEquipmentV4.AvatarAddress => avatarAddress; + int ICombinationEquipmentV4.RecipeId => recipeId; + int ICombinationEquipmentV4.SlotIndex => slotIndex; + int? ICombinationEquipmentV4.SubRecipeId => subRecipeId; + bool ICombinationEquipmentV4.PayByCrystal => payByCrystal; + bool ICombinationEquipmentV4.UseHammerPoint => useHammerPoint; + int? ICombinationEquipmentV4.PetId => petId; + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + [AvatarAddressKey] = avatarAddress.Serialize(), + [SlotIndexKey] = slotIndex.Serialize(), + [RecipeIdKey] = recipeId.Serialize(), + [SubRecipeIdKey] = subRecipeId.Serialize(), + [PayByCrystalKey] = payByCrystal.Serialize(), + [UseHammerPointKey] = useHammerPoint.Serialize(), + [PetIdKey] = petId.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + avatarAddress = plainValue[AvatarAddressKey].ToAddress(); + slotIndex = plainValue[SlotIndexKey].ToInteger(); + recipeId = plainValue[RecipeIdKey].ToInteger(); + subRecipeId = plainValue[SubRecipeIdKey].ToNullableInteger(); + payByCrystal = plainValue[PayByCrystalKey].ToBoolean(); + useHammerPoint = plainValue[UseHammerPointKey].ToBoolean(); + petId = plainValue[PetIdKey].ToNullableInteger(); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + var slotAddress = avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + slotIndex + ) + ); + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}CombinationEquipment exec started", addressesHex); + + if (!states.TryGetAgentAvatarStatesV2(context.Signer, avatarAddress, out var agentState, + out var avatarState, out _)) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + // Validate Required Cleared Tutorial Stage + if (!avatarState.worldInformation.IsStageCleared( + GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException( + addressesHex, + GameConfig.RequireClearedStageLevel.CombinationEquipmentAction, + current); + } + // ~Validate Required Cleared Tutorial Stage + + // Validate SlotIndex + var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); + if (slotState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); + } + + if (!slotState.Validate(avatarState, context.BlockIndex)) + { + throw new CombinationSlotUnlockException( + $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); + } + // ~Validate SlotIndex + + // Validate PetState + PetState petState = null; + if (petId.HasValue) + { + var petStateAddress = PetState.DeriveAddress(avatarAddress, petId.Value); + if (!states.TryGetState(petStateAddress, out List rawState)) + { + throw new FailedLoadStateException($"{addressesHex}Aborted as the {nameof(PetState)} was failed to load."); + } + petState = new PetState(rawState); + + if (!petState.Validate(context.BlockIndex)) + { + throw new PetIsLockedException($"{addressesHex}Aborted as the pet is already in use."); + } + } + // ~Validate PetState + + // Validate Work + var costActionPoint = 0; + var costNcg = 0L; + var endBlockIndex = context.BlockIndex; + var requiredFungibleItems = new Dictionary(); + + Dictionary sheets = states.GetSheets(sheetTypes: new[] + { + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSheet), + typeof(MaterialItemSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(SkillSheet), + typeof(CrystalMaterialCostSheet), + typeof(CrystalFluctuationSheet), + typeof(CrystalHammerPointSheet), + typeof(ConsumableItemRecipeSheet), + }); + + // Validate RecipeId + var equipmentItemRecipeSheet = sheets.GetSheet(); + if (!equipmentItemRecipeSheet.TryGetValue(recipeId, out var recipeRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(EquipmentItemRecipeSheet), + recipeId); + } + // ~Validate RecipeId + + // Validate Recipe ResultEquipmentId + var equipmentItemSheet = sheets.GetSheet(); + if (!equipmentItemSheet.TryGetValue(recipeRow.ResultEquipmentId, out var equipmentRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(equipmentItemSheet), + recipeRow.ResultEquipmentId); + } + // ~Validate Recipe ResultEquipmentId + + // Validate Recipe Material + var materialItemSheet = sheets.GetSheet(); + if (!materialItemSheet.TryGetValue(recipeRow.MaterialId, out var materialRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(MaterialItemSheet), + recipeRow.MaterialId); + } + + if (requiredFungibleItems.ContainsKey(materialRow.Id)) + { + requiredFungibleItems[materialRow.Id] += recipeRow.MaterialCount; + } + else + { + requiredFungibleItems[materialRow.Id] = recipeRow.MaterialCount; + } + // ~Validate Recipe Material + + // Validate Recipe Unlocked. + if (equipmentItemRecipeSheet[recipeId].CRYSTAL != 0) + { + var unlockedRecipeIdsAddress = avatarAddress.Derive("recipe_ids"); + if (!states.TryGetState(unlockedRecipeIdsAddress, out List rawIds)) + { + throw new FailedLoadStateException("can't find UnlockedRecipeList."); + } + + var unlockedIds = rawIds.ToList(StateExtensions.ToInteger); + if (!unlockedIds.Contains(recipeId)) + { + throw new InvalidRecipeIdException($"unlock {recipeId} first."); + } + + if (!avatarState.worldInformation.IsStageCleared(recipeRow.UnlockStage)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException( + addressesHex, + recipeRow.UnlockStage, + current); + } + } + // ~Validate Recipe Unlocked + + // Validate SubRecipeId + EquipmentItemSubRecipeSheetV2.Row subRecipeRow = null; + if (subRecipeId.HasValue) + { + if (!recipeRow.SubRecipeIds.Contains(subRecipeId.Value)) + { + throw new SheetRowColumnException( + $"{addressesHex}Aborted as the sub recipe {subRecipeId.Value} was failed to load from the sheet." + ); + } + + var equipmentItemSubRecipeSheetV2 = sheets.GetSheet(); + if (!equipmentItemSubRecipeSheetV2.TryGetValue(subRecipeId.Value, out subRecipeRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(EquipmentItemSubRecipeSheetV2), + subRecipeId.Value); + } + + // Validate SubRecipe Material + for (var i = subRecipeRow.Materials.Count; i > 0; i--) + { + var materialInfo = subRecipeRow.Materials[i - 1]; + if (!materialItemSheet.TryGetValue(materialInfo.Id, out materialRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(MaterialItemSheet), + materialInfo.Id); + } + + if (requiredFungibleItems.ContainsKey(materialRow.Id)) + { + requiredFungibleItems[materialRow.Id] += materialInfo.Count; + } + else + { + requiredFungibleItems[materialRow.Id] = materialInfo.Count; + } + } + // ~Validate SubRecipe Material + + costActionPoint += subRecipeRow.RequiredActionPoint; + costNcg += subRecipeRow.RequiredGold; + endBlockIndex += subRecipeRow.RequiredBlockIndex; + } + // ~Validate SubRecipeId + + costActionPoint += recipeRow.RequiredActionPoint; + costNcg += recipeRow.RequiredGold; + endBlockIndex += recipeRow.RequiredBlockIndex; + // ~Validate Work + + var existHammerPointSheet = + sheets.TryGetSheet(out CrystalHammerPointSheet hammerPointSheet); + var hammerPointAddress = + Addresses.GetHammerPointStateAddress(avatarAddress, recipeId); + var hammerPointState = new HammerPointState(hammerPointAddress, recipeId); + CrystalHammerPointSheet.Row hammerPointRow = null; + if (existHammerPointSheet) + { + if (states.TryGetState(hammerPointAddress, out List serialized)) + { + hammerPointState = + new HammerPointState(hammerPointAddress, serialized); + } + + // Validate HammerPointSheet by recipeId + if (!hammerPointSheet.TryGetValue(recipeId, out hammerPointRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(CrystalHammerPointSheet), + recipeId); + } + } + + var isMimisbrunnrSubRecipe = subRecipeRow?.IsMimisbrunnrSubRecipe ?? + subRecipeId.HasValue && recipeRow.SubRecipeIds[2] == subRecipeId.Value; + var petOptionSheet = states.GetSheet(); + if (useHammerPoint) + { + if (!existHammerPointSheet) + { + throw new FailedLoadSheetException(typeof(CrystalHammerPointSheet)); + } + + if (isMimisbrunnrSubRecipe) + { + throw new ArgumentException( + $"Can not super craft with mimisbrunnr recipe. Subrecipe id: {subRecipeId}"); + } + + if (hammerPointState.HammerPoint < hammerPointRow.MaxPoint) + { + throw new NotEnoughHammerPointException( + $"Not enough hammer points. Need : {hammerPointRow.MaxPoint}, own : {hammerPointState.HammerPoint}"); + } + + states = UseAssetsBySuperCraft( + states, + context, + hammerPointRow, + hammerPointState); + } + else + { + states = UseAssetsByNormalCombination( + states, + context, + avatarState, + hammerPointState, + petState, + sheets, + materialItemSheet, + hammerPointSheet, + petOptionSheet, + recipeRow, + subRecipeRow, + requiredFungibleItems, + addressesHex); + } + + // Subtract Required ActionPoint + if (costActionPoint > 0) + { + if (avatarState.actionPoint < costActionPoint) + { + throw new NotEnoughActionPointException( + $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + ); + } + + avatarState.actionPoint -= costActionPoint; + } + // ~Subtract Required ActionPoint + + // Transfer Required NCG + if (costNcg > 0L) + { + var arenaSheet = states.GetSheet(); + var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); + var feeStoreAddress = Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); + + states = states.TransferAsset( + context, + context.Signer, + feeStoreAddress, + states.GetGoldCurrency() * costNcg + ); + } + // ~Transfer Required NCG + + // Create Equipment + var equipment = (Equipment) ItemFactory.CreateItemUsable( + equipmentRow, + context.Random.GenerateRandomGuid(), + endBlockIndex, + madeWithMimisbrunnrRecipe: isMimisbrunnrSubRecipe + ); + + if (!(subRecipeRow is null)) + { + AddAndUnlockOption( + agentState, + petState, + equipment, + context.Random, + subRecipeRow, + sheets.GetSheet(), + petOptionSheet, + sheets.GetSheet() + ); + endBlockIndex = equipment.RequiredBlockIndex; + + if (useHammerPoint) + { + if (!equipment.Skills.Any()) + { + AddSkillOption( + agentState, + equipment, + context.Random, + subRecipeRow, + sheets.GetSheet(), + sheets.GetSheet() + ); + } + + var firstFoodRow = sheets.GetSheet() + .First; + if (firstFoodRow is null) + { + throw new SheetRowNotFoundException( + $"{nameof(ConsumableItemRecipeSheet)}'s first row is null.", 0); + } + + endBlockIndex = equipment.RequiredBlockIndex = + context.BlockIndex + firstFoodRow.RequiredBlockIndex; + } + } + // ~Create Equipment + + // Apply block time discount + if (!(petState is null)) + { + var requiredBlockIndex = endBlockIndex - context.BlockIndex; + var gameConfigState = states.GetGameConfigState(); + requiredBlockIndex = PetHelper.CalculateReducedBlockOnCraft( + requiredBlockIndex, + gameConfigState.RequiredAppraiseBlock, + petState, + petOptionSheet); + endBlockIndex = context.BlockIndex + requiredBlockIndex; + equipment.Update(endBlockIndex); + } + + // Add or Update Equipment + avatarState.blockIndex = context.BlockIndex; + avatarState.updatedAt = context.BlockIndex; + avatarState.questList.UpdateCombinationEquipmentQuest(recipeId); + avatarState.UpdateFromCombination(equipment); + avatarState.UpdateQuestRewards(materialItemSheet); + // ~Add or Update Equipment + + // Update Slot + var mailId = context.Random.GenerateRandomGuid(); + var attachmentResult = new CombinationConsumable5.ResultModel + { + id = mailId, + actionPoint = costActionPoint, + gold = costNcg, + materials = requiredFungibleItems.ToDictionary( + e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), + e => e.Value), + itemUsable = equipment, + recipeId = recipeId, + subRecipeId = subRecipeId, + }; + slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex, petId); + // ~Update Slot + + // Update Pet + if (!(petState is null)) + { + petState.Update(endBlockIndex); + var petStateAddress = PetState.DeriveAddress(avatarAddress, petState.PetId); + states = states.SetState(petStateAddress, petState.Serialize()); + } + // ~Update Pet + + // Create Mail + var mail = new CombinationMail( + attachmentResult, + context.BlockIndex, + mailId, + endBlockIndex); + avatarState.Update(mail); + // ~Create Mail + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}CombinationEquipment Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(avatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()) + .SetState(slotAddress, slotState.Serialize()) + .SetState(hammerPointAddress,hammerPointState.Serialize()) + .SetState(context.Signer, agentState.Serialize()); + } + + private IAccount UseAssetsBySuperCraft( + IAccount states, + IActionContext context, + CrystalHammerPointSheet.Row row, + HammerPointState hammerPointState) + { + var crystalBalance = states.GetBalance(context.Signer, CrystalCalculator.CRYSTAL); + var hammerPointCost = CrystalCalculator.CRYSTAL * row.CRYSTAL; + if (crystalBalance < hammerPointCost) + { + throw new NotEnoughFungibleAssetValueException($"required {hammerPointCost}, but balance is {crystalBalance}"); + } + + hammerPointState.ResetHammerPoint(); + return states.TransferAsset( + context, + context.Signer, + Addresses.SuperCraft, + hammerPointCost); + } + + private IAccount UseAssetsByNormalCombination( + IAccount states, + IActionContext context, + AvatarState avatarState, + HammerPointState hammerPointState, + PetState petState, + Dictionary sheets, + MaterialItemSheet materialItemSheet, + CrystalHammerPointSheet hammerPointSheet, + PetOptionSheet petOptionSheet, + EquipmentItemRecipeSheet.Row recipeRow, + EquipmentItemSubRecipeSheetV2.Row subRecipeRow, + Dictionary requiredFungibleItems, + string addressesHex) + { + // Remove Required Materials + var inventory = avatarState.inventory; + var crystalMaterialSheet = sheets.GetSheet(); + var costCrystal = CrystalCalculator.CRYSTAL * 0; + foreach (var pair in requiredFungibleItems.OrderBy(pair => pair.Key)) + { + var itemId = pair.Key; + var requiredCount = pair.Value; + if (materialItemSheet.TryGetValue(itemId, out var materialRow)) + { + int itemCount = inventory.TryGetItem(itemId, out Inventory.Item item) + ? item.count + : 0; + if (itemCount < requiredCount && payByCrystal) + { + costCrystal += CrystalCalculator.CalculateMaterialCost( + itemId, + requiredCount - itemCount, + crystalMaterialSheet); + requiredCount = itemCount; + } + + if (requiredCount > 0 && !inventory.RemoveFungibleItem(materialRow.ItemId, + context.BlockIndex, + requiredCount)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); + } + } + else + { + throw new SheetRowNotFoundException(nameof(MaterialItemSheet), itemId); + } + } + + // ~Remove Required Materials + if (costCrystal > 0 * CrystalCalculator.CRYSTAL) + { + var crystalFluctuationSheet = sheets.GetSheet(); + var row = crystalFluctuationSheet.Values + .First(r => r.Type == CrystalFluctuationSheet.ServiceType.Combination); + var (dailyCostState, weeklyCostState, _, _) = + states.GetCrystalCostStates(context.BlockIndex, row.BlockInterval); + + // 1x fixed crystal cost. + costCrystal = CrystalCalculator.CalculateCombinationCost( + costCrystal, + row: row, + prevWeeklyCostState: null, + beforePrevWeeklyCostState: null); + + // Apply pet discount if possible. + if (!(petState is null)) + { + costCrystal = PetHelper.CalculateDiscountedMaterialCost( + costCrystal, + petState, + petOptionSheet); + } + + // Update Daily Formula. + dailyCostState.Count++; + dailyCostState.CRYSTAL += costCrystal; + // Update Weekly Formula. + weeklyCostState.Count++; + weeklyCostState.CRYSTAL += costCrystal; + + var crystalBalance = + states.GetBalance(context.Signer, CrystalCalculator.CRYSTAL); + if (costCrystal > crystalBalance) + { + throw new NotEnoughFungibleAssetValueException( + $"required {costCrystal}, but balance is {crystalBalance}"); + } + + states = states + .SetState(dailyCostState.Address, dailyCostState.Serialize()) + .SetState(weeklyCostState.Address, weeklyCostState.Serialize()) + .TransferAsset(context, context.Signer, Addresses.MaterialCost, costCrystal); + } + + int hammerPoint; + if (subRecipeRow?.RewardHammerPoint.HasValue ?? false) + { + hammerPoint = subRecipeRow.RewardHammerPoint.Value; + } + else + { + var isBasicSubRecipe = !subRecipeId.HasValue || + recipeRow.SubRecipeIds[0] == subRecipeId.Value; + hammerPoint = isBasicSubRecipe + ? BasicSubRecipeHammerPoint + : SpecialSubRecipeHammerPoint; + } + + hammerPointState.AddHammerPoint(hammerPoint, hammerPointSheet); + return states; + } + + public static void AddAndUnlockOption( + AgentState agentState, + PetState petState, + Equipment equipment, + IRandom random, + EquipmentItemSubRecipeSheetV2.Row subRecipe, + EquipmentItemOptionSheet optionSheet, + PetOptionSheet petOptionSheet, + SkillSheet skillSheet + ) + { + foreach (var optionInfo in subRecipe.Options + .OrderByDescending(e => e.Ratio) + .ThenBy(e => e.RequiredBlockIndex) + .ThenBy(e => e.Id)) + { + if (!optionSheet.TryGetValue(optionInfo.Id, out var optionRow)) + { + continue; + } + + var value = random.Next(1, GameConfig.MaximumProbability + 1); + var ratio = optionInfo.Ratio; + + // Apply pet bonus if possible + if (!(petState is null)) + { + ratio = PetHelper.GetBonusOptionProbability( + ratio, + petState, + petOptionSheet); + } + + if (value > ratio) + { + continue; + } + + if (optionRow.StatType != StatType.NONE) + { + var stat = CombinationEquipment5.GetStat(optionRow, random); + equipment.StatsMap.AddStatAdditionalValue(stat.StatType, stat.BaseValue); + equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); + equipment.optionCountFromCombination++; + agentState.unlockedOptions.Add(optionRow.Id); + } + else + { + var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); + if (!(skill is null)) + { + equipment.Skills.Add(skill); + equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); + equipment.optionCountFromCombination++; + agentState.unlockedOptions.Add(optionRow.Id); + } + } + } + } + + public static Skill GetSkill( + EquipmentItemOptionSheet.Row row, + SkillSheet skillSheet, + IRandom random) + { + var skillRow = skillSheet.OrderedList.FirstOrDefault(r => r.Id == row.SkillId); + if (skillRow == null) + { + return null; + } + + var dmg = random.Next(row.SkillDamageMin, row.SkillDamageMax + 1); + var chance = random.Next(row.SkillChanceMin, row.SkillChanceMax + 1); + + var hasStatDamageRatio = row.StatDamageRatioMin != default && row.StatDamageRatioMax != default; + var statDamageRatio = hasStatDamageRatio ? + random.Next(row.StatDamageRatioMin, row.StatDamageRatioMax + 1) : default; + var refStatType = hasStatDamageRatio ? row.ReferencedStatType : StatType.NONE; + + var skill = SkillFactory.Get(skillRow, dmg, chance, statDamageRatio, refStatType); + return skill; + } + + public static void AddSkillOption( + AgentState agentState, + Equipment equipment, + IRandom random, + EquipmentItemSubRecipeSheetV2.Row subRecipe, + EquipmentItemOptionSheet optionSheet, + SkillSheet skillSheet + ) + { + foreach (var optionInfo in subRecipe.Options) + { + if (!optionSheet.TryGetValue(optionInfo.Id, out var optionRow)) + { + continue; + } + + var skill = GetSkill(optionRow, skillSheet, random); + if (!(skill is null)) + { + equipment.Skills.Add(skill); + equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); + equipment.optionCountFromCombination++; + agentState.unlockedOptions.Add(optionRow.Id); + } + } + } + } +} diff --git a/Lib9c/Action/CreateAvatar.cs b/Lib9c/Action/CreateAvatar.cs index 9b6b106140..def31f5edc 100644 --- a/Lib9c/Action/CreateAvatar.cs +++ b/Lib9c/Action/CreateAvatar.cs @@ -23,11 +23,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/2166 - /// Updated at https://github.com/planetarium/lib9c/pull/2166 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("create_avatar10")] + [ActionType("create_avatar11")] public class CreateAvatar : GameAction, ICreateAvatarV2 { public const string DeriveFormat = "avatar-state-{0}"; diff --git a/Lib9c/Action/CreateAvatar10.cs b/Lib9c/Action/CreateAvatar10.cs new file mode 100644 index 0000000000..8096c63ae0 --- /dev/null +++ b/Lib9c/Action/CreateAvatar10.cs @@ -0,0 +1,317 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Nekoyume.Extensions; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/2166 + /// Updated at https://github.com/planetarium/lib9c/pull/2166 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("create_avatar10")] + public class CreateAvatar10 : GameAction, ICreateAvatarV2 + { + public const string DeriveFormat = "avatar-state-{0}"; + + public int index; + public int hair; + public int lens; + public int ear; + public int tail; + public string name; + + int ICreateAvatarV2.Index => index; + int ICreateAvatarV2.Hair => hair; + int ICreateAvatarV2.Lens => lens; + int ICreateAvatarV2.Ear => ear; + int ICreateAvatarV2.Tail => tail; + string ICreateAvatarV2.Name => name; + + protected override IImmutableDictionary PlainValueInternal => new Dictionary() + { + ["index"] = (Integer) index, + ["hair"] = (Integer) hair, + ["lens"] = (Integer) lens, + ["ear"] = (Integer) ear, + ["tail"] = (Integer) tail, + ["name"] = (Text) name, + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + index = (int) ((Integer) plainValue["index"]).Value; + hair = (int) ((Integer) plainValue["hair"]).Value; + lens = (int) ((Integer) plainValue["lens"]).Value; + ear = (int) ((Integer) plainValue["ear"]).Value; + tail = (int) ((Integer) plainValue["tail"]).Value; + name = (Text) plainValue["name"]; + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + IActionContext ctx = context; + var signer = ctx.Signer; + var states = ctx.PreviousState; + var avatarAddress = signer.Derive( + string.Format( + CultureInfo.InvariantCulture, + DeriveFormat, + index + ) + ); + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + if (ctx.Rehearsal) + { + states = states.SetState(signer, MarkChanged); + for (var i = 0; i < AvatarState.CombinationSlotCapacity; i++) + { + var slotAddress = avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + i + ) + ); + states = states.SetState(slotAddress, MarkChanged); + } + + return states + .SetState(avatarAddress, MarkChanged) + .SetState(inventoryAddress, MarkChanged) + .SetState(worldInformationAddress, MarkChanged) + .SetState(questListAddress, MarkChanged) + .MarkBalanceChanged(ctx, GoldCurrencyMock, signer); + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + + if (!Regex.IsMatch(name, GameConfig.AvatarNickNamePattern)) + { + throw new InvalidNamePatternException( + $"{addressesHex}Aborted as the input name {name} does not follow the allowed name pattern."); + } + + var sw = new Stopwatch(); + sw.Start(); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}CreateAvatar exec started", addressesHex); + AgentState existingAgentState = states.GetAgentState(signer); + var agentState = existingAgentState ?? new AgentState(signer); + var avatarState = states.GetAvatarState(avatarAddress); + if (!(avatarState is null)) + { + throw new InvalidAddressException( + $"{addressesHex}Aborted as there is already an avatar at {avatarAddress}."); + } + + if (!(0 <= index && index < GameConfig.SlotCount)) + { + throw new AvatarIndexOutOfRangeException( + $"{addressesHex}Aborted as the index is out of range #{index}."); + } + + if (agentState.avatarAddresses.ContainsKey(index)) + { + throw new AvatarIndexAlreadyUsedException( + $"{addressesHex}Aborted as the signer already has an avatar at index #{index}."); + } + sw.Stop(); + Log.Verbose("{AddressesHex}CreateAvatar Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); + sw.Restart(); + + Log.Verbose("{AddressesHex}Execute CreateAvatar; player: {AvatarAddress}", addressesHex, avatarAddress); + + agentState.avatarAddresses.Add(index, avatarAddress); + + // Avoid NullReferenceException in test + var materialItemSheet = ctx.PreviousState.GetSheet(); + + avatarState = CreateAvatar0.CreateAvatarState(name, avatarAddress, ctx, materialItemSheet, default); + + if (hair < 0) hair = 0; + if (lens < 0) lens = 0; + if (ear < 0) ear = 0; + if (tail < 0) tail = 0; + + avatarState.Customize(hair, lens, ear, tail); + + foreach (var address in avatarState.combinationSlotAddresses) + { + var slotState = + new CombinationSlotState(address, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + states = states.SetState(address, slotState.Serialize()); + } + + avatarState.UpdateQuestRewards(materialItemSheet); + + // Add Runes when executing on editor mode. +#if LIB9C_DEV_EXTENSIONS || UNITY_EDITOR + states = CreateAvatar0.AddRunesForTest(ctx, avatarAddress, states); + + // Add pets for test + if (states.TryGetSheet(out PetSheet petSheet)) + { + foreach (var row in petSheet) + { + var petState = new PetState(row.Id); + petState.LevelUp(); + var petStateAddress = PetState.DeriveAddress(avatarAddress, row.Id); + states = states.SetState(petStateAddress, petState.Serialize()); + } + } + + var recipeIds = new int[] { + 21, + 62, + 103, + 128, + 148, + 152, + }; + var equipmentSheet = states.GetSheet(); + var recipeSheet = states.GetSheet(); + var subRecipeSheet = states.GetSheet(); + var optionSheet = states.GetSheet(); + var skillSheet = states.GetSheet(); + var characterLevelSheet = states.GetSheet(); + var enhancementCostSheet = states.GetSheet(); + + avatarState.level = 300; + avatarState.exp = characterLevelSheet[300].Exp; + + // prepare equipments for test + foreach (var recipeId in recipeIds) + { + var recipeRow = recipeSheet[recipeId]; + var subRecipeId = recipeRow.SubRecipeIds[1]; + var subRecipeRow = subRecipeSheet[subRecipeId]; + var equipmentRow = equipmentSheet[recipeRow.ResultEquipmentId]; + + var equipment = (Equipment)ItemFactory.CreateItemUsable( + equipmentRow, + context.Random.GenerateRandomGuid(), + 0L, + madeWithMimisbrunnrRecipe: subRecipeRow.IsMimisbrunnrSubRecipe ?? false); + + foreach (var option in subRecipeRow.Options) + { + var optionRow = optionSheet[option.Id]; + // Add stats. + if (optionRow.StatType != StatType.NONE) + { + var statMap = new DecimalStat(optionRow.StatType, optionRow.StatMax); + equipment.StatsMap.AddStatAdditionalValue(statMap.StatType, statMap.TotalValue); + equipment.optionCountFromCombination++; + } + // Add skills. + else + { + var skillRow = skillSheet.OrderedList.First(r => r.Id == optionRow.SkillId); + var skill = SkillFactory.Get( + skillRow, + optionRow.SkillDamageMax, + optionRow.SkillChanceMax, + optionRow.StatDamageRatioMax, + optionRow.ReferencedStatType); + if (skill != null) + { + equipment.Skills.Add(skill); + equipment.optionCountFromCombination++; + } + } + } + + for (int i = 1; i <= 20; ++i) + { + var subType = equipment.ItemSubType; + var grade = equipment.Grade; + var costRow = enhancementCostSheet.Values + .First(x => x.ItemSubType == subType && + x.Grade == grade && + x.Level == i); + equipment.LevelUp(ctx.Random, costRow, true); + } + + avatarState.inventory.AddItem(equipment); + } +#endif + var sheets = ctx.PreviousState.GetSheets(containItemSheet: true, + sheetTypes: new[] {typeof(CreateAvatarItemSheet), typeof(CreateAvatarFavSheet)}); + var itemSheet = sheets.GetItemSheet(); + var createAvatarItemSheet = sheets.GetSheet(); + AddItem(itemSheet, createAvatarItemSheet, avatarState, context.Random); + var createAvatarFavSheet = sheets.GetSheet(); + states = MintAsset(createAvatarFavSheet, avatarState, states, context); + sw.Stop(); + Log.Verbose("{AddressesHex}CreateAvatar CreateAvatarState: {Elapsed}", addressesHex, sw.Elapsed); + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}CreateAvatar Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(signer, agentState.Serialize()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()) + .SetState(avatarAddress, avatarState.SerializeV2()); + } + + public static void AddItem(ItemSheet itemSheet, CreateAvatarItemSheet createAvatarItemSheet, + AvatarState avatarState, IRandom random) + { + foreach (var row in createAvatarItemSheet.Values) + { + var itemId = row.ItemId; + var count = row.Count; + var itemRow = itemSheet[itemId]; + if (itemRow is MaterialItemSheet.Row materialRow) + { + var item = ItemFactory.CreateMaterial(materialRow); + avatarState.inventory.AddItem(item, count); + } + else + { + for (int i = 0; i < count; i++) + { + var item = ItemFactory.CreateItem(itemRow, random); + avatarState.inventory.AddItem(item); + } + } + } + } + + public static IAccount MintAsset(CreateAvatarFavSheet favSheet, + AvatarState avatarState, IAccount states, IActionContext context) + { + foreach (var row in favSheet.Values) + { + var currency = row.Currency; + var targetAddress = row.Target switch + { + CreateAvatarFavSheet.Target.Agent => avatarState.agentAddress, + CreateAvatarFavSheet.Target.Avatar => avatarState.address, + _ => throw new ArgumentOutOfRangeException() + }; + states = states.MintAsset(context, targetAddress, currency * row.Quantity); + } + + return states; + } + } +} diff --git a/Lib9c/Action/EventConsumableItemCrafts.cs b/Lib9c/Action/EventConsumableItemCrafts.cs index a7296a947a..5cde758654 100644 --- a/Lib9c/Action/EventConsumableItemCrafts.cs +++ b/Lib9c/Action/EventConsumableItemCrafts.cs @@ -20,11 +20,14 @@ namespace Nekoyume.Action { + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 + /// [Serializable] [ActionType(ActionTypeText)] public class EventConsumableItemCrafts : GameAction, IEventConsumableItemCraftsV1 { - private const string ActionTypeText = "event_consumable_item_crafts"; + private const string ActionTypeText = "event_consumable_item_crafts1"; public Address AvatarAddress; public int EventScheduleId; diff --git a/Lib9c/Action/EventConsumableItemCrafts0.cs b/Lib9c/Action/EventConsumableItemCrafts0.cs new file mode 100644 index 0000000000..e0a4085595 --- /dev/null +++ b/Lib9c/Action/EventConsumableItemCrafts0.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Extensions; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Event; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType(ActionTypeText)] + public class EventConsumableItemCrafts0 : GameAction, IEventConsumableItemCraftsV1 + { + private const string ActionTypeText = "event_consumable_item_crafts"; + + public Address AvatarAddress; + public int EventScheduleId; + public int EventConsumableItemRecipeId; + public int SlotIndex; + + Address IEventConsumableItemCraftsV1.AvatarAddress => AvatarAddress; + int IEventConsumableItemCraftsV1.EventScheduleId => EventScheduleId; + int IEventConsumableItemCraftsV1.EventConsumableItemRecipeId => EventConsumableItemRecipeId; + int IEventConsumableItemCraftsV1.SlotIndex => SlotIndex; + + protected override IImmutableDictionary PlainValueInternal + { + get + { + var list = Bencodex.Types.List.Empty + .Add(AvatarAddress.Serialize()) + .Add(EventScheduleId.Serialize()) + .Add(EventConsumableItemRecipeId.Serialize()) + .Add(SlotIndex.Serialize()); + + return new Dictionary + { + { "l", list }, + }.ToImmutableDictionary(); + } + } + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + if (!plainValue.TryGetValue("l", out var serialized)) + { + throw new ArgumentException("plainValue must contain 'l'"); + } + + if (!(serialized is Bencodex.Types.List list)) + { + throw new ArgumentException("'l' must be a bencodex list"); + } + + if (list.Count < 4) + { + throw new ArgumentException("'l' must contain at least 4 items"); + } + + AvatarAddress = list[0].ToAddress(); + EventScheduleId = list[1].ToInteger(); + EventConsumableItemRecipeId = list[2].ToInteger(); + SlotIndex = list[3].ToInteger(); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Execute() start", + ActionTypeText, + addressesHex); + + var sw = new Stopwatch(); + // Get AvatarState + sw.Start(); + if (!states.TryGetAvatarStateV2( + context.Signer, + AvatarAddress, + out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + ActionTypeText, + addressesHex, + typeof(AvatarState), + AvatarAddress); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] TryGetAvatarStateV2: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get AvatarState + + // Get sheets + sw.Restart(); + var sheets = states.GetSheets( + sheetTypes: new[] + { + typeof(EventScheduleSheet), + typeof(EventConsumableItemRecipeSheet), + }); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Get sheets: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get sheets + + // Validate Requirements. + sw.Restart(); + avatarState.worldInformation.ValidateFromAction( + GameConfig.RequireClearedStageLevel.CombinationConsumableAction, + ActionTypeText, + addressesHex); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate Requirements. + + // Validate fields. + sw.Restart(); + var scheduleSheet = sheets.GetSheet(); + scheduleSheet.ValidateFromActionForRecipe( + context.BlockIndex, + EventScheduleId, + EventConsumableItemRecipeId, + ActionTypeText, + addressesHex); + + var recipeSheet = sheets.GetSheet(); + var recipeRow = recipeSheet.ValidateFromAction( + EventConsumableItemRecipeId, + ActionTypeText, + addressesHex); + + var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); + if (slotState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the slot state is failed to load: # {SlotIndex}"); + } + + if (!slotState.Validate(avatarState, context.BlockIndex)) + { + throw new CombinationSlotUnlockException( + $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate fields. + + // Validate Work + sw.Restart(); + var costActionPoint = 0; + var endBlockIndex = context.BlockIndex; + var requiredFungibleItems = new Dictionary(); + + // Validate Recipe ResultEquipmentId + var consumableItemSheet = states.GetSheet(); + if (!consumableItemSheet.TryGetValue( + recipeRow.ResultConsumableItemId, + out var consumableRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(consumableItemSheet), + recipeRow.ResultConsumableItemId); + } + // ~Validate Recipe ResultEquipmentId + + // Validate Recipe Material + var materialItemSheet = states.GetSheet(); + materialItemSheet.ValidateFromAction( + recipeRow.Materials, + requiredFungibleItems, + addressesHex); + // ~Validate Recipe Material + + costActionPoint += recipeRow.RequiredActionPoint; + endBlockIndex += recipeRow.RequiredBlockIndex; + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate work: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate Work + + // Remove Required Materials + var inventory = avatarState.inventory; +#pragma warning disable LAA1002 + foreach (var pair in requiredFungibleItems) +#pragma warning restore LAA1002 + { + if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || + !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); + } + } + // ~Remove Required Materials + + // Subtract Required ActionPoint + if (costActionPoint > 0) + { + if (avatarState.actionPoint < costActionPoint) + { + throw new NotEnoughActionPointException( + $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + ); + } + + avatarState.actionPoint -= costActionPoint; + } + // ~Subtract Required ActionPoint + + // Create and Add Consumable + var consumable = ItemFactory.CreateItemUsable( + consumableRow, + context.Random.GenerateRandomGuid(), + endBlockIndex + ); + avatarState.inventory.AddItem(consumable); + // ~Create and Add Consumable + + // Update Slot + var mailId = context.Random.GenerateRandomGuid(); + var attachmentResult = new CombinationConsumable5.ResultModel + { + id = mailId, + actionPoint = costActionPoint, + materials = requiredFungibleItems.ToDictionary( + e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), + e => e.Value), + itemUsable = consumable, + recipeId = EventConsumableItemRecipeId, + }; + slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex); + // ~Update Slot + + // Create Mail + var mail = new CombinationMail( + attachmentResult, + context.BlockIndex, + mailId, + endBlockIndex); + avatarState.Update(mail); + // ~Create Mail + + // Set states + if (migrationRequired) + { + states = states + .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( + CombinationSlotState.DeriveAddress(AvatarAddress, SlotIndex), + slotState.Serialize()); + } + else + { + states = states + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState( + AvatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + CombinationSlotState.DeriveAddress(AvatarAddress, SlotIndex), + slotState.Serialize()); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Set states: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Set states + + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Total elapsed: {Elapsed}", + ActionTypeText, + addressesHex, + DateTimeOffset.UtcNow - started); + + return states; + } + } +} diff --git a/Lib9c/Action/EventDungeonBattle.cs b/Lib9c/Action/EventDungeonBattle.cs index 19b0914146..5da2d84219 100644 --- a/Lib9c/Action/EventDungeonBattle.cs +++ b/Lib9c/Action/EventDungeonBattle.cs @@ -24,13 +24,13 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] [ActionType(ActionTypeText)] public class EventDungeonBattle : GameAction, IEventDungeonBattleV2 { - private const string ActionTypeText = "event_dungeon_battle5"; + private const string ActionTypeText = "event_dungeon_battle6"; public const int PlayCount = 1; public Address AvatarAddress; diff --git a/Lib9c/Action/EventDungeonBattleV5.cs b/Lib9c/Action/EventDungeonBattleV5.cs new file mode 100644 index 0000000000..077ae78dcc --- /dev/null +++ b/Lib9c/Action/EventDungeonBattleV5.cs @@ -0,0 +1,432 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Exceptions; +using Nekoyume.Extensions; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Event; +using Nekoyume.Model.Skill; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Event; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType(ActionTypeText)] + public class EventDungeonBattleV5 : GameAction, IEventDungeonBattleV2 + { + private const string ActionTypeText = "event_dungeon_battle5"; + public const int PlayCount = 1; + + public Address AvatarAddress; + public int EventScheduleId; + public int EventDungeonId; + public int EventDungeonStageId; + public List Equipments; + public List Costumes; + public List Foods; + public bool BuyTicketIfNeeded; + public List RuneInfos; + + Address IEventDungeonBattleV2.AvatarAddress => AvatarAddress; + int IEventDungeonBattleV2.EventScheduleId => EventScheduleId; + int IEventDungeonBattleV2.EventDungeonId => EventDungeonId; + int IEventDungeonBattleV2.EventDungeonStageId => EventDungeonStageId; + IEnumerable IEventDungeonBattleV2.Equipments => Equipments; + IEnumerable IEventDungeonBattleV2.Costumes => Costumes; + IEnumerable IEventDungeonBattleV2.Foods => Foods; + IEnumerable IEventDungeonBattleV2.RuneSlotInfos => + RuneInfos.Select(x => x.Serialize()); + bool IEventDungeonBattleV2.BuyTicketIfNeeded => BuyTicketIfNeeded; + + protected override IImmutableDictionary PlainValueInternal + { + get + { + var list = Bencodex.Types.List.Empty + .Add(AvatarAddress.Serialize()) + .Add(EventScheduleId.Serialize()) + .Add(EventDungeonId.Serialize()) + .Add(EventDungeonStageId.Serialize()) + .Add(new Bencodex.Types.List( + Equipments + .OrderBy(e => e) + .Select(e => e.Serialize()))) + .Add(new Bencodex.Types.List( + Costumes + .OrderBy(e => e) + .Select(e => e.Serialize()))) + .Add(new Bencodex.Types.List( + Foods + .OrderBy(e => e) + .Select(e => e.Serialize()))) + .Add(BuyTicketIfNeeded.Serialize()) + .Add(RuneInfos.OrderBy(x => x.SlotIndex).Select(x => x.Serialize()) + .Serialize()); + + return new Dictionary + { + { "l", list }, + }.ToImmutableDictionary(); + } + } + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + if (!plainValue.TryGetValue("l", out var serialized)) + { + throw new ArgumentException("plainValue must contain 'l'"); + } + + if (!(serialized is Bencodex.Types.List list)) + { + throw new ArgumentException("'l' must be a bencodex list"); + } + + if (list.Count < 9) + { + throw new ArgumentException("'l' must contain at least 9 items"); + } + + AvatarAddress = list[0].ToAddress(); + EventScheduleId = list[1].ToInteger(); + EventDungeonId = list[2].ToInteger(); + EventDungeonStageId = list[3].ToInteger(); + Equipments = ((List)list[4]).ToList(StateExtensions.ToGuid); + Costumes = ((List)list[5]).ToList(StateExtensions.ToGuid); + Foods = ((List)list[6]).ToList(StateExtensions.ToGuid); + BuyTicketIfNeeded = list[7].ToBoolean(); + RuneInfos = list[8].ToList(x => new RuneSlotInfo((List)x)); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Execute() start", + ActionTypeText, + addressesHex); + + var sw = new Stopwatch(); + // Get AvatarState + sw.Start(); + if (!states.TryGetAvatarStateV2( + context.Signer, + AvatarAddress, + out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + ActionTypeText, + addressesHex, + typeof(AvatarState), + AvatarAddress); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] TryGetAvatarStateV2: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get AvatarState + + // Get sheets + sw.Restart(); + var sheets = states.GetSheets( + containSimulatorSheets: true, + containValidateItemRequirementSheets: true, + sheetTypes: new[] + { + typeof(EventScheduleSheet), + typeof(EventDungeonSheet), + typeof(EventDungeonStageSheet), + typeof(EventDungeonStageWaveSheet), + typeof(EnemySkillSheet), + typeof(CostumeStatSheet), + typeof(MaterialItemSheet), + typeof(RuneListSheet), + }); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Get sheets: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get sheets + + // Validate fields. + sw.Restart(); + var scheduleSheet = sheets.GetSheet(); + var scheduleRow = scheduleSheet.ValidateFromActionForDungeon( + context.BlockIndex, + EventScheduleId, + EventDungeonId, + ActionTypeText, + addressesHex); + + var dungeonSheet = sheets.GetSheet(); + var dungeonRow = dungeonSheet.ValidateFromAction( + EventDungeonId, + EventDungeonStageId, + ActionTypeText, + addressesHex); + + var stageSheet = sheets.GetSheet(); + var stageRow = stageSheet.ValidateFromAction( + EventDungeonStageId, + ActionTypeText, + addressesHex); + + var equipmentList = avatarState.ValidateEquipmentsV2(Equipments, context.BlockIndex); + var costumeIds = avatarState.ValidateCostume(Costumes); + var foodIds = avatarState.ValidateConsumable(Foods, context.BlockIndex); + var equipmentAndCostumes = Equipments.Concat(Costumes); + avatarState.EquipItems(equipmentAndCostumes); + avatarState.ValidateItemRequirement( + costumeIds.Concat(foodIds).ToList(), + equipmentList, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + addressesHex); + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate fields. + + // Validate avatar's event dungeon info. + sw.Restart(); + var eventDungeonInfoAddr = EventDungeonInfo.DeriveAddress( + AvatarAddress, + EventDungeonId); + var eventDungeonInfo = states.GetState(eventDungeonInfoAddr) + is Bencodex.Types.List serializedEventDungeonInfoList + ? new EventDungeonInfo(serializedEventDungeonInfoList) + : new EventDungeonInfo(remainingTickets: scheduleRow.DungeonTicketsMax); + + // Update tickets. + { + var blockRange = context.BlockIndex - scheduleRow.StartBlockIndex; + if (blockRange > 0) + { + var interval = + (int)(blockRange / scheduleRow.DungeonTicketsResetIntervalBlockRange); + if (interval > eventDungeonInfo.ResetTicketsInterval) + { + eventDungeonInfo.ResetTickets( + interval, + scheduleRow.DungeonTicketsMax); + } + } + } + // ~Update tickets. + + if (!eventDungeonInfo.TryUseTickets(PlayCount)) + { + if (!BuyTicketIfNeeded) + { + throw new NotEnoughEventDungeonTicketsException( + ActionTypeText, + addressesHex, + PlayCount, + eventDungeonInfo.RemainingTickets); + } + + var currency = states.GetGoldCurrency(); + var cost = scheduleRow.GetDungeonTicketCost( + eventDungeonInfo.NumberOfTicketPurchases, + currency); + if (cost.Sign > 0) + { + states = states.TransferAsset( + context, + context.Signer, + Addresses.EventDungeon, + cost); + } + + // NOTE: The number of ticket purchases should be increased + // even if [`cost`] is 0. + eventDungeonInfo.IncreaseNumberOfTicketPurchases(); + } + + if (EventDungeonStageId != dungeonRow.StageBegin && + !eventDungeonInfo.IsCleared(EventDungeonStageId - 1)) + { + throw new StageNotClearedException( + ActionTypeText, + addressesHex, + EventDungeonStageId - 1, + eventDungeonInfo.ClearedStageId); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate avatar's event dungeon info. + + // update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(AvatarAddress, BattleType.Adventure); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Adventure); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(RuneInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(AvatarAddress, BattleType.Adventure); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Adventure); + itemSlotState.UpdateEquipment(Equipments); + itemSlotState.UpdateCostumes(Costumes); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + // Simulate + sw.Restart(); + var exp = scheduleRow.GetStageExp( + EventDungeonStageId.ToEventDungeonStageNumber(), + PlayCount); + var simulatorSheets = sheets.GetSimulatorSheets(); + var runeStates = new List(); + foreach (var address in RuneInfos.Select(info => RuneState.DeriveAddress(AvatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + runeStates.Add(new RuneState(rawRuneState)); + } + } + + var simulator = new StageSimulator( + context.Random, + avatarState, + Foods, + runeStates, + new List(), + EventDungeonId, + EventDungeonStageId, + stageRow, + sheets.GetSheet()[EventDungeonStageId], + eventDungeonInfo.IsCleared(EventDungeonStageId), + exp, + simulatorSheets, + sheets.GetSheet(), + sheets.GetSheet(), + StageSimulator.GetWaveRewards( + context.Random, + stageRow, + sheets.GetSheet(), + PlayCount)); + simulator.Simulate(); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Simulate: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Simulate + + // Update avatar's event dungeon info. + if (simulator.Log.IsClear) + { + sw.Restart(); + eventDungeonInfo.ClearStage(EventDungeonStageId); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Update event dungeon info: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + } + // ~Update avatar's event dungeon info. + + // Apply player to avatar state + sw.Restart(); + avatarState.Apply(simulator.Player, context.BlockIndex); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Apply player to avatar state: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Apply player to avatar state + + // Set states + sw.Restart(); + if (migrationRequired) + { + states = states + .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(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + } + else + { + states = states + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState( + AvatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState(eventDungeonInfoAddr, eventDungeonInfo.Serialize()); + } + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Set states: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Set states + + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Total elapsed: {Elapsed}", + ActionTypeText, + addressesHex, + DateTimeOffset.UtcNow - started); + return states; + } + } +} diff --git a/Lib9c/Action/EventMaterialItemCrafts.cs b/Lib9c/Action/EventMaterialItemCrafts.cs index 3a7fce5499..b560bacfdd 100644 --- a/Lib9c/Action/EventMaterialItemCrafts.cs +++ b/Lib9c/Action/EventMaterialItemCrafts.cs @@ -20,11 +20,14 @@ namespace Nekoyume.Action { + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 + /// [Serializable] [ActionType(ActionTypeText)] public class EventMaterialItemCrafts : GameAction, IEventMaterialItemCraftsV1 { - private const string ActionTypeText = "event_material_item_crafts"; + private const string ActionTypeText = "event_material_item_crafts1"; public Address AvatarAddress; public int EventScheduleId; public int EventMaterialItemRecipeId; diff --git a/Lib9c/Action/EventMaterialItemCrafts0.cs b/Lib9c/Action/EventMaterialItemCrafts0.cs new file mode 100644 index 0000000000..e703804145 --- /dev/null +++ b/Lib9c/Action/EventMaterialItemCrafts0.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Extensions; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Event; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType(ActionTypeText)] + public class EventMaterialItemCrafts0 : GameAction, IEventMaterialItemCraftsV1 + { + private const string ActionTypeText = "event_material_item_crafts"; + public Address AvatarAddress; + public int EventScheduleId; + public int EventMaterialItemRecipeId; + public Dictionary MaterialsToUse; + + Address IEventMaterialItemCraftsV1.AvatarAddress => AvatarAddress; + int IEventMaterialItemCraftsV1.EventScheduleId => EventScheduleId; + int IEventMaterialItemCraftsV1.EventMaterialItemRecipeId => EventMaterialItemRecipeId; + IReadOnlyDictionary IEventMaterialItemCraftsV1.MaterialsToUse => MaterialsToUse; + + protected override IImmutableDictionary PlainValueInternal + { + get + { + var serialized = new Dictionary(MaterialsToUse + .OrderBy(pair => pair.Key) + .Select(pair => + new KeyValuePair( + (IKey)pair.Key.Serialize(), pair.Value.Serialize() + ) + )); + var list = List.Empty + .Add(AvatarAddress.Serialize()) + .Add(EventScheduleId.Serialize()) + .Add(EventMaterialItemRecipeId.Serialize()) + .Add(serialized); + + return new Dictionary + { + { "l", list }, + }.ToImmutableDictionary(); + } + } + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + if (!plainValue.TryGetValue("l", out var serialized)) + { + throw new ArgumentException("plainValue must contain 'l'"); + } + + if (!(serialized is List list)) + { + throw new ArgumentException("'l' must be a bencodex list"); + } + + if (list.Count < 4) + { + throw new ArgumentException("'l' must contain at least 4 items"); + } + + AvatarAddress = list[0].ToAddress(); + EventScheduleId = list[1].ToInteger(); + EventMaterialItemRecipeId = list[2].ToInteger(); + var deserialized = ((Dictionary)list[3]).ToDictionary(pair => + pair.Key.ToInteger(), pair => pair.Value.ToInteger()); + MaterialsToUse = deserialized; + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug( + "[{ActionTypeString}][{AddressesHex}] Execute() start", + ActionTypeText, + addressesHex); + + var sw = new Stopwatch(); + + // Get AvatarState + sw.Start(); + if (!states.TryGetAvatarStateV2( + context.Signer, + AvatarAddress, + out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + ActionTypeText, + addressesHex, + typeof(AvatarState), + AvatarAddress); + } + sw.Stop(); + + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] TryGetAvatarStateV2: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get AvatarState + + // Get sheets + sw.Restart(); + var sheets = states.GetSheets( + sheetTypes: new[] + { + typeof(EventScheduleSheet), + typeof(EventMaterialItemRecipeSheet), + }); + sw.Stop(); + + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Get sheets: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Get sheets + + // Validate Requirements + sw.Restart(); + avatarState.worldInformation.ValidateFromAction( + GameConfig.RequireClearedStageLevel.CombinationConsumableAction, + ActionTypeText, + addressesHex); + sw.Stop(); + + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate Requirements + + // Validate fields + sw.Restart(); + var scheduleSheet = sheets.GetSheet(); + scheduleSheet.ValidateFromActionForRecipe( + context.BlockIndex, + EventScheduleId, + EventMaterialItemRecipeId, + ActionTypeText, + addressesHex); + + var recipeSheet = sheets.GetSheet(); + var recipeRow = recipeSheet.ValidateFromAction( + EventMaterialItemRecipeId, + ActionTypeText, + addressesHex); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate fields + + // Validate Work + sw.Restart(); + + // Validate Recipe ResultMaterialItemId + var materialItemSheet = states.GetSheet(); + if (!materialItemSheet.TryGetValue( + recipeRow.ResultMaterialItemId, + out var resultMaterialRow)) + { + throw new SheetRowNotFoundException( + addressesHex, + nameof(materialItemSheet), + recipeRow.ResultMaterialItemId); + } + // ~Validate Recipe ResultEquipmentId + + // Validate Recipe Material + recipeRow.ValidateFromAction( + materialItemSheet, + MaterialsToUse, + ActionTypeText, + addressesHex); + // ~Validate Recipe Material + + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Validate work: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Validate Work + + // Remove Required Materials + var inventory = avatarState.inventory; +#pragma warning disable LAA1002 + foreach (var pair in MaterialsToUse) +#pragma warning restore LAA1002 + { + if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || + !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); + } + } + // ~Remove Required Materials + + // Create Material + var materialResult = ItemFactory.CreateMaterial(resultMaterialRow); + avatarState.inventory.AddItem(materialResult, recipeRow.ResultMaterialItemCount); + // ~Create Material + + // Create Mail + var mail = new MaterialCraftMail( + context.BlockIndex, + Id, + context.BlockIndex, + recipeRow.ResultMaterialItemCount, + materialResult.Id); + avatarState.Update(mail); + // ~Create Mail + + // Set states + sw.Restart(); + states = states + .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()); + sw.Stop(); + Log.Verbose( + "[{ActionTypeString}][{AddressesHex}] Set states: {Elapsed}", + ActionTypeText, + addressesHex, + sw.Elapsed); + // ~Set states + + Log.Debug( + "[{ActionTypeString}][{AddressesHex}] Total elapsed: {Elapsed}", + ActionTypeText, + addressesHex, + DateTimeOffset.UtcNow - started); + + return states; + } + + } +} diff --git a/Lib9c/Action/HackAndSlash.cs b/Lib9c/Action/HackAndSlash.cs index 2d382ac704..f1b6bfac4f 100644 --- a/Lib9c/Action/HackAndSlash.cs +++ b/Lib9c/Action/HackAndSlash.cs @@ -23,10 +23,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("hack_and_slash21")] + [ActionType("hack_and_slash22")] public class HackAndSlash : GameAction, IHackAndSlashV10 { public const int UsableApStoneCount = 10; diff --git a/Lib9c/Action/HackAndSlash21.cs b/Lib9c/Action/HackAndSlash21.cs new file mode 100644 index 0000000000..d08f42a4c9 --- /dev/null +++ b/Lib9c/Action/HackAndSlash21.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Crystal; +using Serilog; +using static Lib9c.SerializeKeys; +using Skill = Nekoyume.Model.Skill.Skill; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("hack_and_slash21")] + public class HackAndSlash21 : GameAction, IHackAndSlashV10 + { + public const int UsableApStoneCount = 10; + + public List Costumes; + public List Equipments; + public List Foods; + public List RuneInfos; + public int WorldId; + public int StageId; + public int? StageBuffId; + public Address AvatarAddress; + public int TotalPlayCount = 1; + public int ApStoneCount = 0; + + IEnumerable IHackAndSlashV10.Costumes => Costumes; + IEnumerable IHackAndSlashV10.Equipments => Equipments; + IEnumerable IHackAndSlashV10.Foods => Foods; + IEnumerable IHackAndSlashV10.RuneSlotInfos => RuneInfos.Select(x => x.Serialize()); + int IHackAndSlashV10.WorldId => WorldId; + int IHackAndSlashV10.StageId => StageId; + int IHackAndSlashV10.TotalPlayCount => TotalPlayCount; + int IHackAndSlashV10.ApStoneCount => ApStoneCount; + int? IHackAndSlashV10.StageBuffId => StageBuffId; + Address IHackAndSlashV10.AvatarAddress => AvatarAddress; + + protected override IImmutableDictionary PlainValueInternal + { + get + { + var dict = new Dictionary + { + ["costumes"] = new List(Costumes.OrderBy(i => i).Select(e => e.Serialize())), + ["equipments"] = + new List(Equipments.OrderBy(i => i).Select(e => e.Serialize())), + ["r"] = RuneInfos.OrderBy(x => x.SlotIndex).Select(x=> x.Serialize()).Serialize(), + ["foods"] = new List(Foods.OrderBy(i => i).Select(e => e.Serialize())), + ["worldId"] = WorldId.Serialize(), + ["stageId"] = StageId.Serialize(), + ["avatarAddress"] = AvatarAddress.Serialize(), + ["totalPlayCount"] = TotalPlayCount.Serialize(), + ["apStoneCount"] = ApStoneCount.Serialize(), + }; + if (StageBuffId.HasValue) + { + dict["stageBuffId"] = StageBuffId.Serialize(); + } + return dict.ToImmutableDictionary(); + } + } + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + Costumes = ((List)plainValue["costumes"]).Select(e => e.ToGuid()).ToList(); + Equipments = ((List)plainValue["equipments"]).Select(e => e.ToGuid()).ToList(); + Foods = ((List)plainValue["foods"]).Select(e => e.ToGuid()).ToList(); + RuneInfos = plainValue["r"].ToList(x => new RuneSlotInfo((List)x)); + WorldId = plainValue["worldId"].ToInteger(); + StageId = plainValue["stageId"].ToInteger(); + if (plainValue.ContainsKey("stageBuffId")) + { + StageBuffId = plainValue["stageBuffId"].ToNullableInteger(); + } + AvatarAddress = plainValue["avatarAddress"].ToAddress(); + TotalPlayCount = plainValue["totalPlayCount"].ToInteger(); + ApStoneCount = plainValue["apStoneCount"].ToInteger(); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + if (context.Rehearsal) + { + return context.PreviousState; + } + + return Execute( + context.PreviousState, + context.Signer, + context.BlockIndex, + context.Random); + } + + public IAccount Execute( + IAccount states, + Address signer, + long blockIndex, + IRandom random) + { + var inventoryAddress = AvatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = AvatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = AvatarAddress.Derive(LegacyQuestListKey); + + var addressesHex = $"[{signer.ToHex()}, {AvatarAddress.ToHex()}]"; + var started = DateTimeOffset.UtcNow; + const string source = "HackAndSlash"; + Log.Verbose("{AddressesHex} {Source} from #{BlockIndex} exec started", + addressesHex, source, blockIndex); + + if (ApStoneCount > UsableApStoneCount) + { + throw new UsageLimitExceedException( + "Exceeded the amount of ap stones that can be used " + + $"apStoneCount : {ApStoneCount} > UsableApStoneCount : {UsableApStoneCount}"); + } + + if (ApStoneCount < 0) + { + throw new InvalidItemCountException( + "ApStone count must not be negative. " + + $"Ap stone count: {ApStoneCount}"); + } + + if (TotalPlayCount <= 0) + { + throw new PlayCountIsZeroException( + $"{addressesHex}playCount must not be zero or negative. " + + $"Total play count : {TotalPlayCount}"); + } + + states.ValidateWorldId(AvatarAddress, WorldId); + + var sw = new Stopwatch(); + sw.Start(); + if (!states.TryGetAvatarStateV2(signer, AvatarAddress, out AvatarState avatarState, out _)) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Get AvatarState", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var sheets = states.GetSheets( + containQuestSheet: true, + containSimulatorSheets: true, + sheetTypes: new[] + { + typeof(WorldSheet), + typeof(StageSheet), + typeof(StageWaveSheet), + typeof(EnemySkillSheet), + typeof(CostumeStatSheet), + typeof(SkillSheet), + typeof(QuestRewardSheet), + typeof(QuestItemRewardSheet), + typeof(EquipmentItemRecipeSheet), + typeof(WorldUnlockSheet), + typeof(MaterialItemSheet), + typeof(ItemRequirementSheet), + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(CrystalStageBuffGachaSheet), + typeof(CrystalRandomBuffSheet), + typeof(StakeActionPointCoefficientSheet), + typeof(RuneListSheet), + }); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Get Sheets", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var stakingLevel = 0; + StakeActionPointCoefficientSheet actionPointCoefficientSheet = null; + + var goldCurrency = states.GetGoldCurrency(); + var stakedAmount = states.GetStakedAmount(signer); + if (stakedAmount > goldCurrency * 0 && + sheets.TryGetSheet(out actionPointCoefficientSheet)) + { + stakingLevel = actionPointCoefficientSheet.FindLevelByStakedAmount(signer, stakedAmount); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Check StateState", blockIndex, sw.Elapsed.TotalMilliseconds); + + var worldSheet = sheets.GetSheet(); + if (!worldSheet.TryGetValue(WorldId, out var worldRow, false)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(WorldSheet), WorldId); + } + + if (StageId < worldRow.StageBegin || + StageId > worldRow.StageEnd) + { + throw new SheetRowColumnException( + $"{addressesHex}{WorldId} world is not contains {worldRow.Id} stage: " + + $"{worldRow.StageBegin}-{worldRow.StageEnd}"); + } + + sw.Restart(); + if (!sheets.GetSheet().TryGetValue(StageId, out var stageRow)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(StageSheet), StageId); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Get StageSheet", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var worldInformation = avatarState.worldInformation; + if (!worldInformation.TryGetWorld(WorldId, out var world)) + { + // NOTE: Add new World from WorldSheet + worldInformation.AddAndUnlockNewWorld(worldRow, blockIndex, worldSheet); + worldInformation.TryGetWorld(WorldId, out world); + } + + if (!world.IsUnlocked) + { + throw new InvalidWorldException($"{addressesHex}{WorldId} is locked."); + } + + if (world.StageBegin != worldRow.StageBegin || + world.StageEnd != worldRow.StageEnd) + { + worldInformation.UpdateWorld(worldRow); + } + + if (!world.IsStageCleared && StageId != world.StageBegin) + { + throw new InvalidStageException( + $"{addressesHex}Aborted as the stage ({WorldId}/{StageId - 1}) is not cleared; " + + $"clear the stage ({world.Id}/{world.StageBegin}) first" + ); + } + + if (world.IsStageCleared && StageId - 1 > world.StageClearedId) + { + throw new InvalidStageException( + $"{addressesHex}Aborted as the stage ({WorldId}/{StageId - 1}) is not cleared; " + + $"cleared stage is ({world.Id}/{world.StageClearedId}), so you can play stage " + + $"({world.Id}/{world.StageClearedId + 1})" + ); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Validate World", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var equipmentList = avatarState.ValidateEquipmentsV2(Equipments, blockIndex); + var foodIds = avatarState.ValidateConsumable(Foods, blockIndex); + var costumeIds = avatarState.ValidateCostume(Costumes); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Validate Items", blockIndex, sw.Elapsed.TotalMilliseconds); + sw.Restart(); + var materialItemSheet = sheets.GetSheet(); + var apPlayCount = TotalPlayCount; + var minimumCostAp = stageRow.CostAP; + if (actionPointCoefficientSheet != null && stakingLevel > 0) + { + minimumCostAp = actionPointCoefficientSheet.GetActionPointByStaking( + minimumCostAp, + 1, + stakingLevel); + } + + if (ApStoneCount > 0) + { + var gameConfigState = states.GetGameConfigState(); + if (gameConfigState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the game config state was failed to load."); + } + + // use apStone + var row = materialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone); + if (!avatarState.inventory.RemoveFungibleItem(row.ItemId, blockIndex, + count: ApStoneCount)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({row.Id})"); + } + + var apStonePlayCount = + ApStoneCount * (gameConfigState.ActionPointMax / minimumCostAp); + apPlayCount = TotalPlayCount - apStonePlayCount; + if (apPlayCount < 0) + { + throw new InvalidRepeatPlayException( + $"{addressesHex}Invalid TotalPlayCount({TotalPlayCount}) and ApStoneCount({ApStoneCount}). " + + $"TotalPlayCount must be at least calculated apStonePlayCount({apStonePlayCount}). " + + $"Calculated ap play count: {apPlayCount}"); + } + + Log.Verbose( + "{AddressesHex} {Source} TotalPlayCount: {TotalPlayCount}, " + + "ApStoneCount: {ApStoneCount}, PlayCount by Ap stone: {ApStonePlayCount}, " + + "Ap cost per 1 play: {MinimumCostAp}, " + + "BlockIndex: {BlockIndex}, " + + "PlayCount by action point: {ApPlayCount}, Used AP: {UsedAp}", + addressesHex, + source, + TotalPlayCount, + ApStoneCount, + apStonePlayCount, + minimumCostAp, + apPlayCount, + apPlayCount * minimumCostAp); + } + + if (avatarState.actionPoint < minimumCostAp * apPlayCount) + { + throw new NotEnoughActionPointException( + $"{addressesHex}Aborted due to insufficient action point: " + + $"{avatarState.actionPoint} < cost({minimumCostAp * apPlayCount}))" + ); + } + + avatarState.actionPoint -= minimumCostAp * apPlayCount; + avatarState.ValidateItemRequirement( + costumeIds.Concat(foodIds).ToList(), + equipmentList, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + addressesHex); + + var items = Equipments.Concat(Costumes); + avatarState.EquipItems(items); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Unequip items", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var questSheet = sheets.GetQuestSheet(); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Get QuestSheet", blockIndex, sw.Elapsed.TotalMilliseconds); + + // Update QuestList only when QuestSheet.Count is greater than QuestList.Count + var questList = avatarState.questList; + if (questList.Count() < questSheet.Count) + { + sw.Restart(); + questList.UpdateList( + questSheet, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet()); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Update QuestList", blockIndex, sw.Elapsed.TotalMilliseconds); + } + + sw.Restart(); + + var skillStateAddress = Addresses.GetSkillStateAddressFromAvatarAddress(AvatarAddress); + var isNotClearedStage = !avatarState.worldInformation.IsStageCleared(StageId); + var skillsOnWaveStart = new List(); + CrystalRandomSkillState skillState = null; + if (isNotClearedStage) + { + // If state exists, get CrystalRandomSkillState. If not, create new state. + skillState = states.TryGetState(skillStateAddress, out var serialized) + ? new CrystalRandomSkillState(skillStateAddress, serialized) + : new CrystalRandomSkillState(skillStateAddress, StageId); + + if (skillState.SkillIds.Any()) + { + var crystalRandomBuffSheet = sheets.GetSheet(); + var skillSheet = sheets.GetSheet(); + int selectedId; + if (StageBuffId.HasValue && skillState.SkillIds.Contains(StageBuffId.Value)) + { + selectedId = StageBuffId.Value; + } + else + { + selectedId = skillState.GetHighestRankSkill(crystalRandomBuffSheet); + } + + var skill = CrystalRandomSkillState.GetSkill( + selectedId, + crystalRandomBuffSheet, + skillSheet); + skillsOnWaveStart.Add(skill); + } + } + + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Get skillState", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + var worldUnlockSheet = sheets.GetSheet(); + var crystalStageBuffSheet = sheets.GetSheet(); + sw.Restart(); + // if PlayCount > 1, it is Multi-HAS. + var simulatorSheets = sheets.GetSimulatorSheets(); + + // update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(AvatarAddress, BattleType.Adventure); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Adventure); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(RuneInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(AvatarAddress, BattleType.Adventure); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Adventure); + itemSlotState.UpdateEquipment(Equipments); + itemSlotState.UpdateCostumes(Costumes); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + var runeStates = new List(); + foreach (var address in RuneInfos.Select(info => RuneState.DeriveAddress(AvatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + runeStates.Add(new RuneState(rawRuneState)); + } + } + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Update slotState", blockIndex, sw.Elapsed.TotalMilliseconds); + + var stageWaveRow = sheets.GetSheet()[StageId]; + var enemySkillSheet = sheets.GetSheet(); + var costumeStatSheet = sheets.GetSheet(); + var stageCleared = !isNotClearedStage; + var starCount = 0; + for (var i = 0; i < TotalPlayCount; i++) + { + var rewards = StageSimulator.GetWaveRewards(random, stageRow, materialItemSheet); + sw.Restart(); + // First simulating will use Foods and Random Skills. + // Remainder simulating will not use Foods. + var simulator = new StageSimulator( + random, + avatarState, + i == 0 ? Foods : new List(), + runeStates, + i == 0 ? skillsOnWaveStart : new List(), + WorldId, + StageId, + stageRow, + stageWaveRow, + stageCleared, + StageRewardExpHelper.GetExp(avatarState.level, StageId), + simulatorSheets, + enemySkillSheet, + costumeStatSheet, + rewards, + false); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Initialize Simulator", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + simulator.Simulate(); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Simulator.Simulate()", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + if (simulator.Log.IsClear) + { + if (!stageCleared) + { + avatarState.worldInformation.ClearStage( + WorldId, + StageId, + blockIndex, + worldSheet, + worldUnlockSheet + ); + stageCleared = true; + } + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "ClearStage", blockIndex, sw.Elapsed.TotalMilliseconds); + } + + sw.Restart(); + + // This conditional logic is same as written in the + // MimisbrunnrBattle("mimisbrunnr_battle10") action. + if (blockIndex < ActionObsoleteConfig.V100310ExecutedBlockIndex) + { + var player = simulator.Player; + foreach (var key in player.monsterMapForBeforeV100310.Keys) + { + player.monsterMap.Add(key, player.monsterMapForBeforeV100310[key]); + } + + player.monsterMapForBeforeV100310.Clear(); + + foreach (var key in player.eventMapForBeforeV100310.Keys) + { + player.eventMap.Add(key, player.eventMapForBeforeV100310[key]); + } + + player.eventMapForBeforeV100310.Clear(); + } + + starCount += simulator.Log.clearedWaveNumber; + avatarState.Update(simulator); + + sw.Stop(); + Log.Verbose( + "{AddressesHex} {Source} {Process} by simulator({AvatarAddress}); " + + "blockIndex: {BlockIndex} " + + "worldId: {WorldId}, stageId: {StageId}, result: {Result}, " + + "clearWave: {ClearWave}, totalWave: {TotalWave}", + addressesHex, + source, + "Update avatar", + AvatarAddress, + WorldId, + StageId, + simulator.Log.result, + simulator.Log.clearedWaveNumber, + simulator.Log.waveCount + ); + } + sw.Stop(); + Log.Debug("{AddressesHex} {Source} {Process} from #{BlockIndex}: {Elapsed}, Count: {PlayCount}", + addressesHex, source, "loop Simulate", blockIndex, sw.Elapsed.TotalMilliseconds, TotalPlayCount); + + // Update CrystalRandomSkillState.Stars by clearedWaveNumber. (add) + skillState?.Update(starCount, crystalStageBuffSheet); + sw.Restart(); + avatarState.UpdateQuestRewards(materialItemSheet); + avatarState.updatedAt = blockIndex; + avatarState.mailBox.CleanUp(); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Update AvatarState", blockIndex, sw.Elapsed.TotalMilliseconds); + + sw.Restart(); + if (isNotClearedStage) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var lastClearedStageId); + if (lastClearedStageId >= StageId) + { + // Make new CrystalRandomSkillState by next stage Id. + skillState = new CrystalRandomSkillState(skillStateAddress, StageId + 1); + } + + skillState.Update(new List()); + states = states.SetState(skillStateAddress, skillState.Serialize()); + } + + states = states + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()); + sw.Stop(); + Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", + addressesHex, source, "Set States", blockIndex, sw.Elapsed.TotalMilliseconds); + + var totalElapsed = DateTimeOffset.UtcNow - started; + Log.Verbose("{AddressesHex} {Source} HAS {Process}: {Elapsed}, blockIndex: {BlockIndex}", addressesHex, source, "Total Executed Time", totalElapsed.TotalMilliseconds, blockIndex); + return states; + } + + } +} diff --git a/Lib9c/Action/HackAndSlashSweep.cs b/Lib9c/Action/HackAndSlashSweep.cs index d8a3bcb971..d50d96303c 100644 --- a/Lib9c/Action/HackAndSlashSweep.cs +++ b/Lib9c/Action/HackAndSlashSweep.cs @@ -21,10 +21,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("hack_and_slash_sweep9")] + [ActionType("hack_and_slash_sweep10")] public class HackAndSlashSweep : GameAction, IHackAndSlashSweepV3 { public const int UsableApStoneCount = 10; diff --git a/Lib9c/Action/HackAndSlashSweep9.cs b/Lib9c/Action/HackAndSlashSweep9.cs new file mode 100644 index 0000000000..019d98e72f --- /dev/null +++ b/Lib9c/Action/HackAndSlashSweep9.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1663 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("hack_and_slash_sweep9")] + public class HackAndSlashSweep9 : GameAction, IHackAndSlashSweepV3 + { + public const int UsableApStoneCount = 10; + + public List costumes; + public List equipments; + public List runeInfos; + public Address avatarAddress; + public int apStoneCount; + public int actionPoint; + public int worldId; + public int stageId; + + IEnumerable IHackAndSlashSweepV3.Costumes => costumes; + IEnumerable IHackAndSlashSweepV3.Equipments => equipments; + IEnumerable IHackAndSlashSweepV3.RuneSlotInfos => + runeInfos.Select(x => x.Serialize()); + Address IHackAndSlashSweepV3.AvatarAddress => avatarAddress; + int IHackAndSlashSweepV3.ApStoneCount => apStoneCount; + int IHackAndSlashSweepV3.ActionPoint => actionPoint; + int IHackAndSlashSweepV3.WorldId => worldId; + int IHackAndSlashSweepV3.StageId => stageId; + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary() + { + ["costumes"] = new List(costumes.OrderBy(i => i).Select(e => e.Serialize())), + ["equipments"] = new List(equipments.OrderBy(i => i).Select(e => e.Serialize())), + ["runeInfos"] = runeInfos.OrderBy(x => x.SlotIndex).Select(x=> x.Serialize()).Serialize(), + ["avatarAddress"] = avatarAddress.Serialize(), + ["apStoneCount"] = apStoneCount.Serialize(), + ["actionPoint"] = actionPoint.Serialize(), + ["worldId"] = worldId.Serialize(), + ["stageId"] = stageId.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + costumes = ((List)plainValue["costumes"]).Select(e => e.ToGuid()).ToList(); + equipments = ((List)plainValue["equipments"]).Select(e => e.ToGuid()).ToList(); + runeInfos = plainValue["runeInfos"].ToList(x => new RuneSlotInfo((List)x)); + avatarAddress = plainValue["avatarAddress"].ToAddress(); + apStoneCount = plainValue["apStoneCount"].ToInteger(); + actionPoint = plainValue["actionPoint"].ToInteger(); + worldId = plainValue["worldId"].ToInteger(); + stageId = plainValue["stageId"].ToInteger(); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}HackAndSlashSweep exec started", addressesHex); + + if (apStoneCount > UsableApStoneCount) + { + throw new UsageLimitExceedException( + $"Exceeded the amount of ap stones that can be used " + + $"apStoneCount : {apStoneCount} > UsableApStoneCount : {UsableApStoneCount}"); + } + + states.ValidateWorldId(avatarAddress, worldId); + + if (!states.TryGetAvatarStateV2( + context.Signer, + avatarAddress, + out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + var sheets = states.GetSheets( + sheetTypes: new[] + { + typeof(WorldSheet), + typeof(StageSheet), + typeof(MaterialItemSheet), + typeof(StageWaveSheet), + typeof(CharacterLevelSheet), + typeof(ItemRequirementSheet), + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(CharacterSheet), + typeof(CostumeStatSheet), + typeof(SweepRequiredCPSheet), + typeof(StakeActionPointCoefficientSheet), + typeof(RuneListSheet), + typeof(RuneOptionSheet), + }); + + var worldSheet = sheets.GetSheet(); + if (!worldSheet.TryGetValue(worldId, out var worldRow, false)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(WorldSheet), worldId); + } + + if (stageId < worldRow.StageBegin || + stageId > worldRow.StageEnd) + { + throw new SheetRowColumnException( + $"{addressesHex}{worldId} world is not contains {worldRow.Id} stage: " + + $"{worldRow.StageBegin}-{worldRow.StageEnd}"); + } + + if (!sheets.GetSheet().TryGetValue(stageId, out var stageRow)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(StageSheet), stageId); + } + + var worldInformation = avatarState.worldInformation; + if (!worldInformation.TryGetWorld(worldId, out var world)) + { + // NOTE: Add new World from WorldSheet + worldInformation.AddAndUnlockNewWorld(worldRow, context.BlockIndex, worldSheet); + if (!worldInformation.TryGetWorld(worldId, out world)) + { + // Do nothing. + } + } + + if (!world.IsPlayable(stageId)) + { + throw new InvalidStageException( + $"{addressesHex}Aborted as the stage isn't playable;" + + $"StageClearedId: {world.StageClearedId}" + ); + } + + var equipmentList = avatarState.ValidateEquipmentsV2(equipments, context.BlockIndex); + var costumeIds = avatarState.ValidateCostume(costumes); + var items = equipments.Concat(costumes); + avatarState.EquipItems(items); + avatarState.ValidateItemRequirement( + costumeIds, + equipmentList, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + addressesHex); + + var sweepRequiredCpSheet = sheets.GetSheet(); + if (!sweepRequiredCpSheet.TryGetValue(stageId, out var cpRow)) + { + throw new SheetRowColumnException( + $"{addressesHex}There is no row in SweepRequiredCPSheet: {stageId}"); + } + + var costumeList = new List(); + foreach (var guid in costumes) + { + var costume = avatarState.inventory.Costumes.FirstOrDefault(x => x.ItemId == guid); + if (costume != null) + { + costumeList.Add(costume); + } + } + + // update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Adventure); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Adventure); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(runeInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(avatarAddress, BattleType.Adventure); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Adventure); + itemSlotState.UpdateEquipment(equipments); + itemSlotState.UpdateCostumes(costumes); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + var runeStates = new List(); + foreach (var address in runeInfos.Select(info => RuneState.DeriveAddress(avatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + runeStates.Add(new RuneState(rawRuneState)); + } + } + var runeOptionSheet = sheets.GetSheet(); + var runeOptions = new List(); + foreach (var runeState in runeStates) + { + if (!runeOptionSheet.TryGetValue(runeState.RuneId, out var optionRow)) + { + throw new SheetRowNotFoundException("RuneOptionSheet", runeState.RuneId); + } + + if (!optionRow.LevelOptionMap.TryGetValue(runeState.Level, out var option)) + { + throw new SheetRowNotFoundException("RuneOptionSheet", runeState.Level); + } + + runeOptions.Add(option); + } + + var characterSheet = sheets.GetSheet(); + if (!characterSheet.TryGetValue(avatarState.characterId, out var characterRow)) + { + throw new SheetRowNotFoundException("CharacterSheet", avatarState.characterId); + } + + var costumeStatSheet = sheets.GetSheet(); + var cp = CPHelper.TotalCP( + equipmentList, costumeList, + runeOptions, avatarState.level, + characterRow, costumeStatSheet); + if (cp < cpRow.RequiredCP) + { + throw new NotEnoughCombatPointException( + $"{addressesHex}Aborted due to lack of player cp ({cp} < {cpRow.RequiredCP})"); + } + + var materialItemSheet = sheets.GetSheet(); + if (apStoneCount > 0) + { + // use apStone + var row = materialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone); + if (!avatarState.inventory.RemoveFungibleItem(row.ItemId, context.BlockIndex, + count: apStoneCount)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({row.Id})"); + } + } + + var gameConfigState = states.GetGameConfigState(); + if (gameConfigState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the game config state was failed to load."); + } + + if (actionPoint > avatarState.actionPoint) + { + throw new NotEnoughActionPointException( + $"{addressesHex}Aborted due to insufficient action point: " + + $"use AP({actionPoint}) > current AP({avatarState.actionPoint})" + ); + } + + // burn ap + avatarState.actionPoint -= actionPoint; + var costAp = sheets.GetSheet()[stageId].CostAP; + var goldCurrency = states.GetGoldCurrency(); + var stakedAmount = states.GetStakedAmount(context.Signer); + if (stakedAmount > goldCurrency * 0) + { + var actionPointCoefficientSheet = + sheets.GetSheet(); + var stakingLevel = + actionPointCoefficientSheet.FindLevelByStakedAmount(context.Signer, + stakedAmount); + costAp = actionPointCoefficientSheet.GetActionPointByStaking( + costAp, + 1, + stakingLevel); + } + + var apMaxPlayCount = costAp > 0 ? gameConfigState.ActionPointMax / costAp : 0; + var apStonePlayCount = apMaxPlayCount * apStoneCount; + var apPlayCount = costAp > 0 ? actionPoint / costAp : 0; + var playCount = apStonePlayCount + apPlayCount; + if (playCount <= 0) + { + throw new PlayCountIsZeroException( + $"{addressesHex}playCount must be greater than 0. " + + $"current playCount : {playCount}"); + } + + var stageWaveSheet = sheets.GetSheet(); + avatarState.UpdateMonsterMap(stageWaveSheet, stageId); + + var rewardItems = HackAndSlashSweep6.GetRewardItems( + context.Random, + playCount, + stageRow, + materialItemSheet); + avatarState.UpdateInventory(rewardItems); + + var levelSheet = sheets.GetSheet(); + var (level, exp) = avatarState.GetLevelAndExp(levelSheet, stageId, playCount); + avatarState.UpdateExp(level, exp); + + if (migrationRequired) + { + states = states.SetState( + avatarAddress.Derive(LegacyWorldInformationKey), + avatarState.worldInformation.Serialize()); + } + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}HackAndSlashSweep Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(avatarAddress, avatarState.SerializeV2()) + .SetState( + avatarAddress.Derive(LegacyInventoryKey), + avatarState.inventory.Serialize()) + .SetState( + avatarAddress.Derive(LegacyQuestListKey), + avatarState.questList.Serialize()); + } + } +} diff --git a/Lib9c/Action/ItemEnhancement.cs b/Lib9c/Action/ItemEnhancement.cs index cebebd1455..c8703947a5 100644 --- a/Lib9c/Action/ItemEnhancement.cs +++ b/Lib9c/Action/ItemEnhancement.cs @@ -24,20 +24,12 @@ namespace Nekoyume.Action { /// - /// Updated at https://github.com/planetarium/lib9c/pull/2068 + /// Updated at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("item_enhancement13")] + [ActionType("item_enhancement14")] public class ItemEnhancement : GameAction, IItemEnhancementV4 { - public enum EnhancementResult - { - // Result is fixed to Success. - // GreatSuccess = 0, - Success = 1, - // Fail = 2, - } - public const int MaterialCountLimit = 50; public Guid itemId; @@ -50,53 +42,6 @@ public enum EnhancementResult Address IItemEnhancementV4.AvatarAddress => avatarAddress; int IItemEnhancementV4.SlotIndex => slotIndex; - [Serializable] - public class ResultModel : AttachmentActionResult - { - protected override string TypeId => "item_enhancement13.result"; - public Guid id; - public IEnumerable materialItemIdList; - public BigInteger gold; - public int actionPoint; - public EnhancementResult enhancementResult; - public ItemUsable preItemUsable; - public FungibleAssetValue CRYSTAL; - - public ResultModel() - { - } - - public ResultModel(Dictionary serialized) : base(serialized) - { - id = serialized["id"].ToGuid(); - materialItemIdList = - serialized["materialItemIdList"].ToList(StateExtensions.ToGuid); - gold = serialized["gold"].ToBigInteger(); - actionPoint = serialized["actionPoint"].ToInteger(); - enhancementResult = serialized["enhancementResult"].ToEnum(); - preItemUsable = serialized.ContainsKey("preItemUsable") - ? (ItemUsable)ItemFactory.Deserialize((Dictionary)serialized["preItemUsable"]) - : null; - CRYSTAL = serialized["c"].ToFungibleAssetValue(); - } - - public override IValue Serialize() => -#pragma warning disable LAA1002 - new Dictionary(new Dictionary - { - [(Text)"id"] = id.Serialize(), - [(Text)"materialItemIdList"] = materialItemIdList - .OrderBy(i => i) - .Select(g => g.Serialize()).Serialize(), - [(Text)"gold"] = gold.Serialize(), - [(Text)"actionPoint"] = actionPoint.Serialize(), - [(Text)"enhancementResult"] = enhancementResult.Serialize(), - [(Text)"preItemUsable"] = preItemUsable.Serialize(), - [(Text)"c"] = CRYSTAL.Serialize(), - }.Union((Dictionary)base.Serialize())); -#pragma warning restore LAA1002 - } - protected override IImmutableDictionary PlainValueInternal { get @@ -388,13 +333,13 @@ public override IAccount Execute(IActionContext context) sw.Elapsed); // Send scheduled mail - var result = new ResultModel + var result = new ItemEnhancement13.ResultModel { preItemUsable = preItemUsable, itemUsable = enhancementEquipment, materialItemIdList = uniqueMaterialIds.ToArray(), actionPoint = requiredActionPoint, - enhancementResult = EnhancementResult.Success, // Result is fixed to Success + enhancementResult = ItemEnhancement13.EnhancementResult.Success, // Result is fixed to Success gold = requiredNcg, CRYSTAL = 0 * CrystalCalculator.CRYSTAL, }; diff --git a/Lib9c/Action/ItemEnhancement13.cs b/Lib9c/Action/ItemEnhancement13.cs new file mode 100644 index 0000000000..a97f48caa5 --- /dev/null +++ b/Lib9c/Action/ItemEnhancement13.cs @@ -0,0 +1,468 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Numerics; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.Item; +using Nekoyume.Model.Mail; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Crystal; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Updated at https://github.com/planetarium/lib9c/pull/2068 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("item_enhancement13")] + public class ItemEnhancement13 : GameAction, IItemEnhancementV4 + { + public enum EnhancementResult + { + // Result is fixed to Success. + // GreatSuccess = 0, + Success = 1, + // Fail = 2, + } + + public const int MaterialCountLimit = 50; + + public Guid itemId; + public List materialIds; + public Address avatarAddress; + public int slotIndex; + + Guid IItemEnhancementV4.ItemId => itemId; + List IItemEnhancementV4.MaterialIds => materialIds; + Address IItemEnhancementV4.AvatarAddress => avatarAddress; + int IItemEnhancementV4.SlotIndex => slotIndex; + + [Serializable] + public class ResultModel : AttachmentActionResult + { + protected override string TypeId => "item_enhancement13.result"; + public Guid id; + public IEnumerable materialItemIdList; + public BigInteger gold; + public int actionPoint; + public EnhancementResult enhancementResult; + public ItemUsable preItemUsable; + public FungibleAssetValue CRYSTAL; + + public ResultModel() + { + } + + public ResultModel(Dictionary serialized) : base(serialized) + { + id = serialized["id"].ToGuid(); + materialItemIdList = + serialized["materialItemIdList"].ToList(StateExtensions.ToGuid); + gold = serialized["gold"].ToBigInteger(); + actionPoint = serialized["actionPoint"].ToInteger(); + enhancementResult = serialized["enhancementResult"].ToEnum(); + preItemUsable = serialized.ContainsKey("preItemUsable") + ? (ItemUsable)ItemFactory.Deserialize((Dictionary)serialized["preItemUsable"]) + : null; + CRYSTAL = serialized["c"].ToFungibleAssetValue(); + } + + public override IValue Serialize() => +#pragma warning disable LAA1002 + new Dictionary(new Dictionary + { + [(Text)"id"] = id.Serialize(), + [(Text)"materialItemIdList"] = materialItemIdList + .OrderBy(i => i) + .Select(g => g.Serialize()).Serialize(), + [(Text)"gold"] = gold.Serialize(), + [(Text)"actionPoint"] = actionPoint.Serialize(), + [(Text)"enhancementResult"] = enhancementResult.Serialize(), + [(Text)"preItemUsable"] = preItemUsable.Serialize(), + [(Text)"c"] = CRYSTAL.Serialize(), + }.Union((Dictionary)base.Serialize())); +#pragma warning restore LAA1002 + } + + protected override IImmutableDictionary PlainValueInternal + { + get + { + var dict = new Dictionary + { + ["itemId"] = itemId.Serialize(), + ["materialIds"] = new List( + materialIds.OrderBy(i => i).Select(i => i.Serialize()) + ), + ["avatarAddress"] = avatarAddress.Serialize(), + ["slotIndex"] = slotIndex.Serialize(), + }; + + return dict.ToImmutableDictionary(); + } + } + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + itemId = plainValue["itemId"].ToGuid(); + materialIds = plainValue["materialIds"].ToList(StateExtensions.ToGuid); + avatarAddress = plainValue["avatarAddress"].ToAddress(); + if (plainValue.TryGetValue((Text)"slotIndex", out var value)) + { + slotIndex = value.ToInteger(); + } + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var ctx = context; + var states = ctx.PreviousState; + + if (ctx.Rehearsal) + { + return states; + } + + // Collect addresses + var slotAddress = avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + slotIndex + ) + ); + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + + var sw = new Stopwatch(); + sw.Start(); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex} ItemEnhancement exec started", addressesHex); + + // Validate avatar + if (!states.TryGetAgentAvatarStatesV2(ctx.Signer, avatarAddress, out var agentState, + out var avatarState, out var migrationRequired)) + { + throw new FailedLoadStateException( + $"{addressesHex} Aborted as the avatar state of the signer was failed to load." + ); + } + + // Validate AP + var requiredActionPoint = GetRequiredAp(); + if (avatarState.actionPoint < requiredActionPoint) + { + throw new NotEnoughActionPointException( + $"{addressesHex} Aborted due to insufficient action point: {avatarState.actionPoint} < {requiredActionPoint}" + ); + } + + // Validate target equipment item + if (!avatarState.inventory.TryGetNonFungibleItem(itemId, + out ItemUsable enhancementItem)) + { + throw new ItemDoesNotExistException( + $"{addressesHex} Aborted as the NonFungibleItem ({itemId}) was failed to load from avatar's inventory." + ); + } + + if (enhancementItem.RequiredBlockIndex > context.BlockIndex) + { + throw new RequiredBlockIndexException( + $"{addressesHex} Aborted as the equipment to enhance ({itemId}) is not available yet;" + + $" it will be available at the block #{enhancementItem.RequiredBlockIndex}." + ); + } + + if (!(enhancementItem is Equipment enhancementEquipment)) + { + throw new InvalidCastException( + $"{addressesHex} Aborted as the item is not a {nameof(Equipment)}, but {enhancementItem.GetType().Name}." + ); + } + + // Validate combination slot + var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); + if (slotState is null) + { + throw new FailedLoadStateException( + $"{addressesHex} Aborted as the slot state was failed to load. #{slotIndex}" + ); + } + + if (!slotState.Validate(avatarState, ctx.BlockIndex)) + { + throw new CombinationSlotUnlockException( + $"{addressesHex} Aborted as the slot state was failed to invalid. #{slotIndex}" + ); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} ItemEnhancement Get Equipment: {Elapsed}", addressesHex, + sw.Elapsed); + + sw.Restart(); + + Dictionary sheets = states.GetSheets(sheetTypes: new[] + { + typeof(EquipmentItemSheet), + typeof(EnhancementCostSheetV3), + typeof(MaterialItemSheet), + typeof(CrystalEquipmentGrindingSheet), + typeof(CrystalMonsterCollectionMultiplierSheet), + typeof(StakeRegularRewardSheet) + }); + + // Validate from sheet + var enhancementCostSheet = sheets.GetSheet(); + EnhancementCostSheetV3.Row startCostRow; + if (enhancementEquipment.level == 0) + { + startCostRow = new EnhancementCostSheetV3.Row(); + } + else + { + if (!TryGetRow(enhancementEquipment, enhancementCostSheet, out startCostRow)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(WorldSheet), + enhancementEquipment.level); + } + } + + var maxLevel = GetEquipmentMaxLevel(enhancementEquipment, enhancementCostSheet); + if (enhancementEquipment.level > maxLevel) + { + throw new EquipmentLevelExceededException( + $"{addressesHex} Aborted due to invalid equipment level: {enhancementEquipment.level} < {maxLevel}"); + } + + // Validate enhancement materials + var uniqueMaterialIds = materialIds.Distinct().ToList(); + if (!uniqueMaterialIds.Any() || uniqueMaterialIds.Count > MaterialCountLimit) + { + throw new InvalidItemCountException(); + } + + var materialEquipments = new List(); + + foreach (var materialId in uniqueMaterialIds) + { + if (!avatarState.inventory.TryGetNonFungibleItem(materialId, + out ItemUsable materialItem)) + { + throw new NotEnoughMaterialException( + $"{addressesHex} Aborted as the signer does not have a necessary material ({materialId})." + ); + } + + if (materialItem.RequiredBlockIndex > context.BlockIndex) + { + throw new RequiredBlockIndexException( + $"{addressesHex} Aborted as the material ({materialId}) is not available yet;" + + $" it will be available at the block #{materialItem.RequiredBlockIndex}." + ); + } + + if (!(materialItem is Equipment materialEquipment)) + { + throw new InvalidCastException( + $"{addressesHex} Aborted as the material item is not an {nameof(Equipment)}, but {materialItem.GetType().Name}." + ); + } + + if (enhancementEquipment.ItemId == materialId) + { + throw new InvalidMaterialException( + $"{addressesHex} Aborted as an equipment to enhance ({materialId}) was used as a material too." + ); + } + + if (materialEquipment.ItemSubType != enhancementEquipment.ItemSubType) + { + throw new InvalidMaterialException( + $"{addressesHex} Aborted as the material item is not a {enhancementEquipment.ItemSubType}," + + $" but {materialEquipment.ItemSubType}." + ); + } + + materialEquipments.Add(materialEquipment); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} ItemEnhancement Get Material: {Elapsed}", + addressesHex, sw.Elapsed); + + sw.Restart(); + + // Do the action + var equipmentItemSheet = sheets.GetSheet(); + // Subtract required action point + avatarState.actionPoint -= requiredActionPoint; + + // Unequip items + enhancementEquipment.Unequip(); + foreach (var materialEquipment in materialEquipments) + { + materialEquipment.Unequip(); + } + + // clone enhancement item + var preItemUsable = new Equipment((Dictionary)enhancementEquipment.Serialize()); + + // Equipment level up & Update + enhancementEquipment.Exp = enhancementEquipment.GetRealExp(equipmentItemSheet, + enhancementCostSheet); + + enhancementEquipment.Exp += + materialEquipments.Aggregate(0L, + (total, m) => total + m.GetRealExp(equipmentItemSheet, enhancementCostSheet)); + var row = enhancementCostSheet + .OrderByDescending(r => r.Value.Exp) + .FirstOrDefault(row => + row.Value.ItemSubType == enhancementEquipment.ItemSubType && + row.Value.Grade == enhancementEquipment.Grade && + row.Value.Exp <= enhancementEquipment.Exp + ).Value; + if (!(row is null) && row.Level > enhancementEquipment.level) + { + enhancementEquipment.SetLevel(ctx.Random, row.Level, enhancementCostSheet); + } + + EnhancementCostSheetV3.Row targetCostRow; + if (enhancementEquipment.level == 0) + { + targetCostRow = new EnhancementCostSheetV3.Row(); + } + else + { + if (!TryGetRow(enhancementEquipment, enhancementCostSheet, out targetCostRow)) + { + throw new SheetRowNotFoundException(addressesHex, nameof(WorldSheet), + enhancementEquipment.level); + } + } + + // TransferAsset (NCG) + // Total cost = Total cost to reach target level - total cost to reach start level (already used) + var requiredNcg = targetCostRow.Cost - startCostRow.Cost; + if (requiredNcg > 0) + { + var arenaSheet = states.GetSheet(); + var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); + var feeStoreAddress = + Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); + states = states.TransferAsset(ctx, ctx.Signer, feeStoreAddress, + states.GetGoldCurrency() * requiredNcg); + } + + // Required block index = Total required block to reach target level - total required block to reach start level (already elapsed) + var requiredBlockIndex = + ctx.BlockIndex + + (targetCostRow.RequiredBlockIndex - startCostRow.RequiredBlockIndex); + enhancementEquipment.Update(requiredBlockIndex); + + // Remove materials + foreach (var materialId in uniqueMaterialIds) + { + avatarState.inventory.RemoveNonFungibleItem(materialId); + } + + sw.Stop(); + Log.Verbose("{AddressesHex} ItemEnhancement Upgrade Equipment: {Elapsed}", addressesHex, + sw.Elapsed); + + // Send scheduled mail + var result = new ResultModel + { + preItemUsable = preItemUsable, + itemUsable = enhancementEquipment, + materialItemIdList = uniqueMaterialIds.ToArray(), + actionPoint = requiredActionPoint, + enhancementResult = EnhancementResult.Success, // Result is fixed to Success + gold = requiredNcg, + CRYSTAL = 0 * CrystalCalculator.CRYSTAL, + }; + + var mail = new ItemEnhanceMail( + result, ctx.BlockIndex, ctx.Random.GenerateRandomGuid(), requiredBlockIndex + ); + result.id = mail.id; + avatarState.inventory.RemoveNonFungibleItem(enhancementEquipment); + avatarState.Update(mail); + avatarState.UpdateFromItemEnhancement(enhancementEquipment); + + // Update quest reward + var materialSheet = sheets.GetSheet(); + avatarState.UpdateQuestRewards(materialSheet); + + // Update slot state + slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); + + // Set state + sw.Restart(); + states = states + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()) + .SetState(avatarAddress, avatarState.SerializeV2()); + + sw.Stop(); + Log.Verbose("{AddressesHex} ItemEnhancement Set AvatarState: {Elapsed}", addressesHex, + sw.Elapsed); + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex} ItemEnhancement Total Executed Time: {Elapsed}", addressesHex, + ended - started); + return states.SetState(slotAddress, slotState.Serialize()); + } + + public static int GetRequiredBlockCount(Equipment preEquipment, Equipment targetEquipment, + EnhancementCostSheetV3 sheet) + { + return sheet.OrderedList + .Where(e => + e.ItemSubType == targetEquipment.ItemSubType && + e.Grade == targetEquipment.Grade && + e.Level > preEquipment.level && + e.Level <= targetEquipment.level) + .Aggregate(0, (blocks, row) => blocks + row.RequiredBlockIndex); + } + + public static bool TryGetRow(Equipment equipment, EnhancementCostSheetV3 sheet, + out EnhancementCostSheetV3.Row row) + { + row = sheet.OrderedList.FirstOrDefault(x => + x.Grade == equipment.Grade && + x.Level == equipment.level && + x.ItemSubType == equipment.ItemSubType + ); + return row != null; + } + + public static int GetEquipmentMaxLevel(Equipment equipment, EnhancementCostSheetV3 sheet) + { + return sheet.OrderedList.Where(x => x.Grade == equipment.Grade).Max(x => x.Level); + } + + public static int GetRequiredAp() + { + return GameConfig.EnhanceEquipmentCostAP; + } + } +} diff --git a/Lib9c/Action/JoinArena.cs b/Lib9c/Action/JoinArena.cs index c20f9321fd..401c72dccf 100644 --- a/Lib9c/Action/JoinArena.cs +++ b/Lib9c/Action/JoinArena.cs @@ -21,10 +21,10 @@ namespace Nekoyume.Action { /// - /// Introduced at https://github.com/planetarium/lib9c/pull/1663 + /// Introduced at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("join_arena3")] + [ActionType("join_arena4")] public class JoinArena : GameAction, IJoinArenaV1 { public Address avatarAddress; diff --git a/Lib9c/Action/JoinArena3.cs b/Lib9c/Action/JoinArena3.cs new file mode 100644 index 0000000000..6616e27cd2 --- /dev/null +++ b/Lib9c/Action/JoinArena3.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Arena; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.Arena; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Rune; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; + +namespace Nekoyume.Action +{ + /// + /// Introduced at https://github.com/planetarium/lib9c/pull/1663 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("join_arena3")] + public class JoinArena3 : GameAction, IJoinArenaV1 + { + public Address avatarAddress; + public int championshipId; + public int round; + public List costumes; + public List equipments; + public List runeInfos; + + Address IJoinArenaV1.AvatarAddress => avatarAddress; + int IJoinArenaV1.ChampionshipId => championshipId; + int IJoinArenaV1.Round => round; + IEnumerable IJoinArenaV1.Costumes => costumes; + IEnumerable IJoinArenaV1.Equipments => equipments; + IEnumerable IJoinArenaV1.RuneSlotInfos => runeInfos + .Select(x => x.Serialize()); + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary() + { + ["avatarAddress"] = avatarAddress.Serialize(), + ["championshipId"] = championshipId.Serialize(), + ["round"] = round.Serialize(), + ["costumes"] = new List(costumes + .OrderBy(element => element).Select(e => e.Serialize())), + ["equipments"] = new List(equipments + .OrderBy(element => element).Select(e => e.Serialize())), + ["runeInfos"] = runeInfos.OrderBy(x => x.SlotIndex).Select(x=> x.Serialize()).Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal( + IImmutableDictionary plainValue) + { + avatarAddress = plainValue["avatarAddress"].ToAddress(); + championshipId = plainValue["championshipId"].ToInteger(); + round = plainValue["round"].ToInteger(); + costumes = ((List)plainValue["costumes"]).Select(e => e.ToGuid()).ToList(); + equipments = ((List)plainValue["equipments"]).Select(e => e.ToGuid()).ToList(); + runeInfos = plainValue["runeInfos"].ToList(x => new RuneSlotInfo((List)x)); + } + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}JoinArena exec started", addressesHex); + + if (!states.TryGetAgentAvatarStatesV2(context.Signer, avatarAddress, + out var agentState, out var avatarState, out _)) + { + throw new FailedLoadStateException( + $"[{nameof(JoinArena)}] Aborted as the avatar state of the signer failed to load."); + } + + if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex( + out var world)) + { + throw new NotEnoughClearedStageLevelException( + $"{addressesHex}Aborted as NotEnoughClearedStageLevelException"); + } + + if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) + { + throw new NotEnoughClearedStageLevelException( + addressesHex, + GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, + world.StageClearedId); + } + + var sheets = states.GetSheets( + sheetTypes: new[] + { + typeof(ItemRequirementSheet), + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(ArenaSheet), + typeof(RuneListSheet), + }); + + avatarState.ValidEquipmentAndCostume(costumes, equipments, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + context.BlockIndex, addressesHex); + + // update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Arena); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Arena); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(runeInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(avatarAddress, BattleType.Arena); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Arena); + itemSlotState.UpdateEquipment(equipments); + itemSlotState.UpdateCostumes(costumes); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + var sheet = sheets.GetSheet(); + if (!sheet.TryGetValue(championshipId, out var row)) + { + throw new SheetRowNotFoundException( + nameof(ArenaSheet), $"championship Id : {championshipId}"); + } + + if (!row.TryGetRound(round, out var roundData)) + { + throw new RoundNotFoundException( + $"[{nameof(JoinArena)}] ChampionshipId({row.ChampionshipId}) - round({round})"); + } + + // check fee + + var fee = ArenaHelper.GetEntranceFee(roundData, context.BlockIndex, avatarState.level); + if (fee > 0 * CrystalCalculator.CRYSTAL) + { + var crystalBalance = states.GetBalance(context.Signer, CrystalCalculator.CRYSTAL); + if (fee > crystalBalance) + { + throw new NotEnoughFungibleAssetValueException( + $"required {fee}, but balance is {crystalBalance}"); + } + + var arenaAdr = ArenaHelper.DeriveArenaAddress(roundData.ChampionshipId, roundData.Round); + states = states.TransferAsset(context, context.Signer, arenaAdr, fee); + } + + // check medal + if (roundData.ArenaType.Equals(ArenaType.Championship)) + { + var medalCount = ArenaHelper.GetMedalTotalCount(row, avatarState); + if (medalCount < roundData.RequiredMedalCount) + { + throw new NotEnoughMedalException( + $"[{nameof(JoinArena)}] have({medalCount}) < Required Medal Count({roundData.RequiredMedalCount}) "); + } + } + + // create ArenaScore + var arenaScoreAdr = + ArenaScore.DeriveAddress(avatarAddress, roundData.ChampionshipId, roundData.Round); + if (states.TryGetState(arenaScoreAdr, out List _)) + { + throw new ArenaScoreAlreadyContainsException( + $"[{nameof(JoinArena)}] id({roundData.ChampionshipId}) / round({roundData.Round})"); + } + + var arenaScore = new ArenaScore(avatarAddress, roundData.ChampionshipId, roundData.Round); + + // create ArenaInformation + var arenaInformationAdr = + ArenaInformation.DeriveAddress(avatarAddress, roundData.ChampionshipId, roundData.Round); + if (states.TryGetState(arenaInformationAdr, out List _)) + { + throw new ArenaInformationAlreadyContainsException( + $"[{nameof(JoinArena)}] id({roundData.ChampionshipId}) / round({roundData.Round})"); + } + + var arenaInformation = + new ArenaInformation(avatarAddress, roundData.ChampionshipId, roundData.Round); + + // update ArenaParticipants + var arenaParticipantsAdr = ArenaParticipants.DeriveAddress(roundData.ChampionshipId, roundData.Round); + var arenaParticipants = states.GetArenaParticipants(arenaParticipantsAdr, roundData.ChampionshipId, roundData.Round); + arenaParticipants.Add(avatarAddress); + + // update ArenaAvatarState + var arenaAvatarStateAdr = ArenaAvatarState.DeriveAddress(avatarAddress); + var arenaAvatarState = states.GetArenaAvatarState(arenaAvatarStateAdr, avatarState); + arenaAvatarState.UpdateCostumes(costumes); + arenaAvatarState.UpdateEquipment(equipments); + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}JoinArena Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(arenaScoreAdr, arenaScore.Serialize()) + .SetState(arenaInformationAdr, arenaInformation.Serialize()) + .SetState(arenaParticipantsAdr, arenaParticipants.Serialize()) + .SetState(arenaAvatarStateAdr, arenaAvatarState.Serialize()) + .SetState(context.Signer, agentState.Serialize()); + } + } +} diff --git a/Lib9c/Action/Raid.cs b/Lib9c/Action/Raid.cs index 0c94bf0c73..3fd336c6fd 100644 --- a/Lib9c/Action/Raid.cs +++ b/Lib9c/Action/Raid.cs @@ -22,10 +22,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1858 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("raid6")] + [ActionType("raid7")] public class Raid : GameAction, IRaidV2 { public Address AvatarAddress; diff --git a/Lib9c/Action/Raid6.cs b/Lib9c/Action/Raid6.cs new file mode 100644 index 0000000000..93c837f1c2 --- /dev/null +++ b/Lib9c/Action/Raid6.cs @@ -0,0 +1,373 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.Arena; +using Nekoyume.Model.EnumType; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1858 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("raid6")] + public class Raid6 : GameAction, IRaidV2 + { + public Address AvatarAddress; + public List EquipmentIds; + public List CostumeIds; + public List FoodIds; + public List RuneInfos; + public bool PayNcg; + + Address IRaidV2.AvatarAddress => AvatarAddress; + IEnumerable IRaidV2.EquipmentIds => EquipmentIds; + IEnumerable IRaidV2.CostumeIds => CostumeIds; + IEnumerable IRaidV2.FoodIds => FoodIds; + IEnumerable IRaidV2.RuneSlotInfos => RuneInfos.Select(x => x.Serialize()); + bool IRaidV2.PayNcg => PayNcg; + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + IAccount states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + var addressHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressHex}Raid exec started", addressHex); + if (!states.TryGetAvatarStateV2(context.Signer, AvatarAddress, + out AvatarState avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException( + $"Aborted as the avatar state of the signer was failed to load."); + } + // Check stage level. + if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInRaid)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out int current); + throw new NotEnoughClearedStageLevelException(AvatarAddress.ToHex(), + GameConfig.RequireClearedStageLevel.ActionsInRaid, current); + } + + Dictionary sheets = states.GetSheets( + containRaidSimulatorSheets: true, + sheetTypes: new [] { + typeof(MaterialItemSheet), + typeof(SkillSheet), + typeof(SkillBuffSheet), + typeof(StatBuffSheet), + typeof(CharacterLevelSheet), + typeof(EquipmentItemSetEffectSheet), + typeof(ItemRequirementSheet), + typeof(EquipmentItemRecipeSheet), + typeof(EquipmentItemSubRecipeSheetV2), + typeof(EquipmentItemOptionSheet), + typeof(WorldBossCharacterSheet), + typeof(WorldBossListSheet), + typeof(WorldBossGlobalHpSheet), + typeof(WorldBossActionPatternSheet), + typeof(CharacterSheet), + typeof(CostumeStatSheet), + typeof(RuneWeightSheet), + typeof(WorldBossKillRewardSheet), + typeof(RuneSheet), + typeof(RuneListSheet), + }); + var worldBossListSheet = sheets.GetSheet(); + var row = worldBossListSheet.FindRowByBlockIndex(context.BlockIndex); + int raidId = row.Id; + Address worldBossAddress = Addresses.GetWorldBossAddress(raidId); + Address raiderAddress = Addresses.GetRaiderAddress(AvatarAddress, raidId); + // Check challenge count. + RaiderState raiderState; + if (states.TryGetState(raiderAddress, out List rawState)) + { + raiderState = new RaiderState(rawState); + } + else + { + raiderState = new RaiderState(); + if (row.EntranceFee > 0) + { + FungibleAssetValue crystalCost = CrystalCalculator.CalculateEntranceFee(avatarState.level, row.EntranceFee); + states = states.TransferAsset(context, context.Signer, worldBossAddress, crystalCost); + } + Address raiderListAddress = Addresses.GetRaiderListAddress(raidId); + List
raiderList = + states.TryGetState(raiderListAddress, out List rawRaiderList) + ? rawRaiderList.ToList(StateExtensions.ToAddress) + : new List
(); + raiderList.Add(raiderAddress); + states = states.SetState(raiderListAddress, + new List(raiderList.Select(a => a.Serialize()))); + } + + var gameConfigState = states.GetGameConfigState(); + if (context.BlockIndex - raiderState.UpdatedBlockIndex < gameConfigState.WorldBossRequiredInterval) + { + throw new RequiredBlockIntervalException($"wait for interval. {context.BlockIndex - raiderState.UpdatedBlockIndex}"); + } + + if (WorldBossHelper.CanRefillTicket(context.BlockIndex, raiderState.RefillBlockIndex, + row.StartedBlockIndex, gameConfigState.DailyWorldBossInterval)) + { + raiderState.RemainChallengeCount = WorldBossHelper.MaxChallengeCount; + raiderState.RefillBlockIndex = context.BlockIndex; + } + + if (raiderState.RemainChallengeCount < 1) + { + if (PayNcg) + { + if (raiderState.PurchaseCount >= row.MaxPurchaseCount) + { + throw new ExceedTicketPurchaseLimitException(""); + } + var goldCurrency = states.GetGoldCurrency(); + states = states.TransferAsset(context, context.Signer, worldBossAddress, + WorldBossHelper.CalculateTicketPrice(row, raiderState, goldCurrency)); + raiderState.PurchaseCount++; + } + else + { + throw new ExceedPlayCountException(""); + } + } + + // Validate equipment, costume. + var equipmentList = avatarState.ValidateEquipmentsV2(EquipmentIds, context.BlockIndex); + var foodIds = avatarState.ValidateConsumable(FoodIds, context.BlockIndex); + var costumeIds = avatarState.ValidateCostume(CostumeIds); + + // Update rune slot + var runeSlotStateAddress = RuneSlotState.DeriveAddress(AvatarAddress, BattleType.Raid); + var runeSlotState = states.TryGetState(runeSlotStateAddress, out List rawRuneSlotState) + ? new RuneSlotState(rawRuneSlotState) + : new RuneSlotState(BattleType.Raid); + var runeListSheet = sheets.GetSheet(); + runeSlotState.UpdateSlot(RuneInfos, runeListSheet); + states = states.SetState(runeSlotStateAddress, runeSlotState.Serialize()); + + // Update item slot + var itemSlotStateAddress = ItemSlotState.DeriveAddress(AvatarAddress, BattleType.Raid); + var itemSlotState = states.TryGetState(itemSlotStateAddress, out List rawItemSlotState) + ? new ItemSlotState(rawItemSlotState) + : new ItemSlotState(BattleType.Raid); + itemSlotState.UpdateEquipment(EquipmentIds); + itemSlotState.UpdateCostumes(CostumeIds); + states = states.SetState(itemSlotStateAddress, itemSlotState.Serialize()); + + int previousHighScore = raiderState.HighScore; + WorldBossState bossState; + WorldBossGlobalHpSheet hpSheet = sheets.GetSheet(); + if (states.TryGetState(worldBossAddress, out List rawBossState)) + { + bossState = new WorldBossState(rawBossState); + } + else + { + bossState = new WorldBossState(row, hpSheet[1]); + } + + var addressesHex = $"[{context.Signer.ToHex()}, {AvatarAddress.ToHex()}]"; + var items = EquipmentIds.Concat(CostumeIds); + avatarState.EquipItems(items); + avatarState.ValidateItemRequirement( + costumeIds.Concat(foodIds).ToList(), + equipmentList, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + addressesHex); + + var raidSimulatorSheets = sheets.GetRaidSimulatorSheets(); + var runeStates = new List(); + foreach (var address in RuneInfos.Select(info => RuneState.DeriveAddress(AvatarAddress, info.RuneId))) + { + if (states.TryGetState(address, out List rawRuneState)) + { + runeStates.Add(new RuneState(rawRuneState)); + } + } + + // Simulate. + var simulator = new RaidSimulator( + row.BossId, + context.Random, + avatarState, + FoodIds, + runeStates, + raidSimulatorSheets, + sheets.GetSheet()); + simulator.Simulate(); + avatarState.inventory = simulator.Player.Inventory; + + var costumeList = new List(); + foreach (var guid in CostumeIds) + { + var costume = avatarState.inventory.Costumes.FirstOrDefault(x => x.ItemId == guid); + if (costume != null) + { + costumeList.Add(costume); + } + } + + var runeOptionSheet = sheets.GetSheet(); + var runeOptions = new List(); + foreach (var runeState in runeStates) + { + if (!runeOptionSheet.TryGetValue(runeState.RuneId, out var optionRow)) + { + throw new SheetRowNotFoundException("RuneOptionSheet", runeState.RuneId); + } + + if (!optionRow.LevelOptionMap.TryGetValue(runeState.Level, out var option)) + { + throw new SheetRowNotFoundException("RuneOptionSheet", runeState.Level); + } + + runeOptions.Add(option); + } + + var characterSheet = sheets.GetSheet(); + if (!characterSheet.TryGetValue(avatarState.characterId, out var characterRow)) + { + throw new SheetRowNotFoundException("CharacterSheet", avatarState.characterId); + } + + var costumeStatSheet = sheets.GetSheet(); + var cp = CPHelper.TotalCP( + equipmentList, costumeList, + runeOptions, avatarState.level, + characterRow, costumeStatSheet); + int score = simulator.DamageDealt; + raiderState.Update(avatarState, cp, score, PayNcg, context.BlockIndex); + + // Reward. + bossState.CurrentHp -= score; + if (bossState.CurrentHp <= 0) + { + if (bossState.Level < hpSheet.OrderedList.Last().Level) + { + bossState.Level++; + } + bossState.CurrentHp = hpSheet[bossState.Level].Hp; + } + + // battle reward + foreach (var battleReward in simulator.AssetReward) + { + if (battleReward.Currency.Equals(CrystalCalculator.CRYSTAL)) + { + states = states.MintAsset(context, context.Signer, battleReward); + } + else + { + states = states.MintAsset(context, AvatarAddress, battleReward); + } + } + + if (raiderState.LatestBossLevel < bossState.Level) + { + // kill reward + var worldBossKillRewardRecordAddress = Addresses.GetWorldBossKillRewardRecordAddress(AvatarAddress, raidId); + WorldBossKillRewardRecord rewardRecord; + if (states.TryGetState(worldBossKillRewardRecordAddress, out List rawList)) + { + var bossRow = raidSimulatorSheets.WorldBossCharacterSheet[row.BossId]; + rewardRecord = new WorldBossKillRewardRecord(rawList); + // calculate with previous high score. + int rank = WorldBossHelper.CalculateRank(bossRow, previousHighScore); + states = states.SetWorldBossKillReward( + context, + worldBossKillRewardRecordAddress, + rewardRecord, + rank, + bossState, + sheets.GetSheet(), + sheets.GetSheet(), + sheets.GetSheet(), + context.Random, + AvatarAddress, + context.Signer + ); + } + else + { + rewardRecord = new WorldBossKillRewardRecord(); + } + + // Save level infos; + raiderState.LatestBossLevel = bossState.Level; + if (!rewardRecord.ContainsKey(raiderState.LatestBossLevel)) + { + rewardRecord.Add(raiderState.LatestBossLevel, false); + } + states = states.SetState(worldBossKillRewardRecordAddress, rewardRecord.Serialize()); + } + + var inventoryAddress = AvatarAddress.Derive(LegacyInventoryKey); + var worldInfoAddress = AvatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = AvatarAddress.Derive(LegacyQuestListKey); + + if (migrationRequired) + { + states = states + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInfoAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()); + } + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressHex}Raid Total Executed Time: {Elapsed}", addressHex, ended - started); + return states + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldBossAddress, bossState.Serialize()) + .SetState(raiderAddress, raiderState.Serialize()); + } + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + ["a"] = AvatarAddress.Serialize(), + ["e"] = new List(EquipmentIds.Select(e => e.Serialize())), + ["c"] = new List(CostumeIds.Select(c => c.Serialize())), + ["f"] = new List(FoodIds.Select(f => f.Serialize())), + ["r"] = RuneInfos.OrderBy(x => x.SlotIndex).Select(x=> x.Serialize()).Serialize(), + ["p"] = PayNcg.Serialize(), + } + .ToImmutableDictionary(); + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + AvatarAddress = plainValue["a"].ToAddress(); + EquipmentIds = plainValue["e"].ToList(StateExtensions.ToGuid); + CostumeIds = plainValue["c"].ToList(StateExtensions.ToGuid); + FoodIds = plainValue["f"].ToList(StateExtensions.ToGuid); + RuneInfos = plainValue["r"].ToList(x => new RuneSlotInfo((List)x)); + PayNcg = plainValue["p"].ToBoolean(); + } + } +} diff --git a/Lib9c/Action/RankingBattle.cs b/Lib9c/Action/RankingBattle.cs index 837e453cbb..545a374222 100644 --- a/Lib9c/Action/RankingBattle.cs +++ b/Lib9c/Action/RankingBattle.cs @@ -23,6 +23,7 @@ namespace Nekoyume.Action /// Updated at https://github.com/planetarium/lib9c/pull/1135 /// [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] [ActionType("ranking_battle12")] public class RankingBattle : GameAction, IRankingBattleV2 { diff --git a/Lib9c/Action/RapidCombination.cs b/Lib9c/Action/RapidCombination.cs index f8c59fb1b4..4cd91c7130 100644 --- a/Lib9c/Action/RapidCombination.cs +++ b/Lib9c/Action/RapidCombination.cs @@ -20,10 +20,10 @@ namespace Nekoyume.Action { /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1711 + /// Hard forked at https://github.com/planetarium/lib9c/pull/2195 /// [Serializable] - [ActionType("rapid_combination9")] + [ActionType("rapid_combination10")] public class RapidCombination : GameAction, IRapidCombinationV1 { public Address avatarAddress; diff --git a/Lib9c/Action/RapidCombination9.cs b/Lib9c/Action/RapidCombination9.cs new file mode 100644 index 0000000000..478a81dbd3 --- /dev/null +++ b/Lib9c/Action/RapidCombination9.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using Bencodex.Types; +using Lib9c.Abstractions; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Extensions; +using Nekoyume.Helper; +using Nekoyume.Model.Item; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using Nekoyume.TableData.Pet; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + /// + /// Hard forked at https://github.com/planetarium/lib9c/pull/1711 + /// + [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("rapid_combination9")] + public class RapidCombination9 : GameAction, IRapidCombinationV1 + { + public Address avatarAddress; + public int slotIndex; + + Address IRapidCombinationV1.AvatarAddress => avatarAddress; + int IRapidCombinationV1.SlotIndex => slotIndex; + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + var slotAddress = avatarAddress.Derive( + string.Format( + CultureInfo.InvariantCulture, + CombinationSlotState.DeriveFormat, + slotIndex + ) + ); + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + if (context.Rehearsal) + { + return states + .SetState(avatarAddress, MarkChanged) + .SetState(inventoryAddress, MarkChanged) + .SetState(worldInformationAddress, MarkChanged) + .SetState(questListAddress, MarkChanged) + .SetState(slotAddress, MarkChanged); + } + + var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); + var started = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}RapidCombination exec started", addressesHex); + + if (!states.TryGetAgentAvatarStatesV2( + context.Signer, + avatarAddress, + out var agentState, + out var avatarState, + out _)) + { + throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); + } + + var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); + if (slotState?.Result is null) + { + throw new CombinationSlotResultNullException($"{addressesHex}CombinationSlot Result is null. ({avatarAddress}), ({slotIndex})"); + } + + if(!avatarState.worldInformation.IsStageCleared(slotState.UnlockStage)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException(addressesHex, slotState.UnlockStage, current); + } + + var diff = slotState.Result.itemUsable.RequiredBlockIndex - context.BlockIndex; + if (diff <= 0) + { + throw new RequiredBlockIndexException($"{addressesHex}Already met the required block index. context block index: {context.BlockIndex}, required block index: {slotState.Result.itemUsable.RequiredBlockIndex}"); + } + + var gameConfigState = states.GetGameConfigState(); + if (gameConfigState is null) + { + throw new FailedLoadStateException($"{addressesHex}Aborted as the GameConfigState was failed to load."); + } + + var actionableBlockIndex = slotState.StartBlockIndex + + states.GetGameConfigState().RequiredAppraiseBlock; + if (context.BlockIndex < actionableBlockIndex) + { + throw new AppraiseBlockNotReachedException( + $"{addressesHex}Aborted as Item appraisal block section. " + + $"context block index: {context.BlockIndex}, actionable block index : {actionableBlockIndex}"); + } + + int costHourglassCount = 0; + + PetState petState = null; + if (slotState.PetId.HasValue) + { + var petStateAddress = PetState.DeriveAddress(avatarAddress, slotState.PetId.Value); + if (!states.TryGetState(petStateAddress, out List rawState)) + { + throw new FailedLoadStateException($"{addressesHex}Aborted as the {nameof(PetState)} was failed to load."); + } + + petState = new PetState(rawState); + var petOptionSheet = states.GetSheet(); + costHourglassCount = PetHelper.CalculateDiscountedHourglass( + diff, + gameConfigState.HourglassPerBlock, + petState, + petOptionSheet); + } + else + { + costHourglassCount = RapidCombination0.CalculateHourglassCount(gameConfigState, diff); + } + + var materialItemSheet = states.GetSheet(); + var row = materialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.Hourglass); + var hourGlass = ItemFactory.CreateMaterial(row); + if (!avatarState.inventory.RemoveFungibleItem(hourGlass, context.BlockIndex, costHourglassCount)) + { + throw new NotEnoughMaterialException( + $"{addressesHex}Aborted as the player has no enough material ({row.Id} * {costHourglassCount})"); + } + + if (slotState.TryGetResultId(out var resultId) && + avatarState.mailBox.All(mail => mail.id != resultId) && + slotState.TryGetMail( + context.BlockIndex, + context.BlockIndex, + out var combinationMail, + out var itemEnhanceMail)) + { + if (combinationMail != null) + { + avatarState.Update(combinationMail); + } + else if (itemEnhanceMail != null) + { + avatarState.Update(itemEnhanceMail); + } + } + + slotState.UpdateV2(context.BlockIndex, hourGlass, costHourglassCount); + avatarState.UpdateFromRapidCombinationV2( + (RapidCombination5.ResultModel)slotState.Result, + context.BlockIndex); + + // Update Pet + if (!(petState is null)) + { + petState.Update(context.BlockIndex); + var petStateAddress = PetState.DeriveAddress(avatarAddress, petState.PetId); + states = states.SetState(petStateAddress, petState.Serialize()); + } + + var ended = DateTimeOffset.UtcNow; + Log.Debug("{AddressesHex}RapidCombination Total Executed Time: {Elapsed}", addressesHex, ended - started); + return states + .SetState(avatarAddress, avatarState.SerializeV2()) + .SetState(inventoryAddress, avatarState.inventory.Serialize()) + .SetState(worldInformationAddress, avatarState.worldInformation.Serialize()) + .SetState(questListAddress, avatarState.questList.Serialize()) + .SetState(slotAddress, slotState.Serialize()); + } + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + ["avatarAddress"] = avatarAddress.Serialize(), + ["slotIndex"] = slotIndex.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + avatarAddress = plainValue["avatarAddress"].ToAddress(); + slotIndex = plainValue["slotIndex"].ToInteger(); + } + } +} diff --git a/Lib9c/Action/ReRegisterProduct.cs b/Lib9c/Action/ReRegisterProduct.cs index e2c44bc351..2262d92407 100644 --- a/Lib9c/Action/ReRegisterProduct.cs +++ b/Lib9c/Action/ReRegisterProduct.cs @@ -155,10 +155,10 @@ public override IAccount Execute(IActionContext context) } else { - states = CancelProductRegistration.Cancel(productsState, productInfo, + states = CancelProductRegistration0.Cancel(productsState, productInfo, states, avatarState, context); } - states = RegisterProduct.Register(context, info, avatarState, productsState, states); + states = RegisterProduct2.Register(context, info, avatarState, productsState, states); } states = states diff --git a/Lib9c/Action/ReRegisterProduct0.cs b/Lib9c/Action/ReRegisterProduct0.cs index 6529ae67c0..4cbc1a6b4a 100644 --- a/Lib9c/Action/ReRegisterProduct0.cs +++ b/Lib9c/Action/ReRegisterProduct0.cs @@ -155,7 +155,7 @@ public override IAccount Execute(IActionContext context) } else { - states = CancelProductRegistration.Cancel(productsState, productInfo, + states = CancelProductRegistration0.Cancel(productsState, productInfo, states, avatarState, context); } states = RegisterProduct0.Register(context, info, avatarState, productsState, states); diff --git a/Lib9c/Action/RegisterProduct.cs b/Lib9c/Action/RegisterProduct.cs index 35583fb878..30453cb4ee 100644 --- a/Lib9c/Action/RegisterProduct.cs +++ b/Lib9c/Action/RegisterProduct.cs @@ -16,7 +16,7 @@ namespace Nekoyume.Action { - [ActionType("register_product2")] + [ActionType("register_product3")] public class RegisterProduct : GameAction { public const int CostAp = 5; diff --git a/Lib9c/Action/RegisterProduct0.cs b/Lib9c/Action/RegisterProduct0.cs index f973a3b776..41a9c2286b 100644 --- a/Lib9c/Action/RegisterProduct0.cs +++ b/Lib9c/Action/RegisterProduct0.cs @@ -17,6 +17,7 @@ namespace Nekoyume.Action { [ActionType("register_product")] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] public class RegisterProduct0 : GameAction { public const int CostAp = 5; diff --git a/Lib9c/Action/RegisterProduct2.cs b/Lib9c/Action/RegisterProduct2.cs new file mode 100644 index 0000000000..eea1a1beb5 --- /dev/null +++ b/Lib9c/Action/RegisterProduct2.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Battle; +using Nekoyume.Model.Item; +using Nekoyume.Model.Market; +using Nekoyume.Model.State; +using Nekoyume.TableData; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] + [ActionType("register_product2")] + public class RegisterProduct2 : GameAction + { + public const int CostAp = 5; + public const int Capacity = 100; + public Address AvatarAddress; + public IEnumerable RegisterInfos; + public bool ChargeAp; + + public override IAccount Execute(IActionContext context) + { + context.UseGas(1); + var states = context.PreviousState; + if (context.Rehearsal) + { + return states; + } + + if (!RegisterInfos.Any()) + { + throw new ListEmptyException("RegisterInfos was empty"); + } + + if (RegisterInfos.Count() > Capacity) + { + throw new ArgumentOutOfRangeException($"{nameof(RegisterInfos)} must be less than or equal {Capacity}."); + } + + var ncg = states.GetGoldCurrency(); + foreach (var registerInfo in RegisterInfos) + { + registerInfo.ValidateAddress(AvatarAddress); + registerInfo.ValidatePrice(ncg); + registerInfo.Validate(); + } + + if (!states.TryGetAvatarStateV2(context.Signer, AvatarAddress, out var avatarState, + out var migrationRequired)) + { + throw new FailedLoadStateException("failed to load avatar state."); + } + + if (!avatarState.worldInformation.IsStageCleared( + GameConfig.RequireClearedStageLevel.ActionsInShop)) + { + avatarState.worldInformation.TryGetLastClearedStageId(out var current); + throw new NotEnoughClearedStageLevelException( + AvatarAddress.ToHex(), + GameConfig.RequireClearedStageLevel.ActionsInShop, + current); + } + + avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); + ProductsState productsState; + if (states.TryGetState(productsStateAddress, out List rawProducts)) + { + productsState = new ProductsState(rawProducts); + } + else + { + productsState = new ProductsState(); + var marketState = states.TryGetState(Addresses.Market, out List rawMarketList) + ? new MarketState(rawMarketList) + : new MarketState(); + marketState.AvatarAddresses.Add(AvatarAddress); + states = states.SetState(Addresses.Market, marketState.Serialize()); + } + foreach (var info in RegisterInfos.OrderBy(r => r.Type).ThenBy(r => r.Price)) + { + states = Register(context, info, avatarState, productsState, states); + } + + states = states + .SetState(AvatarAddress.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()) + .SetState(AvatarAddress, avatarState.SerializeV2()) + .SetState(productsStateAddress, productsState.Serialize()); + if (migrationRequired) + { + states = states + .SetState(AvatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) + .SetState(AvatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()); + } + + return states; + } + + public static IAccount Register(IActionContext context, IRegisterInfo info, AvatarState avatarState, + ProductsState productsState, IAccount states) + { + switch (info) + { + case RegisterInfo registerInfo: + switch (info.Type) + { + case ProductType.Fungible: + case ProductType.NonFungible: + { + var tradableId = registerInfo.TradableId; + var itemCount = registerInfo.ItemCount; + var type = registerInfo.Type; + ITradableItem tradableItem = null; + switch (type) + { + case ProductType.Fungible: + { + if (avatarState.inventory.TryGetTradableItems(tradableId, + context.BlockIndex, itemCount, out var items)) + { + int totalCount = itemCount; + tradableItem = (ITradableItem) items.First().item; + foreach (var inventoryItem in items) + { + int removeCount = Math.Min(totalCount, + inventoryItem.count); + ITradableFungibleItem tradableFungibleItem = + (ITradableFungibleItem) inventoryItem.item; + if (!avatarState.inventory.RemoveTradableItem( + tradableId, + tradableFungibleItem.RequiredBlockIndex, + removeCount)) + { + throw new ItemDoesNotExistException( + $"failed to remove tradable material {tradableId}/{itemCount}"); + } + + totalCount -= removeCount; + if (totalCount < 1) + { + break; + } + } + + if (totalCount != 0) + { + throw new InvalidItemCountException(); + } + } + + break; + } + case ProductType.NonFungible: + { + if (avatarState.inventory.TryGetNonFungibleItem(tradableId, + out var item) && + avatarState.inventory.RemoveNonFungibleItem(tradableId)) + { + tradableItem = item.item as ITradableItem; + } + + break; + } + } + + if (tradableItem is null || tradableItem.RequiredBlockIndex > context.BlockIndex) + { + throw new ItemDoesNotExistException($"can't find item: {tradableId}"); + } + + Guid productId = context.Random.GenerateRandomGuid(); + var product = new ItemProduct + { + ProductId = productId, + Price = registerInfo.Price, + TradableItem = tradableItem, + ItemCount = itemCount, + RegisteredBlockIndex = context.BlockIndex, + Type = registerInfo.Type, + SellerAgentAddress = context.Signer, + SellerAvatarAddress = registerInfo.AvatarAddress, + }; + productsState.ProductIds.Add(productId); + states = states.SetState(Product.DeriveAddress(productId), + product.Serialize()); + break; + } + } + + break; + case AssetInfo assetInfo: + { + Guid productId = context.Random.GenerateRandomGuid(); + Address productAddress = Product.DeriveAddress(productId); + FungibleAssetValue asset = assetInfo.Asset; + var product = new FavProduct + { + ProductId = productId, + Price = assetInfo.Price, + Asset = asset, + RegisteredBlockIndex = context.BlockIndex, + Type = assetInfo.Type, + SellerAgentAddress = context.Signer, + SellerAvatarAddress = assetInfo.AvatarAddress, + }; + states = states + .TransferAsset(context, avatarState.address, productAddress, asset) + .SetState(productAddress, product.Serialize()); + productsState.ProductIds.Add(productId); + break; + } + } + + return states; + } + + protected override IImmutableDictionary PlainValueInternal => + new Dictionary + { + ["r"] = new List(RegisterInfos.Select(r => r.Serialize())), + ["a"] = AvatarAddress.Serialize(), + ["c"] = ChargeAp.Serialize(), + }.ToImmutableDictionary(); + + protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) + { + var serialized = (List) plainValue["r"]; + RegisterInfos = serialized.Cast() + .Select(ProductFactory.DeserializeRegisterInfo).ToList(); + AvatarAddress = plainValue["a"].ToAddress(); + ChargeAp = plainValue["c"].ToBoolean(); + } + } +} diff --git a/Lib9c/Action/SellCancellation.cs b/Lib9c/Action/SellCancellation.cs index db55848918..b8f85edc11 100644 --- a/Lib9c/Action/SellCancellation.cs +++ b/Lib9c/Action/SellCancellation.cs @@ -27,6 +27,7 @@ namespace Nekoyume.Action /// Updated at https://github.com/planetarium/lib9c/pull/957 /// [Serializable] + [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] [ActionType("sell_cancellation9")] public class SellCancellation : GameAction, ISellCancellationV3 { diff --git a/Lib9c/ActionObsoleteConfig.cs b/Lib9c/ActionObsoleteConfig.cs index 015ec088f8..c32789e6c9 100644 --- a/Lib9c/ActionObsoleteConfig.cs +++ b/Lib9c/ActionObsoleteConfig.cs @@ -84,6 +84,8 @@ public static class ActionObsoleteConfig public const long V200090ObsoleteIndex = 8_070_865L; + public const long V200092ObsoleteIndex = 8_324_909L; + // While v200020, the action obsolete wasn't work well. // So other previous `V*ObsoletedIndex`s lost its meaning and // this block index will replace them. diff --git a/Lib9c/Extensions/CombinationSlotStateExtensions.cs b/Lib9c/Extensions/CombinationSlotStateExtensions.cs index 9724b67a2b..6d2b906aa6 100644 --- a/Lib9c/Extensions/CombinationSlotStateExtensions.cs +++ b/Lib9c/Extensions/CombinationSlotStateExtensions.cs @@ -47,7 +47,7 @@ public static bool TryGetResultId(this CombinationSlotState state, out Guid resu case DailyReward2.DailyRewardResult r: resultId = r.id; break; - case ItemEnhancement.ResultModel r: + case ItemEnhancement13.ResultModel r: resultId = r.id; break; case ItemEnhancement7.ResultModel r: @@ -108,7 +108,7 @@ public static bool TryGetResultIdV1(this CombinationSlotState state, out Guid re case DailyReward2.DailyRewardResult r: resultId = r.id; break; - case ItemEnhancement.ResultModel r: + case ItemEnhancement13.ResultModel r: resultId = r.id; break; case ItemEnhancement7.ResultModel r: @@ -160,7 +160,7 @@ public static bool TryGetMail( switch (state.Result) { - case ItemEnhancement.ResultModel r: + case ItemEnhancement13.ResultModel r: itemEnhanceMail = new ItemEnhanceMail( r, blockIndex, @@ -225,7 +225,7 @@ public static bool TryGetMailV1( switch (state.Result) { - case ItemEnhancement.ResultModel r: + case ItemEnhancement13.ResultModel r: itemEnhanceMail = new ItemEnhanceMail( r, blockIndex, From 3ae7ae68ad49261bcdd46108397278875ebf08d7 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 11:49:07 +0900 Subject: [PATCH 03/13] Obsolete variable at GameConfig - CombinationEquipmentAction, CombinationConsumableAction --- Lib9c.DevExtensions/Action/CreateTestbed.cs | 2 +- Lib9c/Action/CombinationConsumable.cs | 14 +------------- Lib9c/Action/CombinationEquipment.cs | 16 ++-------------- Lib9c/Action/CreateAvatar.cs | 3 +-- Lib9c/Action/EventConsumableItemCrafts.cs | 16 +--------------- Lib9c/Action/EventMaterialItemCrafts.cs | 15 --------------- Lib9c/Action/ItemEnhancement.cs | 2 +- Lib9c/Action/RapidCombination.cs | 6 ------ Lib9c/GameConfig.cs | 3 +++ Lib9c/Model/State/CombinationSlotState.cs | 13 +++++++++++++ 10 files changed, 23 insertions(+), 67 deletions(-) diff --git a/Lib9c.DevExtensions/Action/CreateTestbed.cs b/Lib9c.DevExtensions/Action/CreateTestbed.cs index 5978131dc8..bbad34b416 100644 --- a/Lib9c.DevExtensions/Action/CreateTestbed.cs +++ b/Lib9c.DevExtensions/Action/CreateTestbed.cs @@ -184,7 +184,7 @@ public override IAccount Execute(IActionContext context) { var slotState = new CombinationSlotState(address, - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + 0); states = states.SetState(address, slotState.Serialize()); } diff --git a/Lib9c/Action/CombinationConsumable.cs b/Lib9c/Action/CombinationConsumable.cs index b76ea81f64..34938204ed 100644 --- a/Lib9c/Action/CombinationConsumable.cs +++ b/Lib9c/Action/CombinationConsumable.cs @@ -91,18 +91,6 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } - // Validate Required Cleared Stage - if (!avatarState.worldInformation.IsStageCleared( - GameConfig.RequireClearedStageLevel.CombinationConsumableAction)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - addressesHex, - GameConfig.RequireClearedStageLevel.CombinationConsumableAction, - current); - } - // ~Validate Required Cleared Stage - // Validate SlotIndex var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); if (slotState is null) @@ -111,7 +99,7 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); } - if (!slotState.Validate(avatarState, context.BlockIndex)) + if (!slotState.ValidateV2(avatarState, context.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); diff --git a/Lib9c/Action/CombinationEquipment.cs b/Lib9c/Action/CombinationEquipment.cs index 8d3780e6a0..354375c038 100644 --- a/Lib9c/Action/CombinationEquipment.cs +++ b/Lib9c/Action/CombinationEquipment.cs @@ -113,18 +113,6 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } - // Validate Required Cleared Tutorial Stage - if (!avatarState.worldInformation.IsStageCleared( - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - addressesHex, - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction, - current); - } - // ~Validate Required Cleared Tutorial Stage - // Validate SlotIndex var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); if (slotState is null) @@ -133,7 +121,7 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); } - if (!slotState.Validate(avatarState, context.BlockIndex)) + if (!slotState.ValidateV2(avatarState, context.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); @@ -703,7 +691,7 @@ SkillSheet skillSheet } else { - var skill = CombinationEquipment.GetSkill(optionRow, skillSheet, random); + var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); if (!(skill is null)) { equipment.Skills.Add(skill); diff --git a/Lib9c/Action/CreateAvatar.cs b/Lib9c/Action/CreateAvatar.cs index def31f5edc..e5c55a4db6 100644 --- a/Lib9c/Action/CreateAvatar.cs +++ b/Lib9c/Action/CreateAvatar.cs @@ -158,8 +158,7 @@ public override IAccount Execute(IActionContext context) foreach (var address in avatarState.combinationSlotAddresses) { - var slotState = - new CombinationSlotState(address, GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + var slotState = new CombinationSlotState(address, 0); states = states.SetState(address, slotState.Serialize()); } diff --git a/Lib9c/Action/EventConsumableItemCrafts.cs b/Lib9c/Action/EventConsumableItemCrafts.cs index 5cde758654..a587ea59d0 100644 --- a/Lib9c/Action/EventConsumableItemCrafts.cs +++ b/Lib9c/Action/EventConsumableItemCrafts.cs @@ -135,20 +135,6 @@ public override IAccount Execute(IActionContext context) sw.Elapsed); // ~Get sheets - // Validate Requirements. - sw.Restart(); - avatarState.worldInformation.ValidateFromAction( - GameConfig.RequireClearedStageLevel.CombinationConsumableAction, - ActionTypeText, - addressesHex); - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Requirements. - // Validate fields. sw.Restart(); var scheduleSheet = sheets.GetSheet(); @@ -172,7 +158,7 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the slot state is failed to load: # {SlotIndex}"); } - if (!slotState.Validate(avatarState, context.BlockIndex)) + if (!slotState.ValidateV2(avatarState, context.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); diff --git a/Lib9c/Action/EventMaterialItemCrafts.cs b/Lib9c/Action/EventMaterialItemCrafts.cs index b560bacfdd..6f6986204c 100644 --- a/Lib9c/Action/EventMaterialItemCrafts.cs +++ b/Lib9c/Action/EventMaterialItemCrafts.cs @@ -145,21 +145,6 @@ public override IAccount Execute(IActionContext context) sw.Elapsed); // ~Get sheets - // Validate Requirements - sw.Restart(); - avatarState.worldInformation.ValidateFromAction( - GameConfig.RequireClearedStageLevel.CombinationConsumableAction, - ActionTypeText, - addressesHex); - sw.Stop(); - - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Requirements - // Validate fields sw.Restart(); var scheduleSheet = sheets.GetSheet(); diff --git a/Lib9c/Action/ItemEnhancement.cs b/Lib9c/Action/ItemEnhancement.cs index c8703947a5..770f3dd6c6 100644 --- a/Lib9c/Action/ItemEnhancement.cs +++ b/Lib9c/Action/ItemEnhancement.cs @@ -151,7 +151,7 @@ public override IAccount Execute(IActionContext context) ); } - if (!slotState.Validate(avatarState, ctx.BlockIndex)) + if (!slotState.ValidateV2(avatarState, ctx.BlockIndex)) { throw new CombinationSlotUnlockException( $"{addressesHex} Aborted as the slot state was failed to invalid. #{slotIndex}" diff --git a/Lib9c/Action/RapidCombination.cs b/Lib9c/Action/RapidCombination.cs index 4cd91c7130..e24a3a3a31 100644 --- a/Lib9c/Action/RapidCombination.cs +++ b/Lib9c/Action/RapidCombination.cs @@ -76,12 +76,6 @@ public override IAccount Execute(IActionContext context) throw new CombinationSlotResultNullException($"{addressesHex}CombinationSlot Result is null. ({avatarAddress}), ({slotIndex})"); } - if(!avatarState.worldInformation.IsStageCleared(slotState.UnlockStage)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(addressesHex, slotState.UnlockStage, current); - } - var diff = slotState.Result.itemUsable.RequiredBlockIndex - context.BlockIndex; if (diff <= 0) { diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index 44d79efb4f..41578c4cba 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -92,8 +92,11 @@ public static class RequireClearedStageLevel { #region action + [Obsolete("Not used anymore since v200092")] public const int CombinationEquipmentAction = 3; + [Obsolete("Not used anymore since v200092")] public const int CombinationConsumableAction = 6; + [Obsolete("Not used anymore since v200092")] public const int ItemEnhancementAction = 9; public const int ActionsInShop = 17; public const int ActionsInRankingBoard = 25; diff --git a/Lib9c/Model/State/CombinationSlotState.cs b/Lib9c/Model/State/CombinationSlotState.cs index c6d60aef89..bc4119a5e4 100644 --- a/Lib9c/Model/State/CombinationSlotState.cs +++ b/Lib9c/Model/State/CombinationSlotState.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -12,6 +13,7 @@ public class CombinationSlotState : State { public const string DeriveFormat = "combination-slot-{0}"; public long UnlockBlockIndex { get; private set; } + [Obsolete("Not used anymore since v200092")] public int UnlockStage { get; private set; } public long StartBlockIndex { get; private set; } public AttachmentActionResult Result { get; private set; } @@ -49,6 +51,7 @@ public CombinationSlotState(Dictionary serialized) : base(serialized) } } + [Obsolete("Use ValidateV2")] public bool Validate(AvatarState avatarState, long blockIndex) { if (avatarState is null) @@ -61,6 +64,16 @@ public bool Validate(AvatarState avatarState, long blockIndex) blockIndex >= UnlockBlockIndex; } + public bool ValidateV2(AvatarState avatarState, long blockIndex) + { + if (avatarState is null) + { + return false; + } + + return blockIndex >= UnlockBlockIndex; + } + public void Update(AttachmentActionResult result, long blockIndex, long unlockBlockIndex, int? petId = null) { Result = result; From 37b60761fb3012cbe14de6b733f4d5d5895e93a1 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 11:52:20 +0900 Subject: [PATCH 04/13] Obsolete variable at GameConfig - ActionsInShop --- Lib9c.DevExtensions/TestbedHelper.cs | 2 +- Lib9c/Action/BuyProduct.cs | 9 --------- Lib9c/Action/CancelProductRegistration.cs | 7 ------- Lib9c/Action/RegisterProduct.cs | 10 ---------- Lib9c/GameConfig.cs | 1 + 5 files changed, 2 insertions(+), 27 deletions(-) diff --git a/Lib9c.DevExtensions/TestbedHelper.cs b/Lib9c.DevExtensions/TestbedHelper.cs index 59989a7c49..eca39da140 100644 --- a/Lib9c.DevExtensions/TestbedHelper.cs +++ b/Lib9c.DevExtensions/TestbedHelper.cs @@ -53,7 +53,7 @@ public static AvatarState CreateAvatarState(string name, worldInformation = new WorldInformation( 0, worldSheet, - GameConfig.RequireClearedStageLevel.ActionsInShop), + 0), }; return avatarState; diff --git a/Lib9c/Action/BuyProduct.cs b/Lib9c/Action/BuyProduct.cs index 590f2125ce..0793833d55 100644 --- a/Lib9c/Action/BuyProduct.cs +++ b/Lib9c/Action/BuyProduct.cs @@ -71,15 +71,6 @@ public override IAccount Execute(IActionContext context) throw new FailedLoadStateException("failed load to buyer avatar state."); } - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - - if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(addressesHex, - GameConfig.RequireClearedStageLevel.ActionsInShop, current); - } - var materialSheet = states.GetSheet(); foreach (var productInfo in ProductInfos.OrderBy(p => p.ProductId).ThenBy(p =>p.Price)) { diff --git a/Lib9c/Action/CancelProductRegistration.cs b/Lib9c/Action/CancelProductRegistration.cs index 2049fb9d9e..769a740144 100644 --- a/Lib9c/Action/CancelProductRegistration.cs +++ b/Lib9c/Action/CancelProductRegistration.cs @@ -60,13 +60,6 @@ public override IAccount Execute(IActionContext context) throw new FailedLoadStateException("failed to load avatar state"); } - if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(AvatarAddress.ToHex(), - GameConfig.RequireClearedStageLevel.ActionsInShop, current); - } - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); ProductsState productsState; diff --git a/Lib9c/Action/RegisterProduct.cs b/Lib9c/Action/RegisterProduct.cs index 30453cb4ee..01f50bea02 100644 --- a/Lib9c/Action/RegisterProduct.cs +++ b/Lib9c/Action/RegisterProduct.cs @@ -58,16 +58,6 @@ public override IAccount Execute(IActionContext context) throw new FailedLoadStateException("failed to load avatar state."); } - if (!avatarState.worldInformation.IsStageCleared( - GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - AvatarAddress.ToHex(), - GameConfig.RequireClearedStageLevel.ActionsInShop, - current); - } - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); ProductsState productsState; diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index 41578c4cba..b4cd6815eb 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -98,6 +98,7 @@ public static class RequireClearedStageLevel public const int CombinationConsumableAction = 6; [Obsolete("Not used anymore since v200092")] public const int ItemEnhancementAction = 9; + [Obsolete("Not used anymore since v200092")] public const int ActionsInShop = 17; public const int ActionsInRankingBoard = 25; public const int ActionsInMimisbrunnr = 100; From 2d7c6052bf8cc224a814e7b3d64c2c3c11885583 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 11:53:38 +0900 Subject: [PATCH 05/13] Obsolete variable at GameConfig - ActionsInRankingBoard --- Lib9c/Action/BattleArena.cs | 15 --------------- Lib9c/Action/JoinArena.cs | 15 --------------- Lib9c/GameConfig.cs | 1 + 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/Lib9c/Action/BattleArena.cs b/Lib9c/Action/BattleArena.cs index 2efc5c0891..e06642155d 100644 --- a/Lib9c/Action/BattleArena.cs +++ b/Lib9c/Action/BattleArena.cs @@ -118,21 +118,6 @@ public override IAccount Execute(IActionContext context) $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); } - if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex( - out var world)) - { - throw new NotEnoughClearedStageLevelException( - $"{addressesHex}Aborted as NotEnoughClearedStageLevelException"); - } - - if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) - { - throw new NotEnoughClearedStageLevelException( - addressesHex, - GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, - world.StageClearedId); - } - var sheets = states.GetSheets( containArenaSimulatorSheets: true, sheetTypes: new[] diff --git a/Lib9c/Action/JoinArena.cs b/Lib9c/Action/JoinArena.cs index 401c72dccf..ef97f50a8f 100644 --- a/Lib9c/Action/JoinArena.cs +++ b/Lib9c/Action/JoinArena.cs @@ -86,21 +86,6 @@ public override IAccount Execute(IActionContext context) $"[{nameof(JoinArena)}] Aborted as the avatar state of the signer failed to load."); } - if (!avatarState.worldInformation.TryGetUnlockedWorldByStageClearedBlockIndex( - out var world)) - { - throw new NotEnoughClearedStageLevelException( - $"{addressesHex}Aborted as NotEnoughClearedStageLevelException"); - } - - if (world.StageClearedId < GameConfig.RequireClearedStageLevel.ActionsInRankingBoard) - { - throw new NotEnoughClearedStageLevelException( - addressesHex, - GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, - world.StageClearedId); - } - var sheets = states.GetSheets( sheetTypes: new[] { diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index b4cd6815eb..a3873bdf8c 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -100,6 +100,7 @@ public static class RequireClearedStageLevel public const int ItemEnhancementAction = 9; [Obsolete("Not used anymore since v200092")] public const int ActionsInShop = 17; + [Obsolete("Not used anymore since v200092")] public const int ActionsInRankingBoard = 25; public const int ActionsInMimisbrunnr = 100; public const int ActionsInRaid = 50; From 8e91e530f42e4d504b138d823be51c1f3d5ef7a6 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 11:54:38 +0900 Subject: [PATCH 06/13] Obsolete variable at GameConfig - ActionsInMimisbrunnr, ActionsInRaid --- Lib9c/Action/Raid.cs | 7 ------- Lib9c/GameConfig.cs | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib9c/Action/Raid.cs b/Lib9c/Action/Raid.cs index 3fd336c6fd..6980094162 100644 --- a/Lib9c/Action/Raid.cs +++ b/Lib9c/Action/Raid.cs @@ -61,13 +61,6 @@ public override IAccount Execute(IActionContext context) throw new FailedLoadStateException( $"Aborted as the avatar state of the signer was failed to load."); } - // Check stage level. - if (!avatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInRaid)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out int current); - throw new NotEnoughClearedStageLevelException(AvatarAddress.ToHex(), - GameConfig.RequireClearedStageLevel.ActionsInRaid, current); - } Dictionary sheets = states.GetSheets( containRaidSimulatorSheets: true, diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index a3873bdf8c..382f51b498 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -102,7 +102,9 @@ public static class RequireClearedStageLevel public const int ActionsInShop = 17; [Obsolete("Not used anymore since v200092")] public const int ActionsInRankingBoard = 25; + [Obsolete("Not used anymore since v200092")] public const int ActionsInMimisbrunnr = 100; + [Obsolete("Not used anymore since v200092")] public const int ActionsInRaid = 50; #endregion From 0a55e691e24635bc2e7049befdde605526801380 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Tue, 31 Oct 2023 18:25:52 +0900 Subject: [PATCH 07/13] Add variable at GameConfig to GameConfigState Add variable at GameConfig to GameConfigState Add variable at GameConfig to GameConfigState --- .Lib9c.Tests/Action/ItemEnhancementTest.cs | 2 +- .../TableCSV/GameConfigSheetFixtures.cs | 18 + .../Model/State/GameConfigStateTest.cs | 21 ++ Lib9c/GameConfig.cs | 18 + Lib9c/Model/State/GameConfigState.cs | 316 ++++++++++++++++++ Lib9c/TableCSV/GameConfigSheet.csv | 18 + 6 files changed, 392 insertions(+), 1 deletion(-) diff --git a/.Lib9c.Tests/Action/ItemEnhancementTest.cs b/.Lib9c.Tests/Action/ItemEnhancementTest.cs index 63a16fc2bc..a63971fffe 100644 --- a/.Lib9c.Tests/Action/ItemEnhancementTest.cs +++ b/.Lib9c.Tests/Action/ItemEnhancementTest.cs @@ -331,7 +331,7 @@ public void Execute( var stateDict = (Dictionary)nextState.GetState(slotAddress); var slot = new CombinationSlotState(stateDict); - var slotResult = (ItemEnhancement.ResultModel)slot.Result; + var slotResult = (ItemEnhancement13.ResultModel)slot.Result; if (startLevel != expectedLevel) { var baseMinAtk = (decimal)preItemUsable.StatsMap.BaseATK; diff --git a/.Lib9c.Tests/Fixtures/TableCSV/GameConfigSheetFixtures.cs b/.Lib9c.Tests/Fixtures/TableCSV/GameConfigSheetFixtures.cs index 49ac471096..3e0229cb40 100644 --- a/.Lib9c.Tests/Fixtures/TableCSV/GameConfigSheetFixtures.cs +++ b/.Lib9c.Tests/Fixtures/TableCSV/GameConfigSheetFixtures.cs @@ -20,6 +20,24 @@ public static class GameConfigSheetFixtures stake_regular_reward_sheet_v3_start_block_index,6700000 stake_regular_reward_sheet_v4_start_block_index,6910000 stake_regular_reward_sheet_v5_start_block_index,7650000 +character_full_costume_slot,2 +character_hair_costume_slot,2 +character_ear_costume_slot,2 +character_eye_costume_slot,2 +character_tail_costume_slot,2 +character_title_costume_slot,1 +character_equipment_slot_weapon,1 +character_equipment_slot_armor,3 +character_equipment_slot_belt,5 +character_equipment_slot_necklace,8 +character_equipment_slot_ring1,13 +character_equipment_slot_ring2,46 +character_equipment_slot_aura,1 +character_consumable_slot_1,1 +character_consumable_slot_2,35 +character_consumable_slot_3,100 +character_consumable_slot_4,200 +character_consumable_slot_5,350 "; } } diff --git a/.Lib9c.Tests/Model/State/GameConfigStateTest.cs b/.Lib9c.Tests/Model/State/GameConfigStateTest.cs index 8302cf27ee..8b4e4cf0ea 100644 --- a/.Lib9c.Tests/Model/State/GameConfigStateTest.cs +++ b/.Lib9c.Tests/Model/State/GameConfigStateTest.cs @@ -99,6 +99,27 @@ private static void AssertDefaultGameConfigState(GameConfigState state) Assert.Equal(6_700_000L, state.StakeRegularRewardSheet_V3_StartBlockIndex); Assert.Equal(6_910_000L, state.StakeRegularRewardSheet_V4_StartBlockIndex); Assert.Equal(7_650_000L, state.StakeRegularRewardSheet_V5_StartBlockIndex); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, state.RequireCharacterLevel_FullCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterHairCostumeSlot, state.RequireCharacterLevel_HairCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEarCostumeSlot, state.RequireCharacterLevel_EarCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEyeCostumeSlot, state.RequireCharacterLevel_EyeCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterTailCostumeSlot, state.RequireCharacterLevel_TailCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterTitleSlot, state.RequireCharacterLevel_TitleSlot); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotWeapon, state.RequireCharacterLevel_EquipmentSlotWeapon); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotArmor, state.RequireCharacterLevel_EquipmentSlotArmor); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotBelt, state.RequireCharacterLevel_EquipmentSlotBelt); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotNecklace, state.RequireCharacterLevel_EquipmentSlotNecklace); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing1, state.RequireCharacterLevel_EquipmentSlotRing1); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing2, state.RequireCharacterLevel_EquipmentSlotRing2); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotAura, state.RequireCharacterLevel_EquipmentSlotAura); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot1, state.RequireCharacterLevel_ConsumableSlot1); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot2, state.RequireCharacterLevel_ConsumableSlot2); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot3, state.RequireCharacterLevel_ConsumableSlot3); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot4, state.RequireCharacterLevel_ConsumableSlot4); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot5, state.RequireCharacterLevel_ConsumableSlot5); } private static GameConfigSheet GetDefaultGameConfigSheet() diff --git a/Lib9c/GameConfig.cs b/Lib9c/GameConfig.cs index 382f51b498..ace55871c4 100644 --- a/Lib9c/GameConfig.cs +++ b/Lib9c/GameConfig.cs @@ -56,33 +56,51 @@ public static class RequireCharacterLevel { #region character costume slot + [Obsolete("Use GameConfigState.RequireCharacterLevel_FullCostumeSlot")] public const int CharacterFullCostumeSlot = 2; + [Obsolete("Use GameConfigState.RequireCharacterLevel_HairCostumeSlot")] public const int CharacterHairCostumeSlot = 2; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EarCostumeSlot")] public const int CharacterEarCostumeSlot = 2; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EyeCostumeSlot")] public const int CharacterEyeCostumeSlot = 2; + [Obsolete("Use GameConfigState.RequireCharacterLevel_TailCostumeSlot")] public const int CharacterTailCostumeSlot = 2; + [Obsolete("Use GameConfigState.RequireCharacterLevel_TitleSlot")] public const int CharacterTitleSlot = 1; #endregion #region character equipment slot + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotWeapon")] public const int CharacterEquipmentSlotWeapon = 1; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotArmor")] public const int CharacterEquipmentSlotArmor = 3; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotBelt")] public const int CharacterEquipmentSlotBelt = 5; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotNecklace")] public const int CharacterEquipmentSlotNecklace = 8; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotRing1")] public const int CharacterEquipmentSlotRing1 = 13; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotRing2")] public const int CharacterEquipmentSlotRing2 = 46; + [Obsolete("Use GameConfigState.RequireCharacterLevel_EquipmentSlotAura")] public const int CharacterEquipmentSlotAura = 1; #endregion #region character consumable slot + [Obsolete("Use GameConfigState.RequireCharacterLevel_ConsumableSlot1")] public const int CharacterConsumableSlot1 = 1; + [Obsolete("Use GameConfigState.RequireCharacterLevel_ConsumableSlot2")] public const int CharacterConsumableSlot2 = 35; + [Obsolete("Use GameConfigState.RequireCharacterLevel_ConsumableSlot3")] public const int CharacterConsumableSlot3 = 100; + [Obsolete("Use GameConfigState.RequireCharacterLevel_ConsumableSlot4")] public const int CharacterConsumableSlot4 = 200; + [Obsolete("Use GameConfigState.RequireCharacterLevel_ConsumableSlot5")] public const int CharacterConsumableSlot5 = 350; #endregion diff --git a/Lib9c/Model/State/GameConfigState.cs b/Lib9c/Model/State/GameConfigState.cs index 3f9cbb8e48..c63f0e6151 100644 --- a/Lib9c/Model/State/GameConfigState.cs +++ b/Lib9c/Model/State/GameConfigState.cs @@ -31,6 +31,27 @@ public class GameConfigState : State public long StakeRegularRewardSheet_V4_StartBlockIndex { get; private set; } public long StakeRegularRewardSheet_V5_StartBlockIndex { get; private set; } + public int RequireCharacterLevel_FullCostumeSlot { get; private set; } + public int RequireCharacterLevel_HairCostumeSlot { get; private set; } + public int RequireCharacterLevel_EarCostumeSlot { get; private set; } + public int RequireCharacterLevel_EyeCostumeSlot { get; private set; } + public int RequireCharacterLevel_TailCostumeSlot { get; private set; } + public int RequireCharacterLevel_TitleSlot { get; private set; } + + public int RequireCharacterLevel_EquipmentSlotWeapon { get; private set; } + public int RequireCharacterLevel_EquipmentSlotArmor { get; private set; } + public int RequireCharacterLevel_EquipmentSlotBelt { get; private set; } + public int RequireCharacterLevel_EquipmentSlotNecklace { get; private set; } + public int RequireCharacterLevel_EquipmentSlotRing1 { get; private set; } + public int RequireCharacterLevel_EquipmentSlotRing2 { get; private set; } + public int RequireCharacterLevel_EquipmentSlotAura { get; private set; } + + public int RequireCharacterLevel_ConsumableSlot1 { get; private set; } + public int RequireCharacterLevel_ConsumableSlot2 { get; private set; } + public int RequireCharacterLevel_ConsumableSlot3 { get; private set; } + public int RequireCharacterLevel_ConsumableSlot4 { get; private set; } + public int RequireCharacterLevel_ConsumableSlot5 { get; private set; } + public GameConfigState() : base(Address) { } @@ -120,6 +141,99 @@ public GameConfigState(Dictionary serialized) : base(serialized) StakeRegularRewardSheet_V5_StartBlockIndex = stakeRegularRewardSheetV5StartBlockIndex.ToLong(); } + + if (serialized.TryGetValue((Text)"character_full_costume_slot", + out var characterFullCostumeSlot)) + { + RequireCharacterLevel_FullCostumeSlot = characterFullCostumeSlot.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_hair_costume_slot", + out var characterHairCostumeSlot)) + { + RequireCharacterLevel_HairCostumeSlot = characterHairCostumeSlot.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_ear_costume_slot", + out var characterEarCostumeSlot)) + { + RequireCharacterLevel_EarCostumeSlot = characterEarCostumeSlot.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_eye_costume_slot", + out var characterEyeCostumeSlot)) + { + RequireCharacterLevel_EyeCostumeSlot = characterEyeCostumeSlot.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_tail_costume_slot", + out var characterTailCostumeSlot)) + { + RequireCharacterLevel_TailCostumeSlot = characterTailCostumeSlot.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_title_costume_slot", + out var characterTitleSlot)) + { + RequireCharacterLevel_TitleSlot = characterTitleSlot.ToInteger(); + } + + if (serialized.TryGetValue((Text)"character_equipment_slot_weapon", + out var characterEquipmentSlotWeapon)) + { + RequireCharacterLevel_EquipmentSlotWeapon = characterEquipmentSlotWeapon.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_armor", + out var characterEquipmentSlotArmor)) + { + RequireCharacterLevel_EquipmentSlotArmor = characterEquipmentSlotArmor.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_belt", out var characterEquipmentSlotBelt)) + { + RequireCharacterLevel_EquipmentSlotBelt = characterEquipmentSlotBelt.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_necklace", + out var characterEquipmentSlotNecklace)) + { + RequireCharacterLevel_EquipmentSlotNecklace = characterEquipmentSlotNecklace.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_ring1", + out var characterEquipmentSlotRing1)) + { + RequireCharacterLevel_EquipmentSlotRing1 = characterEquipmentSlotRing1.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_ring2", + out var characterEquipmentSlotRing2)) + { + RequireCharacterLevel_EquipmentSlotRing2 = characterEquipmentSlotRing2.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_equipment_slot_aura", + out var characterEquipmentSlotAura)) + { + RequireCharacterLevel_EquipmentSlotAura = characterEquipmentSlotAura.ToInteger(); + } + + if (serialized.TryGetValue((Text)"character_consumable_slot_1", + out var characterConsumableSlot1)) + { + RequireCharacterLevel_ConsumableSlot1 = characterConsumableSlot1.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_consumable_slot_2", + out var characterConsumableSlot2)) + { + RequireCharacterLevel_ConsumableSlot2 = characterConsumableSlot2.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_consumable_slot_3", + out var characterConsumableSlot3)) + { + RequireCharacterLevel_ConsumableSlot3 = characterConsumableSlot3.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_consumable_slot_4", + out var characterConsumableSlot4)) + { + RequireCharacterLevel_ConsumableSlot4 = characterConsumableSlot4.ToInteger(); + } + if (serialized.TryGetValue((Text)"character_consumable_slot_5", + out var characterConsumableSlot5)) + { + RequireCharacterLevel_ConsumableSlot5 = characterConsumableSlot5.ToInteger(); + } + } public GameConfigState(string csv) : base(Address) @@ -208,6 +322,132 @@ public override IValue Serialize() StakeRegularRewardSheet_V5_StartBlockIndex.Serialize()); } + if (RequireCharacterLevel_FullCostumeSlot > 0) + { + values.Add( + (Text)"character_full_costume_slot", + RequireCharacterLevel_FullCostumeSlot.Serialize()); + } + + if (RequireCharacterLevel_HairCostumeSlot > 0) + { + values.Add( + (Text)"character_hair_costume_slot", + RequireCharacterLevel_HairCostumeSlot.Serialize()); + } + + if (RequireCharacterLevel_EarCostumeSlot > 0) + { + values.Add( + (Text)"character_ear_costume_slot", + RequireCharacterLevel_EarCostumeSlot.Serialize()); + } + + if (RequireCharacterLevel_EyeCostumeSlot > 0) + { + values.Add( + (Text)"character_eye_costume_slot", + RequireCharacterLevel_EyeCostumeSlot.Serialize()); + } + + if (RequireCharacterLevel_TailCostumeSlot > 0) + { + values.Add( + (Text)"character_tail_costume_slot", + RequireCharacterLevel_TailCostumeSlot.Serialize()); + } + + if (RequireCharacterLevel_TitleSlot > 0) + { + values.Add( + (Text)"character_title_costume_slot", + RequireCharacterLevel_TitleSlot.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotWeapon > 0) + { + values.Add( + (Text)"character_equipment_slot_weapon", + RequireCharacterLevel_EquipmentSlotWeapon.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotArmor > 0) + { + values.Add( + (Text)"character_equipment_slot_armor", + RequireCharacterLevel_EquipmentSlotArmor.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotBelt > 0) + { + values.Add( + (Text)"character_equipment_slot_belt", + RequireCharacterLevel_EquipmentSlotBelt.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotNecklace > 0) + { + values.Add( + (Text)"character_equipment_slot_necklace", + RequireCharacterLevel_EquipmentSlotNecklace.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotRing1 > 0) + { + values.Add( + (Text)"character_equipment_slot_ring1", + RequireCharacterLevel_EquipmentSlotRing1.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotRing2 > 0) + { + values.Add( + (Text)"character_equipment_slot_ring2", + RequireCharacterLevel_EquipmentSlotRing2.Serialize()); + } + + if (RequireCharacterLevel_EquipmentSlotAura > 0) + { + values.Add( + (Text)"character_equipment_slot_aura", + RequireCharacterLevel_EquipmentSlotAura.Serialize()); + } + + if (RequireCharacterLevel_ConsumableSlot1 > 0) + { + values.Add( + (Text)"character_consumable_slot_1", + RequireCharacterLevel_ConsumableSlot1.Serialize()); + } + + if (RequireCharacterLevel_ConsumableSlot2 > 0) + { + values.Add( + (Text)"character_consumable_slot_2", + RequireCharacterLevel_ConsumableSlot2.Serialize()); + } + + if (RequireCharacterLevel_ConsumableSlot3 > 0) + { + values.Add( + (Text)"character_consumable_slot_3", + RequireCharacterLevel_ConsumableSlot3.Serialize()); + } + + if (RequireCharacterLevel_ConsumableSlot4 > 0) + { + values.Add( + (Text)"character_consumable_slot_4", + RequireCharacterLevel_ConsumableSlot4.Serialize()); + } + + if (RequireCharacterLevel_ConsumableSlot5 > 0) + { + values.Add( + (Text)"character_consumable_slot_5", + RequireCharacterLevel_ConsumableSlot5.Serialize()); + } + #pragma warning disable LAA1002 return new Dictionary(values.Union((Dictionary) base.Serialize())); #pragma warning restore LAA1002 @@ -286,6 +526,82 @@ public void Update(GameConfigSheet.Row row) StakeRegularRewardSheet_V5_StartBlockIndex = TableExtensions.ParseLong(row.Value); break; + + case "character_full_costume_slot": + RequireCharacterLevel_FullCostumeSlot = + TableExtensions.ParseInt(row.Value); + break; + case "character_hair_costume_slot": + RequireCharacterLevel_HairCostumeSlot = + TableExtensions.ParseInt(row.Value); + break; + case "character_ear_costume_slot": + RequireCharacterLevel_EarCostumeSlot = + TableExtensions.ParseInt(row.Value); + break; + case "character_eye_costume_slot": + RequireCharacterLevel_EyeCostumeSlot = + TableExtensions.ParseInt(row.Value); + break; + case "character_tail_costume_slot": + RequireCharacterLevel_TailCostumeSlot = + TableExtensions.ParseInt(row.Value); + break; + case "character_title_costume_slot": + RequireCharacterLevel_TitleSlot = + TableExtensions.ParseInt(row.Value); + break; + + case "character_equipment_slot_weapon": + RequireCharacterLevel_EquipmentSlotWeapon = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_armor": + RequireCharacterLevel_EquipmentSlotArmor = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_belt": + RequireCharacterLevel_EquipmentSlotBelt = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_necklace": + RequireCharacterLevel_EquipmentSlotNecklace = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_ring1": + RequireCharacterLevel_EquipmentSlotRing1 = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_ring2": + RequireCharacterLevel_EquipmentSlotRing2 = + TableExtensions.ParseInt(row.Value); + break; + case "character_equipment_slot_aura": + RequireCharacterLevel_EquipmentSlotAura = + TableExtensions.ParseInt(row.Value); + break; + + case "character_consumable_slot_1": + RequireCharacterLevel_ConsumableSlot1 = + TableExtensions.ParseInt(row.Value); + break; + case "character_consumable_slot_2": + RequireCharacterLevel_ConsumableSlot2 = + TableExtensions.ParseInt(row.Value); + break; + case "character_consumable_slot_3": + RequireCharacterLevel_ConsumableSlot3 = + TableExtensions.ParseInt(row.Value); + break; + case "character_consumable_slot_4": + RequireCharacterLevel_ConsumableSlot4 = + TableExtensions.ParseInt(row.Value); + break; + case "character_consumable_slot_5": + RequireCharacterLevel_ConsumableSlot5 = + TableExtensions.ParseInt(row.Value); + break; + } } } diff --git a/Lib9c/TableCSV/GameConfigSheet.csv b/Lib9c/TableCSV/GameConfigSheet.csv index 9917cf44c0..59d2b5d5ed 100644 --- a/Lib9c/TableCSV/GameConfigSheet.csv +++ b/Lib9c/TableCSV/GameConfigSheet.csv @@ -16,3 +16,21 @@ stake_regular_reward_sheet_v2_start_block_index,5510416 stake_regular_reward_sheet_v3_start_block_index,6700000 stake_regular_reward_sheet_v4_start_block_index,6910000 stake_regular_reward_sheet_v5_start_block_index,7650000 +character_full_costume_slot,2 +character_hair_costume_slot,2 +character_ear_costume_slot,2 +character_eye_costume_slot,2 +character_tail_costume_slot,2 +character_title_costume_slot,1 +character_equipment_slot_weapon,1 +character_equipment_slot_armor,3 +character_equipment_slot_belt,5 +character_equipment_slot_necklace,8 +character_equipment_slot_ring1,13 +character_equipment_slot_ring2,46 +character_equipment_slot_aura,1 +character_consumable_slot_1,1 +character_consumable_slot_2,35 +character_consumable_slot_3,100 +character_consumable_slot_4,200 +character_consumable_slot_5,350 From 8643163b13fac3c84cb262878baf590f349b4453 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 11:20:00 +0900 Subject: [PATCH 08/13] Add new Validate methods(Equipment, Costume, Consumable) at AvatarState --- Lib9c/Action/BattleArena.cs | 6 +- Lib9c/Action/EventDungeonBattle.cs | 8 +- Lib9c/Action/HackAndSlash.cs | 21 +-- Lib9c/Action/HackAndSlashSweep.cs | 19 +-- Lib9c/Action/JoinArena.cs | 5 +- Lib9c/Action/Raid.cs | 8 +- Lib9c/Helper/AvatarStateExtensions.cs | 23 ++++ Lib9c/Model/State/AvatarState.cs | 189 ++++++++++++++++++++++++++ 8 files changed, 249 insertions(+), 30 deletions(-) diff --git a/Lib9c/Action/BattleArena.cs b/Lib9c/Action/BattleArena.cs index e06642155d..fb363154c0 100644 --- a/Lib9c/Action/BattleArena.cs +++ b/Lib9c/Action/BattleArena.cs @@ -131,12 +131,13 @@ public override IAccount Execute(IActionContext context) typeof(RuneListSheet), }); - avatarState.ValidEquipmentAndCostume(costumes, equipments, + var gameConfigState = states.GetGameConfigState(); + avatarState.ValidEquipmentAndCostumeV2(costumes, equipments, sheets.GetSheet(), sheets.GetSheet(), sheets.GetSheet(), sheets.GetSheet(), - context.BlockIndex, addressesHex); + context.BlockIndex, addressesHex, gameConfigState); // update rune slot var runeSlotStateAddress = RuneSlotState.DeriveAddress(myAvatarAddress, BattleType.Arena); @@ -205,7 +206,6 @@ public override IAccount Execute(IActionContext context) $"[{nameof(BattleArena)}] my avatar address : {myAvatarAddress}"); } - var gameConfigState = states.GetGameConfigState(); var battleArenaInterval = roundData.ArenaType == ArenaType.OffSeason ? 1 : gameConfigState.BattleArenaInterval; diff --git a/Lib9c/Action/EventDungeonBattle.cs b/Lib9c/Action/EventDungeonBattle.cs index 5da2d84219..26ecb4d120 100644 --- a/Lib9c/Action/EventDungeonBattle.cs +++ b/Lib9c/Action/EventDungeonBattle.cs @@ -202,9 +202,11 @@ public override IAccount Execute(IActionContext context) ActionTypeText, addressesHex); - var equipmentList = avatarState.ValidateEquipmentsV2(Equipments, context.BlockIndex); - var costumeIds = avatarState.ValidateCostume(Costumes); - var foodIds = avatarState.ValidateConsumable(Foods, context.BlockIndex); + var gameConfigState = states.GetGameConfigState(); + var equipmentList = avatarState.ValidateEquipmentsV3( + Equipments, context.BlockIndex, gameConfigState); + var costumeIds = avatarState.ValidateCostumeV2(Costumes, gameConfigState); + var foodIds = avatarState.ValidateConsumableV2(Foods, context.BlockIndex, gameConfigState); var equipmentAndCostumes = Equipments.Concat(Costumes); avatarState.EquipItems(equipmentAndCostumes); avatarState.ValidateItemRequirement( diff --git a/Lib9c/Action/HackAndSlash.cs b/Lib9c/Action/HackAndSlash.cs index f1b6bfac4f..8dcbae479d 100644 --- a/Lib9c/Action/HackAndSlash.cs +++ b/Lib9c/Action/HackAndSlash.cs @@ -274,9 +274,17 @@ public IAccount Execute( addressesHex, source, "Validate World", blockIndex, sw.Elapsed.TotalMilliseconds); sw.Restart(); - var equipmentList = avatarState.ValidateEquipmentsV2(Equipments, blockIndex); - var foodIds = avatarState.ValidateConsumable(Foods, blockIndex); - var costumeIds = avatarState.ValidateCostume(Costumes); + var gameConfigState = states.GetGameConfigState(); + if (gameConfigState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the game config state was failed to load."); + } + + var equipmentList = avatarState.ValidateEquipmentsV3( + Equipments, blockIndex, gameConfigState); + var foodIds = avatarState.ValidateConsumableV2(Foods, blockIndex, gameConfigState); + var costumeIds = avatarState.ValidateCostumeV2(Costumes, gameConfigState); sw.Stop(); Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", addressesHex, source, "Validate Items", blockIndex, sw.Elapsed.TotalMilliseconds); @@ -294,13 +302,6 @@ public IAccount Execute( if (ApStoneCount > 0) { - var gameConfigState = states.GetGameConfigState(); - if (gameConfigState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the game config state was failed to load."); - } - // use apStone var row = materialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone); if (!avatarState.inventory.RemoveFungibleItem(row.ItemId, blockIndex, diff --git a/Lib9c/Action/HackAndSlashSweep.cs b/Lib9c/Action/HackAndSlashSweep.cs index d50d96303c..02243eadd7 100644 --- a/Lib9c/Action/HackAndSlashSweep.cs +++ b/Lib9c/Action/HackAndSlashSweep.cs @@ -164,8 +164,16 @@ public override IAccount Execute(IActionContext context) ); } - var equipmentList = avatarState.ValidateEquipmentsV2(equipments, context.BlockIndex); - var costumeIds = avatarState.ValidateCostume(costumes); + var gameConfigState = states.GetGameConfigState(); + if (gameConfigState is null) + { + throw new FailedLoadStateException( + $"{addressesHex}Aborted as the game config state was failed to load."); + } + + var equipmentList = avatarState.ValidateEquipmentsV3( + equipments, context.BlockIndex, gameConfigState); + var costumeIds = avatarState.ValidateCostumeV2(costumes, gameConfigState); var items = equipments.Concat(costumes); avatarState.EquipItems(items); avatarState.ValidateItemRequirement( @@ -267,13 +275,6 @@ public override IAccount Execute(IActionContext context) } } - var gameConfigState = states.GetGameConfigState(); - if (gameConfigState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the game config state was failed to load."); - } - if (actionPoint > avatarState.actionPoint) { throw new NotEnoughActionPointException( diff --git a/Lib9c/Action/JoinArena.cs b/Lib9c/Action/JoinArena.cs index ef97f50a8f..9be265d12d 100644 --- a/Lib9c/Action/JoinArena.cs +++ b/Lib9c/Action/JoinArena.cs @@ -97,12 +97,13 @@ public override IAccount Execute(IActionContext context) typeof(RuneListSheet), }); - avatarState.ValidEquipmentAndCostume(costumes, equipments, + var gameConfigState = states.GetGameConfigState(); + avatarState.ValidEquipmentAndCostumeV2(costumes, equipments, sheets.GetSheet(), sheets.GetSheet(), sheets.GetSheet(), sheets.GetSheet(), - context.BlockIndex, addressesHex); + context.BlockIndex, addressesHex, gameConfigState); // update rune slot var runeSlotStateAddress = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Arena); diff --git a/Lib9c/Action/Raid.cs b/Lib9c/Action/Raid.cs index 6980094162..f7b0dbe7c7 100644 --- a/Lib9c/Action/Raid.cs +++ b/Lib9c/Action/Raid.cs @@ -148,9 +148,11 @@ public override IAccount Execute(IActionContext context) } // Validate equipment, costume. - var equipmentList = avatarState.ValidateEquipmentsV2(EquipmentIds, context.BlockIndex); - var foodIds = avatarState.ValidateConsumable(FoodIds, context.BlockIndex); - var costumeIds = avatarState.ValidateCostume(CostumeIds); + var equipmentList = avatarState.ValidateEquipmentsV3( + EquipmentIds, context.BlockIndex, gameConfigState); + var foodIds = avatarState.ValidateConsumableV2( + FoodIds, context.BlockIndex, gameConfigState); + var costumeIds = avatarState.ValidateCostumeV2(CostumeIds, gameConfigState); // Update rune slot var runeSlotStateAddress = RuneSlotState.DeriveAddress(AvatarAddress, BattleType.Raid); diff --git a/Lib9c/Helper/AvatarStateExtensions.cs b/Lib9c/Helper/AvatarStateExtensions.cs index fad4cd054b..b03018552b 100644 --- a/Lib9c/Helper/AvatarStateExtensions.cs +++ b/Lib9c/Helper/AvatarStateExtensions.cs @@ -138,5 +138,28 @@ public static void ValidEquipmentAndCostume(this AvatarState avatarState, equipmentItemOptionSheet, addressesHex); } + + public static void ValidEquipmentAndCostumeV2(this AvatarState avatarState, + IEnumerable costumeIds, + List equipmentIds, + ItemRequirementSheet itemRequirementSheet, + EquipmentItemRecipeSheet equipmentItemRecipeSheet, + EquipmentItemSubRecipeSheetV2 equipmentItemSubRecipeSheetV2, + EquipmentItemOptionSheet equipmentItemOptionSheet, + long blockIndex, + string addressesHex, + GameConfigState gameConfigState) + { + var equipments = avatarState.ValidateEquipmentsV3(equipmentIds, blockIndex,gameConfigState); + var costumeItemIds = avatarState.ValidateCostumeV2(costumeIds, gameConfigState); + avatarState.ValidateItemRequirement( + costumeItemIds.ToList(), + equipments, + itemRequirementSheet, + equipmentItemRecipeSheet, + equipmentItemSubRecipeSheetV2, + equipmentItemOptionSheet, + addressesHex); + } } } diff --git a/Lib9c/Model/State/AvatarState.cs b/Lib9c/Model/State/AvatarState.cs index a1aefde576..05d261689f 100644 --- a/Lib9c/Model/State/AvatarState.cs +++ b/Lib9c/Model/State/AvatarState.cs @@ -696,6 +696,88 @@ public List ValidateEquipmentsV2(List equipmentIds, long blockI return list; } + public List ValidateEquipmentsV3(List equipmentIds, long blockIndex, GameConfigState gameConfigState) + { + var countMap = new Dictionary(); + var list = new List(); + foreach (var itemId in equipmentIds) + { + if (!inventory.TryGetNonFungibleItem(itemId, out ItemUsable outNonFungibleItem)) + { + continue; + } + + var equipment = (Equipment)outNonFungibleItem; + if (equipment.RequiredBlockIndex > blockIndex) + { + throw new RequiredBlockIndexException($"{equipment.ItemSubType} / unlock on {equipment.RequiredBlockIndex}"); + } + + var type = equipment.ItemSubType; + if (!countMap.ContainsKey(type)) + { + countMap[type] = 0; + } + + countMap[type] += 1; + + var requiredLevel = 0; + var isSlotEnough = true; + switch (equipment.ItemSubType) + { + case ItemSubType.Weapon: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Weapon; + requiredLevel = isSlotEnough ? + gameConfigState.RequireCharacterLevel_EquipmentSlotWeapon : int.MaxValue; + break; + case ItemSubType.Armor: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Armor; + requiredLevel = isSlotEnough ? + gameConfigState.RequireCharacterLevel_EquipmentSlotArmor : int.MaxValue; + break; + case ItemSubType.Belt: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Belt; + requiredLevel = isSlotEnough ? + gameConfigState.RequireCharacterLevel_EquipmentSlotBelt : int.MaxValue; + break; + case ItemSubType.Necklace: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Necklace; + requiredLevel = isSlotEnough ? + gameConfigState.RequireCharacterLevel_EquipmentSlotNecklace : int.MaxValue; + break; + case ItemSubType.Ring: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Ring; + requiredLevel = countMap[ItemSubType.Ring] == 1 + ? gameConfigState.RequireCharacterLevel_EquipmentSlotRing1 + : countMap[ItemSubType.Ring] == 2 + ? gameConfigState.RequireCharacterLevel_EquipmentSlotRing2 + : int.MaxValue; + break; + case ItemSubType.Aura: + isSlotEnough = countMap[type] <= GameConfig.MaxEquipmentSlotCount.Aura; + requiredLevel = isSlotEnough ? + gameConfigState.RequireCharacterLevel_EquipmentSlotAura : int.MaxValue; + break; + default: + throw new ArgumentOutOfRangeException($"{equipment.ItemSubType} / invalid equipment type"); + } + + if (!isSlotEnough) + { + throw new DuplicateEquipmentException($"Equipment slot of {equipment.ItemSubType} is full, but tried to equip {equipment.Id}"); + } + + if (level < requiredLevel) + { + throw new EquipmentSlotUnlockException($"{equipment.ItemSubType} / not enough level. required: {requiredLevel}"); + } + + list.Add(equipment); + } + + return list; + } + public List ValidateConsumable(List consumableIds, long currentBlockIndex) { var list = new List(); @@ -748,6 +830,58 @@ public List ValidateConsumable(List consumableIds, long currentBlockI return list; } + public List ValidateConsumableV2(List consumableIds, long currentBlockIndex, GameConfigState gameConfigState) + { + var list = new List(); + for (var slotIndex = 0; slotIndex < consumableIds.Count; slotIndex++) + { + var consumableId = consumableIds[slotIndex]; + + if (!inventory.TryGetNonFungibleItem(consumableId, out ItemUsable outNonFungibleItem)) + { + continue; + } + + var equipment = (Consumable) outNonFungibleItem; + if (equipment.RequiredBlockIndex > currentBlockIndex) + { + throw new RequiredBlockIndexException( + $"{equipment.ItemSubType} / unlock on {equipment.RequiredBlockIndex}"); + } + + int requiredLevel; + switch (slotIndex) + { + case 0: + requiredLevel = gameConfigState.RequireCharacterLevel_ConsumableSlot1; + break; + case 1: + requiredLevel = gameConfigState.RequireCharacterLevel_ConsumableSlot2; + break; + case 2: + requiredLevel = gameConfigState.RequireCharacterLevel_ConsumableSlot3; + break; + case 3: + requiredLevel = gameConfigState.RequireCharacterLevel_ConsumableSlot4; + break; + case 4: + requiredLevel = gameConfigState.RequireCharacterLevel_ConsumableSlot5; + break; + default: + throw new ConsumableSlotOutOfRangeException(); + } + + if (level < requiredLevel) + { + throw new ConsumableSlotUnlockException($"not enough level. required: {requiredLevel}"); + } + + list.Add(equipment.Id); + } + + return list; + } + public List ValidateCostume(IEnumerable costumeIds) { var subTypes = new List(); @@ -803,6 +937,61 @@ public List ValidateCostume(IEnumerable costumeIds) return list; } + public List ValidateCostumeV2(IEnumerable costumeIds, GameConfigState gameConfigState) + { + var subTypes = new List(); + var list = new List(); + foreach (var costumeId in costumeIds) + { + if (!inventory.TryGetNonFungibleItem(costumeId, out var costume)) + { + continue; + } + + if (subTypes.Contains(costume.ItemSubType)) + { + throw new DuplicateCostumeException($"can't equip duplicate costume type : {costume.ItemSubType}"); + } + + subTypes.Add(costume.ItemSubType); + + int requiredLevel; + switch (costume.ItemSubType) + { + case ItemSubType.FullCostume: + requiredLevel = gameConfigState.RequireCharacterLevel_FullCostumeSlot; + break; + case ItemSubType.HairCostume: + requiredLevel = gameConfigState.RequireCharacterLevel_HairCostumeSlot; + break; + case ItemSubType.EarCostume: + requiredLevel = gameConfigState.RequireCharacterLevel_EarCostumeSlot; + break; + case ItemSubType.EyeCostume: + requiredLevel = gameConfigState.RequireCharacterLevel_EyeCostumeSlot; + break; + case ItemSubType.TailCostume: + requiredLevel = gameConfigState.RequireCharacterLevel_TailCostumeSlot; + break; + case ItemSubType.Title: + requiredLevel = gameConfigState.RequireCharacterLevel_TitleSlot; + break; + default: + throw new InvalidItemTypeException( + $"Costume[id: {costumeId}] isn't expected type. [type: {costume.ItemSubType}]"); + } + + if (level < requiredLevel) + { + throw new CostumeSlotUnlockException($"not enough level. required: {requiredLevel}"); + } + + list.Add(costume.Id); + } + + return list; + } + public void ValidateCostume(HashSet costumeIds) { var subTypes = new List(); From dc662ed793ce0597c767c00d39f6889574c272c4 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 13:29:00 +0900 Subject: [PATCH 09/13] add namespace for client --- Lib9c/Action/CreateAvatar10.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib9c/Action/CreateAvatar10.cs b/Lib9c/Action/CreateAvatar10.cs index 8096c63ae0..f9cf7f04d5 100644 --- a/Lib9c/Action/CreateAvatar10.cs +++ b/Lib9c/Action/CreateAvatar10.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; using Bencodex.Types; using Lib9c.Abstractions; @@ -10,6 +11,8 @@ using Libplanet.Action.State; using Nekoyume.Extensions; using Nekoyume.Model.Item; +using Nekoyume.Model.Skill; +using Nekoyume.Model.Stat; using Nekoyume.Model.State; using Nekoyume.TableData; using Serilog; From 17ea2c6498aac35b87613f447b2f22c8779cf44c Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 13:52:10 +0900 Subject: [PATCH 10/13] Apply code review --- Lib9c/Action/CancelProductRegistration.cs | 2 +- Lib9c/Action/EventConsumableItemCrafts.cs | 2 +- Lib9c/Action/EventMaterialItemCrafts.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib9c/Action/CancelProductRegistration.cs b/Lib9c/Action/CancelProductRegistration.cs index 769a740144..5ccd1c61e4 100644 --- a/Lib9c/Action/CancelProductRegistration.cs +++ b/Lib9c/Action/CancelProductRegistration.cs @@ -17,7 +17,7 @@ namespace Nekoyume.Action { - [ActionType("cancel_product_registration1")] + [ActionType("cancel_product_registration2")] public class CancelProductRegistration : GameAction { public const int CostAp = 5; diff --git a/Lib9c/Action/EventConsumableItemCrafts.cs b/Lib9c/Action/EventConsumableItemCrafts.cs index a587ea59d0..be76fb536c 100644 --- a/Lib9c/Action/EventConsumableItemCrafts.cs +++ b/Lib9c/Action/EventConsumableItemCrafts.cs @@ -27,7 +27,7 @@ namespace Nekoyume.Action [ActionType(ActionTypeText)] public class EventConsumableItemCrafts : GameAction, IEventConsumableItemCraftsV1 { - private const string ActionTypeText = "event_consumable_item_crafts1"; + private const string ActionTypeText = "event_consumable_item_crafts2"; public Address AvatarAddress; public int EventScheduleId; diff --git a/Lib9c/Action/EventMaterialItemCrafts.cs b/Lib9c/Action/EventMaterialItemCrafts.cs index 6f6986204c..0e39c381a7 100644 --- a/Lib9c/Action/EventMaterialItemCrafts.cs +++ b/Lib9c/Action/EventMaterialItemCrafts.cs @@ -27,7 +27,7 @@ namespace Nekoyume.Action [ActionType(ActionTypeText)] public class EventMaterialItemCrafts : GameAction, IEventMaterialItemCraftsV1 { - private const string ActionTypeText = "event_material_item_crafts1"; + private const string ActionTypeText = "event_material_item_crafts2"; public Address AvatarAddress; public int EventScheduleId; public int EventMaterialItemRecipeId; From c313a132c76f57915782f2620aa8e357955ba51e Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 15:01:16 +0900 Subject: [PATCH 11/13] Add patch table test --- .Lib9c.Tests/Action/PatchTableSheetTest.cs | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.Lib9c.Tests/Action/PatchTableSheetTest.cs b/.Lib9c.Tests/Action/PatchTableSheetTest.cs index d5e7bb3c55..847e6dad0c 100644 --- a/.Lib9c.Tests/Action/PatchTableSheetTest.cs +++ b/.Lib9c.Tests/Action/PatchTableSheetTest.cs @@ -80,6 +80,37 @@ public void Execute() Assert.Equal(worldSheetRowCount, nextWorldSheet.Count); } + [Fact] + public void Execute_GameConfigSheet() + { + var sheetCsv = _initialState.GetSheetCsv(); + var sheet = new GameConfigSheet(); + sheet.Set(sheetCsv); + var state = new GameConfigState(); + state.Set(sheet); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, state.RequireCharacterLevel_FullCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterHairCostumeSlot, state.RequireCharacterLevel_HairCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEarCostumeSlot, state.RequireCharacterLevel_EarCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEyeCostumeSlot, state.RequireCharacterLevel_EyeCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterTailCostumeSlot, state.RequireCharacterLevel_TailCostumeSlot); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterTitleSlot, state.RequireCharacterLevel_TitleSlot); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotWeapon, state.RequireCharacterLevel_EquipmentSlotWeapon); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotArmor, state.RequireCharacterLevel_EquipmentSlotArmor); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotBelt, state.RequireCharacterLevel_EquipmentSlotBelt); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotNecklace, state.RequireCharacterLevel_EquipmentSlotNecklace); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing1, state.RequireCharacterLevel_EquipmentSlotRing1); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotRing2, state.RequireCharacterLevel_EquipmentSlotRing2); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterEquipmentSlotAura, state.RequireCharacterLevel_EquipmentSlotAura); + + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot1, state.RequireCharacterLevel_ConsumableSlot1); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot2, state.RequireCharacterLevel_ConsumableSlot2); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot3, state.RequireCharacterLevel_ConsumableSlot3); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot4, state.RequireCharacterLevel_ConsumableSlot4); + Assert.Equal(GameConfig.RequireCharacterLevel.CharacterConsumableSlot5, state.RequireCharacterLevel_ConsumableSlot5); + } + [Fact] public void CheckPermission() { From 65b6789038d0591c5fb0c805e63f2baf8b335247 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 16:47:20 +0900 Subject: [PATCH 12/13] add version action tests --- .Lib9c.Tests/Action/BattleArena13Test.cs | 36 ++++++------ .../{BuyProductTest.cs => BuyProduct2Test.cs} | 10 ++-- ...t.cs => CancelProductRegistration0Test.cs} | 14 ++--- ...eTest.cs => CombinationConsumable8Test.cs} | 6 +- ...tTest.cs => CombinationEquipment16Test.cs} | 12 ++-- ...ateAvatarTest.cs => CreateAvatar10Test.cs} | 24 ++++---- ...t.cs => EventConsumableItemCrafts0Test.cs} | 6 +- .../Action/EventDungeonBattleV5Test.cs | 2 +- ...est.cs => EventMaterialItemCrafts0Test.cs} | 6 +- .Lib9c.Tests/Action/HackAndSlash21Test.cs | 56 +++++++++---------- .Lib9c.Tests/Action/HackAndSlashSweep9Test.cs | 26 ++++----- ...cementTest.cs => ItemEnhancement13Test.cs} | 6 +- .Lib9c.Tests/Action/JoinArena3Test.cs | 20 +++---- .Lib9c.Tests/Action/Raid5Test.cs | 4 +- .Lib9c.Tests/Action/Raid6Test.cs | 6 +- ...nationTest.cs => RapidCombination9Test.cs} | 20 +++---- ...ProductTest.cs => RegisterProduct2Test.cs} | 16 +++--- 17 files changed, 135 insertions(+), 135 deletions(-) rename .Lib9c.Tests/Action/{BuyProductTest.cs => BuyProduct2Test.cs} (98%) rename .Lib9c.Tests/Action/{CancelProductRegistrationTest.cs => CancelProductRegistration0Test.cs} (94%) rename .Lib9c.Tests/Action/{CombinationConsumableTest.cs => CombinationConsumable8Test.cs} (97%) rename .Lib9c.Tests/Action/{CombinationEquipmentTest.cs => CombinationEquipment16Test.cs} (98%) rename .Lib9c.Tests/Action/{CreateAvatarTest.cs => CreateAvatar10Test.cs} (94%) rename .Lib9c.Tests/Action/{EventConsumableItemCraftsTest.cs => EventConsumableItemCrafts0Test.cs} (97%) rename .Lib9c.Tests/Action/{EventMaterialItemCraftsTest.cs => EventMaterialItemCrafts0Test.cs} (98%) rename .Lib9c.Tests/Action/{ItemEnhancementTest.cs => ItemEnhancement13Test.cs} (99%) rename .Lib9c.Tests/Action/{RapidCombinationTest.cs => RapidCombination9Test.cs} (98%) rename .Lib9c.Tests/Action/{RegisterProductTest.cs => RegisterProduct2Test.cs} (97%) diff --git a/.Lib9c.Tests/Action/BattleArena13Test.cs b/.Lib9c.Tests/Action/BattleArena13Test.cs index cca3f8f7fa..439cf3071c 100644 --- a/.Lib9c.Tests/Action/BattleArena13Test.cs +++ b/.Lib9c.Tests/Action/BattleArena13Test.cs @@ -195,7 +195,7 @@ public void Execute_Backward_Compatibility_Success() [Fact] public void Execute_InvalidAddressException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar1Address, @@ -218,7 +218,7 @@ public void Execute_InvalidAddressException() [Fact] public void Execute_FailedLoadStateException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar2Address, enemyAvatarAddress = _avatar1Address, @@ -241,7 +241,7 @@ public void Execute_FailedLoadStateException() [Fact] public void Execute_NotEnoughClearedStageLevelException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar4Address, enemyAvatarAddress = _avatar2Address, @@ -266,7 +266,7 @@ public void Execute_NotEnoughClearedStageLevelException() [Fact] public void Execute_SheetRowNotFoundException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -289,7 +289,7 @@ public void Execute_SheetRowNotFoundException() [Fact] public void Execute_ThisArenaIsClosedException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -313,7 +313,7 @@ public void Execute_ThisArenaIsClosedException() [Fact] public void Execute_ArenaParticipantsNotFoundException() { - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -374,7 +374,7 @@ public void Execute_AddressNotFoundInArenaParticipantsException(bool excludeMe) round, random); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -444,7 +444,7 @@ public void Execute_ValidateScoreDifferenceException(bool isSigner) arenaScore.AddScore(900); previousStates = previousStates.SetState(arenaScoreAdr, arenaScore.Serialize()); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -513,7 +513,7 @@ public void Execute_InsufficientBalanceException() beforeInfo.UseTicket(beforeInfo.Ticket); previousStates = previousStates.SetState(arenaInfoAdr, beforeInfo.Serialize()); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -579,7 +579,7 @@ public void Execute_ExceedPlayCountException() throw new ArenaInformationNotFoundException($"arenaInfoAdr : {arenaInfoAdr}"); } - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -659,7 +659,7 @@ public void Execute_ExceedTicketPurchaseLimitException() previousStates.GetGoldCurrency()); previousStates = previousStates.MintAsset(context, _agent1Address, price); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -732,7 +732,7 @@ public void Execute_ExceedTicketPurchaseLimitDuringIntervalException() beforeInfo.BuyTicket(roundData.MaxPurchaseCount); } - var purchasedCountDuringInterval = arenaInfoAdr.Derive(BattleArena.PurchasedCountKey); + var purchasedCountDuringInterval = arenaInfoAdr.Derive(BattleArena13.PurchasedCountKey); previousStates = previousStates .SetState(arenaInfoAdr, beforeInfo.Serialize()) .SetState( @@ -744,7 +744,7 @@ public void Execute_ExceedTicketPurchaseLimitDuringIntervalException() previousStates.GetGoldCurrency()); previousStates = previousStates.MintAsset(context, _agent1Address, price); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -823,7 +823,7 @@ public void Execute_CoolDownBlockException() beforeInfo.BuyTicket(roundData.MaxPurchaseCount); } - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -922,7 +922,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 Random = new TestRandom(), }); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -1029,7 +1029,7 @@ public void Execute_ValidateDuplicateTicketPurchaseException() beforeInfo.UseTicket(ArenaInformation.MaxTicketCount); - var purchasedCountDuringInterval = arenaInfoAdr.Derive(BattleArena.PurchasedCountKey); + var purchasedCountDuringInterval = arenaInfoAdr.Derive(BattleArena13.PurchasedCountKey); previousStates = previousStates .SetState(arenaInfoAdr, beforeInfo.Serialize()) .SetState( @@ -1041,7 +1041,7 @@ public void Execute_ValidateDuplicateTicketPurchaseException() previousStates.GetGoldCurrency()); previousStates = previousStates.MintAsset(context, _agent1Address, price); - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = _avatar1Address, enemyAvatarAddress = _avatar2Address, @@ -1157,7 +1157,7 @@ private void Execute( } } - var action = new BattleArena + var action = new BattleArena13 { myAvatarAddress = myAvatarAddress, enemyAvatarAddress = enemyAvatarAddress, diff --git a/.Lib9c.Tests/Action/BuyProductTest.cs b/.Lib9c.Tests/Action/BuyProduct2Test.cs similarity index 98% rename from .Lib9c.Tests/Action/BuyProductTest.cs rename to .Lib9c.Tests/Action/BuyProduct2Test.cs index 4753df2d98..07f65702ec 100644 --- a/.Lib9c.Tests/Action/BuyProductTest.cs +++ b/.Lib9c.Tests/Action/BuyProduct2Test.cs @@ -18,7 +18,7 @@ namespace Lib9c.Tests.Action using Xunit; using Xunit.Abstractions; - public class BuyProductTest + public class BuyProduct2Test { private static readonly Address BuyerAgentAddress = new Address("47d082a115c63e7b58b1532d20e631538eafadde"); private static readonly Address BuyerAvatarAddress = new Address("340f110b91d0577a9ae0ea69ce15269436f217da"); @@ -37,7 +37,7 @@ public class BuyProductTest private readonly Guid _orderId; private IAccount _initialState; - public BuyProductTest(ITestOutputHelper outputHelper) + public BuyProduct2Test(ITestOutputHelper outputHelper) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() @@ -296,7 +296,7 @@ public void Execute_Throw_Exception(params ExecuteMember[] validateMembers) foreach (var productInfo in validateMember.ProductInfos) { - var action = new BuyProduct + var action = new BuyProduct2 { AvatarAddress = BuyerAvatarAddress, ProductInfos = new[] { productInfo }, @@ -315,12 +315,12 @@ public void Execute_Throw_Exception(params ExecuteMember[] validateMembers) public void Execute_Throw_ArgumentOutOfRangeException() { var productInfos = new List(); - for (int i = 0; i < BuyProduct.Capacity + 1; i++) + for (int i = 0; i < BuyProduct2.Capacity + 1; i++) { productInfos.Add(new ItemProductInfo()); } - var action = new BuyProduct + var action = new BuyProduct2 { AvatarAddress = _sellerAvatarAddress2, ProductInfos = productInfos, diff --git a/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs b/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs similarity index 94% rename from .Lib9c.Tests/Action/CancelProductRegistrationTest.cs rename to .Lib9c.Tests/Action/CancelProductRegistration0Test.cs index 58bfa70e07..098bb71e06 100644 --- a/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs +++ b/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs @@ -17,7 +17,7 @@ namespace Lib9c.Tests.Action using Xunit; using Xunit.Abstractions; - public class CancelProductRegistrationTest + public class CancelProductRegistration0Test { private readonly IAccount _initialState; private readonly Address _agentAddress; @@ -26,7 +26,7 @@ public class CancelProductRegistrationTest private readonly TableSheets _tableSheets; private readonly GameConfigState _gameConfigState; - public CancelProductRegistrationTest(ITestOutputHelper outputHelper) + public CancelProductRegistration0Test(ITestOutputHelper outputHelper) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() @@ -84,7 +84,7 @@ public void Execute_Throw_InvalidAddressException( bool invalidAgentAddress ) { - var action = new CancelProductRegistration + var action = new CancelProductRegistration0 { AvatarAddress = _avatarAddress, ProductInfos = new List @@ -129,7 +129,7 @@ public void Execute_Throw_ProductNotFoundException() { var context = new ActionContext(); var prevState = _initialState.MintAsset(context, _avatarAddress, 1 * RuneHelper.StakeRune); - var registerProduct = new RegisterProduct + var registerProduct = new RegisterProduct2 { AvatarAddress = _avatarAddress, RegisterInfos = new List @@ -159,7 +159,7 @@ public void Execute_Throw_ProductNotFoundException() (List)nexState.GetState(ProductsState.DeriveAddress(_avatarAddress))); var productId = Assert.Single(productsState.ProductIds); - var action = new CancelProductRegistration + var action = new CancelProductRegistration0 { AvatarAddress = _avatarAddress, ProductInfos = new List @@ -196,12 +196,12 @@ public void Execute_Throw_ProductNotFoundException() public void Execute_Throw_ArgumentOutOfRangeException() { var productInfos = new List(); - for (int i = 0; i < CancelProductRegistration.Capacity + 1; i++) + for (int i = 0; i < CancelProductRegistration0.Capacity + 1; i++) { productInfos.Add(new ItemProductInfo()); } - var action = new CancelProductRegistration + var action = new CancelProductRegistration0 { AvatarAddress = _avatarAddress, ProductInfos = productInfos, diff --git a/.Lib9c.Tests/Action/CombinationConsumableTest.cs b/.Lib9c.Tests/Action/CombinationConsumable8Test.cs similarity index 97% rename from .Lib9c.Tests/Action/CombinationConsumableTest.cs rename to .Lib9c.Tests/Action/CombinationConsumable8Test.cs index 9164c1f93e..f61f645056 100644 --- a/.Lib9c.Tests/Action/CombinationConsumableTest.cs +++ b/.Lib9c.Tests/Action/CombinationConsumable8Test.cs @@ -15,7 +15,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class CombinationConsumableTest + public class CombinationConsumable8Test { private readonly Address _agentAddress; private readonly Address _avatarAddress; @@ -23,7 +23,7 @@ public class CombinationConsumableTest private readonly TableSheets _tableSheets; private IAccount _initialState; - public CombinationConsumableTest() + public CombinationConsumable8Test() { _agentAddress = new PrivateKey().ToAddress(); _avatarAddress = _agentAddress.Derive("avatar"); @@ -113,7 +113,7 @@ public void Execute(bool backward) .SetState(_avatarAddress, avatarState.SerializeV2()); } - var action = new CombinationConsumable + var action = new CombinationConsumable8 { avatarAddress = _avatarAddress, recipeId = row.Id, diff --git a/.Lib9c.Tests/Action/CombinationEquipmentTest.cs b/.Lib9c.Tests/Action/CombinationEquipment16Test.cs similarity index 98% rename from .Lib9c.Tests/Action/CombinationEquipmentTest.cs rename to .Lib9c.Tests/Action/CombinationEquipment16Test.cs index 0f80092a1b..4c64920205 100644 --- a/.Lib9c.Tests/Action/CombinationEquipmentTest.cs +++ b/.Lib9c.Tests/Action/CombinationEquipment16Test.cs @@ -23,7 +23,7 @@ namespace Lib9c.Tests.Action using Xunit.Abstractions; using static Lib9c.SerializeKeys; - public class CombinationEquipmentTest + public class CombinationEquipment16Test { private readonly Address _agentAddress; private readonly Address _avatarAddress; @@ -34,7 +34,7 @@ public class CombinationEquipmentTest private readonly AgentState _agentState; private readonly AvatarState _avatarState; - public CombinationEquipmentTest(ITestOutputHelper outputHelper) + public CombinationEquipment16Test(ITestOutputHelper outputHelper) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() @@ -75,7 +75,7 @@ public CombinationEquipmentTest(ITestOutputHelper outputHelper) var combinationSlotState = new CombinationSlotState( _slotAddress, - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + 0); _initialState = new MockStateDelta() .SetState(_slotAddress, combinationSlotState.Serialize()) @@ -280,7 +280,7 @@ bool previousCostStateExist Assert.Null(state.GetState(dailyCostAddress)); Assert.Null(state.GetState(weeklyCostAddress)); - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddress, slotIndex = slotIndex, @@ -460,7 +460,7 @@ public void ExecuteWithCheckingHammerPointState( } } - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -520,7 +520,7 @@ public void AddAndUnlockOption() Guid.NewGuid(), default); Assert.Equal(0, equipment.optionCountFromCombination); - CombinationEquipment.AddAndUnlockOption( + CombinationEquipment16.AddAndUnlockOption( _agentState, null, equipment, diff --git a/.Lib9c.Tests/Action/CreateAvatarTest.cs b/.Lib9c.Tests/Action/CreateAvatar10Test.cs similarity index 94% rename from .Lib9c.Tests/Action/CreateAvatarTest.cs rename to .Lib9c.Tests/Action/CreateAvatar10Test.cs index b1b16177bb..a1597eb49b 100644 --- a/.Lib9c.Tests/Action/CreateAvatarTest.cs +++ b/.Lib9c.Tests/Action/CreateAvatar10Test.cs @@ -16,12 +16,12 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class CreateAvatarTest + public class CreateAvatar10Test { private readonly Address _agentAddress; private readonly TableSheets _tableSheets; - public CreateAvatarTest() + public CreateAvatar10Test() { _agentAddress = default; _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); @@ -33,7 +33,7 @@ public CreateAvatarTest() [InlineData(7_210_001L)] public void Execute(long blockIndex) { - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = 0, hair = 0, @@ -105,7 +105,7 @@ public void ExecuteThrowInvalidNamePatterException(string nickName) { var agentAddress = default(Address); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = 0, hair = 0, @@ -146,7 +146,7 @@ public void ExecuteThrowInvalidAddressException() default ); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = 0, hair = 0, @@ -174,7 +174,7 @@ public void ExecuteThrowAvatarIndexOutOfRangeException(int index) { var agentState = new AgentState(_agentAddress); var state = new MockStateDelta().SetState(_agentAddress, agentState.Serialize()); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = index, hair = 0, @@ -210,7 +210,7 @@ public void ExecuteThrowAvatarIndexAlreadyUsedException(int index) agentState.avatarAddresses[index] = avatarAddress; var state = new MockStateDelta().SetState(_agentAddress, agentState.Serialize()); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = index, hair = 0, @@ -244,7 +244,7 @@ public void Rehearsal(int index) ) ); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = index, hair = 0, @@ -298,7 +298,7 @@ public void Rehearsal(int index) public void Serialize_With_DotnetAPI() { var formatter = new BinaryFormatter(); - var action = new CreateAvatar() + var action = new CreateAvatar10() { index = 2, hair = 1, @@ -312,7 +312,7 @@ public void Serialize_With_DotnetAPI() formatter.Serialize(ms, action); ms.Seek(0, SeekOrigin.Begin); - var deserialized = (CreateAvatar)formatter.Deserialize(ms); + var deserialized = (CreateAvatar10)formatter.Deserialize(ms); Assert.Equal(2, deserialized.index); Assert.Equal(1, deserialized.hair); @@ -333,7 +333,7 @@ public void AddItem() 600201,2 "); var avatarState = new AvatarState(default, default, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); - CreateAvatar.AddItem(itemSheet, createAvatarItemSheet, avatarState, new TestRandom()); + CreateAvatar10.AddItem(itemSheet, createAvatarItemSheet, avatarState, new TestRandom()); foreach (var row in createAvatarItemSheet.Values) { Assert.True(avatarState.inventory.HasItem(row.ItemId, row.Count)); @@ -358,7 +358,7 @@ public void MintAsset() var avatarAddress = new PrivateKey().ToAddress(); var agentAddress = new PrivateKey().ToAddress(); var avatarState = new AvatarState(avatarAddress, agentAddress, 0L, _tableSheets.GetAvatarSheets(), new GameConfigState(), default, "test"); - var nextState = CreateAvatar.MintAsset(createAvatarFavSheet, avatarState, new MockStateDelta(), new ActionContext()); + var nextState = CreateAvatar10.MintAsset(createAvatarFavSheet, avatarState, new MockStateDelta(), new ActionContext()); foreach (var row in createAvatarFavSheet.Values) { var targetAddress = row.Target == CreateAvatarFavSheet.Target.Agent diff --git a/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs b/.Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs similarity index 97% rename from .Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs rename to .Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs index c9f12960fd..ec05b42bdd 100644 --- a/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs +++ b/.Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs @@ -14,7 +14,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class EventConsumableItemCraftsTest + public class EventConsumableItemCrafts0Test { private readonly IAccount _initialStates; private readonly TableSheets _tableSheets; @@ -22,7 +22,7 @@ public class EventConsumableItemCraftsTest private readonly Address _agentAddress; private readonly Address _avatarAddress; - public EventConsumableItemCraftsTest() + public EventConsumableItemCrafts0Test() { _initialStates = new MockStateDelta(); var sheets = TableSheetsImporter.ImportSheets(); @@ -143,7 +143,7 @@ private void Execute( .Count(e => e.Id == recipeRow.ResultConsumableItemId); var previousMailCount = previousAvatarState.mailBox.Count; - var action = new EventConsumableItemCrafts + var action = new EventConsumableItemCrafts0 { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs b/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs index bd0aa106a3..44af7acb39 100644 --- a/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs +++ b/.Lib9c.Tests/Action/EventDungeonBattleV5Test.cs @@ -456,7 +456,7 @@ private IAccount Execute( previousAvatarState.inventory.AddItem(equipment, iLock: null); } - var action = new EventDungeonBattle + var action = new EventDungeonBattleV5 { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs b/.Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs similarity index 98% rename from .Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs rename to .Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs index 5f8f6709bc..96a38cdc9c 100644 --- a/.Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs +++ b/.Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs @@ -16,7 +16,7 @@ namespace Lib9c.Tests.Action using Xunit; using static SerializeKeys; - public class EventMaterialItemCraftsTest + public class EventMaterialItemCrafts0Test { private readonly IAccount _initialStates; private readonly TableSheets _tableSheets; @@ -24,7 +24,7 @@ public class EventMaterialItemCraftsTest private readonly Address _agentAddress; private readonly Address _avatarAddress; - public EventMaterialItemCraftsTest() + public EventMaterialItemCrafts0Test() { _initialStates = new MockStateDelta(); var sheets = TableSheetsImporter.ImportSheets(); @@ -252,7 +252,7 @@ private void Execute( .Sum(i => i.item.Id == recipeRow.ResultMaterialItemId ? i.count : 0); var previousMailCount = previousAvatarState.mailBox.Count; - var action = new EventMaterialItemCrafts + var action = new EventMaterialItemCrafts0 { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/HackAndSlash21Test.cs b/.Lib9c.Tests/Action/HackAndSlash21Test.cs index 8ca1a206fc..11ef66ea06 100644 --- a/.Lib9c.Tests/Action/HackAndSlash21Test.cs +++ b/.Lib9c.Tests/Action/HackAndSlash21Test.cs @@ -191,7 +191,7 @@ public void Execute(int avatarLevel, int worldId, int stageId, bool backward, bo List.Empty.Add(worldId.Serialize()) ); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments.Select(e => e.NonFungibleId).ToList(), @@ -262,7 +262,7 @@ public void Execute_With_UpdateQuestList(int worldId, int stageId) Assert.Equal(equipments.Count, avatarState.inventory.Items.Count); // HackAndSlash - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = equipments.Select(e => e.NonFungibleId).ToList(), @@ -335,7 +335,7 @@ public void MaxLevelTest() var state = _initialState.SetState(_avatarAddress, previousAvatarState.SerializeV2()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -397,7 +397,7 @@ public void MultipleEquipmentTest(ItemSubType type, int maxCount) .SetState(_avatarAddress, previousAvatarState.SerializeV2()) .SetState(_inventoryAddress, previousAvatarState.inventory.Serialize()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = equipments, @@ -424,7 +424,7 @@ public void MultipleEquipmentTest(ItemSubType type, int maxCount) [InlineData(false)] public void Execute_Throw_FailedLoadStateException(bool backward) { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -460,7 +460,7 @@ public void Execute_Throw_FailedLoadStateException(bool backward) [InlineData(51)] public void ExecuteThrowSheetRowColumnException(int stageId) { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -484,7 +484,7 @@ public void ExecuteThrowSheetRowColumnException(int stageId) [Fact] public void ExecuteThrowSheetRowNotFoundExceptionByStage() { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -511,7 +511,7 @@ public void ExecuteThrowSheetRowNotFoundExceptionByStage() [Fact] public void ExecuteThrowFailedAddWorldException() { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -551,7 +551,7 @@ public void ExecuteThrowFailedAddWorldException() [InlineData(2, 51, true)] public void Execute_Throw_InvalidWorldException(int worldId, int stageId, bool unlockedIdsExist) { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -584,7 +584,7 @@ public void Execute_Throw_InvalidWorldException(int worldId, int stageId, bool u [Fact] public void ExecuteThrowInvalidStageException() { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -625,7 +625,7 @@ public void ExecuteThrowInvalidStageException() [Fact] public void ExecuteThrowInvalidStageExceptionUnlockedWorld() { - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -662,7 +662,7 @@ public void ExecuteThrowInvalidEquipmentException(ItemSubType itemSubType) var equipment = ItemFactory.CreateItemUsable(equipRow, Guid.NewGuid(), 100); avatarState.inventory.AddItem(equipment); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List @@ -710,7 +710,7 @@ public void ExecuteThrowEquipmentSlotUnlockException(ItemSubType itemSubType) avatarState.inventory.AddItem(equipment); state = state.SetState(_inventoryAddress, avatarState.inventory.Serialize()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List @@ -745,7 +745,7 @@ public void ExecuteThrowNotEnoughActionPointException(int ap, int playCount = 1) actionPoint = ap, }; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -813,7 +813,7 @@ public void ExecuteWithoutPlayCount() _avatarAddress.Derive(LegacyQuestListKey), previousAvatarState.questList.Serialize()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments, @@ -883,7 +883,7 @@ public void Execute_Throw_NotEnoughAvatarLevelException(int avatarLevel) avatarState.address.Derive(LegacyInventoryKey), avatarState.inventory.Serialize()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments, @@ -916,7 +916,7 @@ public void ExecuteThrowInvalidItemCountException() }; var state = _initialState; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -953,7 +953,7 @@ public void ExecuteThrowPlayCountIsZeroException(int totalPlayCount, int apStone }; var state = _initialState; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -980,7 +980,7 @@ public void ExecuteThrowPlayCountIsZeroException(int totalPlayCount, int apStone public void ExecuteThrowUsageLimitExceedException() { var state = _initialState; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -1007,7 +1007,7 @@ public void ExecuteThrowUsageLimitExceedException() public void ExecuteThrowNotEnoughMaterialException() { var state = _initialState; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -1115,7 +1115,7 @@ public void CheckRewardItems(bool backward, int worldId, int stageId) Enumerable.Range(1, worldId).ToList().Select(i => i.Serialize()).Serialize() ); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments.Select(e => e.NonFungibleId).ToList(), @@ -1281,7 +1281,7 @@ public void CheckCrystalRandomSkillState( previousAvatarState.EquipItems(costumes.Concat(equipments.Select(e => e.ItemId))); } - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = clear ? costumes : new List(), Equipments = clear @@ -1406,7 +1406,7 @@ public void CheckUsedApByStaking(int level, int playCount) var expectedAp = previousAvatarState.actionPoint - _tableSheets.StakeActionPointCoefficientSheet.GetActionPointByStaking( _tableSheets.StageSheet[stageId].CostAP, playCount, level); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -1485,7 +1485,7 @@ public void CheckUsingApStoneWithStaking(int level, int apStoneCount, int totalR var itemCount = previousAvatarState.inventory.Items .FirstOrDefault(i => i.item.Id == itemId)?.count ?? 0; var expectedItemCount = itemCount + totalRepeatCount; - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -1538,7 +1538,7 @@ public void ExecuteThrowInvalidRepeatPlayException() .SetState( _avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = new List(), Equipments = new List(), @@ -1631,7 +1631,7 @@ public void ExecuteTwoRepetitions() List.Empty.Add(worldId.Serialize()) ); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments.Select(e => e.NonFungibleId).ToList(), @@ -1655,7 +1655,7 @@ public void ExecuteTwoRepetitions() BlockIndex = ActionObsoleteConfig.V100301ExecutedBlockIndex, }); - var action2 = new HackAndSlash + var action2 = new HackAndSlash21 { Costumes = costumes, Equipments = equipments.Select(e => e.NonFungibleId).ToList(), @@ -1770,7 +1770,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 Random = new TestRandom(), }); - var action = new HackAndSlash + var action = new HackAndSlash21 { Costumes = costumes, Equipments = equipments.Select(e => e.NonFungibleId).ToList(), diff --git a/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs b/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs index 94e83ef5e8..e767e36ff5 100644 --- a/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs +++ b/.Lib9c.Tests/Action/HackAndSlashSweep9Test.cs @@ -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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { 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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { 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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { 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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { 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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { runeInfos = new List(), apStoneCount = 1, @@ -427,7 +427,7 @@ public void Execute_InvalidWorldException(int worldId, bool backward, int stageI ); } - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { runeInfos = new List(), apStoneCount = 1, @@ -482,7 +482,7 @@ public void Execute_UsageLimitExceedException(int apStoneCount, bool backward) avatarState.questList.Serialize()); } - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { 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 HackAndSlashSweep + var action = new HackAndSlashSweep9 { equipments = equipments, costumes = costumes, @@ -633,7 +633,7 @@ public void Execute_NotEnoughActionPointException(bool backward) playCount); var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { runeInfos = new List(), costumes = costumes, @@ -709,7 +709,7 @@ public void Execute_PlayCountIsZeroException(bool backward) playCount); var (equipments, costumes) = GetDummyItems(avatarState); - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { costumes = costumes, equipments = equipments, @@ -784,7 +784,7 @@ public void Execute_NotEnoughCombatPointException(int worldId, int stageId, bool stageId, playCount); - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { costumes = new List(), equipments = new List(), @@ -858,7 +858,7 @@ public void ExecuteWithStake(int stakingLevel) stageId, playCount); - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { costumes = new List(), equipments = new List(), @@ -953,7 +953,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 Random = new TestRandom(), }); - var action = new HackAndSlashSweep + var action = new HackAndSlashSweep9 { costumes = new List(), equipments = new List(), diff --git a/.Lib9c.Tests/Action/ItemEnhancementTest.cs b/.Lib9c.Tests/Action/ItemEnhancement13Test.cs similarity index 99% rename from .Lib9c.Tests/Action/ItemEnhancementTest.cs rename to .Lib9c.Tests/Action/ItemEnhancement13Test.cs index a63971fffe..a998650cd9 100644 --- a/.Lib9c.Tests/Action/ItemEnhancementTest.cs +++ b/.Lib9c.Tests/Action/ItemEnhancement13Test.cs @@ -17,7 +17,7 @@ namespace Lib9c.Tests.Action using Xunit; using static SerializeKeys; - public class ItemEnhancementTest + public class ItemEnhancement13Test { private readonly TableSheets _tableSheets; private readonly Address _agentAddress; @@ -26,7 +26,7 @@ public class ItemEnhancementTest private readonly Currency _currency; private IAccount _initialState; - public ItemEnhancementTest() + public ItemEnhancement13Test() { var sheets = TableSheetsImporter.ImportSheets(); _tableSheets = new TableSheets(sheets); @@ -292,7 +292,7 @@ public void Execute( ) .SetState(_avatarAddress, _avatarState.SerializeV2()); - var action = new ItemEnhancement + var action = new ItemEnhancement13 { itemId = default, materialIds = materialIds, diff --git a/.Lib9c.Tests/Action/JoinArena3Test.cs b/.Lib9c.Tests/Action/JoinArena3Test.cs index 6828e0f517..64b1c2035c 100644 --- a/.Lib9c.Tests/Action/JoinArena3Test.cs +++ b/.Lib9c.Tests/Action/JoinArena3Test.cs @@ -210,7 +210,7 @@ public void Execute(long blockIndex, int championshipId, int round, string balan ? _state : _state.MintAsset(context, _signer, FungibleAssetValue.Parse(_currency, balance)); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = championshipId, round = round, @@ -288,7 +288,7 @@ public void Execute_SheetRowNotFoundException(int championshipId) avatarState = GetAvatarState(avatarState, out var equipments, out var costumes); var state = _state.SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = championshipId, round = 1, @@ -314,7 +314,7 @@ public void Execute_RoundNotFoundByIdsException(int round) avatarState = GetAvatarState(avatarState, out var equipments, out var costumes); var state = _state.SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = 1, round = round, @@ -343,7 +343,7 @@ public void Execute_NotEnoughMedalException(int round) var context = new ActionContext(); var state = _state.MintAsset(context, _signer, preCurrency); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = 1, round = round, @@ -371,7 +371,7 @@ public void Execute_NotEnoughFungibleAssetValueException(int round, long blockIn GetAvatarState(avatarState, out var equipments, out var costumes); var state = _state.SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = 1, round = round, @@ -397,7 +397,7 @@ public void Execute_ArenaScoreAlreadyContainsException() avatarState = GetAvatarState(avatarState, out var equipments, out var costumes); var state = _state.SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = 1, round = 1, @@ -439,7 +439,7 @@ public void Execute_ArenaScoreAlreadyContainsException2() var arenaScore = new ArenaScore(_avatarAddress, championshipId, round); state = state.SetState(arenaScoreAdr, arenaScore.Serialize()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = championshipId, round = round, @@ -472,7 +472,7 @@ public void Execute_ArenaInformationAlreadyContainsException() var arenaInformation = new ArenaInformation(_avatarAddress, championshipId, round); state = state.SetState(arenaInformationAdr, arenaInformation.Serialize()); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = championshipId, round = round, @@ -494,7 +494,7 @@ public void Execute_ArenaInformationAlreadyContainsException() [Fact] public void Execute_NotEnoughClearedStageLevelException() { - var action = new JoinArena() + var action = new JoinArena3() { championshipId = 1, round = 1, @@ -548,7 +548,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 Random = new TestRandom(), }); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = championshipId, round = round, diff --git a/.Lib9c.Tests/Action/Raid5Test.cs b/.Lib9c.Tests/Action/Raid5Test.cs index 1d1b2b590c..866736a787 100644 --- a/.Lib9c.Tests/Action/Raid5Test.cs +++ b/.Lib9c.Tests/Action/Raid5Test.cs @@ -419,7 +419,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Reward() { - var action = new Raid + var action = new Raid5 { AvatarAddress = _avatarAddress, EquipmentIds = new List(), @@ -574,7 +574,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Free_Crystal_Fee() { - var action = new Raid + var action = new Raid5 { AvatarAddress = _avatarAddress, EquipmentIds = new List(), diff --git a/.Lib9c.Tests/Action/Raid6Test.cs b/.Lib9c.Tests/Action/Raid6Test.cs index c26630b61b..b6f3b26ac9 100644 --- a/.Lib9c.Tests/Action/Raid6Test.cs +++ b/.Lib9c.Tests/Action/Raid6Test.cs @@ -105,7 +105,7 @@ int runeId2 }) .StartedBlockIndex; - var action = new Raid + var action = new Raid6 { AvatarAddress = _avatarAddress, EquipmentIds = new List(), @@ -419,7 +419,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Reward() { - var action = new Raid + var action = new Raid6 { AvatarAddress = _avatarAddress, EquipmentIds = new List(), @@ -574,7 +574,7 @@ Dictionary rewardMap [Fact] public void Execute_With_Free_Crystal_Fee() { - var action = new Raid + var action = new Raid6 { AvatarAddress = _avatarAddress, EquipmentIds = new List(), diff --git a/.Lib9c.Tests/Action/RapidCombinationTest.cs b/.Lib9c.Tests/Action/RapidCombination9Test.cs similarity index 98% rename from .Lib9c.Tests/Action/RapidCombinationTest.cs rename to .Lib9c.Tests/Action/RapidCombination9Test.cs index c973ff1326..f9327425d7 100644 --- a/.Lib9c.Tests/Action/RapidCombinationTest.cs +++ b/.Lib9c.Tests/Action/RapidCombination9Test.cs @@ -20,7 +20,7 @@ namespace Lib9c.Tests.Action using Xunit; using static Lib9c.SerializeKeys; - public class RapidCombinationTest + public class RapidCombination9Test { private readonly IAccount _initialState; @@ -29,7 +29,7 @@ public class RapidCombinationTest private readonly Address _agentAddress; private readonly Address _avatarAddress; - public RapidCombinationTest() + public RapidCombination9Test() { _initialState = new MockStateDelta(); @@ -130,7 +130,7 @@ public void Execute(bool backward) .SetState(_avatarAddress, avatarState.SerializeV2()); } - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -164,7 +164,7 @@ public void Execute_Throw_CombinationSlotResultNullException() var tempState = _initialState .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -218,7 +218,7 @@ public void Execute_Throw_NotEnoughClearedStageLevelException(int avatarClearedS .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -274,7 +274,7 @@ public void Execute_Throw_RequiredBlockIndexException(int itemRequiredBlockIndex .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -350,7 +350,7 @@ public void Execute_Throw_NotEnoughMaterialException(int materialCount, int trad .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -386,7 +386,7 @@ public void Rehearsal() var state = new MockStateDelta(); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -518,7 +518,7 @@ public void Execute_Throw_RequiredAppraiseBlockException() .SetState(_avatarAddress, avatarState.Serialize()) .SetState(slotAddress, slotState.Serialize()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, @@ -689,7 +689,7 @@ public void Execute_NotThrow_InvalidOperationException_When_TargetSlotCreatedBy( .SetState(_avatarAddress.Derive(LegacyQuestListKey), avatarState.questList.Serialize()) .SetState(_avatarAddress, avatarState.SerializeV2()); - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddress, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/RegisterProductTest.cs b/.Lib9c.Tests/Action/RegisterProduct2Test.cs similarity index 97% rename from .Lib9c.Tests/Action/RegisterProductTest.cs rename to .Lib9c.Tests/Action/RegisterProduct2Test.cs index 0b327f8d09..233de15cbd 100644 --- a/.Lib9c.Tests/Action/RegisterProductTest.cs +++ b/.Lib9c.Tests/Action/RegisterProduct2Test.cs @@ -19,7 +19,7 @@ namespace Lib9c.Tests.Action using Nekoyume.TableData; using Xunit; - public class RegisterProductTest + public class RegisterProduct2Test { private static readonly Address AvatarAddress = new Address("47d082a115c63e7b58b1532d20e631538eafadde"); @@ -32,7 +32,7 @@ public class RegisterProductTest private readonly GameConfigState _gameConfigState; private IAccount _initialState; - public RegisterProductTest() + public RegisterProduct2Test() { _agentAddress = new PrivateKey().ToAddress(); var agentState = new AgentState(_agentAddress); @@ -209,7 +209,7 @@ public void Execute() _initialState = _initialState .SetState(AvatarAddress, _avatarState.Serialize()) .MintAsset(context, AvatarAddress, asset); - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = AvatarAddress, RegisterInfos = new List @@ -249,7 +249,7 @@ public void Execute() var nextAvatarState = nextState.GetAvatarStateV2(AvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetState(Addresses.Market)); Assert.Contains(AvatarAddress, marketState.AvatarAddresses); @@ -288,7 +288,7 @@ public void Execute_Validate_RegisterInfos(params ValidateMember[] validateMembe { foreach (var registerInfo in validateMember.RegisterInfos) { - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = AvatarAddress, RegisterInfos = new[] { registerInfo }, @@ -348,7 +348,7 @@ public void Execute_Throw_ItemDoesNotExistException(ProductType type, int itemCo } _initialState = _initialState.SetState(AvatarAddress, _avatarState.Serialize()); - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = AvatarAddress, RegisterInfos = new List @@ -377,12 +377,12 @@ public void Execute_Throw_ItemDoesNotExistException(ProductType type, int itemCo public void Execute_Throw_ArgumentOutOfRangeException() { var registerInfos = new List(); - for (int i = 0; i < RegisterProduct.Capacity + 1; i++) + for (int i = 0; i < RegisterProduct2.Capacity + 1; i++) { registerInfos.Add(new RegisterInfo()); } - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = _avatarState.address, RegisterInfos = registerInfos, From 825be37968b41e7548cbde6720c7c02be55d0272 Mon Sep 17 00:00:00 2001 From: tyrosine1153 Date: Wed, 1 Nov 2023 16:48:26 +0900 Subject: [PATCH 13/13] delete reference of new action from test code --- .Lib9c.Tests/Action/BuyTest.cs | 4 +-- .Lib9c.Tests/Action/MarketValidationTest.cs | 6 ++-- .../Action/Scenario/ArenaScenarioTest.cs | 2 +- .../Action/Scenario/AuraScenarioTest.cs | 10 +++---- .../CombinationAndRapidCombinationTest.cs | 2 +- .Lib9c.Tests/Action/Scenario/ItemCraftTest.cs | 8 +++--- .../Action/Scenario/MarketScenarioTest.cs | 28 +++++++++---------- .../AdditionalOptionRateByFixedValueTest.cs | 4 +-- .../Action/Scenario/Pet/CommonTest.cs | 4 +-- .../Pet/DiscountMaterialCostCrystalTest.cs | 2 +- .../Pet/IncreaseBlockPerHourglassTest.cs | 4 +-- .../Scenario/Pet/ReduceRequiredBlockTest.cs | 2 +- .../Action/Scenario/RapidCombinationTest.cs | 4 +-- .../Action/Scenario/RuneScenarioTest.cs | 2 +- .Lib9c.Tests/Action/SellCancellationTest.cs | 2 +- .../Extensions/EquipmentExtensionsTest.cs | 2 +- .../Action/CreateOrReplaceAvatar.cs | 2 +- Lib9c/Action/AuraSummon.cs | 2 +- 18 files changed, 45 insertions(+), 45 deletions(-) diff --git a/.Lib9c.Tests/Action/BuyTest.cs b/.Lib9c.Tests/Action/BuyTest.cs index b26a6cf18e..e37e9a3a3f 100644 --- a/.Lib9c.Tests/Action/BuyTest.cs +++ b/.Lib9c.Tests/Action/BuyTest.cs @@ -351,7 +351,7 @@ public void Execute(params OrderData[] orderDataList) Signer = _buyerAgentAddress, }); - var buyProductAction = new BuyProduct + var buyProductAction = new BuyProduct2 { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfos, @@ -630,7 +630,7 @@ public void Execute_ErrorCode(ErrorCodeMember errorCodeMember) action.errors.Select(r => r.errorCode) ); - var buyProductAction = new BuyProduct + var buyProductAction = new BuyProduct2 { AvatarAddress = _buyerAvatarAddress, ProductInfos = new[] { productInfo }, diff --git a/.Lib9c.Tests/Action/MarketValidationTest.cs b/.Lib9c.Tests/Action/MarketValidationTest.cs index 77f7eadc1a..e66d1ee252 100644 --- a/.Lib9c.Tests/Action/MarketValidationTest.cs +++ b/.Lib9c.Tests/Action/MarketValidationTest.cs @@ -203,7 +203,7 @@ public void Validate_RegisterInfo(params RegisterInfosMember[] validateMembers) { foreach (var registerInfo in validateMember.RegisterInfos) { - var registerProduct = new RegisterProduct + var registerProduct = new RegisterProduct2 { AvatarAddress = AvatarAddress, RegisterInfos = new[] { registerInfo }, @@ -237,7 +237,7 @@ public void Validate_ProductInfo(params ProductInfosMember[] validateMembers) { foreach (var productInfo in validateMember.ProductInfos) { - var buyProduct = new BuyProduct + var buyProduct = new BuyProduct2 { AvatarAddress = AvatarAddress, ProductInfos = new[] { productInfo }, @@ -245,7 +245,7 @@ public void Validate_ProductInfo(params ProductInfosMember[] validateMembers) Assert.Throws(validateMember.Exc, () => buyProduct.Execute(actionContext)); - var cancelRegister = new CancelProductRegistration + var cancelRegister = new CancelProductRegistration0 { AvatarAddress = AvatarAddress, ProductInfos = new List() { productInfo }, diff --git a/.Lib9c.Tests/Action/Scenario/ArenaScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/ArenaScenarioTest.cs index d3a9c06aeb..48aefabda1 100644 --- a/.Lib9c.Tests/Action/Scenario/ArenaScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/ArenaScenarioTest.cs @@ -102,7 +102,7 @@ public IAccount JoinArena( var preCurrency = roundData.EntranceFee * _crystal; _state = _state.MintAsset(context, signer, preCurrency); - var action = new JoinArena() + var action = new JoinArena3() { championshipId = roundData.ChampionshipId, round = roundData.Round, diff --git a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs index 84a505b9ed..d870f5023e 100644 --- a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs @@ -97,7 +97,7 @@ public void HackAndSlash() var itemSlotStateAddress = ItemSlotState.DeriveAddress(_avatarAddress, BattleType.Adventure); Assert.Null(_initialState.GetState(itemSlotStateAddress)); - var has = new HackAndSlash + var has = new HackAndSlash21 { StageId = 1, AvatarAddress = _avatarAddress, @@ -154,7 +154,7 @@ public void Raid() avatarState.worldInformation.Serialize() ); - var raid = new Raid + var raid = new Raid6 { AvatarAddress = _avatarAddress, EquipmentIds = new List @@ -197,7 +197,7 @@ public void Arena() avatarState.worldInformation.Serialize() ); - var join = new JoinArena + var join = new JoinArena3 { avatarAddress = avatarAddress, championshipId = 1, @@ -227,7 +227,7 @@ public void Arena() var enemyAvatarAddress = avatarAddress.Equals(_avatarAddress) ? _enemyAvatarAddress : _avatarAddress; - var battle = new BattleArena + var battle = new BattleArena13 { myAvatarAddress = avatarAddress, enemyAvatarAddress = enemyAvatarAddress, @@ -328,7 +328,7 @@ public void Market() _avatarAddress.Derive(LegacyWorldInformationKey), avatarState.worldInformation.Serialize()); - var register = new RegisterProduct + var register = new RegisterProduct2 { AvatarAddress = _avatarAddress, RegisterInfos = new List diff --git a/.Lib9c.Tests/Action/Scenario/CombinationAndRapidCombinationTest.cs b/.Lib9c.Tests/Action/Scenario/CombinationAndRapidCombinationTest.cs index 1de62f4895..fb12529d15 100644 --- a/.Lib9c.Tests/Action/Scenario/CombinationAndRapidCombinationTest.cs +++ b/.Lib9c.Tests/Action/Scenario/CombinationAndRapidCombinationTest.cs @@ -145,7 +145,7 @@ public void Case(int randomSeed, int[] optionNumbers) e.RequiredGold == 0); var recipeRow = _tableSheets.EquipmentItemRecipeSheet.OrderedList.First(e => e.SubRecipeIds.Contains(subRecipeRow.Id)); - var combinationEquipmentAction = new CombinationEquipment + var combinationEquipmentAction = new CombinationEquipment16 { avatarAddress = _avatarAddress, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs b/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs index 6e3d994bb3..c1bcf4a79f 100644 --- a/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs +++ b/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs @@ -117,7 +117,7 @@ public void CraftEquipmentTest(int randomSeed, int[] targetItemIdList) ); // Do Combination Action - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = i, @@ -205,7 +205,7 @@ public void CraftConsumableTest(int randomSeed, int[] targetItemIdList) { // Do combination action var recipe = recipeList[i]; - var action = new CombinationConsumable + var action = new CombinationConsumable8 { avatarAddress = _avatarAddr, slotIndex = i, @@ -288,7 +288,7 @@ int[] targetItemIdList var eventRow = _tableSheets.EventScheduleSheet[eventScheduleId]; // Do combination action var recipe = recipeList[i]; - var action = new EventConsumableItemCrafts + var action = new EventConsumableItemCrafts0 { AvatarAddress = _avatarAddr, EventScheduleId = eventScheduleId, @@ -386,7 +386,7 @@ int[] targetItemIdList } } - var action = new EventMaterialItemCrafts + var action = new EventMaterialItemCrafts0 { AvatarAddress = _avatarAddr, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs index c0013b6e43..58275cd63c 100644 --- a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs @@ -135,7 +135,7 @@ public void Register_And_Buy() var random = new TestRandom(); var productInfoList = new List(); - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -166,7 +166,7 @@ public void Register_And_Buy() }); var nextAvatarState = nextState.GetAvatarStateV2(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); var productsState = new ProductsState((List)nextState.GetState(ProductsState.DeriveAddress(_sellerAvatarAddress))); @@ -209,7 +209,7 @@ public void Register_And_Buy() } } - var action2 = new RegisterProduct + var action2 = new RegisterProduct2 { AvatarAddress = _sellerAvatarAddress2, RegisterInfos = new List @@ -240,7 +240,7 @@ public void Register_And_Buy() }); var nextAvatarState2 = nextState2.GetAvatarStateV2(_sellerAvatarAddress2); Assert.Empty(nextAvatarState2.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState2.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState2.actionPoint); var productList2 = new ProductsState((List)nextState2.GetState(ProductsState.DeriveAddress(_sellerAvatarAddress2))); @@ -283,7 +283,7 @@ public void Register_And_Buy() } } - var action3 = new BuyProduct + var action3 = new BuyProduct2 { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfoList, @@ -358,7 +358,7 @@ public void Register_And_Cancel() _initialState = _initialState .SetState(_sellerAvatarAddress, _sellerAvatarState.Serialize()) .MintAsset(context, _sellerAvatarAddress, 1 * RuneHelper.StakeRune); - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -398,7 +398,7 @@ public void Register_And_Cancel() var nextAvatarState = nextState.GetAvatarStateV2(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -441,7 +441,7 @@ public void Register_And_Cancel() } Assert.All(new[] { nonFungibleProductId, fungibleProductId, assetProductId }, productId => Assert.NotEqual(default, productId)); - var action2 = new CancelProductRegistration + var action2 = new CancelProductRegistration0 { AvatarAddress = _sellerAvatarAddress, ProductInfos = new List @@ -493,7 +493,7 @@ public void Register_And_Cancel() ); } - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp - CancelProductRegistration.CostAp, latestAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp - CancelProductRegistration0.CostAp, latestAvatarState.actionPoint); var sellProductList = new ProductsState((List)latestState.GetState(productsStateAddress)); Assert.Empty(sellProductList.ProductIds); @@ -530,7 +530,7 @@ public void Register_And_ReRegister() _initialState = _initialState .MintAsset(context, _sellerAvatarAddress, 2 * RuneHelper.StakeRune) .SetState(_sellerAvatarAddress, _sellerAvatarState.Serialize()); - var action = new RegisterProduct + var action = new RegisterProduct2 { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -570,7 +570,7 @@ public void Register_And_ReRegister() var nextAvatarState = nextState.GetAvatarStateV2(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -693,7 +693,7 @@ public void Register_And_ReRegister() }); var latestAvatarState = latestState.GetAvatarStateV2(_sellerAvatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp - ReRegisterProduct.CostAp, latestAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp - ReRegisterProduct.CostAp, latestAvatarState.actionPoint); var inventoryItem = Assert.Single(latestAvatarState.inventory.Items); Assert.Equal(1, inventoryItem.count); Assert.IsType(inventoryItem.item); @@ -960,7 +960,7 @@ public void HardFork() }, }; //Cancel - var cancelAction = new CancelProductRegistration + var cancelAction = new CancelProductRegistration0 { AvatarAddress = _sellerAvatarAddress, ProductInfos = productInfos, @@ -1057,7 +1057,7 @@ public void HardFork() })); //Buy - var buyAction = new BuyProduct + var buyAction = new BuyProduct2 { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfos, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs index 43531f9b23..366f4f8aec 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs @@ -117,7 +117,7 @@ int petLevel ); // Do combination without pet - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 0, @@ -156,7 +156,7 @@ int petLevel random ); - var petAction = new CombinationEquipment + var petAction = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 1, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs index 38cfe804b3..7209d4e657 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs @@ -104,7 +104,7 @@ public void PetCannotBeUsedToTwoSlotsAtTheSameTime() ); // Combination1 - var action1 = new CombinationEquipment + var action1 = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 0, @@ -121,7 +121,7 @@ public void PetCannotBeUsedToTwoSlotsAtTheSameTime() }); // Combination2: Raises error - var action2 = new CombinationEquipment + var action2 = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 1, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs index 8aa4d0e82a..6c9302a09d 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs @@ -114,7 +114,7 @@ public void CraftEquipmentTest( ); // Do combination - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs index b65b4776c0..e519af41b0 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs @@ -149,7 +149,7 @@ public void RapidCombinationTest_Equipment( ); // Do combination - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 0, @@ -167,7 +167,7 @@ public void RapidCombinationTest_Equipment( }); // Do rapid combination - var rapidAction = new RapidCombination + var rapidAction = new RapidCombination9 { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs index a4913fade7..ff22a793ad 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs @@ -104,7 +104,7 @@ public void CombinationEquipmentTest( ); // Do Combination - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/RapidCombinationTest.cs b/.Lib9c.Tests/Action/Scenario/RapidCombinationTest.cs index b1799613bd..cd6334b6f9 100644 --- a/.Lib9c.Tests/Action/Scenario/RapidCombinationTest.cs +++ b/.Lib9c.Tests/Action/Scenario/RapidCombinationTest.cs @@ -136,7 +136,7 @@ int expectedHourGlassCount // Do combination action var recipe = recipeList[i]; - var action = new CombinationEquipment + var action = new CombinationEquipment16 { avatarAddress = _avatarAddr, slotIndex = i, @@ -161,7 +161,7 @@ int expectedHourGlassCount // Do RapidCombination for (var i = 0; i < recipeList.Count; i++) { - var action = new RapidCombination + var action = new RapidCombination9 { avatarAddress = _avatarAddr, slotIndex = i, diff --git a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs index 358b25859a..6fd99485c7 100644 --- a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs @@ -91,7 +91,7 @@ public void Craft_And_Equip() var runeSlotStateAddress = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Adventure); Assert.Null(state.GetState(runeSlotStateAddress)); - var has = new HackAndSlash + var has = new HackAndSlash21 { StageId = 1, AvatarAddress = avatarAddress, diff --git a/.Lib9c.Tests/Action/SellCancellationTest.cs b/.Lib9c.Tests/Action/SellCancellationTest.cs index f3d5fbdc1e..6a239f218b 100644 --- a/.Lib9c.Tests/Action/SellCancellationTest.cs +++ b/.Lib9c.Tests/Action/SellCancellationTest.cs @@ -238,7 +238,7 @@ bool fromPreviousAction Signer = _agentAddress, }); - var cancelProductRegistration = new CancelProductRegistration + var cancelProductRegistration = new CancelProductRegistration0 { AvatarAddress = _avatarAddress, ProductInfos = new List diff --git a/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs b/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs index 0c3c79d618..dec0621092 100644 --- a/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs +++ b/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs @@ -137,7 +137,7 @@ private Equipment CreateEquipment( if (!(subRecipeRow is null)) { - CombinationEquipment.AddAndUnlockOption( + CombinationEquipment16.AddAndUnlockOption( new AgentState(new PrivateKey().ToAddress()), null, equipment, diff --git a/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs b/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs index cef36cc607..0568b185e4 100644 --- a/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs +++ b/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs @@ -490,7 +490,7 @@ public IAccount Execute( var recipe = recipeSheet.OrderedList! .First(e => e.ResultEquipmentId == equipmentId); var subRecipe = subRecipeSheetV2[recipe.SubRecipeIds[1]]; - CombinationEquipment.AddAndUnlockOption( + CombinationEquipment16.AddAndUnlockOption( agent, null, equipment, diff --git a/Lib9c/Action/AuraSummon.cs b/Lib9c/Action/AuraSummon.cs index 4380f0c2f0..fa1c7324ca 100644 --- a/Lib9c/Action/AuraSummon.cs +++ b/Lib9c/Action/AuraSummon.cs @@ -320,7 +320,7 @@ SkillSheet skillSheet } else { - var skill = CombinationEquipment.GetSkill(optionRow, skillSheet, random); + var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); if (skill is null) continue; equipment.Skills.Add(skill);