Skip to content

Commit

Permalink
Merge pull request #2300 from planetarium/feature/crystal-rune-slot
Browse files Browse the repository at this point in the history
�Introduce crystal rune slot
  • Loading branch information
ipdae authored Dec 14, 2023
2 parents 28eb062 + 84d033d commit f864d49
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 22 deletions.
36 changes: 30 additions & 6 deletions .Lib9c.Tests/Action/Scenario/RuneScenarioTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -69,27 +69,51 @@ 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,
RandomSeed = 0,
Signer = agentAddress,
});

var rawRuneState = Assert.IsType<List>(state.GetState(runeAddress));
var rawRuneState = Assert.IsType<List>(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
{
Expand All @@ -101,7 +125,7 @@ public void Craft_And_Equip()
WorldId = 1,
RuneInfos = new List<RuneSlotInfo>
{
new RuneSlotInfo(0, runeId),
new RuneSlotInfo(6, runeId),
},
};

Expand All @@ -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);
}
}
}
73 changes: 73 additions & 0 deletions .Lib9c.Tests/Action/UnlockRuneSlotTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
15 changes: 15 additions & 0 deletions .Lib9c.Tests/Model/RuneSlotStateTest.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Lib9c.Tests.Model
{
using System.Linq;
using Bencodex.Types;
using Nekoyume.Model.EnumType;
using Nekoyume.Model.State;
Expand All @@ -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));
}
}
}
10 changes: 9 additions & 1 deletion .gitallowed
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,12 @@ f8960846e9ae4ad1c23686f74c8e5f80f22336b6f2175be21db82afa8823c92d
210d1374d8f068de657de6b991e63888da9cadbc68e505ac917b35568b5340f8

# For MintAssetsTest
7f5d25371e58c0f3d5a33511450f73c2e0fa4fac32a92e1cbe64d3bf2fef6328
7f5d25371e58c0f3d5a33511450f73c2e0fa4fac32a92e1cbe64d3bf2fef6328

# For IssueTokensFromGarageTest
baa2081d3b485ef2906c95a3965531ec750a74cfaefe91d0c3061865608b426c

# For Genesis
4582250d0da33b06779a8475d283d5dd210c683b9b999d74d03fac4f58fa6bce
ade4c29773fe83c1a51da6a667a5a26f08848155674637d43fe636b94a320514
209b22087045ec834f01249c8661c2734cea41ccc5d8c9a273a4c8c0521d22ec
34 changes: 22 additions & 12 deletions Lib9c/Action/UnlockRuneSlot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<ArenaSheet>();
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());
Expand Down
1 change: 1 addition & 0 deletions Lib9c/Model/EnumType/RuneSlotType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ public enum RuneSlotType
Default = 1,
Ncg = 2,
Stake = 3,
Crystal = 4,
}
}
32 changes: 30 additions & 2 deletions Lib9c/Model/State/GameConfigState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -601,7 +630,6 @@ public void Update(GameConfigSheet.Row row)
RequireCharacterLevel_ConsumableSlot5 =
TableExtensions.ParseInt(row.Value);
break;

}
}
}
Expand Down
7 changes: 7 additions & 0 deletions Lib9c/Model/State/RuneSlotState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BattleType>();
_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()
Expand Down
4 changes: 3 additions & 1 deletion Lib9c/TableCSV/GameConfigSheet.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
character_consumable_slot_5,350

0 comments on commit f864d49

Please sign in to comment.