Skip to content

Commit

Permalink
Merge pull request #2651 from planetarium/feature/adv-boss/optimize-c…
Browse files Browse the repository at this point in the history
…laim-reward

Optimize ClaimAdventureBossReward action
  • Loading branch information
U-lis authored Jun 27, 2024
2 parents a281f48 + 0328a43 commit 8ac115d
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 203 deletions.
45 changes: 29 additions & 16 deletions .Lib9c.Tests/Action/AdventureBoss/ClaimAdventureBossRewardTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace Lib9c.Tests.Action.AdventureBoss
using Nekoyume;
using Nekoyume.Action;
using Nekoyume.Action.AdventureBoss;
using Nekoyume.Action.Exceptions;
using Nekoyume.Data;
using Nekoyume.Helper;
using Nekoyume.Model.AdventureBoss;
Expand Down Expand Up @@ -289,6 +290,7 @@ public static IEnumerable<object[]> GetPrevRewardTestData()
{ 30001, 0 },
},
},
null,
};
yield return new object[]
{
Expand All @@ -307,6 +309,7 @@ public static IEnumerable<object[]> GetPrevRewardTestData()
{ 30001, 0 },
},
},
null,
};
yield return new object[]
{
Expand All @@ -326,6 +329,7 @@ public static IEnumerable<object[]> GetPrevRewardTestData()
{ 30001, 0 },
},
},
null,
};
yield return new object[]
{
Expand All @@ -344,6 +348,7 @@ public static IEnumerable<object[]> GetPrevRewardTestData()
{ 30001, 0 },
},
},
typeof(EmptyRewardException),
};
}

Expand Down Expand Up @@ -407,7 +412,6 @@ AdventureBossGameData.ClaimableReward expectedReward
// Test
var resultState = new ClaimAdventureBossReward
{
Season = 1,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
{
Expand Down Expand Up @@ -500,7 +504,6 @@ public void WantedMultipleSeason()
// Test
var resultState = new ClaimAdventureBossReward
{
Season = 3,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
{
Expand Down Expand Up @@ -622,7 +625,6 @@ FungibleAssetValue expectedRemainingNcg
// Claim
var resultState = new ClaimAdventureBossReward
{
Season = 1,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
{
Expand Down Expand Up @@ -659,7 +661,6 @@ FungibleAssetValue expectedRemainingNcg
// Claim another rewards
resultState = new ClaimAdventureBossReward
{
Season = 1,
AvatarAddress = ExplorerAvatarAddress,
}.Execute(new ActionContext
{
Expand Down Expand Up @@ -786,7 +787,6 @@ public void ExploreMultipleSeason()
// Test
var resultState = new ClaimAdventureBossReward
{
Season = 3,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
{
Expand Down Expand Up @@ -874,7 +874,6 @@ public void AllReward()
// Test
var resultState = new ClaimAdventureBossReward
{
Season = 1,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
{
Expand All @@ -892,7 +891,8 @@ public void AllReward()
public void PrevReward(
bool wanted,
bool explore,
AdventureBossGameData.ClaimableReward expectedReward
AdventureBossGameData.ClaimableReward expectedReward,
Type exc
)
{
// Settings
Expand Down Expand Up @@ -966,18 +966,31 @@ AdventureBossGameData.ClaimableReward expectedReward
);

// Test
var resultState = new ClaimAdventureBossReward
var action = new ClaimAdventureBossReward
{
Season = 2,
AvatarAddress = TesterAvatarAddress,
}.Execute(new ActionContext
};
if (exc is null)
{
PreviousState = state,
Signer = TesterAddress,
BlockIndex = state.GetLatestAdventureBossSeason().EndBlockIndex,
RandomSeed = seed + 2,
});
Test(resultState, expectedReward);
var resultState = action.Execute(new ActionContext
{
PreviousState = state,
Signer = TesterAddress,
BlockIndex = state.GetLatestAdventureBossSeason().EndBlockIndex,
RandomSeed = seed + 2,
});
Test(resultState, expectedReward);
}
else
{
Assert.Throws(exc, () => action.Execute(new ActionContext
{
PreviousState = state,
Signer = TesterAddress,
BlockIndex = state.GetLatestAdventureBossSeason().EndBlockIndex,
RandomSeed = seed + 2,
}));
}
}

[Fact]
Expand Down
149 changes: 96 additions & 53 deletions Lib9c/Action/AdventureBoss/ClaimAdventureBossReward.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
using Libplanet.Action;
using Libplanet.Action.State;
using Libplanet.Crypto;
using Nekoyume.Action.Exceptions.AdventureBoss;
using Nekoyume.Action.Exceptions;
using Nekoyume.Data;
using Nekoyume.Exceptions;
using Nekoyume.Helper;
using Nekoyume.Model.Item;
using Nekoyume.Model.State;
using Nekoyume.Module;
using Nekoyume.TableData;
using Nekoyume.TableData.AdventureBoss;

namespace Nekoyume.Action.AdventureBoss
{
Expand All @@ -24,28 +25,26 @@ public class ClaimAdventureBossReward : GameAction
{
public const string TypeIdentifier = "claim_adventure_boss_reward";

public long Season;
public Address AvatarAddress;

protected override IImmutableDictionary<string, IValue> PlainValueInternal =>
new Dictionary<string, IValue>
{
["s"] = (Integer)Season,
["a"] = AvatarAddress.Serialize(),
}.ToImmutableDictionary();

protected override void LoadPlainValueInternal(
IImmutableDictionary<string, IValue> plainValue
)
{
Season = (Integer)plainValue["s"];
AvatarAddress = plainValue["a"].ToAddress();
}

public override IWorld Execute(IActionContext context)
{
context.UseGas(1);
var states = context.PreviousState;
var ncg = states.GetGoldCurrency();

// Validation
var addresses = GetSignerAndOtherAddressesHex(context, AvatarAddress);
Expand All @@ -62,65 +61,109 @@ public override IWorld Execute(IActionContext context)

var gameConfig = states.GetGameConfigState();
var latestSeason = states.GetLatestAdventureBossSeason();
if (Season > latestSeason.Season)
var myReward = new AdventureBossGameData.ClaimableReward
{
throw new InvalidAdventureBossSeasonException(
$"Given season {Season} is not valid.");
}
NcgReward = null,
ItemReward = new Dictionary<int, int>(),
FavReward = new Dictionary<int, int>(),
};

var seasonInfo = states.GetSeasonInfo(Season);
if (seasonInfo.EndBlockIndex > context.BlockIndex)
{
throw new SeasonInProgressException(
$"Adventure boss season {Season} will be finished at {seasonInfo.EndBlockIndex}: current block is {context.BlockIndex}"
);
}
var ncgRewardRatioSheet = states.GetSheet<AdventureBossNcgRewardRatioSheet>();
var continueInv = true;
var continueExp = true;
var random = context.GetRandom();

if (seasonInfo.EndBlockIndex + gameConfig.AdventureBossClaimInterval <
context.BlockIndex)
for (var szn = latestSeason.Season; szn > 0; szn--)
{
throw new ClaimExpiredException(
$"Claim expired at block {seasonInfo.EndBlockIndex + gameConfig.AdventureBossClaimInterval}: current block index is {context.BlockIndex}"
);
}
var seasonInfo = states.GetSeasonInfo(szn);
if (seasonInfo.EndBlockIndex > context.BlockIndex)
{
// Season in progress. Skip this season.
continue;
}

if (seasonInfo.EndBlockIndex + gameConfig.AdventureBossClaimInterval <
context.BlockIndex)
{
// Claim interval expired.
break;
}

// Pick raffle winner if not exists
states = AdventureBossHelper.PickRaffleWinner(states, context, Season);
var bountyBoard = states.GetBountyBoard(szn);
var exploreBoard = states.GetExploreBoard(szn);
var explorerList = states.GetExplorerList(szn);

// Send 80% NCG to operational account. 20% are for rewards.
var seasonBountyBoardAddress =
Addresses.BountyBoard.Derive(AdventureBossHelper.GetSeasonAsAddressForm(Season));
var bountyBoard = states.GetBountyBoard(Season);
if (bountyBoard.totalBounty() ==
states.GetBalance(seasonBountyBoardAddress, bountyBoard.totalBounty().Currency)
)
{
states = states.TransferAsset(context, seasonBountyBoardAddress,
// FIXME: Set operational account address
new Address(),
(bountyBoard.totalBounty() * 80).DivRem(100, out _)
// Pick explore raffle winner
if (exploreBoard.RaffleReward is null)
{
exploreBoard =
AdventureBossHelper.PickExploreRaffle(bountyBoard, exploreBoard,
explorerList, random);
states = states.SetExploreBoard(szn, exploreBoard);
}

// Send 80% NCG to operational account. 20% are for rewards.
var seasonBountyBoardAddress = Addresses.BountyBoard.Derive(
AdventureBossHelper.GetSeasonAsAddressForm(szn)
);
if (bountyBoard.totalBounty() ==
states.GetBalance(seasonBountyBoardAddress, bountyBoard.totalBounty().Currency)
)
{
states = states.TransferAsset(context, seasonBountyBoardAddress,
// FIXME: Set operational account address
new Address(),
(bountyBoard.totalBounty() * 80).DivRem(100, out _)
);
}

var investor =
bountyBoard.Investors.FirstOrDefault(inv => inv.AvatarAddress == AvatarAddress);
var ncgReward = 0 * bountyBoard.totalBounty().Currency;
var explorer = states.TryGetExplorer(szn, AvatarAddress, out var exp) ? exp : null;

if ((investor is not null && investor.Claimed) ||
(explorer is not null && explorer.Claimed))
{
// Already claimed reward. Stop here.
break;
}

if (investor is not null)
{
continueInv = AdventureBossHelper.CollectWantedReward(myReward,
gameConfig, ncgRewardRatioSheet, seasonInfo, bountyBoard, investor,
context.BlockIndex, AvatarAddress, ref myReward
);
investor.Claimed = true;
states = states.SetBountyBoard(szn, bountyBoard);
}

if (explorer is not null)
{
continueExp = AdventureBossHelper.CollectExploreReward(myReward, gameConfig,
ncgRewardRatioSheet, seasonInfo, bountyBoard, exploreBoard, explorer,
context.BlockIndex, AvatarAddress, ref myReward, out ncgReward);
explorer.Claimed = true;
states = states.SetExplorer(szn, explorer);
}

if (ncgReward > 0 * ncg)
{
states = states.TransferAsset(context, seasonBountyBoardAddress,
context.Signer, ncgReward);
}

if (!continueInv && !continueExp)
{
break;
}
}

var currentBlockIndex = context.BlockIndex;
var myReward = new AdventureBossGameData.ClaimableReward
if (myReward.IsEmpty())
{
NcgReward = null,
ItemReward = new Dictionary<int, int>(),
FavReward = new Dictionary<int, int>(),
};

// Collect wanted reward
states = AdventureBossHelper.CollectWantedReward(
states, context, myReward, currentBlockIndex, Season, context.Signer, AvatarAddress,
gameConfig.AdventureBossClaimInterval, out myReward
);

// Collect explore reward
states = AdventureBossHelper.CollectExploreReward(
states, context, myReward, currentBlockIndex, Season, context.Signer, AvatarAddress,
gameConfig.AdventureBossClaimInterval, out myReward
);
throw new EmptyRewardException($"{AvatarAddress} has no reward to receive.");
}

// Give rewards
// NOTE: NCG must be transferred from seasonal address. So this must be done in collection stage.
Expand Down
12 changes: 12 additions & 0 deletions Lib9c/Action/Exceptions/EmptyRewardException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Nekoyume.Action.Exceptions
{
[Serializable]
public class EmptyRewardException : Exception
{
public EmptyRewardException(string msg) : base(msg)
{
}
}
}
6 changes: 6 additions & 0 deletions Lib9c/Data/AdventureBossGameData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public struct ClaimableReward
public FungibleAssetValue? NcgReward;
public Dictionary<int, int> ItemReward;
public Dictionary<int, int> FavReward;

public bool IsEmpty()
{
return (NcgReward is null || ((FungibleAssetValue)NcgReward).RawValue <= 0) &&
ItemReward.Count == 0 && FavReward.Count == 0;
}
}
}
}
Loading

0 comments on commit 8ac115d

Please sign in to comment.