Skip to content

Commit

Permalink
Merge pull request #2497 from U-lis/feature/rune-multi-levelup
Browse files Browse the repository at this point in the history
Rune multi level up
  • Loading branch information
U-lis authored Apr 1, 2024
2 parents e0b0ced + d1495d9 commit 2fde39b
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 170 deletions.
154 changes: 59 additions & 95 deletions .Lib9c.Tests/Action/RuneEnhancementTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,34 @@ public RuneEnhancementTest()
}

[Theory]
[InlineData(10000)]
[InlineData(1)]
public void Execute(int seed)
// All success
[InlineData(1, 1, 2, 0, 0, 1, null, 0)]
[InlineData(1, 2, 3, 0, 0, 2, null, 0)]
// Success 1 of 2
[InlineData(202, 2, 203, 0, 1000, 40, null, 2)]
// All fail
[InlineData(202, 2, 202, 0, 1000, 40, null, 0)]
// Reaching max level
[InlineData(299, 1, 300, 0, 500, 20, null, 1)]
// Cannot exceed max level
[InlineData(299, 2, 299, 0, 0, 0, typeof(RuneCostDataNotFoundException), 0)]
[InlineData(300, 1, 300, 0, 0, 0, typeof(RuneCostDataNotFoundException), 0)]
public void Execute(
int startLevel,
int tryCount,
int expectedLevel,
int expectedNcgCost,
int expectedCrystalCost,
int expectedRuneCost,
Type expectedException,
int seed
)
{
const int initialNcg = 10_000;
const int initialCrystal = 1_000_000;
const int initialRune = 1_000;
var s = seed;
// Set states
var agentAddress = new PrivateKey().Address;
var avatarAddress = new PrivateKey().Address;
var sheets = TableSheetsImporter.ImportSheets();
Expand Down Expand Up @@ -66,55 +90,22 @@ public void Execute(int seed)
var runeId = runeListSheet.First().Value.Id;
var runeStateAddress = RuneState.DeriveAddress(avatarState.address, runeId);
var runeState = new RuneState(runeId);
runeState.LevelUp(startLevel);
state = state.SetLegacyState(runeStateAddress, runeState.Serialize());

var costSheet = state.GetSheet<RuneCostSheet>();
if (!costSheet.TryGetValue(runeId, out var costRow))
{
throw new RuneCostNotFoundException($"[{nameof(Execute)}] ");
}

if (!costRow.TryGetCost(runeState.Level + 1, out var cost))
{
throw new RuneCostDataNotFoundException($"[{nameof(Execute)}] ");
}

var runeSheet = state.GetSheet<RuneSheet>();
if (!runeSheet.TryGetValue(runeId, out var runeRow))
{
throw new RuneNotFoundException($"[{nameof(Execute)}] ");
}

// Prepare materials
var ncgCurrency = state.GetGoldCurrency();
var crystalCurrency = CrystalCalculator.CRYSTAL;
var runeCurrency = Currency.Legacy(runeRow.Ticker, 0, minters: null);

var ncgBal = cost.NcgQuantity * ncgCurrency * 10000;
var crystalBal = cost.CrystalQuantity * crystalCurrency * 10000;
var runeBal = cost.RuneStoneQuantity * runeCurrency * 10000;

var rand = new TestRandom(seed);
if (!RuneHelper.TryEnhancement(ncgBal, crystalBal, runeBal, ncgCurrency, crystalCurrency, runeCurrency, cost, rand, 99, out var tryCount))
{
throw new RuneNotFoundException($"[{nameof(Execute)}] ");
}

if (ncgBal.Sign > 0)
{
state = state.MintAsset(context, agentAddress, ncgBal);
}
var runeTicker = tableSheets.RuneSheet.Values.First(r => r.Id == runeId).Ticker;
var runeCurrency = Currency.Legacy(runeTicker, 0, minters: null);
var r = new TestRandom(seed: 1);

if (crystalBal.Sign > 0)
{
state = state.MintAsset(context, agentAddress, crystalBal);
}
state = state.MintAsset(context, agentAddress, ncgCurrency * initialNcg);
state = state.MintAsset(context, agentAddress, crystalCurrency * initialCrystal);
state = state.MintAsset(context, avatarAddress, runeCurrency * initialRune);

if (runeBal.Sign > 0)
{
state = state.MintAsset(context, avatarState.address, runeBal);
}

var action = new RuneEnhancement()
// Action
var action = new RuneEnhancement
{
AvatarAddress = avatarState.address,
RuneId = runeId,
Expand All @@ -124,62 +115,35 @@ public void Execute(int seed)
{
BlockIndex = blockIndex,
PreviousState = state,
RandomSeed = rand.Seed,
RandomSeed = seed,
Signer = agentAddress,
};

var nextState = action.Execute(ctx);
if (!nextState.TryGetLegacyState(runeStateAddress, out List nextRuneRawState))
{
throw new Exception();
}

var nextRunState = new RuneState(nextRuneRawState);
var nextNcgBal = nextState.GetBalance(agentAddress, ncgCurrency);
var nextCrystalBal = nextState.GetBalance(agentAddress, crystalCurrency);
var nextRuneBal = nextState.GetBalance(agentAddress, runeCurrency);

if (cost.NcgQuantity != 0)
{
Assert.NotEqual(ncgBal, nextNcgBal);
}

if (cost.CrystalQuantity != 0)
if (expectedException is not null)
{
Assert.NotEqual(crystalBal, nextCrystalBal);
Assert.Throws(expectedException, () => { action.Execute(ctx); });
}

if (cost.RuneStoneQuantity != 0)
else
{
Assert.NotEqual(runeBal, nextRuneBal);
}

var costNcg = tryCount * cost.NcgQuantity * ncgCurrency;
var costCrystal = tryCount * cost.CrystalQuantity * crystalCurrency;
var costRune = tryCount * cost.RuneStoneQuantity * runeCurrency;

if (costNcg.Sign > 0)
{
nextState = nextState.MintAsset(context, agentAddress, costNcg);
}

if (costCrystal.Sign > 0)
{
nextState = nextState.MintAsset(context, agentAddress, costCrystal);
}

if (costRune.Sign > 0)
{
nextState = nextState.MintAsset(context, avatarState.address, costRune);
var nextState = action.Execute(ctx);
if (!nextState.TryGetLegacyState(runeStateAddress, out List nextRuneRawState))
{
throw new Exception();
}

var nextRuneState = new RuneState(nextRuneRawState);
var nextNcgBal = nextState.GetBalance(agentAddress, ncgCurrency);
var nextCrystalBal = nextState.GetBalance(agentAddress, crystalCurrency);
var nextRuneBal = nextState.GetBalance(avatarAddress, runeCurrency);

Assert.Equal((initialNcg - expectedNcgCost) * ncgCurrency, nextNcgBal);
Assert.Equal(
(initialCrystal - expectedCrystalCost) * crystalCurrency,
nextCrystalBal
);
Assert.Equal((initialRune - expectedRuneCost) * runeCurrency, nextRuneBal);
Assert.Equal(expectedLevel, nextRuneState.Level);
}

var finalNcgBal = nextState.GetBalance(agentAddress, ncgCurrency);
var finalCrystalBal = nextState.GetBalance(agentAddress, crystalCurrency);
var finalRuneBal = nextState.GetBalance(avatarState.address, runeCurrency);
Assert.Equal(ncgBal, finalNcgBal);
Assert.Equal(crystalBal, finalCrystalBal);
Assert.Equal(runeBal, finalRuneBal);
Assert.Equal(runeState.Level + 1, nextRunState.Level);
}

[Fact]
Expand Down
81 changes: 53 additions & 28 deletions Lib9c/Action/RuneEnhancement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ public class RuneEnhancement : GameAction, IRuneEnhancementV1
public int RuneId;
public int TryCount = 1;

public struct LevelUpResult
{
public int LevelUpCount { get; set; }
public int NcgCost { get; set; }
public int CrystalCost { get; set; }
public int RuneCost { get; set; }

public override string ToString() =>
$"{LevelUpCount} level up with cost {NcgCost} NCG, {CrystalCost} Crystal, {RuneCost} Runestone.";
}

Address IRuneEnhancementV1.AvatarAddress => AvatarAddress;
int IRuneEnhancementV1.RuneId => RuneId;
int IRuneEnhancementV1.TryCount => TryCount;
Expand Down Expand Up @@ -64,23 +75,18 @@ public override IWorld Execute(IActionContext context)
typeof(RuneCostSheet),
});

// Validation
if (TryCount < 1)
{
throw new TryCountIsZeroException(
$"{AvatarAddress}TryCount must be greater than 0. " +
$"current TryCount : {TryCount}");
}

RuneState runeState;
var runeStateAddress = RuneState.DeriveAddress(AvatarAddress, RuneId);
if (states.TryGetLegacyState(runeStateAddress, out List rawState))
{
runeState = new RuneState(rawState);
}
else
{
runeState = new RuneState(RuneId);
}
var runeState = states.TryGetLegacyState(runeStateAddress, out List rawState)
? new RuneState(rawState)
: new RuneState(RuneId);

var costSheet = sheets.GetSheet<RuneCostSheet>();
if (!costSheet.TryGetValue(runeState.RuneId, out var costRow))
Expand All @@ -89,11 +95,11 @@ public override IWorld Execute(IActionContext context)
$"[{nameof(RuneEnhancement)}] my avatar address : {AvatarAddress}");
}

var targetLevel = runeState.Level + 1;
if (!costRow.TryGetCost(targetLevel, out var cost))
var targetLevel = runeState.Level + TryCount;
if (!costRow.TryGetCost(targetLevel, out _))
{
throw new RuneCostDataNotFoundException(
$"[{nameof(RuneEnhancement)}] my avatar address : {AvatarAddress}");
$"[{nameof(RuneEnhancement)}] my avatar address : {AvatarAddress} : Maybe max level reached");
}

var runeSheet = sheets.GetSheet<RuneSheet>();
Expand All @@ -103,41 +109,60 @@ public override IWorld Execute(IActionContext context)
$"[{nameof(RuneEnhancement)}] my avatar address : {AvatarAddress}");
}

var random = context.GetRandom();
if (!RuneHelper.TryEnhancement(runeState.Level, costRow, random, TryCount,
out var levelUpResult))
{
// Rune cost not found while level up
throw new RuneCostDataNotFoundException(
$"[{nameof(RuneEnhancement)}] my avatar address : {AvatarAddress} : Maybe max level reached");
}

// Check final balance
var ncgCurrency = states.GetGoldCurrency();
var crystalCurrency = CrystalCalculator.CRYSTAL;
var runeCurrency = Currency.Legacy(runeRow.Ticker, 0, minters: null);
var ncgBalance = states.GetBalance(context.Signer, ncgCurrency);
var crystalBalance = states.GetBalance(context.Signer, crystalCurrency);
var runeBalance = states.GetBalance(AvatarAddress, runeCurrency);
var random = context.GetRandom();
if (RuneHelper.TryEnhancement(ncgBalance, crystalBalance, runeBalance,
ncgCurrency, crystalCurrency, runeCurrency,
cost, random, TryCount, out var tryCount))

if (ncgBalance < levelUpResult.NcgCost * ncgCurrency ||
crystalBalance < levelUpResult.CrystalCost * crystalCurrency ||
runeBalance < levelUpResult.RuneCost * runeCurrency)
{
runeState.LevelUp();
states = states.SetLegacyState(runeStateAddress, runeState.Serialize());
throw new NotEnoughFungibleAssetValueException(
$"{nameof(RuneEnhancement)}" +
$"[ncg:{ncgBalance} < {levelUpResult.NcgCost * ncgCurrency}] " +
$"[crystal:{crystalBalance} < {levelUpResult.CrystalCost * crystalCurrency}] " +
$"[rune:{runeBalance} < {levelUpResult.RuneCost * runeCurrency}]"
);
}

runeState.LevelUp(levelUpResult.LevelUpCount);
states = states.SetLegacyState(runeStateAddress, runeState.Serialize());

var arenaSheet = sheets.GetSheet<ArenaSheet>();
var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex);
var feeStoreAddress = Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round);
var feeStoreAddress =
Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round);

var ncgCost = cost.NcgQuantity * tryCount * ncgCurrency;
if (cost.NcgQuantity > 0)
// Burn costs
if (levelUpResult.NcgCost > 0)
{
states = states.TransferAsset(context, context.Signer, feeStoreAddress, ncgCost);
states = states.TransferAsset(context, context.Signer, feeStoreAddress,
levelUpResult.NcgCost * ncgCurrency);
}

var crystalCost = cost.CrystalQuantity * tryCount * crystalCurrency;
if (cost.CrystalQuantity > 0)
if (levelUpResult.CrystalCost > 0)
{
states = states.TransferAsset(context, context.Signer, feeStoreAddress, crystalCost);
states = states.TransferAsset(context, context.Signer, feeStoreAddress,
levelUpResult.CrystalCost * crystalCurrency);
}

var runeCost = cost.RuneStoneQuantity * tryCount * runeCurrency;
if (cost.RuneStoneQuantity > 0)
if (levelUpResult.RuneCost > 0)
{
states = states.TransferAsset(context, AvatarAddress, feeStoreAddress, runeCost);
states = states.TransferAsset(context, AvatarAddress, feeStoreAddress,
levelUpResult.RuneCost * runeCurrency);
}

return states;
Expand Down
Loading

0 comments on commit 2fde39b

Please sign in to comment.