Skip to content

Commit

Permalink
Introduce ClaimPatrolReward
Browse files Browse the repository at this point in the history
  • Loading branch information
ipdae committed Dec 13, 2024
1 parent d037b26 commit 0d55b4c
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 1 deletion.
111 changes: 111 additions & 0 deletions .Lib9c.Tests/Action/ClaimPatrolRewardTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
namespace Lib9c.Tests.Action
{
using System.Linq;
using Lib9c.Tests.Util;
using Libplanet.Action.State;
using Libplanet.Crypto;
using Libplanet.Mocks;
using Nekoyume;
using Nekoyume.Action;
using Nekoyume.Model.Mail;
using Nekoyume.Model.State;
using Nekoyume.Module;
using Serilog;
using Xunit;
using Xunit.Abstractions;

public class ClaimPatrolRewardTest
{
private readonly IWorld _initialState;
private readonly TableSheets _tableSheets;

public ClaimPatrolRewardTest(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);
}

[Fact]
public void Execute()
{
var privateKey = new PrivateKey();
var agentAddress = privateKey.Address;
var row = _tableSheets.PatrolRewardSheet.Values.First();
var (state, avatar, _) = InitializeUtil.AddAvatar(_initialState, _tableSheets.GetAvatarSheets(), agentAddress);
var avatarAddress = avatar.address;
var action = new ClaimPatrolReward(avatar.address);

var nextState = action.Execute(new ActionContext
{
Signer = agentAddress,
BlockIndex = 1L,
PreviousState = state,
RandomSeed = 0,
});

var avatarState = nextState.GetAvatarState(avatarAddress);
var itemSheet = _tableSheets.ItemSheet;
var mail = Assert.IsType<PatrolRewardMail>(avatarState.mailBox.Single());

foreach (var reward in row.Rewards)
{
var ticker = reward.Ticker;
if (string.IsNullOrEmpty(ticker))
{
var itemId = reward.ItemId;
var rowId = itemSheet[itemId].Id;
Assert.True(avatarState.inventory.HasItem(rowId, reward.Count));
var item = mail.Items.First(i => i.id == itemId);
Assert.Equal(item.count, reward.Count);
}
else
{
var currency = Currencies.GetMinterlessCurrency(ticker);
var recipient = Currencies.PickAddress(currency, agentAddress, avatarAddress);
var fav = nextState.GetBalance(recipient, currency);
Assert.Equal(currency * reward.Count, fav);
Assert.Contains(fav, mail.FungibleAssetValues);
}
}

Assert.Equal(1L, nextState.GetPatrolRewardClaimedBlockIndex(avatarAddress));
Assert.True(row.Interval > 1L);

// Throw RequiredBlockIndex by reward interval
Assert.Throws<RequiredBlockIndexException>(() => action.Execute(new ActionContext
{
Signer = agentAddress,
BlockIndex = 2L,
PreviousState = nextState,
RandomSeed = 0,
}));
}

[Fact]
public void Execute_Throw_InvalidAddressException()
{
var signer = new PrivateKey().Address;
var action = new ClaimPatrolReward(signer);

Assert.Throws<InvalidAddressException>(() => action.Execute(new ActionContext
{
Signer = signer,
BlockIndex = 0,
PreviousState = _initialState,
}));
}
}
}
113 changes: 113 additions & 0 deletions Lib9c/Action/ClaimPatrolReward.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using Bencodex.Types;
using Lib9c;
using Libplanet.Action;
using Libplanet.Action.State;
using Libplanet.Crypto;
using Libplanet.Types.Assets;
using Nekoyume.Extensions;
using Nekoyume.Helper;
using Nekoyume.Model.Item;
using Nekoyume.Model.Mail;
using Nekoyume.Model.State;
using Nekoyume.Module;
using Nekoyume.TableData;
using Nekoyume.TableData.Event;

namespace Nekoyume.Action
{
/// <summary>
/// Claim patrol reward
/// </summary>
[Serializable]
[ActionType(TypeIdentifier)]
public class ClaimPatrolReward : ActionBase
{
public const string TypeIdentifier = "claim_patrol_reward";
public Address AvatarAddress;

public ClaimPatrolReward()
{
}

public ClaimPatrolReward(Address avatarAddress)
{
AvatarAddress = avatarAddress;
}
public override IWorld Execute(IActionContext context)
{
GasTracer.UseGas(1);
var signer = context.Signer;
var states = context.PreviousState;
if (!Addresses.CheckAvatarAddrIsContainedInAgent(signer, AvatarAddress))
{
throw new InvalidAddressException();
}

// avatar
var avatarState = states.GetAvatarState(AvatarAddress, true, false, false);
var avatarLevel = avatarState.level;
var inventory = avatarState.inventory;

// sheets
var sheets = states.GetSheets(containItemSheet: true, sheetTypes: new[]
{
typeof(PatrolRewardSheet)
});
var patrolRewardSheet = sheets.GetSheet<PatrolRewardSheet>();
var itemSheet = sheets.GetItemSheet();

// validate
states.TryGetPatrolRewardClaimedBlockIndex(AvatarAddress, out var claimedBlockIndex);
var row = patrolRewardSheet.FindByLevel(avatarLevel, context.BlockIndex);
if (claimedBlockIndex > 0L && claimedBlockIndex + row.Interval > context.BlockIndex)
{
throw new RequiredBlockIndexException();
}

// mit rewards
var random = context.GetRandom();
var favs = new List<FungibleAssetValue>();
var items = new List<(int id, int count)>();
foreach (var reward in row.Rewards)
{
var ticker = reward.Ticker;
if (string.IsNullOrEmpty(ticker))
{
var itemRow = itemSheet[reward.ItemId];
inventory.MintItem(itemRow, reward.Count, false, random);
items.Add(new (reward.ItemId, reward.Count));
}
else
{
var currency = Currencies.GetMinterlessCurrency(ticker);
var recipient = Currencies.PickAddress(currency, signer, AvatarAddress);
var fav = currency * reward.Count;
states = states.MintAsset(context, recipient, fav);
favs.Add(fav);
}
}

var mailBox = avatarState.mailBox;
var mail = new PatrolRewardMail(context.BlockIndex, random.GenerateRandomGuid(), context.BlockIndex, favs, items);
mailBox.Add(mail);
mailBox.CleanUp();
avatarState.mailBox = mailBox;

// set states
states = states
.SetAvatarState(AvatarAddress, avatarState, setAvatar: true, setInventory: true, setWorldInformation: false, setQuestList: false)
.SetPatrolRewardClaimedBlockIndex(AvatarAddress, context.BlockIndex);
return states;
}

public override IValue PlainValue => Dictionary.Empty
.Add("type_id", TypeIdentifier)
.Add("values", AvatarAddress.Serialize());
public override void LoadPlainValue(IValue plainValue)
{
AvatarAddress = ((Dictionary)plainValue)["values"].ToAddress();
}
}
}
3 changes: 2 additions & 1 deletion Lib9c/Addresses.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public static class Addresses

public static readonly Address CombinationSlot = new("0000000000000000000000000000000000000024");
public static readonly Address ClaimedGiftIds = new("0000000000000000000000000000000000000025");
public static readonly Address PatrolReward = new("0000000000000000000000000000000000000026");

// Adventure Boss
public static readonly Address AdventureBoss = new("0000000000000000000000000000000000000100");
Expand Down Expand Up @@ -106,7 +107,7 @@ public static Address GetGuildBanAccountAddress(Address guildAddress) =>

/// <summary>
/// An address of an account having <see cref="Delegation.DelegateeMetadata"/>.
/// </summary>
/// </summary>
public static readonly Address GuildDelegateeMetadata
= new Address("0000000000000000000000000000000000000210");

Expand Down
43 changes: 43 additions & 0 deletions Lib9c/Module/PatrolRewardModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Bencodex.Types;
using Libplanet.Action.State;
using Libplanet.Crypto;
using Nekoyume.Action;

namespace Nekoyume.Module
{
public static class PatrolRewardModule
{
public static long GetPatrolRewardClaimedBlockIndex(this IWorldState worldState, Address avatarAddress)
{
var value = worldState.GetAccountState(Addresses.PatrolReward).GetState(avatarAddress);
if (value is Integer integer)
{
return integer;
}

throw new FailedLoadStateException("");
}

public static bool TryGetPatrolRewardClaimedBlockIndex(this IWorldState worldState, Address avatarAddress, out long blockIndex)
{
blockIndex = 0L;
try
{
var temp = GetPatrolRewardClaimedBlockIndex(worldState, avatarAddress);
blockIndex = temp;
return true;
}
catch (FailedLoadStateException)
{
return false;
}
}

public static IWorld SetPatrolRewardClaimedBlockIndex(this IWorld world, Address avatarAddress, long blockIndex)
{
var account = world.GetAccount(Addresses.PatrolReward);
account = account.SetState(avatarAddress, (Integer)blockIndex);
return world.SetAccount(Addresses.PatrolReward, account);
}
}
}

0 comments on commit 0d55b4c

Please sign in to comment.