diff --git a/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs b/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs index c25149aa93..0e622181d4 100644 --- a/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs +++ b/.Lib9c.Tests/Action/CancelProductRegistrationTest.cs @@ -74,7 +74,8 @@ public CancelProductRegistrationTest(ITestOutputHelper outputHelper) .SetLegacyState(GoldCurrencyState.Address, _goldCurrencyState.Serialize()) .SetAgentState(_agentAddress, agentState) .SetLegacyState(Addresses.Shop, new ShopState().Serialize()) - .SetAvatarState(_avatarAddress, avatarState); + .SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); } [Theory] diff --git a/.Lib9c.Tests/Action/ChargeActionPointTest.cs b/.Lib9c.Tests/Action/ChargeActionPointTest.cs index 2b57f745f7..2ab13ed3ce 100644 --- a/.Lib9c.Tests/Action/ChargeActionPointTest.cs +++ b/.Lib9c.Tests/Action/ChargeActionPointTest.cs @@ -41,10 +41,7 @@ public ChargeActionPointTest() _tableSheets.GetAvatarSheets(), gameConfigState, default - ) - { - actionPoint = 0, - }; + ); agent.avatarAddresses.Add(0, _avatarAddress); _initialState = new World(MockUtil.MockModernWorldState) @@ -76,11 +73,10 @@ public void Execute(bool useTradable) avatarState.inventory.AddItem(apStone); } - Assert.Equal(0, avatarState.actionPoint); - - IWorld state; - state = _initialState.SetAvatarState(_avatarAddress, avatarState); + Assert.False(_initialState.TryGetActionPoint(_avatarAddress, out var actionPoint)); + Assert.Equal(0L, actionPoint); + var state = _initialState.SetAvatarState(_avatarAddress, avatarState); foreach (var (key, value) in _sheets) { state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); @@ -98,9 +94,8 @@ public void Execute(bool useTradable) RandomSeed = 0, }); - var nextAvatarState = nextState.GetAvatarState(_avatarAddress); - var gameConfigState = nextState.GetGameConfigState(); - Assert.Equal(gameConfigState.ActionPointMax, nextAvatarState.actionPoint); + Assert.True(nextState.TryGetActionPoint(_avatarAddress, out var nextActionPoint)); + Assert.Equal(DailyReward.ActionPointMax, nextActionPoint); } [Theory] @@ -109,11 +104,11 @@ public void Execute(bool useTradable) [InlineData(true, true, false, false, typeof(NotEnoughMaterialException))] [InlineData(true, false, true, true, typeof(ActionPointExceededException))] [InlineData(true, true, true, true, typeof(ActionPointExceededException))] - public void Execute_Throw_Exception(bool useAvatarAddress, bool useTradable, bool enough, bool charge, Type exc) + public void Execute_Throw_Exception(bool useAvatarAddress, bool useTradable, bool enoughApStone, bool actionPointIsAlreadyCharged, Type exc) { var avatarState = _initialState.GetAvatarState(_avatarAddress); - - Assert.Equal(0, avatarState.actionPoint); + _initialState.TryGetActionPoint(_avatarAddress, out var prevActionPoint); + Assert.Equal(0L, prevActionPoint); var avatarAddress = useAvatarAddress ? _avatarAddress : default; var state = _initialState; @@ -123,22 +118,21 @@ public void Execute_Throw_Exception(bool useAvatarAddress, bool useTradable, boo : ItemFactory.CreateMaterial(row); if (apStone is TradableMaterial tradableMaterial) { - if (!enough) + if (!enoughApStone) { tradableMaterial.RequiredBlockIndex = 10; } } - if (enough) + if (enoughApStone) { avatarState.inventory.AddItem(apStone); state = state.SetAvatarState(_avatarAddress, avatarState); } - if (charge) + if (actionPointIsAlreadyCharged) { - avatarState.actionPoint = state.GetGameConfigState().ActionPointMax; - state = state.SetAvatarState(_avatarAddress, avatarState); + state = state.SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); } var action = new ChargeActionPoint() diff --git a/.Lib9c.Tests/Action/CombinationConsumableTest.cs b/.Lib9c.Tests/Action/CombinationConsumableTest.cs index 00116f45b5..bb42d749d8 100644 --- a/.Lib9c.Tests/Action/CombinationConsumableTest.cs +++ b/.Lib9c.Tests/Action/CombinationConsumableTest.cs @@ -88,7 +88,7 @@ public void Execute() avatarState.inventory.AddItem(material, materialInfo.Count); } - var previousActionPoint = avatarState.actionPoint; + _initialState.TryGetActionPoint(_avatarAddress, out var previousActionPoint); var previousResultConsumableCount = avatarState.inventory.Equipments.Count(e => e.Id == row.ResultConsumableItemId); var previousMailCount = avatarState.mailBox.Count; @@ -123,7 +123,11 @@ public void Execute() Assert.NotNull(consumable); var nextAvatarState = nextState.GetAvatarState(_avatarAddress); - Assert.Equal(previousActionPoint - costActionPoint, nextAvatarState.actionPoint); + if (nextState.TryGetActionPoint(_avatarAddress, out var nextActionPoint)) + { + Assert.Equal(previousActionPoint - costActionPoint, nextActionPoint); + } + Assert.Equal(previousMailCount + 1, nextAvatarState.mailBox.Count); Assert.IsType(nextAvatarState.mailBox.First()); Assert.Equal( diff --git a/.Lib9c.Tests/Action/CombinationEquipmentTest.cs b/.Lib9c.Tests/Action/CombinationEquipmentTest.cs index fe9b374b27..a1aad86cb4 100644 --- a/.Lib9c.Tests/Action/CombinationEquipmentTest.cs +++ b/.Lib9c.Tests/Action/CombinationEquipmentTest.cs @@ -79,7 +79,8 @@ public CombinationEquipmentTest(ITestOutputHelper outputHelper) _initialState = new World(MockUtil.MockModernWorldState) .SetLegacyState(_slotAddress, combinationSlotState.Serialize()) - .SetLegacyState(GoldCurrencyState.Address, gold.Serialize()); + .SetLegacyState(GoldCurrencyState.Address, gold.Serialize()) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); foreach (var (key, value) in sheets) { @@ -291,6 +292,9 @@ bool previousCostStateExist Assert.NotNull(slotState.Result.itemUsable); var equipment = (Equipment)slotState.Result.itemUsable; + var expectedActionPoint = DailyReward.ActionPointMax - _tableSheets + .EquipmentItemRecipeSheet[recipeId] + .RequiredActionPoint; if (subRecipeId.HasValue) { Assert.True(equipment.optionCountFromCombination > 0); @@ -302,6 +306,10 @@ bool previousCostStateExist var feeStoreAddress = Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); Assert.Equal(450 * currency, nextState.GetBalance(feeStoreAddress, currency)); } + + expectedActionPoint -= _tableSheets + .EquipmentItemSubRecipeSheetV2[subRecipeId.Value] + .RequiredActionPoint; } else { @@ -328,6 +336,7 @@ bool previousCostStateExist } Assert.Equal(expectedCrystal * CrystalCalculator.CRYSTAL, nextState.GetBalance(Addresses.MaterialCost, CrystalCalculator.CRYSTAL)); + Assert.Equal(expectedActionPoint, nextState.GetActionPoint(_avatarAddress)); } else { diff --git a/.Lib9c.Tests/Action/CreateAvatarTest.cs b/.Lib9c.Tests/Action/CreateAvatarTest.cs index b6e4b39ae4..3704c5fbf3 100644 --- a/.Lib9c.Tests/Action/CreateAvatarTest.cs +++ b/.Lib9c.Tests/Action/CreateAvatarTest.cs @@ -85,6 +85,9 @@ public void Execute(long blockIndex) Assert.True(agentState.avatarAddresses.Any()); Assert.Equal("test", nextAvatarState.name); Assert.Equal(200_000 * CrystalCalculator.CRYSTAL, nextState.GetBalance(_agentAddress, CrystalCalculator.CRYSTAL)); + Assert.Equal(DailyReward.ActionPointMax, nextState.GetActionPoint(avatarAddress)); + Assert.True(nextState.TryGetDailyRewardReceivedBlockIndex(avatarAddress, out var nextDailyRewardReceivedIndex)); + Assert.Equal(0L, nextDailyRewardReceivedIndex); var avatarItemSheet = nextState.GetSheet(); foreach (var row in avatarItemSheet.Values) { diff --git a/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs b/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs index 72fddc9d3d..a0920abcfa 100644 --- a/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs +++ b/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs @@ -127,7 +127,7 @@ private void Execute( previousStates = previousStates .SetAvatarState(_avatarAddress, previousAvatarState); - var previousActionPoint = previousAvatarState.actionPoint; + previousStates.TryGetActionPoint(_avatarAddress, out var previousActionPoint); var previousResultConsumableCount = previousAvatarState.inventory.Equipments .Count(e => e.Id == recipeRow.ResultConsumableItemId); @@ -157,9 +157,13 @@ private void Execute( Assert.NotNull(consumable); var nextAvatarState = nextStates.GetAvatarState(_avatarAddress); - Assert.Equal( - previousActionPoint - recipeRow.RequiredActionPoint, - nextAvatarState.actionPoint); + if (nextStates.TryGetActionPoint(_avatarAddress, out var nextAp)) + { + Assert.Equal( + previousActionPoint - recipeRow.RequiredActionPoint, + nextAp); + } + Assert.Equal( previousMailCount + 1, nextAvatarState.mailBox.Count); diff --git a/.Lib9c.Tests/Action/GrindingTest.cs b/.Lib9c.Tests/Action/GrindingTest.cs index 892e1c71c3..5542b2b2d9 100644 --- a/.Lib9c.Tests/Action/GrindingTest.cs +++ b/.Lib9c.Tests/Action/GrindingTest.cs @@ -132,7 +132,7 @@ Type exc if (avatarExist) { - _avatarState.actionPoint = ap; + state = state.SetActionPoint(_avatarAddress, ap); if (equipmentExist) { @@ -229,7 +229,7 @@ Type exc Assert.Equal(asset, nextState.GetBalance(_agentAddress, _crystalCurrency)); Assert.False(nextAvatarState.inventory.HasNonFungibleItem(default)); - Assert.Equal(115, nextAvatarState.actionPoint); + Assert.Equal(115, nextState.GetActionPoint(_avatarAddress)); var mail = nextAvatarState.mailBox.OfType().First(i => i.id.Equals(action.Id)); diff --git a/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs index 154542a581..aa88ec40e8 100644 --- a/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs +++ b/.Lib9c.Tests/Action/HackAndSlashSweepTest.cs @@ -74,7 +74,8 @@ public HackAndSlashSweepTest() .SetAgentState(_agentAddress, agentState) .SetAvatarState(_avatarAddress, _avatarState) .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()) - .SetLegacyState(Addresses.GoldCurrency, goldCurrencyState.Serialize()); + .SetLegacyState(Addresses.GoldCurrency, goldCurrencyState.Serialize()) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); foreach (var (key, value) in _sheets) { @@ -329,19 +330,20 @@ public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingA avatarState.inventory.AddItem(apStone, holdingApStoneCount); IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); + var actionPoint = _initialState.GetActionPoint(_avatarAddress); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); if (stageSheet.TryGetValue(2, out var stageRow)) { var itemPlayCount = - gameConfigState.ActionPointMax / stageRow.CostAP * useApStoneCount; - var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + DailyReward.ActionPointMax / stageRow.CostAP * useApStoneCount; + var apPlayCount = actionPoint / stageRow.CostAP; var playCount = apPlayCount + itemPlayCount; (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _tableSheets.CharacterLevelSheet, 2, - playCount); + (int)playCount); var (equipments, costumes) = GetDummyItems(avatarState); @@ -351,7 +353,7 @@ public void Execute_NotEnoughMaterialException(int useApStoneCount, int holdingA costumes = costumes, runeInfos = new List(), avatarAddress = _avatarAddress, - actionPoint = avatarState.actionPoint, + actionPoint = (int)actionPoint, apStoneCount = useApStoneCount, worldId = 1, stageId = 2, @@ -381,23 +383,24 @@ public void Execute_NotEnoughActionPointException() worldInformation = new WorldInformation(0, _initialState.GetSheet(), 25), level = 400, - actionPoint = 0, }; - IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, 0); + var actionPoint = _initialState.GetActionPoint(_avatarAddress); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); if (stageSheet.TryGetValue(2, out var stageRow)) { var itemPlayCount = - gameConfigState.ActionPointMax / stageRow.CostAP * 1; - var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + DailyReward.ActionPointMax / stageRow.CostAP * 1; + var apPlayCount = actionPoint / stageRow.CostAP; var playCount = apPlayCount + itemPlayCount; (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _tableSheets.CharacterLevelSheet, 2, - playCount); + (int)playCount); var (equipments, costumes) = GetDummyItems(avatarState); var action = new HackAndSlashSweep @@ -437,10 +440,11 @@ public void Execute_PlayCountIsZeroException() worldInformation = new WorldInformation(0, _initialState.GetSheet(), 25), level = 400, - actionPoint = 0, }; - IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, 0); + var actionPoint = state.GetActionPoint(_avatarAddress); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); @@ -448,12 +452,12 @@ public void Execute_PlayCountIsZeroException() { var itemPlayCount = gameConfigState.ActionPointMax / stageRow.CostAP * 1; - var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + var apPlayCount = actionPoint / stageRow.CostAP; var playCount = apPlayCount + itemPlayCount; (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _tableSheets.CharacterLevelSheet, 2, - playCount); + (int)playCount); var (equipments, costumes) = GetDummyItems(avatarState); var action = new HackAndSlashSweep @@ -492,11 +496,12 @@ public void Execute_NotEnoughCombatPointException() { worldInformation = new WorldInformation(0, _initialState.GetSheet(), 25), - actionPoint = 0, level = 1, }; - IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState); + IWorld state = _initialState.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, 0); + var actionPoint = state.GetActionPoint(_avatarAddress); var stageSheet = _initialState.GetSheet(); var (expectedLevel, expectedExp) = (0, 0L); @@ -504,13 +509,13 @@ public void Execute_NotEnoughCombatPointException() if (stageSheet.TryGetValue(stageId, out var stageRow)) { var itemPlayCount = - gameConfigState.ActionPointMax / stageRow.CostAP * 1; - var apPlayCount = avatarState.actionPoint / stageRow.CostAP; + DailyReward.ActionPointMax / stageRow.CostAP * 1; + var apPlayCount = actionPoint / stageRow.CostAP; var playCount = apPlayCount + itemPlayCount; (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _tableSheets.CharacterLevelSheet, stageId, - playCount); + (int)playCount); var action = new HackAndSlashSweep { @@ -518,7 +523,7 @@ public void Execute_NotEnoughCombatPointException() equipments = new List(), runeInfos = new List(), avatarAddress = _avatarAddress, - actionPoint = avatarState.actionPoint, + actionPoint = (int)actionPoint, apStoneCount = 1, worldId = 1, stageId = stageId, @@ -555,7 +560,6 @@ public void ExecuteWithStake(int stakingLevel) { worldInformation = new WorldInformation(0, _initialState.GetSheet(), 25), - actionPoint = 120, level = 3, }; var itemRow = _tableSheets.MaterialItemSheet.Values.First(r => @@ -576,15 +580,16 @@ public void ExecuteWithStake(int stakingLevel) if (stageSheet.TryGetValue(stageId, out var stageRow)) { var apSheet = _initialState.GetSheet(); + var actionPoint = _initialState.GetActionPoint(_avatarAddress); var costAp = apSheet.GetActionPointByStaking(stageRow.CostAP, 1, stakingLevel); var itemPlayCount = gameConfigState.ActionPointMax / costAp * 1; - var apPlayCount = avatarState.actionPoint / costAp; + var apPlayCount = actionPoint / costAp; var playCount = apPlayCount + itemPlayCount; var (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _initialState.GetSheet(), stageId, - playCount); + (int)playCount); var action = new HackAndSlashSweep { @@ -592,7 +597,7 @@ public void ExecuteWithStake(int stakingLevel) equipments = new List(), runeInfos = new List(), avatarAddress = _avatarAddress, - actionPoint = avatarState.actionPoint, + actionPoint = (int)actionPoint, apStoneCount = 1, worldId = worldId, stageId = stageId, @@ -633,7 +638,6 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 { worldInformation = new WorldInformation(0, _initialState.GetSheet(), 25), - actionPoint = 120, level = 3, }; var itemRow = _tableSheets.MaterialItemSheet.Values.First(r => @@ -655,14 +659,15 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 { var apSheet = _initialState.GetSheet(); var costAp = apSheet.GetActionPointByStaking(stageRow.CostAP, 1, stakingLevel); + var actionPoint = _initialState.GetActionPoint(_avatarAddress); var itemPlayCount = - gameConfigState.ActionPointMax / costAp * 1; - var apPlayCount = avatarState.actionPoint / costAp; + DailyReward.ActionPointMax / costAp * 1; + var apPlayCount = actionPoint / costAp; var playCount = apPlayCount + itemPlayCount; var (expectedLevel, expectedExp) = avatarState.GetLevelAndExp( _initialState.GetSheet(), stageId, - playCount); + (int)playCount); var ncgCurrency = state.GetGoldCurrency(); state = state.MintAsset(context, _agentAddress, 99999 * ncgCurrency); @@ -691,7 +696,7 @@ public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2 new RuneSlotInfo(slotIndex2, runeId2), }, avatarAddress = _avatarAddress, - actionPoint = avatarState.actionPoint, + actionPoint = (int)actionPoint, apStoneCount = 1, worldId = worldId, stageId = stageId, diff --git a/.Lib9c.Tests/Action/HackAndSlashTest.cs b/.Lib9c.Tests/Action/HackAndSlashTest.cs index 291a3f853c..ecbef6a22c 100644 --- a/.Lib9c.Tests/Action/HackAndSlashTest.cs +++ b/.Lib9c.Tests/Action/HackAndSlashTest.cs @@ -86,7 +86,8 @@ public HackAndSlashTest() .SetLegacyState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) .SetAgentState(_agentAddress, agentState) .SetAvatarState(_avatarAddress, _avatarState) - .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); foreach (var (key, value) in _sheets) { @@ -710,10 +711,7 @@ public void ExecuteThrowEquipmentSlotUnlockException(ItemSubType itemSubType) [InlineData(120, 25)] public void ExecuteThrowNotEnoughActionPointException(int ap, int playCount = 1) { - var avatarState = new AvatarState(_avatarState) - { - actionPoint = ap, - }; + var avatarState = new AvatarState(_avatarState); var action = new HackAndSlash { @@ -728,7 +726,8 @@ public void ExecuteThrowNotEnoughActionPointException(int ap, int playCount = 1) }; var state = _initialState; - state = state.SetAvatarState(_avatarAddress, avatarState); + state = state.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, ap); var exec = Assert.Throws(() => action.Execute(new ActionContext { @@ -809,7 +808,6 @@ public void Execute_Throw_NotEnoughAvatarLevelException(int avatarLevel) { var avatarState = new AvatarState(_avatarState) { - actionPoint = 99999999, level = avatarLevel, }; @@ -837,7 +835,8 @@ public void Execute_Throw_NotEnoughAvatarLevelException(int avatarLevel) costumes.Add(((INonFungibleItem)costume).NonFungibleId); } - state = state.SetAvatarState(avatarState.address, avatarState); + state = state.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, 99999999); var action = new HackAndSlash { @@ -867,11 +866,10 @@ public void ExecuteThrowInvalidItemCountException() { var avatarState = new AvatarState(_avatarState) { - actionPoint = 99999999, level = 1, }; - var state = _initialState; + var state = _initialState.SetActionPoint(_avatarAddress, 99999999); var action = new HackAndSlash { Costumes = new List(), @@ -904,11 +902,10 @@ public void ExecuteThrowPlayCountIsZeroException(int totalPlayCount, int apStone { var avatarState = new AvatarState(_avatarState) { - actionPoint = 99999999, level = 1, }; - var state = _initialState; + var state = _initialState.SetActionPoint(_avatarAddress, 99999999); var action = new HackAndSlash { Costumes = new List(), @@ -999,7 +996,6 @@ public void CheckRewardItems(int worldId, int stageId) Assert.True(_tableSheets.StageSheet.TryGetValue(stageId, out var stageRow)); var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); - previousAvatarState.actionPoint = 999999; previousAvatarState.level = 400; previousAvatarState.worldInformation = new WorldInformation( 0, @@ -1046,7 +1042,8 @@ public void CheckRewardItems(int worldId, int stageId) .SetAvatarState(_avatarAddress, previousAvatarState) .SetLegacyState( _avatarAddress.Derive("world_ids"), - Enumerable.Range(1, worldId).ToList().Select(i => i.Serialize()).Serialize()); + Enumerable.Range(1, worldId).ToList().Select(i => i.Serialize()).Serialize()) + .SetActionPoint(_avatarAddress, 999999); var action = new HackAndSlash { @@ -1126,7 +1123,6 @@ public void CheckCrystalRandomSkillState( const int stageId = 10; const int clearedStageId = 9; var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); - previousAvatarState.actionPoint = 999999; previousAvatarState.level = clear ? 400 : 1; previousAvatarState.worldInformation = new WorldInformation( 0, @@ -1169,7 +1165,8 @@ public void CheckCrystalRandomSkillState( previousAvatarState.Update(mail); } - var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState) + .SetActionPoint(_avatarAddress, 999999); state = state.SetLegacyState( _avatarAddress.Derive("world_ids"), @@ -1295,7 +1292,6 @@ public void CheckUsedApByStaking(int level, int playCount) const int stageId = 5; const int clearedStageId = 4; var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); - previousAvatarState.actionPoint = 120; previousAvatarState.level = 400; previousAvatarState.worldInformation = new WorldInformation( 0, @@ -1309,13 +1305,14 @@ public void CheckUsedApByStaking(int level, int playCount) var context = new ActionContext(); var state = _initialState .SetAvatarState(_avatarAddress, previousAvatarState) + .SetActionPoint(_avatarAddress, 120) .SetLegacyState(stakeStateAddress, stakeState.SerializeV2()) .SetLegacyState( _avatarAddress.Derive("world_ids"), List.Empty.Add(worldId.Serialize())) .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); - var expectedAp = previousAvatarState.actionPoint - + var expectedAp = state.GetActionPoint(_avatarAddress) - _tableSheets.StakeActionPointCoefficientSheet.GetActionPointByStaking( _tableSheets.StageSheet[stageId].CostAP, playCount, level); var action = new HackAndSlash @@ -1339,8 +1336,7 @@ public void CheckUsedApByStaking(int level, int playCount) BlockIndex = 1, }; var nextState = action.Execute(ctx); - var nextAvatar = nextState.GetAvatarState(_avatarAddress); - Assert.Equal(expectedAp, nextAvatar.actionPoint); + Assert.Equal(expectedAp, nextState.GetActionPoint(_avatarAddress)); } [Theory] @@ -1361,7 +1357,6 @@ public void CheckUsingApStoneWithStaking(int level, int apStoneCount, int totalR const int clearedStageId = 4; const int itemId = 303100; var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); - previousAvatarState.actionPoint = expectedUsingAp; previousAvatarState.level = 400; previousAvatarState.worldInformation = new WorldInformation( 0, @@ -1378,6 +1373,7 @@ public void CheckUsingApStoneWithStaking(int level, int apStoneCount, int totalR var context = new ActionContext(); var state = _initialState .SetAvatarState(_avatarAddress, previousAvatarState) + .SetActionPoint(_avatarAddress, expectedUsingAp) .SetLegacyState(stakeStateAddress, stakeState.SerializeV2()) .SetLegacyState( _avatarAddress.Derive("world_ids"), @@ -1412,7 +1408,7 @@ public void CheckUsingApStoneWithStaking(int level, int apStoneCount, int totalR var nextAvatar = nextState.GetAvatarState(_avatarAddress); Assert.Equal(expectedItemCount, nextAvatar.inventory.Items.First(i => i.item.Id == itemId).count); Assert.False(nextAvatar.inventory.HasItem(apStoneRow.Id)); - Assert.Equal(0, nextAvatar.actionPoint); + Assert.Equal(0, nextState.GetActionPoint(_avatarAddress)); } [Fact] @@ -1420,7 +1416,6 @@ public void ExecuteThrowInvalidRepeatPlayException() { var avatarState = new AvatarState(_avatarState) { - actionPoint = 99999999, level = 1, }; @@ -1428,7 +1423,8 @@ public void ExecuteThrowInvalidRepeatPlayException() r.ItemSubType == ItemSubType.ApStone); var apStone = ItemFactory.CreateTradableMaterial(apStoneRow); avatarState.inventory.AddItem(apStone); - var state = _initialState.SetAvatarState(_avatarAddress, avatarState); + var state = _initialState.SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, 99999); var action = new HackAndSlash { Costumes = new List(), diff --git a/.Lib9c.Tests/Action/ReRegisterProduct0Test.cs b/.Lib9c.Tests/Action/ReRegisterProduct0Test.cs deleted file mode 100644 index c200602f7e..0000000000 --- a/.Lib9c.Tests/Action/ReRegisterProduct0Test.cs +++ /dev/null @@ -1,335 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Bencodex.Types; - using Lib9c.Model.Order; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Libplanet.Mocks; - using Libplanet.Types.Assets; - using Nekoyume; - using Nekoyume.Action; - using Nekoyume.Battle; - using Nekoyume.Model; - using Nekoyume.Model.Item; - using Nekoyume.Model.Market; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Serilog; - using Xunit; - using Xunit.Abstractions; - using static Lib9c.SerializeKeys; - - public class ReRegisterProduct0Test - { - private const long ProductPrice = 100; - private readonly Address _agentAddress; - private readonly Address _avatarAddress; - private readonly Currency _currency; - private readonly AvatarState _avatarState; - private readonly TableSheets _tableSheets; - private readonly GoldCurrencyState _goldCurrencyState; - private readonly GameConfigState _gameConfigState; - private IWorld _initialState; - - public ReRegisterProduct0Test(ITestOutputHelper outputHelper) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.TestOutput(outputHelper) - .CreateLogger(); - - _initialState = new World(MockUtil.MockModernWorldState); - var sheets = TableSheetsImporter.ImportSheets(); - foreach (var (key, value) in sheets) - { - _initialState = _initialState - .SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); - } - - _tableSheets = new TableSheets(sheets); - -#pragma warning disable CS0618 - // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 - _currency = Currency.Legacy("NCG", 2, null); -#pragma warning restore CS0618 - _goldCurrencyState = new GoldCurrencyState(_currency); - - var shopState = new ShopState(); - - _agentAddress = new PrivateKey().Address; - var agentState = new AgentState(_agentAddress); - _avatarAddress = new PrivateKey().Address; - var rankingMapAddress = new PrivateKey().Address; - _gameConfigState = new GameConfigState((Text)_tableSheets.GameConfigSheet.Serialize()); - _avatarState = new AvatarState( - _avatarAddress, - _agentAddress, - 0, - _tableSheets.GetAvatarSheets(), - _gameConfigState, - rankingMapAddress) - { - worldInformation = new WorldInformation( - 0, - _tableSheets.WorldSheet, - GameConfig.RequireClearedStageLevel.ActionsInShop), - }; - agentState.avatarAddresses[0] = _avatarAddress; - - _initialState = _initialState - .SetLegacyState(GoldCurrencyState.Address, _goldCurrencyState.Serialize()) - .SetLegacyState(Addresses.Shop, shopState.Serialize()) - .SetAgentState(_agentAddress, agentState) - .SetLegacyState(Addresses.GameConfig, _gameConfigState.Serialize()) - .SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(_avatarState)); - } - - [Theory] - [InlineData(ItemType.Equipment, "F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4", 1, 1, 1, true)] - [InlineData(ItemType.Costume, "936DA01F-9ABD-4d9d-80C7-02AF85C822A8", 1, 1, 1, true)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 1, 1, 1, true)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 2, 1, 2, true)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 2, 2, 3, true)] - [InlineData(ItemType.Equipment, "F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4", 1, 1, 1, false)] - [InlineData(ItemType.Costume, "936DA01F-9ABD-4d9d-80C7-02AF85C822A8", 1, 1, 1, false)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 1, 1, 1, false)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 2, 1, 2, false)] - [InlineData(ItemType.Material, "15396359-04db-68d5-f24a-d89c18665900", 2, 2, 3, false)] - public void Execute_BackwardCompatibility( - ItemType itemType, - string guid, - int itemCount, - int inventoryCount, - int expectedCount, - bool fromPreviousAction - ) - { - var avatarState = _initialState.GetAvatarState(_avatarAddress); - ITradableItem tradableItem; - var itemId = new Guid(guid); - var orderId = Guid.NewGuid(); - var updateSellOrderId = Guid.NewGuid(); - ItemSubType itemSubType; - const long requiredBlockIndex = Order.ExpirationInterval; - switch (itemType) - { - case ItemType.Equipment: - { - var itemUsable = ItemFactory.CreateItemUsable( - _tableSheets.EquipmentItemSheet.First, - itemId, - requiredBlockIndex); - tradableItem = (ITradableItem)itemUsable; - itemSubType = itemUsable.ItemSubType; - break; - } - - case ItemType.Costume: - { - var costume = ItemFactory.CreateCostume(_tableSheets.CostumeItemSheet.First, itemId); - costume.Update(requiredBlockIndex); - tradableItem = costume; - itemSubType = costume.ItemSubType; - break; - } - - default: - { - var material = ItemFactory.CreateTradableMaterial( - _tableSheets.MaterialItemSheet.OrderedList.First(r => r.ItemSubType == ItemSubType.Hourglass)); - itemSubType = material.ItemSubType; - material.RequiredBlockIndex = requiredBlockIndex; - tradableItem = material; - break; - } - } - - var shardedShopAddress = ShardedShopStateV2.DeriveAddress(itemSubType, orderId); - var shopState = new ShardedShopStateV2(shardedShopAddress); - var order = OrderFactory.Create( - _agentAddress, - _avatarAddress, - orderId, - new FungibleAssetValue(_goldCurrencyState.Currency, 100, 0), - tradableItem.TradableId, - requiredBlockIndex, - itemSubType, - itemCount - ); - - var orderDigestList = new OrderDigestListState(OrderDigestListState.DeriveAddress(_avatarAddress)); - var prevState = _initialState; - - if (inventoryCount > 1) - { - for (int i = 0; i < inventoryCount; i++) - { - // Different RequiredBlockIndex for divide inventory slot. - if (tradableItem is ITradableFungibleItem tradableFungibleItem) - { - var tradable = (TradableMaterial)tradableFungibleItem.Clone(); - tradable.RequiredBlockIndex = tradableItem.RequiredBlockIndex - i; - avatarState.inventory.AddItem(tradable, 2 - i); - } - } - } - else - { - avatarState.inventory.AddItem((ItemBase)tradableItem, itemCount); - } - - var sellItem = order.Sell(avatarState); - var orderDigest = order.Digest(avatarState, _tableSheets.CostumeStatSheet); - shopState.Add(orderDigest, requiredBlockIndex); - orderDigestList.Add(orderDigest); - - Assert.True(avatarState.inventory.TryGetLockedItem(new OrderLock(orderId), out _)); - - Assert.Equal(inventoryCount, avatarState.inventory.Items.Count); - Assert.Equal(expectedCount, avatarState.inventory.Items.Sum(i => i.count)); - - Assert.Single(shopState.OrderDigestList); - Assert.Single(orderDigestList.OrderDigestList); - - Assert.Equal(requiredBlockIndex * 2, sellItem.RequiredBlockIndex); - - if (fromPreviousAction) - { - prevState = prevState.SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(avatarState)); - } - else - { - prevState = prevState - .SetAvatarState(_avatarAddress, avatarState); - } - - prevState = prevState - .SetLegacyState(Addresses.GetItemAddress(itemId), sellItem.Serialize()) - .SetLegacyState(Order.DeriveAddress(order.OrderId), order.Serialize()) - .SetLegacyState(orderDigestList.Address, orderDigestList.Serialize()) - .SetLegacyState(shardedShopAddress, shopState.Serialize()); - - var currencyState = prevState.GetGoldCurrency(); - var price = new FungibleAssetValue(currencyState, ProductPrice, 0); - - var updateSellInfo = new UpdateSellInfo( - orderId, - updateSellOrderId, - itemId, - itemSubType, - price, - itemCount - ); - - var action = new UpdateSell - { - sellerAvatarAddress = _avatarAddress, - updateSellInfos = new[] { updateSellInfo }, - }; - - var expectedState = action.Execute(new ActionContext - { - BlockIndex = 101, - PreviousState = prevState, - RandomSeed = 0, - Signer = _agentAddress, - }); - - var updateSellShopAddress = ShardedShopStateV2.DeriveAddress(itemSubType, updateSellOrderId); - var nextShopState = new ShardedShopStateV2((Dictionary)expectedState.GetLegacyState(updateSellShopAddress)); - Assert.Equal(1, nextShopState.OrderDigestList.Count); - Assert.NotEqual(orderId, nextShopState.OrderDigestList.First().OrderId); - Assert.Equal(updateSellOrderId, nextShopState.OrderDigestList.First().OrderId); - Assert.Equal(itemId, nextShopState.OrderDigestList.First().TradableId); - Assert.Equal(requiredBlockIndex + 101, nextShopState.OrderDigestList.First().ExpiredBlockIndex); - - var productType = tradableItem is TradableMaterial - ? ProductType.Fungible - : ProductType.NonFungible; - var reRegister = new ReRegisterProduct0 - { - AvatarAddress = _avatarAddress, - ReRegisterInfos = new List<(IProductInfo, IRegisterInfo)> - { - ( - new ItemProductInfo - { - AgentAddress = _agentAddress, - AvatarAddress = _avatarAddress, - Legacy = true, - Price = order.Price, - ProductId = orderId, - Type = productType, - }, - new RegisterInfo - { - AvatarAddress = _avatarAddress, - ItemCount = itemCount, - Price = updateSellInfo.price, - TradableId = order.TradableId, - Type = productType, - } - ), - }, - }; - - var actualState = reRegister.Execute(new ActionContext - { - BlockIndex = 101, - PreviousState = prevState, - RandomSeed = 0, - Signer = _agentAddress, - }); - - var targetShopState = new ShardedShopStateV2((Dictionary)actualState.GetLegacyState(shardedShopAddress)); - var nextOrderDigestListState = new OrderDigestListState((Dictionary)actualState.GetLegacyState(orderDigestList.Address)); - Assert.Empty(targetShopState.OrderDigestList); - Assert.Empty(nextOrderDigestListState.OrderDigestList); - var productsState = - new ProductsState( - (List)actualState.GetLegacyState(ProductsState.DeriveAddress(_avatarAddress))); - var productId = Assert.Single(productsState.ProductIds); - var product = - ProductFactory.DeserializeProduct((List)actualState.GetLegacyState(Product.DeriveAddress(productId))); - Assert.Equal(productId, product.ProductId); - Assert.Equal(productType, product.Type); - Assert.Equal(order.Price, product.Price); - - var nextAvatarState = actualState.GetAvatarState(_avatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - ReRegisterProduct0.CostAp, nextAvatarState.actionPoint); - } - - [Fact] - public void Execute_Throw_ListEmptyException() - { - var action = new ReRegisterProduct0 - { - AvatarAddress = _avatarAddress, - ReRegisterInfos = new List<(IProductInfo, IRegisterInfo)>(), - }; - - Assert.Throws(() => action.Execute(new ActionContext())); - } - - [Fact] - public void Execute_Throw_ArgumentOutOfRangeException() - { - var reRegisterInfos = new List<(IProductInfo, IRegisterInfo)>(); - for (int i = 0; i < ReRegisterProduct0.Capacity + 1; i++) - { - reRegisterInfos.Add((new ItemProductInfo(), new RegisterInfo())); - } - - var action = new ReRegisterProduct0 - { - AvatarAddress = _avatarAddress, - ReRegisterInfos = reRegisterInfos, - }; - - Assert.Throws(() => action.Execute(new ActionContext())); - } - } -} diff --git a/.Lib9c.Tests/Action/ReRegisterProductTest.cs b/.Lib9c.Tests/Action/ReRegisterProductTest.cs index 9b74188d4e..065b433ecb 100644 --- a/.Lib9c.Tests/Action/ReRegisterProductTest.cs +++ b/.Lib9c.Tests/Action/ReRegisterProductTest.cs @@ -84,7 +84,8 @@ public ReRegisterProductTest(ITestOutputHelper outputHelper) .SetLegacyState(Addresses.Shop, shopState.Serialize()) .SetAgentState(_agentAddress, agentState) .SetLegacyState(Addresses.GameConfig, _gameConfigState.Serialize()) - .SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(_avatarState)); + .SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(_avatarState)) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); } [Theory] @@ -296,9 +297,7 @@ bool fromPreviousAction Assert.Equal(productId, product.ProductId); Assert.Equal(productType, product.Type); Assert.Equal(order.Price, product.Price); - - var nextAvatarState = actualState.GetAvatarState(_avatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - ReRegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - ReRegisterProduct.CostAp, actualState.GetActionPoint(_avatarAddress)); } [Fact] diff --git a/.Lib9c.Tests/Action/RegisterProductTest.cs b/.Lib9c.Tests/Action/RegisterProductTest.cs index 9ee04961cf..c88f8a2160 100644 --- a/.Lib9c.Tests/Action/RegisterProductTest.cs +++ b/.Lib9c.Tests/Action/RegisterProductTest.cs @@ -61,7 +61,8 @@ public RegisterProductTest() .SetLegacyState(Addresses.GetSheetAddress(), _tableSheets.MaterialItemSheet.Serialize()) .SetLegacyState(Addresses.GameConfig, _gameConfigState.Serialize()) .SetAgentState(_agentAddress, agentState) - .SetAvatarState(AvatarAddress, _avatarState); + .SetAvatarState(AvatarAddress, _avatarState) + .SetActionPoint(AvatarAddress, DailyReward.ActionPointMax); } public static IEnumerable Execute_Validate_MemberData() @@ -251,7 +252,7 @@ public void Execute() var nextAvatarState = nextState.GetAvatarState(AvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp, nextState.GetActionPoint(AvatarAddress)); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(AvatarAddress, marketState.AvatarAddresses); diff --git a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs index 2fa35fe85d..17e41e9444 100644 --- a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs @@ -65,7 +65,8 @@ public AuraScenarioTest() rankingMapAddress ); avatarState.inventory.AddItem(_aura); - _initialState = _initialState.SetAvatarState(avatarAddress, avatarState); + _initialState = _initialState.SetAvatarState(avatarAddress, avatarState) + .SetActionPoint(avatarAddress, DailyReward.ActionPointMax); } _currency = Currency.Legacy("NCG", 2, minters: null); diff --git a/.Lib9c.Tests/Action/Scenario/CollectionScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/CollectionScenarioTest.cs index 745a8415c4..828ed77c3c 100644 --- a/.Lib9c.Tests/Action/Scenario/CollectionScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/CollectionScenarioTest.cs @@ -50,7 +50,8 @@ public CollectionScenarioTest() rankingMapAddress ); _initialState = _initialState.SetAvatarState( - avatarAddress, avatarState, true, true, true, true); + avatarAddress, avatarState, true, true, true, true) + .SetActionPoint(avatarAddress, DailyReward.ActionPointMax); } var currency = Currency.Legacy("NCG", 2, minters: null); diff --git a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs index ac9bb76e65..763bc76890 100644 --- a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs @@ -111,8 +111,10 @@ public MarketScenarioTest(ITestOutputHelper outputHelper) .SetLegacyState(Addresses.GetSheetAddress(), _tableSheets.ArenaSheet.Serialize()) .SetAgentState(_sellerAgentAddress, agentState) .SetAvatarState(_sellerAvatarAddress, _sellerAvatarState) + .SetActionPoint(_sellerAvatarAddress, DailyReward.ActionPointMax) .SetAgentState(_sellerAgentAddress2, agentState2) .SetAvatarState(_sellerAvatarAddress2, _sellerAvatarState2) + .SetActionPoint(_sellerAvatarAddress2, DailyReward.ActionPointMax) .SetAgentState(_buyerAgentAddress, agentState3) .SetAvatarState(_buyerAvatarAddress, buyerAvatarState); } @@ -170,7 +172,7 @@ public void Register_And_Buy() var nextState = action.Execute(ctx); var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp, nextState.GetActionPoint(_sellerAvatarAddress)); var productsState = new ProductsState((List)nextState.GetLegacyState(ProductsState.DeriveAddress(_sellerAvatarAddress))); @@ -245,7 +247,7 @@ public void Register_And_Buy() var nextState2 = action2.Execute(ctx); var nextAvatarState2 = nextState2.GetAvatarState(_sellerAvatarAddress2); Assert.Empty(nextAvatarState2.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState2.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp, nextState2.GetActionPoint(_sellerAvatarAddress2)); var productList2 = new ProductsState((List)nextState2.GetLegacyState(ProductsState.DeriveAddress(_sellerAvatarAddress2))); @@ -404,7 +406,7 @@ public void Register_And_Cancel() var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp, nextState.GetActionPoint(_sellerAvatarAddress)); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -500,8 +502,8 @@ public void Register_And_Cancel() } Assert.Equal( - _gameConfigState.ActionPointMax - RegisterProduct.CostAp - CancelProductRegistration.CostAp, - latestAvatarState.actionPoint); + DailyReward.ActionPointMax - RegisterProduct.CostAp - CancelProductRegistration.CostAp, + latestState.GetActionPoint(_sellerAvatarAddress)); var sellProductList = new ProductsState((List)latestState.GetLegacyState(productsStateAddress)); Assert.Empty(sellProductList.ProductIds); @@ -578,7 +580,7 @@ public void Register_And_ReRegister() var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp, nextState.GetActionPoint(_sellerAvatarAddress)); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -702,7 +704,7 @@ public void Register_And_ReRegister() var latestState = action2.Execute(ctx); var latestAvatarState = latestState.GetAvatarState(_sellerAvatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp - ReRegisterProduct.CostAp, latestAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - RegisterProduct.CostAp - ReRegisterProduct.CostAp, latestState.GetActionPoint(_sellerAvatarAddress)); var inventoryItem = Assert.Single(latestAvatarState.inventory.Items); Assert.Equal(1, inventoryItem.count); Assert.IsType(inventoryItem.item); @@ -838,7 +840,7 @@ public void ReRegister_Order() } var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - ReRegisterProduct.CostAp, nextAvatarState.actionPoint); + Assert.Equal(DailyReward.ActionPointMax - ReRegisterProduct.CostAp, nextState.GetActionPoint(_sellerAvatarAddress)); Assert.Empty(nextAvatarState.inventory.Items); foreach (var shopAddress in shopAddressList) diff --git a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs index 48cb43d1bb..25250bb334 100644 --- a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs @@ -47,7 +47,8 @@ public void Craft_And_Unlock_And_Equip() .SetLegacyState( Addresses.GoldCurrency, new GoldCurrencyState(Currency.Legacy("NCG", 2, minters: null)).Serialize()) - .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()) + .SetActionPoint(avatarAddress, DailyReward.ActionPointMax); foreach (var (key, value) in sheets) { initialState = initialState diff --git a/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs index fbe0dc1065..c3808cc470 100644 --- a/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs @@ -62,6 +62,7 @@ public WorldUnlockScenarioTest() .SetLegacyState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) .SetAgentState(_agentAddress, agentState) .SetAvatarState(_avatarAddress, avatarState) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax) .SetLegacyState(_rankingMapAddress, new RankingMapState(_rankingMapAddress).Serialize()) .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); diff --git a/.Lib9c.Tests/Action/SellCancellationTest.cs b/.Lib9c.Tests/Action/SellCancellationTest.cs index 59f84113d7..03aee527cb 100644 --- a/.Lib9c.Tests/Action/SellCancellationTest.cs +++ b/.Lib9c.Tests/Action/SellCancellationTest.cs @@ -80,7 +80,8 @@ public SellCancellationTest(ITestOutputHelper outputHelper) .SetAgentState(_agentAddress, agentState) .SetLegacyState(Addresses.Shop, new ShopState().Serialize()) .SetLegacyState(Addresses.GameConfig, _gameConfigState.Serialize()) - .SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(avatarState)); + .SetLegacyState(_avatarAddress, MigrationAvatarState.LegacySerializeV1(avatarState)) + .SetActionPoint(_avatarAddress, DailyReward.ActionPointMax); } [Theory] diff --git a/.Lib9c.Tests/Helper/InventoryExtensionsTest.cs b/.Lib9c.Tests/Helper/InventoryExtensionsTest.cs new file mode 100644 index 0000000000..a3f667c521 --- /dev/null +++ b/.Lib9c.Tests/Helper/InventoryExtensionsTest.cs @@ -0,0 +1,71 @@ +namespace Lib9c.Tests.Helper +{ + using System; + using System.Linq; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Mocks; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Helper; + using Nekoyume.Model.Item; + using Nekoyume.Model.State; + using Nekoyume.Module; + using Xunit; + + public class InventoryExtensionsTest + { + private readonly TableSheets _tableSheets; + private readonly IWorld _state; + + public InventoryExtensionsTest() + { + var sheets = TableSheetsImporter.ImportSheets(); + _tableSheets = new TableSheets(sheets); + _state = new World(MockUtil.MockModernWorldState); + foreach (var kv in sheets) + { + _state = _state.SetLegacyState(Addresses.GetSheetAddress(kv.Key), kv.Value.Serialize()); + } + } + + [Theory] + [InlineData(0, 5, false, false, typeof(NotEnoughActionPointException))] + [InlineData(0, 5, true, false, typeof(NotEnoughMaterialException))] + [InlineData(120, 5, false, true, null)] + [InlineData(120, 5, false, false, null)] + [InlineData(120, 5, true, false, null)] + [InlineData(120, 5, true, true, null)] + public void UseAp(int ap, int requiredAp, bool chargeAp, bool materialExist, Type exc) + { + var avatarAddress = new PrivateKey().Address; + var inventory = new Inventory(); + var row = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + if (materialExist) + { + var apStone = ItemFactory.CreateMaterial(row); + inventory.AddItem(apStone); + } + + Assert.Equal(inventory.HasItem(row.Id), materialExist); + if (exc is null) + { + var resultActionPoint = inventory.UseActionPoint(ap, requiredAp, chargeAp, _tableSheets.MaterialItemSheet, 0L); + Assert.Equal(materialExist, inventory.TryGetItem(row.Id, out var inventoryItem)); + if (materialExist) + { + Assert.Equal(1, inventoryItem.count); + } + + Assert.Equal(DailyReward.ActionPointMax - requiredAp, resultActionPoint); + } + else + { + Assert.Throws( + exc, () => inventory.UseActionPoint(ap, requiredAp, chargeAp, _tableSheets.MaterialItemSheet, 0L) + ); + } + } + } +} diff --git a/.Lib9c.Tests/Model/State/AvatarStateTest.cs b/.Lib9c.Tests/Model/State/AvatarStateTest.cs index 84830574c0..05556c7e79 100644 --- a/.Lib9c.Tests/Model/State/AvatarStateTest.cs +++ b/.Lib9c.Tests/Model/State/AvatarStateTest.cs @@ -400,48 +400,6 @@ public void EquipItems() } } - [Theory] - [InlineData(0, 5, false, false, typeof(NotEnoughActionPointException))] - [InlineData(0, 5, true, false, typeof(NotEnoughMaterialException))] - [InlineData(120, 5, false, true, null)] - [InlineData(120, 5, false, false, null)] - [InlineData(120, 5, true, false, null)] - [InlineData(120, 5, true, true, null)] - public void UseAp(int ap, int requiredAp, bool chargeAp, bool materialExist, Type exc) - { - var avatarState = GetNewAvatarState(default, default); - var gameConfigState = new GameConfigState((Text)_tableSheets.GameConfigSheet.Serialize()); - var row = _tableSheets.MaterialItemSheet.Values.First(r => - r.ItemSubType == ItemSubType.ApStone); - if (materialExist) - { - var apStone = ItemFactory.CreateMaterial(row); - avatarState.inventory.AddItem(apStone); - } - - avatarState.actionPoint = ap; - - Assert.Equal(avatarState.inventory.HasItem(row.Id), materialExist); - - if (exc is null) - { - avatarState.UseAp(requiredAp, chargeAp, _tableSheets.MaterialItemSheet, 0L, gameConfigState); - Assert.Equal(materialExist, avatarState.inventory.TryGetItem(row.Id, out var inventoryItem)); - if (materialExist) - { - Assert.Equal(1, inventoryItem.count); - } - - Assert.Equal(gameConfigState.ActionPointMax - requiredAp, avatarState.actionPoint); - } - else - { - Assert.Throws( - exc, () => avatarState.UseAp(requiredAp, chargeAp, _tableSheets.MaterialItemSheet, 0L, gameConfigState) - ); - } - } - [Fact] public void Clone() { diff --git a/Lib9c/Action/CancelProductRegistration.cs b/Lib9c/Action/CancelProductRegistration.cs index 5f3ba57b24..a751ce25bd 100644 --- a/Lib9c/Action/CancelProductRegistration.cs +++ b/Lib9c/Action/CancelProductRegistration.cs @@ -8,6 +8,7 @@ using Libplanet.Action.State; using Libplanet.Crypto; using Nekoyume.Battle; +using Nekoyume.Helper; using Nekoyume.Model.Item; using Nekoyume.Model.Mail; using Nekoyume.Model.Market; @@ -56,7 +57,16 @@ public override IWorld Execute(IActionContext context) throw new FailedLoadStateException("failed to load avatar state"); } - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + var resultActionPoint = avatarState.inventory.UseActionPoint(actionPoint, + CostAp, + ChargeAp, + states.GetSheet(), + context.BlockIndex); var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); ProductsState productsState; if (states.TryGetLegacyState(productsStateAddress, out List rawProductList)) @@ -117,6 +127,7 @@ public override IWorld Execute(IActionContext context) states = states .SetAvatarState(AvatarAddress, avatarState) + .SetActionPoint(AvatarAddress, resultActionPoint) .SetLegacyState(productsStateAddress, productsState.Serialize()); return states; diff --git a/Lib9c/Action/ChargeActionPoint.cs b/Lib9c/Action/ChargeActionPoint.cs index 51f28ddde6..e38c667ce0 100644 --- a/Lib9c/Action/ChargeActionPoint.cs +++ b/Lib9c/Action/ChargeActionPoint.cs @@ -52,22 +52,16 @@ public override IWorld Execute(IActionContext context) $"{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 (avatarState.actionPoint == gameConfigState.ActionPointMax) + states.TryGetActionPoint(avatarAddress, out var actionPoint); + if (actionPoint == DailyReward.ActionPointMax) { throw new ActionPointExceededException(); } - avatarState.actionPoint = gameConfigState.ActionPointMax; var ended = DateTimeOffset.UtcNow; Log.Debug("{AddressesHex}ChargeActionPoint Total Executed Time: {Elapsed}", addressesHex, ended - started); - return states.SetAvatarState(avatarAddress, avatarState); + return states.SetAvatarState(avatarAddress, avatarState, false, true, false, false) + .SetActionPoint(avatarAddress, DailyReward.ActionPointMax); } protected override IImmutableDictionary PlainValueInternal => diff --git a/Lib9c/Action/CombinationConsumable.cs b/Lib9c/Action/CombinationConsumable.cs index a2cc2a9766..2c9dc6907c 100644 --- a/Lib9c/Action/CombinationConsumable.cs +++ b/Lib9c/Action/CombinationConsumable.cs @@ -158,16 +158,23 @@ public override IWorld Execute(IActionContext context) // ~Remove Required Materials // Subtract Required ActionPoint + // 2024-03-29 기준: 레시피에 CostActionPoint가 포함된 케이스는 없으나 TableSheets 상태에 의해 동작이 변경될 수 있기에 작성해둔다. if (costActionPoint > 0) { - if (avatarState.actionPoint < costActionPoint) + if (!states.TryGetActionPoint(avatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + if (actionPoint < costActionPoint) { throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + $"{addressesHex}Aborted due to insufficient action point: {actionPoint} < {costActionPoint}" ); } - avatarState.actionPoint -= costActionPoint; + actionPoint -= costActionPoint; + states = states.SetActionPoint(avatarAddress, actionPoint); } // ~Subtract Required ActionPoint diff --git a/Lib9c/Action/CombinationEquipment.cs b/Lib9c/Action/CombinationEquipment.cs index 0b9f038191..2e3d2034e8 100644 --- a/Lib9c/Action/CombinationEquipment.cs +++ b/Lib9c/Action/CombinationEquipment.cs @@ -359,16 +359,23 @@ public override IWorld Execute(IActionContext context) } // Subtract Required ActionPoint + // 2024-03-29 기준: 레시피에 CostActionPoint가 포함된 케이스는 없으나 TableSheets 상태에 의해 동작이 변경될 수 있기에 작성해둔다. if (costActionPoint > 0) { - if (avatarState.actionPoint < costActionPoint) + if (!states.TryGetActionPoint(avatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + if (actionPoint < costActionPoint) { throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + $"{addressesHex}Aborted due to insufficient action point: {actionPoint} < {costActionPoint}" ); } - avatarState.actionPoint -= costActionPoint; + actionPoint -= costActionPoint; + states = states.SetActionPoint(avatarAddress, actionPoint); } // ~Subtract Required ActionPoint diff --git a/Lib9c/Action/CreateAvatar.cs b/Lib9c/Action/CreateAvatar.cs index 293eb72828..c40683f7b5 100644 --- a/Lib9c/Action/CreateAvatar.cs +++ b/Lib9c/Action/CreateAvatar.cs @@ -238,7 +238,9 @@ public override IWorld Execute(IActionContext context) Log.Debug("{AddressesHex}CreateAvatar Total Executed Time: {Elapsed}", addressesHex, ended - started); return states .SetAgentState(signer, agentState) - .SetAvatarState(avatarAddress, avatarState); + .SetAvatarState(avatarAddress, avatarState) + .SetActionPoint(avatarAddress, DailyReward.ActionPointMax) + .SetDailyRewardReceivedBlockIndex(avatarAddress, 0L); } public static void AddItem(ItemSheet itemSheet, CreateAvatarItemSheet createAvatarItemSheet, diff --git a/Lib9c/Action/EventConsumableItemCrafts.cs b/Lib9c/Action/EventConsumableItemCrafts.cs index 1b2992a97e..1803abaa9b 100644 --- a/Lib9c/Action/EventConsumableItemCrafts.cs +++ b/Lib9c/Action/EventConsumableItemCrafts.cs @@ -221,14 +221,20 @@ public override IWorld Execute(IActionContext context) // Subtract Required ActionPoint if (costActionPoint > 0) { - if (avatarState.actionPoint < costActionPoint) + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + if (actionPoint < costActionPoint) { throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" + $"{addressesHex}Aborted due to insufficient action point: {actionPoint} < {costActionPoint}" ); } - avatarState.actionPoint -= costActionPoint; + actionPoint -= costActionPoint; + states = states.SetActionPoint(AvatarAddress, actionPoint); } // ~Subtract Required ActionPoint diff --git a/Lib9c/Action/Grinding.cs b/Lib9c/Action/Grinding.cs index 2ade7a4e31..8e6db34e43 100644 --- a/Lib9c/Action/Grinding.cs +++ b/Lib9c/Action/Grinding.cs @@ -79,7 +79,12 @@ public override IWorld Execute(IActionContext context) stakedAmount = states.GetBalance(monsterCollectionAddress, currency); } - if (avatarState.actionPoint < CostAp) + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + if (actionPoint < CostAp) { switch (ChargeAp) { @@ -94,14 +99,13 @@ public override IWorld Execute(IActionContext context) { throw new NotEnoughMaterialException("not enough ap stone."); } - GameConfigState gameConfigState = states.GetGameConfigState(); - avatarState.actionPoint = gameConfigState.ActionPointMax; + actionPoint = DailyReward.ActionPointMax; break; } } } - avatarState.actionPoint -= CostAp; + actionPoint -= CostAp; List equipmentList = new List(); foreach (var equipmentId in EquipmentIds) @@ -148,7 +152,8 @@ public override IWorld Execute(IActionContext context) var ended = DateTimeOffset.UtcNow; Log.Debug("{AddressesHex}Grinding Total Executed Time: {Elapsed}", addressesHex, ended - started); return states - .SetAvatarState(AvatarAddress, avatarState) + .SetAvatarState(AvatarAddress, avatarState, true, true, false, false) + .SetActionPoint(AvatarAddress, actionPoint) .MintAsset(context, context.Signer, crystal); } diff --git a/Lib9c/Action/HackAndSlash.cs b/Lib9c/Action/HackAndSlash.cs index b2545d3a7c..b91cecc313 100644 --- a/Lib9c/Action/HackAndSlash.cs +++ b/Lib9c/Action/HackAndSlash.cs @@ -311,7 +311,7 @@ public IWorld Execute( } var apStonePlayCount = - ApStoneCount * (gameConfigState.ActionPointMax / minimumCostAp); + ApStoneCount * (DailyReward.ActionPointMax / minimumCostAp); apPlayCount = TotalPlayCount - apStonePlayCount; if (apPlayCount < 0) { @@ -337,15 +337,21 @@ public IWorld Execute( apPlayCount * minimumCostAp); } - if (avatarState.actionPoint < minimumCostAp * apPlayCount) + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + if (actionPoint < minimumCostAp * apPlayCount) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: " + - $"{avatarState.actionPoint} < cost({minimumCostAp * apPlayCount}))" + $"{actionPoint} < cost({minimumCostAp * apPlayCount}))" ); } - avatarState.actionPoint -= minimumCostAp * apPlayCount; + actionPoint -= minimumCostAp * apPlayCount; + states = states.SetActionPoint(AvatarAddress, actionPoint); avatarState.ValidateItemRequirement( costumeIds.Concat(foodIds).ToList(), equipmentList, diff --git a/Lib9c/Action/HackAndSlashSweep.cs b/Lib9c/Action/HackAndSlashSweep.cs index 2284f26be8..17e9e8a638 100644 --- a/Lib9c/Action/HackAndSlashSweep.cs +++ b/Lib9c/Action/HackAndSlashSweep.cs @@ -286,16 +286,21 @@ public override IWorld Execute(IActionContext context) } } - if (actionPoint > avatarState.actionPoint) + if (!states.TryGetActionPoint(avatarAddress, out var hasActionPoint)) + { + hasActionPoint = avatarState.actionPoint; + } + + if (actionPoint > hasActionPoint) { throw new NotEnoughActionPointException( $"{addressesHex}Aborted due to insufficient action point: " + - $"use AP({actionPoint}) > current AP({avatarState.actionPoint})" + $"use AP({actionPoint}) > current AP({hasActionPoint})" ); } // burn ap - avatarState.actionPoint -= actionPoint; + states = states.SetActionPoint(avatarAddress, hasActionPoint - actionPoint); var costAp = sheets.GetSheet()[stageId].CostAP; var goldCurrency = states.GetGoldCurrency(); var stakedAmount = states.GetStakedAmount(context.Signer); diff --git a/Lib9c/Action/ItemEnhancement.cs b/Lib9c/Action/ItemEnhancement.cs index 6566ad792a..2cded47439 100644 --- a/Lib9c/Action/ItemEnhancement.cs +++ b/Lib9c/Action/ItemEnhancement.cs @@ -107,15 +107,6 @@ public override IWorld Execute(IActionContext context) ); } - // 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)) @@ -255,8 +246,6 @@ public override IWorld Execute(IActionContext context) // Do the action var equipmentItemSheet = sheets.GetSheet(); - // Subtract required action point - avatarState.actionPoint -= requiredActionPoint; // Unequip items enhancementEquipment.Unequip(); @@ -337,7 +326,6 @@ public override IWorld Execute(IActionContext context) preItemUsable = preItemUsable, itemUsable = enhancementEquipment, materialItemIdList = uniqueMaterialIds.ToArray(), - actionPoint = requiredActionPoint, enhancementResult = ItemEnhancement13.EnhancementResult.Success, // Result is fixed to Success gold = requiredNcg, CRYSTAL = 0 * CrystalCalculator.CRYSTAL, @@ -398,10 +386,5 @@ public static int GetEquipmentMaxLevel(Equipment equipment, EnhancementCostSheet { 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/ItemEnhancement7.cs b/Lib9c/Action/ItemEnhancement7.cs index 6eac5bc942..0db2c54f34 100644 --- a/Lib9c/Action/ItemEnhancement7.cs +++ b/Lib9c/Action/ItemEnhancement7.cs @@ -162,7 +162,7 @@ public override IWorld Execute(IActionContext context) materialItemIdList = new[] { materialId } }; - var requiredAP = ItemEnhancement.GetRequiredAp(); + var requiredAP = GetRequiredAp(); if (avatarState.actionPoint < requiredAP) { throw new NotEnoughActionPointException( diff --git a/Lib9c/Action/ReRegisterProduct.cs b/Lib9c/Action/ReRegisterProduct.cs index bf05bd2568..a6dad6fb4e 100644 --- a/Lib9c/Action/ReRegisterProduct.cs +++ b/Lib9c/Action/ReRegisterProduct.cs @@ -8,6 +8,7 @@ using Libplanet.Action.State; using Libplanet.Crypto; using Nekoyume.Battle; +using Nekoyume.Helper; using Nekoyume.Model.Market; using Nekoyume.Model.State; using Nekoyume.Module; @@ -59,7 +60,16 @@ public override IWorld Execute(IActionContext context) throw new FailedLoadStateException("failed to load avatar state"); } - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + var resultActionPoint = avatarState.inventory.UseActionPoint(actionPoint, + CostAp, + ChargeAp, + states.GetSheet(), + context.BlockIndex); var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); ProductsState productsState; if (states.TryGetLegacyState(productsStateAddress, out List rawProductList)) @@ -162,6 +172,7 @@ public override IWorld Execute(IActionContext context) states = states .SetAvatarState(AvatarAddress, avatarState) + .SetActionPoint(AvatarAddress, resultActionPoint) .SetLegacyState(productsStateAddress, productsState.Serialize()); return states; diff --git a/Lib9c/Action/ReRegisterProduct0.cs b/Lib9c/Action/ReRegisterProduct0.cs deleted file mode 100644 index 504f3f5767..0000000000 --- a/Lib9c/Action/ReRegisterProduct0.cs +++ /dev/null @@ -1,197 +0,0 @@ -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.Market; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using static Lib9c.SerializeKeys; - -namespace Nekoyume.Action -{ - [ActionType("re_register_product")] - public class ReRegisterProduct0 : GameAction - { - public const int CostAp = 5; - public const int Capacity = 100; - public Address AvatarAddress; - public List<(IProductInfo, IRegisterInfo)> ReRegisterInfos; - public bool ChargeAp; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IWorld states = context.PreviousState; - - if (!ReRegisterInfos.Any()) - { - throw new ListEmptyException($"ReRegisterInfos was empty."); - } - - if (ReRegisterInfos.Count > Capacity) - { - throw new ArgumentOutOfRangeException($"{nameof(ReRegisterInfos)} must be less than or equal {Capacity}."); - } - - var ncg = states.GetGoldCurrency(); - foreach (var (productInfo, registerInfo) in ReRegisterInfos) - { - registerInfo.ValidateAddress(AvatarAddress); - registerInfo.ValidatePrice(ncg); - registerInfo.Validate(); - productInfo.ValidateType(); - if (productInfo.AvatarAddress != AvatarAddress || - productInfo.AgentAddress != context.Signer) - { - throw new InvalidAddressException(); - } - } - - if (!states.TryGetAvatarState(context.Signer, AvatarAddress, out var avatarState)) - { - throw new FailedLoadStateException("failed to load avatar state"); - } - - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); - var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); - ProductsState productsState; - if (states.TryGetLegacyState(productsStateAddress, out List rawProductList)) - { - productsState = new ProductsState(rawProductList); - } - else - { - var marketState = states.TryGetLegacyState(Addresses.Market, out List rawMarketList) - ? rawMarketList - : List.Empty; - productsState = new ProductsState(); - marketState = marketState.Add(AvatarAddress.Serialize()); - states = states.SetLegacyState(Addresses.Market, marketState); - } - - var random = context.GetRandom(); - foreach (var (productInfo, info) in ReRegisterInfos.OrderBy(tuple => tuple.Item2.Type).ThenBy(tuple => tuple.Item2.Price)) - { - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - if (productInfo is ItemProductInfo {Legacy: true}) - { - // if product is order. it move to products state from sharded shop state. - var productType = productInfo.Type; - var avatarAddress = avatarState.address; - if (productType == ProductType.FungibleAssetValue) - { - // 잘못된 타입 - throw new InvalidProductTypeException( - $"Order not support {productType}"); - } - - var digestListAddress = - OrderDigestListState.DeriveAddress(avatarAddress); - if (!states.TryGetLegacyState(digestListAddress, out Dictionary rawList)) - { - throw new FailedLoadStateException( - $"{addressesHex} failed to load {nameof(OrderDigest)}({digestListAddress})."); - } - - var digestList = new OrderDigestListState(rawList); - var orderAddress = Order.DeriveAddress(productInfo.ProductId); - if (!states.TryGetLegacyState(orderAddress, out Dictionary rawOrder)) - { - throw new FailedLoadStateException( - $"{addressesHex} failed to load {nameof(Order)}({orderAddress})."); - } - - var order = OrderFactory.Deserialize(rawOrder); - var itemCount = 1; - switch (order) - { - case FungibleOrder fungibleOrder: - itemCount = fungibleOrder.ItemCount; - 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)); - } - - if (order.SellerAvatarAddress != avatarAddress || - order.SellerAgentAddress != context.Signer) - { - throw new InvalidAddressException(); - } - - if (!order.Price.Equals(productInfo.Price)) - { - throw new InvalidPriceException($"order price does not match information. expected: {order.Price} actual: {productInfo.Price}"); - } - - var updateSellInfo = new UpdateSellInfo(productInfo.ProductId, - productInfo.ProductId, order.TradableId, - order.ItemSubType, productInfo.Price, itemCount); - states = UpdateSell.Cancel(states, updateSellInfo, addressesHex, - avatarState, digestList, context, - avatarState.address); - } - else - { - states = CancelProductRegistration.Cancel(productsState, productInfo, - states, avatarState, context); - } - - states = RegisterProduct0.Register(context, info, avatarState, productsState, states, random); - } - - states = states - .SetAvatarState(AvatarAddress, avatarState) - .SetLegacyState(productsStateAddress, productsState.Serialize()); - - return states; - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["a"] = AvatarAddress.Serialize(), - ["r"] = new List(ReRegisterInfos.Select(tuple => - List.Empty.Add(tuple.Item1.Serialize()).Add(tuple.Item2.Serialize()))), - ["c"] = ChargeAp.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - AvatarAddress = plainValue["a"].ToAddress(); - ReRegisterInfos = new List<(IProductInfo, IRegisterInfo)>(); - var serialized = (List) plainValue["r"]; - foreach (var value in serialized) - { - var innerList = (List) value; - var productList = (List) innerList[0]; - var registerList = (List) innerList[1]; - IRegisterInfo info = ProductFactory.DeserializeRegisterInfo(registerList); - IProductInfo productInfo = ProductFactory.DeserializeProductInfo(productList); - ReRegisterInfos.Add((productInfo, info)); - } - - ChargeAp = plainValue["c"].ToBoolean(); - } - } -} diff --git a/Lib9c/Action/RegisterProduct.cs b/Lib9c/Action/RegisterProduct.cs index 325b92a9bc..503d575b88 100644 --- a/Lib9c/Action/RegisterProduct.cs +++ b/Lib9c/Action/RegisterProduct.cs @@ -9,6 +9,7 @@ using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume.Battle; +using Nekoyume.Helper; using Nekoyume.Model.Item; using Nekoyume.Model.Market; using Nekoyume.Model.State; @@ -64,7 +65,17 @@ public override IWorld Execute(IActionContext context) nameof(RegisterProduct), "Get States And Validate", context.BlockIndex, sw.Elapsed.TotalMilliseconds); sw.Restart(); - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + if (!states.TryGetActionPoint(AvatarAddress, out var actionPoint)) + { + actionPoint = avatarState.actionPoint; + } + + var resultActionPoint = avatarState.inventory.UseActionPoint( + actionPoint, + CostAp, + ChargeAp, + states.GetSheet(), + context.BlockIndex); sw.Stop(); Log.Debug("{Source} {Process} from #{BlockIndex}: {Elapsed}", nameof(RegisterProduct), "UseAp", context.BlockIndex, sw.Elapsed.TotalMilliseconds); @@ -102,6 +113,7 @@ public override IWorld Execute(IActionContext context) sw.Restart(); states = states .SetAvatarState(AvatarAddress, avatarState) + .SetActionPoint(AvatarAddress, resultActionPoint) .SetLegacyState(productsStateAddress, productsState.Serialize()); sw.Stop(); diff --git a/Lib9c/Action/RegisterProduct0.cs b/Lib9c/Action/RegisterProduct0.cs index cd82c66e18..3d0fdb30b6 100644 --- a/Lib9c/Action/RegisterProduct0.cs +++ b/Lib9c/Action/RegisterProduct0.cs @@ -8,6 +8,7 @@ using Libplanet.Crypto; using Libplanet.Types.Assets; using Nekoyume.Battle; +using Nekoyume.Helper; using Nekoyume.Model.Item; using Nekoyume.Model.Market; using Nekoyume.Model.State; @@ -65,7 +66,27 @@ public override IWorld Execute(IActionContext context) current); } - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); + if (avatarState.actionPoint < CostAp) + { + switch (ChargeAp) + { + case true: + var row = states.GetSheet() + .OrderedList! + .First(r => r.ItemSubType == ItemSubType.ApStone); + if (!avatarState.inventory.RemoveFungibleItem(row.ItemId, context.BlockIndex)) + { + throw new NotEnoughMaterialException("not enough ap stone."); + } + + avatarState.actionPoint = states.GetGameConfigState().ActionPointMax; + break; + case false: + throw new NotEnoughActionPointException(""); + } + } + + avatarState.actionPoint -= CostAp; var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); ProductsState productsState; if (states.TryGetLegacyState(productsStateAddress, out List rawProducts)) diff --git a/Lib9c/Helper/InventoryExtensions.cs b/Lib9c/Helper/InventoryExtensions.cs index eee0c0070a..e45b1e8e04 100644 --- a/Lib9c/Helper/InventoryExtensions.cs +++ b/Lib9c/Helper/InventoryExtensions.cs @@ -1,5 +1,9 @@ using System.Linq; +using Libplanet.Action.State; +using Nekoyume.Action; using Nekoyume.Model.Item; +using Nekoyume.Module; +using Nekoyume.TableData; namespace Nekoyume.Helper { @@ -25,5 +29,37 @@ public static int GetEquippedFullCostumeOrArmorId(this Inventory inventory) return GameConfig.DefaultAvatarArmorId; } + + public static long UseActionPoint( + this Inventory inventory, + long originalAp, + int requiredAp, + bool chargeAp, + MaterialItemSheet materialItemSheet, + long blockIndex) + { + if (originalAp < requiredAp) + { + switch (chargeAp) + { + case true: + var row = materialItemSheet + .OrderedList! + .First(r => r.ItemSubType == ItemSubType.ApStone); + if (!inventory.RemoveFungibleItem(row.ItemId, blockIndex)) + { + throw new NotEnoughMaterialException("not enough ap stone."); + } + + originalAp = DailyReward.ActionPointMax; + break; + case false: + throw new NotEnoughActionPointException(""); + } + } + + originalAp -= requiredAp; + return originalAp; + } } } diff --git a/Lib9c/Helper/Validator.cs b/Lib9c/Helper/Validator.cs deleted file mode 100644 index 8135deef00..0000000000 --- a/Lib9c/Helper/Validator.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Libplanet.Crypto; -using Nekoyume.Action; -using Nekoyume.Extensions; -using Nekoyume.Model.State; -using Nekoyume.TableData; -using Serilog; - -namespace Nekoyume.Helper -{ - public static class Validator - { - [Obsolete("Not use since \"hack_and_slash20\".")] - public static void ValidateForHackAndSlash( - AvatarState avatarState, - Dictionary sheets, - int worldId, - int stageId, - List equipments, - List costumes, - List foods, - Stopwatch sw, - long blockIndex, - string addressesHex, - int playCount = 1, - int stakingLevel = 0) - { - 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}HAS Get StageSheet: {Elapsed}", addressesHex, sw.Elapsed); - - 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}HAS Validate World: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - var equipmentList = avatarState.ValidateEquipmentsV2(equipments, blockIndex); - var foodIds = avatarState.ValidateConsumable(foods, blockIndex); - var costumeIds = avatarState.ValidateCostume(costumes); - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Validate Items: {Elapsed}", addressesHex, sw.Elapsed); - - var costAp = stageRow.CostAP; - if (stakingLevel > 0 && sheets.TryGetSheet(out var apSheet)) - { - costAp = apSheet.GetActionPointByStaking(costAp, 1, stakingLevel); - } - - if (avatarState.actionPoint < costAp * playCount) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: " + - $"{avatarState.actionPoint} < cost({costAp * playCount}))" - ); - } - - avatarState.ValidateItemRequirement( - costumeIds.Concat(foodIds).ToList(), - equipmentList, - sheets.GetSheet(), - sheets.GetSheet(), - sheets.GetSheet(), - sheets.GetSheet(), - addressesHex); - } - - #region ObsoletedValidating - - [Obsolete("Use ValidateForHackAndSlash")] - public static void ValidateForHackAndSlashV1( - AvatarState avatarState, - Dictionary sheets, - int worldId, - int stageId, - List equipments, - List costumes, - List foods, - Stopwatch sw, - long blockIndex, - string addressesHex, - int playCount = 1) - { - 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}HAS Get StageSheet: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - var worldInformation = avatarState.worldInformation; - if (!worldInformation.TryGetWorld(worldId, out var world)) - { - // NOTE: Add new World from WorldSheet - worldInformation.AddAndUnlockNewWorld(worldRow, blockIndex, worldSheet); - } - - 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.StageClearedId + 1 || - !world.IsStageCleared && stageId != world.StageBegin) - { - throw new InvalidStageException( - $"{addressesHex}Aborted as the stage ({worldId}/{stageId}) is not cleared; " + - $"cleared stage: {world.StageClearedId}" - ); - } - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Validate World: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - var equipmentList = avatarState.ValidateEquipmentsV2(equipments, blockIndex); - var foodIds = avatarState.ValidateConsumable(foods, blockIndex); - var costumeIds = avatarState.ValidateCostume(costumes); - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Validate Items: {Elapsed}", addressesHex, sw.Elapsed); - - if (avatarState.actionPoint < stageRow.CostAP * playCount) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: " + - $"{avatarState.actionPoint} < cost({stageRow.CostAP * playCount}))" - ); - } - - avatarState.ValidateItemRequirement( - costumeIds.Concat(foodIds).ToList(), - equipmentList, - sheets.GetSheet(), - sheets.GetSheet(), - sheets.GetSheet(), - sheets.GetSheet(), - addressesHex); - } - - #endregion - } -} diff --git a/Lib9c/Model/State/AvatarState.cs b/Lib9c/Model/State/AvatarState.cs index a76e738afd..e974ba1269 100644 --- a/Lib9c/Model/State/AvatarState.cs +++ b/Lib9c/Model/State/AvatarState.cs @@ -1246,29 +1246,6 @@ public List GetNonFungibleItems(List itemIds) return items; } - public void UseAp(int requiredAp, bool chargeAp, MaterialItemSheet materialItemSheet, long blockIndex, GameConfigState gameConfigState) - { - if (actionPoint < requiredAp) - { - switch (chargeAp) - { - case true: - MaterialItemSheet.Row row = materialItemSheet - .OrderedList! - .First(r => r.ItemSubType == ItemSubType.ApStone); - if (!inventory.RemoveFungibleItem(row.ItemId, blockIndex)) - { - throw new NotEnoughMaterialException("not enough ap stone."); - } - actionPoint = gameConfigState.ActionPointMax; - break; - case false: - throw new NotEnoughActionPointException(""); - } - } - actionPoint -= requiredAp; - } - public override IValue Serialize() { throw new NotSupportedException(); diff --git a/Lib9c/Module/ActionPointModule.cs b/Lib9c/Module/ActionPointModule.cs index 9e0e912195..a1fed6064b 100644 --- a/Lib9c/Module/ActionPointModule.cs +++ b/Lib9c/Module/ActionPointModule.cs @@ -1,6 +1,7 @@ using Bencodex.Types; using Libplanet.Action.State; using Libplanet.Crypto; +using Nekoyume.Action; namespace Nekoyume.Module { @@ -14,7 +15,22 @@ public static long GetActionPoint(this IWorldState worldState, Address avatarAdd return integer; } - return 0; + throw new FailedLoadStateException(""); + } + + public static bool TryGetActionPoint(this IWorldState worldState, Address avatarAddress, out long actionPoint) + { + actionPoint = 0L; + try + { + var temp = GetActionPoint(worldState, avatarAddress); + actionPoint = temp; + return true; + } + catch (FailedLoadStateException) + { + return false; + } } public static IWorld SetActionPoint(this IWorld world, Address avatarAddress, long actionPoint)