Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ClaimPatrolReward #3088

Open
wants to merge 5 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
}));
}
}
}
88 changes: 88 additions & 0 deletions .Lib9c.Tests/TableData/Event/PatrolRewardSheetTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace Lib9c.Tests.TableData.Event
{
using System.Linq;
using Nekoyume.TableData.Event;
using Xunit;

public class PatrolRewardSheetTest
{
private readonly PatrolRewardSheet _sheet = new ();

public PatrolRewardSheetTest()
{
const string csv =
"id,start,end,interval,min_level,max_level,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker\n1,0,100,8400,1,200,1,500000,,1,600201,\n2,100,200,8400,201,350,1,500000,,2,600201,,100000,,CRYSTAL\n3,200,300,8400,351,,2,500000,,3,600201,,200000,,CRYSTAL,1,600202,\n";
_sheet.Set(csv);
}

[Fact]
public void Set()
{
var row = _sheet[1];
Assert.Equal(0L, row.StartedBlockIndex);
Assert.Equal(100L, row.EndedBlockIndex);
Assert.Equal(8400L, row.Interval);
Assert.Equal(1, row.MinimumLevel);
Assert.Equal(200, row.MaxLevel);
var apReward = row.Rewards.First();
Assert.Equal(1, apReward.Count);
Assert.Equal(500000, apReward.ItemId);
Assert.True(string.IsNullOrEmpty(apReward.Ticker));
var gdReward = row.Rewards.Last();
Assert.Equal(1, gdReward.Count);
Assert.Equal(600201, gdReward.ItemId);
Assert.True(string.IsNullOrEmpty(gdReward.Ticker));

row = _sheet[2];
Assert.Equal(100L, row.StartedBlockIndex);
Assert.Equal(200L, row.EndedBlockIndex);
Assert.Equal(8400L, row.Interval);
Assert.Equal(201, row.MinimumLevel);
Assert.Equal(350, row.MaxLevel);
apReward = row.Rewards.First();
Assert.Equal(1, apReward.Count);
Assert.Equal(500000, apReward.ItemId);
Assert.True(string.IsNullOrEmpty(apReward.Ticker));
gdReward = row.Rewards[1];
Assert.Equal(2, gdReward.Count);
Assert.Equal(600201, gdReward.ItemId);
Assert.True(string.IsNullOrEmpty(gdReward.Ticker));
var crystalReward = row.Rewards.Last();
Assert.Equal(100000, crystalReward.Count);
Assert.Equal(0, crystalReward.ItemId);
Assert.Equal("CRYSTAL", crystalReward.Ticker);

row = _sheet[3];
Assert.Equal(200L, row.StartedBlockIndex);
Assert.Equal(300L, row.EndedBlockIndex);
Assert.Equal(8400L, row.Interval);
Assert.Equal(351, row.MinimumLevel);
Assert.Null(row.MaxLevel);
apReward = row.Rewards.First();
Assert.Equal(2, apReward.Count);
Assert.Equal(500000, apReward.ItemId);
Assert.True(string.IsNullOrEmpty(apReward.Ticker));
gdReward = row.Rewards[1];
Assert.Equal(3, gdReward.Count);
Assert.Equal(600201, gdReward.ItemId);
Assert.True(string.IsNullOrEmpty(gdReward.Ticker));
crystalReward = row.Rewards[2];
Assert.Equal(200000, crystalReward.Count);
Assert.Equal(0, crystalReward.ItemId);
Assert.Equal("CRYSTAL", crystalReward.Ticker);
var rdReward = row.Rewards.Last();
Assert.Equal(1, rdReward.Count);
Assert.Equal(600202, rdReward.ItemId);
Assert.True(string.IsNullOrEmpty(rdReward.Ticker));
}

[Theory]
[InlineData(2, 1, 0)]
[InlineData(350, 2, 150)]
[InlineData(500, 3, 300)]
public void FindByLevel(int level, int id, long blockIndex)
{
Assert.Equal(id, _sheet.FindByLevel(level, blockIndex).Id);
}
}
}
2 changes: 2 additions & 0 deletions .Lib9c.Tests/TableSheets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ public TableSheets(Dictionary<string, string> sheets, bool ignoreFailedGetProper

public SynthesizeWeightSheet SynthesizeWeightSheet { get; private set; }

public PatrolRewardSheet PatrolRewardSheet { get; private set; }

public void ItemSheetInitialize()
{
ItemSheet ??= new ItemSheet();
Expand Down
17 changes: 2 additions & 15 deletions Lib9c/Action/ClaimGifts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Libplanet.Crypto;
using Nekoyume.Exceptions;
using Nekoyume.Extensions;
using Nekoyume.Helper;
using Nekoyume.Model.Item;
using Nekoyume.Model.State;
using Nekoyume.Module;
Expand Down Expand Up @@ -123,21 +124,7 @@ public override IWorld Execute(IActionContext context)
foreach (var (itemId, quantity, tradable) in giftRow.Items)
{
var itemRow = itemSheet[itemId];
if (itemRow is MaterialItemSheet.Row materialRow)
{
var item = tradable
? ItemFactory.CreateTradableMaterial(materialRow)
: ItemFactory.CreateMaterial(materialRow);
inventory.AddItem(item, quantity);
}
else
{
foreach (var _ in Enumerable.Range(0, quantity))
{
var item = ItemFactory.CreateItem(itemRow, random);
inventory.AddItem(item);
}
}
inventory.MintItem(itemRow, quantity, tradable, random);
}

claimedGiftIds.Add(giftRow.Id);
Expand Down
18 changes: 2 additions & 16 deletions Lib9c/Action/ClaimItems.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;
Expand Down Expand Up @@ -134,22 +135,7 @@ public override IWorld Execute(IActionContext context)
// it's only right that this is fixed in Inventory.
var itemRow = itemSheet[itemId];
var itemCount = (int)fungibleAssetValue.RawValue;
if (itemRow is MaterialItemSheet.Row materialRow)
{
var item = tradable
? ItemFactory.CreateTradableMaterial(materialRow)
: ItemFactory.CreateMaterial(materialRow);
avatarState.inventory.AddItem(item, itemCount);
}
else
{
foreach (var _ in Enumerable.Range(0, itemCount))
{
var item = ItemFactory.CreateItem(itemRow, random);
avatarState.inventory.AddItem(item);
}
}

avatarState.inventory.MintItem(itemRow, itemCount, tradable, random);
items.Add((itemRow.Id, itemCount));
}
}
Expand Down
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();
}
}
}
Loading
Loading