From 954364f2d19e1f2eefd1e17153dc1da6307fc471 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 13 Dec 2023 18:31:19 +0900 Subject: [PATCH 1/5] Allow gensis, test hash --- .gitallowed | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitallowed b/.gitallowed index fc39bc3afa..b8206fbff0 100644 --- a/.gitallowed +++ b/.gitallowed @@ -17,4 +17,12 @@ f8960846e9ae4ad1c23686f74c8e5f80f22336b6f2175be21db82afa8823c92d 210d1374d8f068de657de6b991e63888da9cadbc68e505ac917b35568b5340f8 # For MintAssetsTest -7f5d25371e58c0f3d5a33511450f73c2e0fa4fac32a92e1cbe64d3bf2fef6328 \ No newline at end of file +7f5d25371e58c0f3d5a33511450f73c2e0fa4fac32a92e1cbe64d3bf2fef6328 + +# For IssueTokensFromGarageTest +baa2081d3b485ef2906c95a3965531ec750a74cfaefe91d0c3061865608b426c + +# For Genesis +4582250d0da33b06779a8475d283d5dd210c683b9b999d74d03fac4f58fa6bce +ade4c29773fe83c1a51da6a667a5a26f08848155674637d43fe636b94a320514 +209b22087045ec834f01249c8661c2734cea41ccc5d8c9a273a4c8c0521d22ec \ No newline at end of file From 03d5dae56a470feb8d446a3750f0a0057a293fbc Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 13 Dec 2023 18:17:50 +0900 Subject: [PATCH 2/5] Add crystal rune unlock cost --- Lib9c/Model/State/GameConfigState.cs | 32 ++++++++++++++++++++++++++-- Lib9c/TableCSV/GameConfigSheet.csv | 4 +++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Lib9c/Model/State/GameConfigState.cs b/Lib9c/Model/State/GameConfigState.cs index c63f0e6151..a30d9deb69 100644 --- a/Lib9c/Model/State/GameConfigState.cs +++ b/Lib9c/Model/State/GameConfigState.cs @@ -22,6 +22,8 @@ public class GameConfigState : State public int BattleArenaInterval { get; private set; } public int RuneStatSlotUnlockCost { get; private set; } public int RuneSkillSlotUnlockCost { get; private set; } + public int RuneStatSlotCrystalUnlockCost { get; private set; } + public int RuneSkillSlotCrystalUnlockCost { get; private set; } public int DailyRuneRewardAmount { get; private set; } public int DailyWorldBossInterval { get; private set; } public int WorldBossRequiredInterval { get; private set; } @@ -233,7 +235,14 @@ public GameConfigState(Dictionary serialized) : base(serialized) { RequireCharacterLevel_ConsumableSlot5 = characterConsumableSlot5.ToInteger(); } - + if (serialized.TryGetValue((Text)"rune_stat_slot_crystal_unlock_cost", out var rscc)) + { + RuneStatSlotCrystalUnlockCost = (Integer)rscc; + } + if (serialized.TryGetValue((Text)"rune_skill_slot_crystal_unlock_cost", out var rscc2)) + { + RuneSkillSlotCrystalUnlockCost = (Integer)rscc2; + } } public GameConfigState(string csv) : base(Address) @@ -448,6 +457,20 @@ public override IValue Serialize() RequireCharacterLevel_ConsumableSlot5.Serialize()); } + if (RuneSkillSlotCrystalUnlockCost > 0) + { + values.Add( + (Text)"rune_skill_slot_crystal_unlock_cost", + (Integer)RuneSkillSlotCrystalUnlockCost); + } + + if (RuneStatSlotCrystalUnlockCost > 0) + { + values.Add( + (Text)"rune_stat_slot_crystal_unlock_cost", + (Integer)RuneStatSlotCrystalUnlockCost); + } + #pragma warning disable LAA1002 return new Dictionary(values.Union((Dictionary) base.Serialize())); #pragma warning restore LAA1002 @@ -497,6 +520,12 @@ public void Update(GameConfigSheet.Row row) case "rune_skill_slot_unlock_cost": RuneSkillSlotUnlockCost = TableExtensions.ParseInt(row.Value); break; + case "rune_stat_slot_crystal_unlock_cost": + RuneStatSlotCrystalUnlockCost = TableExtensions.ParseInt(row.Value); + break; + case "rune_skill_slot_crystal_unlock_cost": + RuneSkillSlotCrystalUnlockCost = TableExtensions.ParseInt(row.Value); + break; case "daily_rune_reward_amount": DailyRuneRewardAmount = TableExtensions.ParseInt(row.Value); break; @@ -601,7 +630,6 @@ public void Update(GameConfigSheet.Row row) RequireCharacterLevel_ConsumableSlot5 = TableExtensions.ParseInt(row.Value); break; - } } } diff --git a/Lib9c/TableCSV/GameConfigSheet.csv b/Lib9c/TableCSV/GameConfigSheet.csv index 374d3f0322..29cd53f513 100644 --- a/Lib9c/TableCSV/GameConfigSheet.csv +++ b/Lib9c/TableCSV/GameConfigSheet.csv @@ -8,6 +8,8 @@ required_appraise_block,0 battle_arena_interval,4 rune_stat_slot_unlock_cost,100 rune_skill_slot_unlock_cost,1000 +rune_stat_slot_crystal_unlock_cost,100 +rune_skill_slot_crystal_unlock_cost,1000 daily_rune_reward_amount,1 daily_worldboss_interval,10368 worldboss_required_interval,5 @@ -33,4 +35,4 @@ 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 \ No newline at end of file +character_consumable_slot_5,350 From 9a32c18facecf65ee141b01dc852205b5c57eef7 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 13 Dec 2023 18:27:03 +0900 Subject: [PATCH 3/5] Add crystal rune slot when deserialize exist state --- .Lib9c.Tests/Model/RuneSlotStateTest.cs | 15 +++++++++++++++ Lib9c/Model/EnumType/RuneSlotType.cs | 1 + Lib9c/Model/State/RuneSlotState.cs | 7 +++++++ 3 files changed, 23 insertions(+) diff --git a/.Lib9c.Tests/Model/RuneSlotStateTest.cs b/.Lib9c.Tests/Model/RuneSlotStateTest.cs index 1d389d0419..25ac8d098c 100644 --- a/.Lib9c.Tests/Model/RuneSlotStateTest.cs +++ b/.Lib9c.Tests/Model/RuneSlotStateTest.cs @@ -1,5 +1,6 @@ namespace Lib9c.Tests.Model { + using System.Linq; using Bencodex.Types; using Nekoyume.Model.EnumType; using Nekoyume.Model.State; @@ -16,5 +17,19 @@ public void Serialize() Assert.Equal(state.Serialize(), deserialized.Serialize()); } + + [Fact] + public void Deserialize_Add_Slots() + { + var runeSlotState = new RuneSlotState(BattleType.Adventure); + var serialized = (List)runeSlotState.Serialize(); + var rawSlots = new List(((List)serialized[1]).Take(6)); + serialized = List.Empty.Add(BattleType.Adventure.Serialize()).Add(rawSlots); + var deserialized = new RuneSlotState(serialized); + + var runeSlots = deserialized.GetRuneSlot(); + Assert.Equal(8, runeSlots.Count); + Assert.Equal(2, runeSlots.Count(r => r.RuneSlotType == RuneSlotType.Crystal)); + } } } diff --git a/Lib9c/Model/EnumType/RuneSlotType.cs b/Lib9c/Model/EnumType/RuneSlotType.cs index 5a829c632e..ddb2804235 100644 --- a/Lib9c/Model/EnumType/RuneSlotType.cs +++ b/Lib9c/Model/EnumType/RuneSlotType.cs @@ -5,5 +5,6 @@ public enum RuneSlotType Default = 1, Ncg = 2, Stake = 3, + Crystal = 4, } } diff --git a/Lib9c/Model/State/RuneSlotState.cs b/Lib9c/Model/State/RuneSlotState.cs index f6a0027adf..19dc61bfaf 100644 --- a/Lib9c/Model/State/RuneSlotState.cs +++ b/Lib9c/Model/State/RuneSlotState.cs @@ -28,12 +28,19 @@ public RuneSlotState(BattleType battleType) _slots.Add(new RuneSlot(3, RuneSlotType.Default, RuneType.Skill, false)); _slots.Add(new RuneSlot(4, RuneSlotType.Ncg, RuneType.Skill, true)); _slots.Add(new RuneSlot(5, RuneSlotType.Stake, RuneType.Skill,true)); + _slots.Add(new RuneSlot(6, RuneSlotType.Crystal, RuneType.Stat, true)); + _slots.Add(new RuneSlot(7, RuneSlotType.Crystal, RuneType.Skill,true)); } public RuneSlotState(List serialized) { BattleType = serialized[0].ToEnum(); _slots = ((List)serialized[1]).Select(x => new RuneSlot((List)x)).ToList(); + if (_slots.Count == 6) + { + _slots.Add(new RuneSlot(6, RuneSlotType.Crystal, RuneType.Stat, true)); + _slots.Add(new RuneSlot(7, RuneSlotType.Crystal, RuneType.Skill,true)); + } } public IValue Serialize() From e5885c28ae9af3f0cb649f05454686e6763ba8ed Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 13 Dec 2023 18:27:58 +0900 Subject: [PATCH 4/5] Unlock crystal rune slot --- .Lib9c.Tests/Action/UnlockRuneSlotTest.cs | 73 +++++++++++++++++++++++ Lib9c/Action/UnlockRuneSlot.cs | 34 +++++++---- 2 files changed, 95 insertions(+), 12 deletions(-) diff --git a/.Lib9c.Tests/Action/UnlockRuneSlotTest.cs b/.Lib9c.Tests/Action/UnlockRuneSlotTest.cs index 7f18abbd16..40b896186a 100644 --- a/.Lib9c.Tests/Action/UnlockRuneSlotTest.cs +++ b/.Lib9c.Tests/Action/UnlockRuneSlotTest.cs @@ -231,5 +231,78 @@ public void Execute_SlotIsAlreadyUnlockedException() BlockIndex = blockIndex, })); } + + [Theory] + [InlineData(true, 6)] + [InlineData(false, 6)] + [InlineData(true, 7)] + [InlineData(false, 7)] + public void Execute_CRYSTAL(bool legacyState, int slotIndex) + { + var context = new ActionContext(); + var state = Init(out var agentAddress, out var avatarAddress, out var blockIndex); + var gameConfig = state.GetGameConfigState(); + var cost = slotIndex == 6 + ? gameConfig.RuneStatSlotCrystalUnlockCost + : gameConfig.RuneSkillSlotCrystalUnlockCost; + state = state.MintAsset(context, agentAddress, cost * Currencies.Crystal); + if (legacyState) + { + foreach (var battleType in new[] { BattleType.Adventure, BattleType.Arena, BattleType.Raid }) + { + var runeSlotState = new RuneSlotState(battleType); + var serialized = (List)runeSlotState.Serialize(); + var rawSlots = new List(((List)serialized[1]).Take(6)); + state = state.SetState( + RuneSlotState.DeriveAddress(avatarAddress, battleType), + List.Empty.Add(battleType.Serialize()).Add(rawSlots)); + } + } + + var action = new UnlockRuneSlot() + { + AvatarAddress = avatarAddress, + SlotIndex = slotIndex, + }; + + var ctx = new ActionContext + { + BlockIndex = blockIndex, + PreviousState = state, + RandomSeed = 0, + Signer = agentAddress, + }; + + state = action.Execute(ctx); + var adventureAddr = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Adventure); + if (state.TryGetState(adventureAddr, out List adventureRaw)) + { + var s = new RuneSlotState(adventureRaw); + var slot = s.GetRuneSlot().FirstOrDefault(x => x.Index == slotIndex); + Assert.NotNull(slot); + Assert.False(slot.IsLock); + } + + var arenaAddr = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Arena); + if (state.TryGetState(arenaAddr, out List arenaRaw)) + { + var s = new RuneSlotState(arenaRaw); + var slot = s.GetRuneSlot().FirstOrDefault(x => x.Index == slotIndex); + Assert.NotNull(slot); + Assert.False(slot.IsLock); + } + + var raidAddr = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Raid); + if (state.TryGetState(raidAddr, out List raidRaw)) + { + var s = new RuneSlotState(raidRaw); + var slot = s.GetRuneSlot().FirstOrDefault(x => x.Index == slotIndex); + Assert.NotNull(slot); + Assert.False(slot.IsLock); + } + + var balance = state.GetBalance(agentAddress, Currencies.Crystal); + Assert.Equal("0", balance.GetQuantityString()); + } } } diff --git a/Lib9c/Action/UnlockRuneSlot.cs b/Lib9c/Action/UnlockRuneSlot.cs index 1350193de3..ac619ea924 100644 --- a/Lib9c/Action/UnlockRuneSlot.cs +++ b/Lib9c/Action/UnlockRuneSlot.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Linq; using Bencodex.Types; +using Lib9c; using Lib9c.Abstractions; using Libplanet.Action; using Libplanet.Action.State; @@ -74,28 +75,37 @@ public override IAccount Execute(IActionContext context) $"[{nameof(UnlockRuneSlot)}] Index : {SlotIndex}"); } - // note : You will need to modify it later when applying staking unlock. - if (slot.RuneSlotType != RuneSlotType.Ncg) - { - throw new MismatchRuneSlotTypeException( - $"[{nameof(UnlockRuneSlot)}] RuneSlotType : {slot.RuneSlotType}"); - } - var gameConfigState = states.GetGameConfigState(); - var cost = slot.RuneType == RuneType.Stat - ? gameConfigState.RuneStatSlotUnlockCost - : gameConfigState.RuneSkillSlotUnlockCost; - var ncgCurrency = states.GetGoldCurrency(); var arenaSheet = sheets.GetSheet(); var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); var feeStoreAddress = Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); + int cost; + Currency currency; + switch (slot.RuneSlotType) + { + case RuneSlotType.Ncg: + cost = slot.RuneType == RuneType.Stat + ? gameConfigState.RuneStatSlotUnlockCost + : gameConfigState.RuneSkillSlotUnlockCost; + currency = states.GetGoldCurrency(); + break; + case RuneSlotType.Crystal: + cost = slot.RuneType == RuneType.Stat + ? gameConfigState.RuneStatSlotCrystalUnlockCost + : gameConfigState.RuneSkillSlotCrystalUnlockCost; + currency = Currencies.Crystal; + break; + default: + throw new MismatchRuneSlotTypeException( + $"[{nameof(UnlockRuneSlot)}] RuneSlotType : {slot.RuneSlotType}"); + } adventureSlotState.Unlock(SlotIndex); arenaSlotState.Unlock(SlotIndex); raidSlotState.Unlock(SlotIndex); return states - .TransferAsset(context, context.Signer, feeStoreAddress, cost * ncgCurrency) + .TransferAsset(context, context.Signer, feeStoreAddress, cost * currency) .SetState(adventureSlotStateAddress, adventureSlotState.Serialize()) .SetState(arenaSlotStateAddress, arenaSlotState.Serialize()) .SetState(raidSlotStateAddress, raidSlotState.Serialize()); From 84d033d891dac293464b7a2794aa917fc977be3b Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 14 Dec 2023 10:26:31 +0900 Subject: [PATCH 5/5] Add Unlock case --- .../Action/Scenario/RuneScenarioTest.cs | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs index 5a7760ef92..104c4d6644 100644 --- a/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs @@ -19,7 +19,7 @@ namespace Lib9c.Tests.Action.Scenario public class RuneScenarioTest { [Fact] - public void Craft_And_Equip() + public void Craft_And_Unlock_And_Equip() { var agentAddress = new PrivateKey().Address; var agentState = new AgentState(agentAddress); @@ -69,13 +69,19 @@ public void Craft_And_Equip() var runeAddress = RuneState.DeriveAddress(avatarAddress, runeId); Assert.Null(initialState.GetState(runeAddress)); + initialState = initialState.MintAsset( + new ActionContext(), + agentAddress, + gameConfigState.RuneSkillSlotCrystalUnlockCost * Currencies.Crystal + ); + var craftAction = new RuneEnhancement { AvatarAddress = avatarAddress, RuneId = runeId, }; - var state = craftAction.Execute(new ActionContext + var prevState = craftAction.Execute(new ActionContext { BlockIndex = 1, PreviousState = initialState, @@ -83,13 +89,31 @@ public void Craft_And_Equip() Signer = agentAddress, }); - var rawRuneState = Assert.IsType(state.GetState(runeAddress)); + var rawRuneState = Assert.IsType(prevState.GetState(runeAddress)); var runeState = new RuneState(rawRuneState); Assert.Equal(1, runeState.Level); Assert.Equal(runeId, runeState.RuneId); var runeSlotStateAddress = RuneSlotState.DeriveAddress(avatarAddress, BattleType.Adventure); - Assert.Null(state.GetState(runeSlotStateAddress)); + Assert.Null(prevState.GetState(runeSlotStateAddress)); + + var unlockAction = new UnlockRuneSlot + { + AvatarAddress = avatarAddress, + SlotIndex = 6, + }; + + var state = unlockAction.Execute(new ActionContext + { + BlockIndex = 1, + PreviousState = prevState, + RandomSeed = 0, + Signer = agentAddress, + }); + + var runeSlotState = new RuneSlotState((List)state.GetState(runeSlotStateAddress)); + Assert.Single(runeSlotState.GetRuneSlot().Where(r => r.RuneSlotType == RuneSlotType.Crystal && !r.IsLock)); + Assert.Single(runeSlotState.GetRuneSlot().Where(r => r.RuneSlotType == RuneSlotType.Crystal && r.IsLock)); var has = new HackAndSlash { @@ -101,7 +125,7 @@ public void Craft_And_Equip() WorldId = 1, RuneInfos = new List { - new RuneSlotInfo(0, runeId), + new RuneSlotInfo(6, runeId), }, }; @@ -120,7 +144,7 @@ public void Craft_And_Equip() var runeSlotInfo = runeSlot.GetEquippedRuneSlotInfos().Single(); Assert.Equal(runeId, runeSlotInfo.RuneId); - Assert.Equal(0, runeSlotInfo.SlotIndex); + Assert.Equal(6, runeSlotInfo.SlotIndex); } } }