diff --git a/.Lib9c.Tests/Action/ActionEvaluationTest.cs b/.Lib9c.Tests/Action/ActionEvaluationTest.cs index 3a58483705..d6ee4a2392 100644 --- a/.Lib9c.Tests/Action/ActionEvaluationTest.cs +++ b/.Lib9c.Tests/Action/ActionEvaluationTest.cs @@ -92,6 +92,7 @@ public ActionEvaluationTest() [InlineData(typeof(TransferAssets))] [InlineData(typeof(RuneSummon))] [InlineData(typeof(ActivateCollection))] + [InlineData(typeof(RetrieveAvatarAssets))] public void Serialize_With_MessagePack(Type actionType) { var action = GetAction(actionType); @@ -481,6 +482,7 @@ private ActionBase GetAction(Type type) ), }, }, + RetrieveAvatarAssets _ => new RetrieveAvatarAssets(avatarAddress: new PrivateKey().Address), _ => throw new InvalidCastException(), }; } diff --git a/.Lib9c.Tests/Action/ActivateCollectionTest.cs b/.Lib9c.Tests/Action/ActivateCollectionTest.cs index e51d4fbb7f..40ecd9fe93 100644 --- a/.Lib9c.Tests/Action/ActivateCollectionTest.cs +++ b/.Lib9c.Tests/Action/ActivateCollectionTest.cs @@ -136,6 +136,12 @@ public void Execute() var nextAvatarState = nextState.GetAvatarState(_avatarAddress); Assert.Empty(nextAvatarState.inventory.Items); + + Assert.Throws(() => activateCollection.Execute(new ActionContext + { + PreviousState = nextState, + Signer = _agentAddress, + })); } } } diff --git a/.Lib9c.Tests/Action/AddActivatedAccount0Test.cs b/.Lib9c.Tests/Action/AddActivatedAccount0Test.cs deleted file mode 100644 index a1d2fbf71a..0000000000 --- a/.Lib9c.Tests/Action/AddActivatedAccount0Test.cs +++ /dev/null @@ -1,97 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System.Collections.Immutable; - using Bencodex.Types; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Nekoyume.Action; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Xunit; - - public class AddActivatedAccount0Test - { - [Fact] - public void Execute() - { - var admin = new Address("8d9f76aF8Dc5A812aCeA15d8bf56E2F790F47fd7"); - var state = new World( - new MockWorldState() - .SetState(ReservedAddresses.LegacyAccount, AdminState.Address, new AdminState(admin, 100).Serialize()) - .SetState(ReservedAddresses.LegacyAccount, ActivatedAccountsState.Address, new ActivatedAccountsState().Serialize())); - var newComer = new Address("399bddF9F7B6d902ea27037B907B2486C9910730"); - var action = new AddActivatedAccount0(newComer); - - IWorld nextState = action.Execute(new ActionContext() - { - BlockIndex = 1, - Miner = default, - PreviousState = state, - Signer = admin, - }); - - var nextAccountStates = new ActivatedAccountsState( - (Dictionary)nextState.GetLegacyState(ActivatedAccountsState.Address) - ); - - Assert.Equal( - ImmutableHashSet.Create(newComer), - nextAccountStates.Accounts - ); - } - - [Fact] - public void ExecuteWithNonExistsAccounts() - { - var admin = new Address("8d9f76aF8Dc5A812aCeA15d8bf56E2F790F47fd7"); - var state = new World(new MockWorldState()); - var newComer = new Address("399bddF9F7B6d902ea27037B907B2486C9910730"); - var action = new AddActivatedAccount0(newComer); - - Assert.Throws(() => - { - action.Execute(new ActionContext() - { - BlockIndex = 1, - Miner = default, - PreviousState = state, - Signer = admin, - }); - }); - } - - [Fact] - public void CheckPermission() - { - var admin = new Address("8d9f76aF8Dc5A812aCeA15d8bf56E2F790F47fd7"); - var state = new World( - new MockWorldState() - .SetState(ReservedAddresses.LegacyAccount, AdminState.Address, new AdminState(admin, 100).Serialize()) - .SetState(ReservedAddresses.LegacyAccount, ActivatedAccountsState.Address, new ActivatedAccountsState().Serialize())); - var newComer = new Address("399bddF9F7B6d902ea27037B907B2486C9910730"); - var action = new AddActivatedAccount0(newComer); - - Assert.Throws(() => - { - action.Execute(new ActionContext() - { - BlockIndex = 1, - Miner = default, - PreviousState = state, - Signer = newComer, - }); - }); - - Assert.Throws(() => - { - action.Execute(new ActionContext() - { - BlockIndex = 101, - Miner = default, - PreviousState = state, - Signer = admin, - }); - }); - } - } -} diff --git a/.Lib9c.Tests/Action/BuyProduct0Test.cs b/.Lib9c.Tests/Action/BuyProduct0Test.cs deleted file mode 100644 index e04dc74eb5..0000000000 --- a/.Lib9c.Tests/Action/BuyProduct0Test.cs +++ /dev/null @@ -1,344 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Libplanet.Types.Assets; - using Nekoyume; - using Nekoyume.Action; - using Nekoyume.Battle; - using Nekoyume.Helper; - using Nekoyume.Model; - using Nekoyume.Model.Item; - using Nekoyume.Model.Market; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Serilog; - using Xunit; - using Xunit.Abstractions; - - public class BuyProduct0Test - { - private static readonly Address BuyerAgentAddress = new Address("47d082a115c63e7b58b1532d20e631538eafadde"); - private static readonly Address BuyerAvatarAddress = new Address("340f110b91d0577a9ae0ea69ce15269436f217da"); - private static readonly Address SellerAgentAddress = new Address("F9A15F870701268Bd7bBeA6502eB15F4997f32f9"); - private static readonly Address SellerAvatarAddress = new Address("Fb90278C67f9b266eA309E6AE8463042f5461449"); - private static readonly Guid ProductId = Guid.NewGuid(); - private static readonly Currency Gold = Currency.Legacy("NCG", 2, null); - private static readonly TableSheets TableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); - private static readonly ITradableItem TradableItem = - (ITradableItem)ItemFactory.CreateItemUsable(TableSheets.EquipmentItemSheet.OrderedList.First(r => r.ItemSubType == ItemSubType.Armor), Guid.NewGuid(), 1L); - - private readonly Address _sellerAgentAddress2; - private readonly Address _sellerAvatarAddress2; - private readonly AvatarState _buyerAvatarState; - private readonly GoldCurrencyState _goldCurrencyState; - private readonly Guid _orderId; - private IWorld _initialState; - - public BuyProduct0Test(ITestOutputHelper outputHelper) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.TestOutput(outputHelper) - .CreateLogger(); - - var context = new ActionContext(); - _initialState = new World(new MockWorldState()); - var sheets = TableSheetsImporter.ImportSheets(); - foreach (var (key, value) in sheets) - { - _initialState = _initialState - .SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); - } - -#pragma warning disable CS0618 - // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 -#pragma warning restore CS0618 - _goldCurrencyState = new GoldCurrencyState(Gold); - - var sellerAgentState = new AgentState(SellerAgentAddress); - var rankingMapAddress = new PrivateKey().Address; - var sellerAvatarState = new AvatarState( - SellerAvatarAddress, - SellerAgentAddress, - 0, - TableSheets.GetAvatarSheets(), - new GameConfigState(), - rankingMapAddress) - { - worldInformation = new WorldInformation( - 0, - TableSheets.WorldSheet, - GameConfig.RequireClearedStageLevel.ActionsInShop), - }; - sellerAgentState.avatarAddresses[0] = SellerAvatarAddress; - - _sellerAgentAddress2 = new PrivateKey().Address; - var agentState2 = new AgentState(_sellerAgentAddress2); - _sellerAvatarAddress2 = new PrivateKey().Address; - var sellerAvatarState2 = new AvatarState( - _sellerAvatarAddress2, - _sellerAgentAddress2, - 0, - TableSheets.GetAvatarSheets(), - new GameConfigState(), - rankingMapAddress) - { - worldInformation = new WorldInformation( - 0, - TableSheets.WorldSheet, - GameConfig.RequireClearedStageLevel.ActionsInShop), - }; - agentState2.avatarAddresses[0] = _sellerAvatarAddress2; - - var buyerAgentState = new AgentState(BuyerAgentAddress); - _buyerAvatarState = new AvatarState( - BuyerAvatarAddress, - BuyerAgentAddress, - 0, - TableSheets.GetAvatarSheets(), - new GameConfigState(), - rankingMapAddress) - { - worldInformation = new WorldInformation( - 0, - TableSheets.WorldSheet, - GameConfig.RequireClearedStageLevel.ActionsInShop), - }; - buyerAgentState.avatarAddresses[0] = BuyerAvatarAddress; - - _orderId = new Guid("6d460c1a-755d-48e4-ad67-65d5f519dbc8"); - _initialState = _initialState - .SetLegacyState(GoldCurrencyState.Address, _goldCurrencyState.Serialize()) - .SetAgentState(SellerAgentAddress, sellerAgentState) - .SetLegacyState(SellerAvatarAddress, MigrationAvatarState.LegacySerializeV1(sellerAvatarState)) - .SetAgentState(_sellerAgentAddress2, agentState2) - .SetLegacyState(_sellerAvatarAddress2, MigrationAvatarState.LegacySerializeV1(sellerAvatarState2)) - .SetAgentState(BuyerAgentAddress, buyerAgentState) - .SetLegacyState(BuyerAvatarAddress, MigrationAvatarState.LegacySerializeV1(_buyerAvatarState)) - .MintAsset(context, BuyerAgentAddress, _goldCurrencyState.Currency * 1); - } - - public static IEnumerable Execute_MemberData() - { - yield return new object[] - { - new ExecuteMember - { - ProductInfos = new List(), - Exc = typeof(ListEmptyException), - }, - new ExecuteMember - { - ProductInfos = new IProductInfo[] - { - new ItemProductInfo - { - AvatarAddress = BuyerAvatarAddress, - }, - new ItemProductInfo - { - AgentAddress = BuyerAgentAddress, - }, - new FavProductInfo - { - AvatarAddress = BuyerAvatarAddress, - }, - new FavProductInfo - { - AgentAddress = BuyerAgentAddress, - }, - }, - Exc = typeof(InvalidAddressException), - }, - new ExecuteMember - { - ProductInfos = new IProductInfo[] - { - new ItemProductInfo - { - AvatarAddress = SellerAvatarAddress, - AgentAddress = SellerAgentAddress, - }, - }, - Exc = typeof(ProductNotFoundException), - ProductsState = new ProductsState(), - }, - new ExecuteMember - { - ProductInfos = new IProductInfo[] - { - new FavProductInfo - { - AvatarAddress = SellerAvatarAddress, - AgentAddress = SellerAgentAddress, - ProductId = ProductId, - Price = 2 * Gold, - Type = ProductType.FungibleAssetValue, - }, - }, - Exc = typeof(InvalidPriceException), - ProductsState = new ProductsState - { - ProductIds = new List - { - ProductId, - }, - }, - Product = new FavProduct - { - SellerAgentAddress = SellerAgentAddress, - SellerAvatarAddress = SellerAvatarAddress, - Asset = 1 * RuneHelper.StakeRune, - RegisteredBlockIndex = 1L, - ProductId = ProductId, - Price = 1 * Gold, - Type = ProductType.FungibleAssetValue, - }, - }, - new ExecuteMember - { - ProductInfos = new IProductInfo[] - { - new ItemProductInfo - { - AvatarAddress = SellerAvatarAddress, - AgentAddress = SellerAgentAddress, - ProductId = ProductId, - Price = 1 * Gold, - Type = ProductType.NonFungible, - TradableId = Guid.NewGuid(), - ItemSubType = ItemSubType.Belt, - }, - }, - Exc = typeof(InvalidTradableIdException), - ProductsState = new ProductsState - { - ProductIds = new List - { - ProductId, - }, - }, - Product = new ItemProduct - { - SellerAgentAddress = SellerAgentAddress, - SellerAvatarAddress = SellerAvatarAddress, - RegisteredBlockIndex = 1L, - ProductId = ProductId, - Price = 1 * Gold, - Type = ProductType.NonFungible, - ItemCount = 1, - TradableItem = TradableItem, - }, - }, - new ExecuteMember - { - ProductInfos = new IProductInfo[] - { - new ItemProductInfo - { - AvatarAddress = SellerAvatarAddress, - AgentAddress = SellerAgentAddress, - ProductId = ProductId, - Price = 1 * Gold, - Type = ProductType.NonFungible, - TradableId = TradableItem.TradableId, - ItemSubType = ItemSubType.Belt, - }, - }, - Exc = typeof(InvalidItemTypeException), - ProductsState = new ProductsState - { - ProductIds = new List - { - ProductId, - }, - }, - Product = new ItemProduct - { - SellerAgentAddress = SellerAgentAddress, - SellerAvatarAddress = SellerAvatarAddress, - RegisteredBlockIndex = 1L, - ProductId = ProductId, - Price = 1 * Gold, - Type = ProductType.NonFungible, - ItemCount = 1, - TradableItem = TradableItem, - }, - }, - }; - } - - [Theory] - [MemberData(nameof(Execute_MemberData))] - public void Execute_Throw_Exception(params ExecuteMember[] validateMembers) - { - foreach (var validateMember in validateMembers) - { - var previousState = _initialState; - var productsState = validateMember.ProductsState; - if (!(productsState is null)) - { - previousState = previousState.SetLegacyState( - ProductsState.DeriveAddress(SellerAvatarAddress), - productsState.Serialize()); - } - - var product = validateMember.Product; - if (!(product is null)) - { - previousState = previousState.SetLegacyState( - Product.DeriveAddress(product.ProductId), - product.Serialize()); - } - - foreach (var productInfo in validateMember.ProductInfos) - { - var action = new BuyProduct0 - { - AvatarAddress = BuyerAvatarAddress, - ProductInfos = new[] { productInfo }, - }; - Assert.Throws(validateMember.Exc, () => action.Execute(new ActionContext - { - PreviousState = previousState, - RandomSeed = 0, - Signer = BuyerAgentAddress, - })); - } - } - } - - [Fact] - public void Execute_Throw_ArgumentOutOfRangeException() - { - var productInfos = new List(); - for (int i = 0; i < BuyProduct0.Capacity + 1; i++) - { - productInfos.Add(new ItemProductInfo()); - } - - var action = new BuyProduct0 - { - AvatarAddress = _sellerAvatarAddress2, - ProductInfos = productInfos, - }; - - Assert.Throws(() => action.Execute(new ActionContext())); - } - - public class ExecuteMember - { - public IEnumerable ProductInfos { get; set; } - - public Product Product { get; set; } - - public ProductsState ProductsState { get; set; } - - public Type Exc { get; set; } - } - } -} diff --git a/.Lib9c.Tests/Action/BuyProduct2Test.cs b/.Lib9c.Tests/Action/BuyProductTest.cs similarity index 98% rename from .Lib9c.Tests/Action/BuyProduct2Test.cs rename to .Lib9c.Tests/Action/BuyProductTest.cs index 99414aca0f..509175c5ee 100644 --- a/.Lib9c.Tests/Action/BuyProduct2Test.cs +++ b/.Lib9c.Tests/Action/BuyProductTest.cs @@ -19,7 +19,7 @@ namespace Lib9c.Tests.Action using Xunit; using Xunit.Abstractions; - public class BuyProduct2Test + public class BuyProductTest { private static readonly Address BuyerAgentAddress = new Address("47d082a115c63e7b58b1532d20e631538eafadde"); private static readonly Address BuyerAvatarAddress = new Address("340f110b91d0577a9ae0ea69ce15269436f217da"); @@ -38,7 +38,7 @@ public class BuyProduct2Test private readonly Guid _orderId; private IWorld _initialState; - public BuyProduct2Test(ITestOutputHelper outputHelper) + public BuyProductTest(ITestOutputHelper outputHelper) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() @@ -297,7 +297,7 @@ public void Execute_Throw_Exception(params ExecuteMember[] validateMembers) foreach (var productInfo in validateMember.ProductInfos) { - var action = new BuyProduct2 + var action = new BuyProduct { AvatarAddress = BuyerAvatarAddress, ProductInfos = new[] { productInfo }, @@ -316,12 +316,12 @@ public void Execute_Throw_Exception(params ExecuteMember[] validateMembers) public void Execute_Throw_ArgumentOutOfRangeException() { var productInfos = new List(); - for (int i = 0; i < BuyProduct2.Capacity + 1; i++) + for (int i = 0; i < BuyProduct.Capacity + 1; i++) { productInfos.Add(new ItemProductInfo()); } - var action = new BuyProduct2 + var action = new BuyProduct { AvatarAddress = _sellerAvatarAddress2, ProductInfos = productInfos, diff --git a/.Lib9c.Tests/Action/BuyTest.cs b/.Lib9c.Tests/Action/BuyTest.cs index 8c48716b26..9ad40c8916 100644 --- a/.Lib9c.Tests/Action/BuyTest.cs +++ b/.Lib9c.Tests/Action/BuyTest.cs @@ -351,7 +351,7 @@ public void Execute(params OrderData[] orderDataList) Signer = _buyerAgentAddress, }); - var buyProductAction = new BuyProduct2 + var buyProductAction = new BuyProduct { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfos, @@ -629,7 +629,7 @@ public void Execute_ErrorCode(ErrorCodeMember errorCodeMember) action.errors.Select(r => r.errorCode) ); - var buyProductAction = new BuyProduct2 + var buyProductAction = new BuyProduct { AvatarAddress = _buyerAvatarAddress, ProductInfos = new[] { productInfo }, diff --git a/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs b/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs index e5e6477f89..97b097daaa 100644 --- a/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs +++ b/.Lib9c.Tests/Action/CancelProductRegistration0Test.cs @@ -129,7 +129,7 @@ public void Execute_Throw_ProductNotFoundException() { var context = new ActionContext(); var prevState = _initialState.MintAsset(context, _avatarAddress, 1 * RuneHelper.StakeRune); - var registerProduct = new RegisterProduct2 + var registerProduct = new RegisterProduct { AvatarAddress = _avatarAddress, RegisterInfos = new List diff --git a/.Lib9c.Tests/Action/ChargeActionPoint0Test.cs b/.Lib9c.Tests/Action/ChargeActionPoint0Test.cs deleted file mode 100644 index 5944da1af2..0000000000 --- a/.Lib9c.Tests/Action/ChargeActionPoint0Test.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System.Collections.Generic; - using System.Linq; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Nekoyume; - using Nekoyume.Action; - using Nekoyume.Model.Item; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Nekoyume.TableData; - using Xunit; - - public class ChargeActionPoint0Test - { - private readonly Dictionary _sheets; - private readonly TableSheets _tableSheets; - - public ChargeActionPoint0Test() - { - _sheets = TableSheetsImporter.ImportSheets(); - _tableSheets = new TableSheets(_sheets); - } - - [Fact] - public void Execute() - { - var privateKey = new PrivateKey(); - var agentAddress = privateKey.PublicKey.Address; - var agent = new AgentState(agentAddress); - - var avatarAddress = agentAddress.Derive("avatar"); - var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); - var avatarState = new AvatarState( - avatarAddress, - agentAddress, - 0, - _tableSheets.GetAvatarSheets(), - gameConfigState, - default - ) - { - actionPoint = 0, - }; - agent.avatarAddresses.Add(0, avatarAddress); - - var apStone = - ItemFactory.CreateItem( - _tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone), - new TestRandom()); - avatarState.inventory.AddItem2(apStone); - - Assert.Equal(0, avatarState.actionPoint); - - var state = new World(new MockWorldState()) - .SetLegacyState(Addresses.GameConfig, gameConfigState.Serialize()) - .SetAgentState(agentAddress, agent) - .SetAvatarState(avatarAddress, avatarState); - - foreach (var (key, value) in _sheets) - { - state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); - } - - var action = new ChargeActionPoint0() - { - avatarAddress = avatarAddress, - }; - - var nextState = action.Execute(new ActionContext() - { - PreviousState = state, - Signer = agentAddress, - RandomSeed = 0, - }); - - var nextAvatarState = nextState.GetAvatarState(avatarAddress); - - Assert.Equal(gameConfigState.ActionPointMax, nextAvatarState.actionPoint); - } - } -} diff --git a/.Lib9c.Tests/Action/ChargeActionPoint2Test.cs b/.Lib9c.Tests/Action/ChargeActionPoint2Test.cs deleted file mode 100644 index a67ab1ab69..0000000000 --- a/.Lib9c.Tests/Action/ChargeActionPoint2Test.cs +++ /dev/null @@ -1,153 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System.Collections.Generic; - using System.Linq; - using Libplanet.Action.State; - using Libplanet.Crypto; - using Nekoyume; - using Nekoyume.Action; - using Nekoyume.Model.Item; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Nekoyume.TableData; - using Xunit; - - public class ChargeActionPoint2Test - { - private readonly Dictionary _sheets; - private readonly TableSheets _tableSheets; - private readonly Address _agentAddress; - private readonly Address _avatarAddress; - private readonly IWorld _initialState; - - public ChargeActionPoint2Test() - { - _sheets = TableSheetsImporter.ImportSheets(); - _tableSheets = new TableSheets(_sheets); - - var privateKey = new PrivateKey(); - _agentAddress = privateKey.PublicKey.Address; - var agent = new AgentState(_agentAddress); - - _avatarAddress = _agentAddress.Derive("avatar"); - var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); - var avatarState = new AvatarState( - _avatarAddress, - _agentAddress, - 0, - _tableSheets.GetAvatarSheets(), - gameConfigState, - default - ) - { - actionPoint = 0, - }; - agent.avatarAddresses.Add(0, _avatarAddress); - - _initialState = new World(new MockWorldState()) - .SetLegacyState(Addresses.GameConfig, gameConfigState.Serialize()) - .SetAgentState(_agentAddress, agent) - .SetAvatarState(_avatarAddress, avatarState); - - foreach (var (key, value) in _sheets) - { - _initialState = _initialState.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute(bool useTradable) - { - var avatarState = _initialState.GetAvatarState(_avatarAddress); - var row = _tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone); - if (useTradable) - { - var apStone = ItemFactory.CreateTradableMaterial(row); - avatarState.inventory.AddItem2(apStone); - } - else - { - var apStone = ItemFactory.CreateItem(row, new TestRandom()); - avatarState.inventory.AddItem2(apStone); - } - - Assert.Equal(0, avatarState.actionPoint); - - var state = _initialState.SetAvatarState(_avatarAddress, avatarState); - - foreach (var (key, value) in _sheets) - { - state = state.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); - } - - var action = new ChargeActionPoint2() - { - avatarAddress = _avatarAddress, - }; - - var nextState = action.Execute(new ActionContext() - { - PreviousState = state, - Signer = _agentAddress, - RandomSeed = 0, - }); - - var nextAvatarState = nextState.GetAvatarState(_avatarAddress); - var gameConfigState = nextState.GetGameConfigState(); - Assert.Equal(gameConfigState.ActionPointMax, nextAvatarState.actionPoint); - } - - [Fact] - public void Execute_Throw_FailedLoadStateException() - { - var action = new ChargeActionPoint2 - { - avatarAddress = default, - }; - - Assert.Throws(() => action.Execute(new ActionContext() - { - BlockIndex = 0, - PreviousState = new World(new MockWorldState()), - RandomSeed = 0, - Signer = default, - }) - ); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Execute_Throw_NotEnoughMaterialException(bool useTradable) - { - var avatarState = _initialState.GetAvatarState(_avatarAddress); - - if (useTradable) - { - var row = _tableSheets.MaterialItemSheet.Values.First(r => r.ItemSubType == ItemSubType.ApStone); - var apStone = ItemFactory.CreateTradableMaterial(row); - apStone.RequiredBlockIndex = 10; - avatarState.inventory.AddItem2(apStone); - } - - Assert.Equal(0, avatarState.actionPoint); - - var state = _initialState.SetAvatarState(_avatarAddress, avatarState); - - var action = new ChargeActionPoint2() - { - avatarAddress = _avatarAddress, - }; - - Assert.Throws(() => action.Execute(new ActionContext() - { - PreviousState = state, - Signer = _agentAddress, - RandomSeed = 0, - }) - ); - } - } -} diff --git a/.Lib9c.Tests/Action/CombinationEquipment16Test.cs b/.Lib9c.Tests/Action/CombinationEquipmentTest.cs similarity index 97% rename from .Lib9c.Tests/Action/CombinationEquipment16Test.cs rename to .Lib9c.Tests/Action/CombinationEquipmentTest.cs index b0d41e67f5..e790df9d75 100644 --- a/.Lib9c.Tests/Action/CombinationEquipment16Test.cs +++ b/.Lib9c.Tests/Action/CombinationEquipmentTest.cs @@ -22,7 +22,7 @@ namespace Lib9c.Tests.Action using Xunit.Abstractions; using static Lib9c.SerializeKeys; - public class CombinationEquipment16Test + public class CombinationEquipmentTest { private readonly Address _agentAddress; private readonly Address _avatarAddress; @@ -33,7 +33,7 @@ public class CombinationEquipment16Test private readonly AgentState _agentState; private readonly AvatarState _avatarState; - public CombinationEquipment16Test(ITestOutputHelper outputHelper) + public CombinationEquipmentTest(ITestOutputHelper outputHelper) { Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() @@ -111,8 +111,6 @@ public CombinationEquipment16Test(ITestOutputHelper outputHelper) // AvatarState not exist. [InlineData(typeof(FailedLoadStateException), true, true, true, false, false, 3, 0, true, 0L, 1, null, true, false, false, false)] [InlineData(typeof(FailedLoadStateException), true, true, true, false, true, 3, 0, true, 0L, 1, null, true, false, false, false)] - // Tutorial not cleared. - [InlineData(typeof(NotEnoughClearedStageLevelException), true, true, true, true, false, 1, 0, true, 0L, 1, null, true, false, false, false)] // CombinationSlotState not exist. [InlineData(typeof(FailedLoadStateException), true, true, true, true, false, 3, 5, true, 0L, 1, null, true, false, false, false)] // CombinationSlotState locked. @@ -210,8 +208,10 @@ bool previousCostStateExist // Lock slot. state = state.SetLegacyState( _slotAddress, - new CombinationSlotState(_slotAddress, stageId + 1).Serialize() - ); + new CombinationSlotState( + ((Dictionary)new CombinationSlotState(_slotAddress, 0).Serialize()) + .SetItem("unlockBlockIndex", (blockIndex + 1).Serialize())) + .Serialize()); } } } @@ -262,7 +262,7 @@ bool previousCostStateExist Assert.Null(state.GetLegacyState(dailyCostAddress)); Assert.Null(state.GetLegacyState(weeklyCostAddress)); - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddress, slotIndex = slotIndex, @@ -417,7 +417,7 @@ public void ExecuteWithCheckingHammerPointState( } } - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddress, slotIndex = 0, @@ -477,7 +477,7 @@ public void AddAndUnlockOption() Guid.NewGuid(), default); Assert.Equal(0, equipment.optionCountFromCombination); - CombinationEquipment16.AddAndUnlockOption( + CombinationEquipment.AddAndUnlockOption( _agentState, null, equipment, diff --git a/.Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs b/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs similarity index 97% rename from .Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs rename to .Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs index 636d29a4a9..dcf9a59df5 100644 --- a/.Lib9c.Tests/Action/EventConsumableItemCrafts0Test.cs +++ b/.Lib9c.Tests/Action/EventConsumableItemCraftsTest.cs @@ -14,7 +14,7 @@ namespace Lib9c.Tests.Action using Nekoyume.TableData.Event; using Xunit; - public class EventConsumableItemCrafts0Test + public class EventConsumableItemCraftsTest { private readonly IWorld _initialStates; private readonly TableSheets _tableSheets; @@ -22,7 +22,7 @@ public class EventConsumableItemCrafts0Test private readonly Address _agentAddress; private readonly Address _avatarAddress; - public EventConsumableItemCrafts0Test() + public EventConsumableItemCraftsTest() { _initialStates = new World(new MockWorldState()); var sheets = TableSheetsImporter.ImportSheets(); @@ -132,7 +132,7 @@ private void Execute( .Count(e => e.Id == recipeRow.ResultConsumableItemId); var previousMailCount = previousAvatarState.mailBox.Count; - var action = new EventConsumableItemCrafts0 + var action = new EventConsumableItemCrafts { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs b/.Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs similarity index 98% rename from .Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs rename to .Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs index f06296b5d2..e29c0b5779 100644 --- a/.Lib9c.Tests/Action/EventMaterialItemCrafts0Test.cs +++ b/.Lib9c.Tests/Action/EventMaterialItemCraftsTest.cs @@ -17,7 +17,7 @@ namespace Lib9c.Tests.Action using Xunit; using static SerializeKeys; - public class EventMaterialItemCrafts0Test + public class EventMaterialItemCraftsTest { private readonly IWorld _initialStates; private readonly TableSheets _tableSheets; @@ -25,7 +25,7 @@ public class EventMaterialItemCrafts0Test private readonly Address _agentAddress; private readonly Address _avatarAddress; - public EventMaterialItemCrafts0Test() + public EventMaterialItemCraftsTest() { _initialStates = new World(new MockWorldState()); var sheets = TableSheetsImporter.ImportSheets(); @@ -226,7 +226,7 @@ private void Execute( .Sum(i => i.item.Id == recipeRow.ResultMaterialItemId ? i.count : 0); var previousMailCount = previousAvatarState.mailBox.Count; - var action = new EventMaterialItemCrafts0 + var action = new EventMaterialItemCrafts { AvatarAddress = _avatarAddress, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/HackAndSlashTest.cs b/.Lib9c.Tests/Action/HackAndSlashTest.cs new file mode 100644 index 0000000000..b99f062702 --- /dev/null +++ b/.Lib9c.Tests/Action/HackAndSlashTest.cs @@ -0,0 +1,1688 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.Serialization.Formatters.Binary; + using Bencodex.Types; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Battle; + using Nekoyume.Blockchain.Policy; + using Nekoyume.Extensions; + using Nekoyume.Model; + using Nekoyume.Model.Item; + using Nekoyume.Model.Mail; + using Nekoyume.Model.Quest; + using Nekoyume.Model.Rune; + using Nekoyume.Model.Skill; + using Nekoyume.Model.State; + using Nekoyume.Module; + using Nekoyume.TableData; + using Xunit; + using static Lib9c.SerializeKeys; + + public class HackAndSlashTest + { + private readonly Dictionary _sheets; + private readonly TableSheets _tableSheets; + + private readonly Address _agentAddress; + + private readonly Address _avatarAddress; + private readonly AvatarState _avatarState; + + private readonly Address _inventoryAddress; + private readonly Address _worldInformationAddress; + private readonly Address _questListAddress; + + private readonly Address _rankingMapAddress; + + private readonly WeeklyArenaState _weeklyArenaState; + private readonly IWorld _initialState; + + public HackAndSlashTest() + { + _sheets = TableSheetsImporter.ImportSheets(); + _tableSheets = new TableSheets(_sheets); + + var privateKey = new PrivateKey(); + _agentAddress = privateKey.PublicKey.Address; + var agentState = new AgentState(_agentAddress); + + _avatarAddress = _agentAddress.Derive("avatar"); + var gameConfigState = new GameConfigState(_sheets[nameof(GameConfigSheet)]); + _rankingMapAddress = _avatarAddress.Derive("ranking_map"); + _avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + _tableSheets.GetAvatarSheets(), + gameConfigState, + _rankingMapAddress + ) + { + level = 100, + }; + _inventoryAddress = _avatarAddress.Derive(LegacyInventoryKey); + _worldInformationAddress = _avatarAddress.Derive(LegacyWorldInformationKey); + _questListAddress = _avatarAddress.Derive(LegacyQuestListKey); + agentState.avatarAddresses.Add(0, _avatarAddress); + _weeklyArenaState = new WeeklyArenaState(0); +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + var currency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + var goldCurrencyState = new GoldCurrencyState(currency); + _initialState = new World(new MockWorldState()); + _initialState = _initialState + .SetLegacyState(Addresses.GoldCurrency, goldCurrencyState.Serialize()) + .SetLegacyState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) + .SetAgentState(_agentAddress, agentState) + .SetAvatarState(_avatarAddress, _avatarState) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); + + foreach (var (key, value) in _sheets) + { + _initialState = _initialState + .SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); + } + + foreach (var address in _avatarState.combinationSlotAddresses) + { + var slotState = new CombinationSlotState( + address, + GameConfig.RequireClearedStageLevel.CombinationEquipmentAction); + _initialState = _initialState.SetLegacyState(address, slotState.Serialize()); + } + } + + [Theory] + [InlineData(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, 1, 2, false, true)] + [InlineData(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, 1, 2, true, true)] + [InlineData(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, 1, 1, false, true)] + [InlineData(200, 1, GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, false, true)] + [InlineData(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, 1, 1, false, false)] + [InlineData(GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot, 1, 1, true, false)] + [InlineData(200, 1, GameConfig.RequireClearedStageLevel.ActionsInRankingBoard, false, false)] + public void Execute(int avatarLevel, int worldId, int stageId, bool isWeaponLock, bool isClearedBefore) + { + Assert.True(_tableSheets.WorldSheet.TryGetValue(worldId, out var worldRow)); + Assert.True(stageId >= worldRow.StageBegin); + Assert.True(stageId <= worldRow.StageEnd); + Assert.True(_tableSheets.StageSheet.TryGetValue(stageId, out _)); + + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.level = avatarLevel; + var clearedStageId = _tableSheets.StageSheet.First?.Id ?? 0; + clearedStageId = isClearedBefore ? Math.Max(clearedStageId, stageId - 1) : stageId - 1; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var costumes = new List(); + IRandom random = new TestRandom(); + if (avatarLevel >= GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot) + { + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + previousAvatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + } + + var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + var iLock = equipment.ItemSubType == ItemSubType.Weapon && isWeaponLock + ? new OrderLock(Guid.NewGuid()) + : (ILock)null; + previousAvatarState.inventory.AddItem(equipment, iLock: iLock); + } + + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + IWorld state = _initialState + .SetAvatarState(_avatarAddress, previousAvatarState) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize())); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = ActionObsoleteConfig.V100301ExecutedBlockIndex, + }); + + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); + + Assert.True(nextAvatarState.worldInformation.IsStageCleared(stageId)); + Assert.Equal(30, nextAvatarState.mailBox.Count); + Assert.Equal(!isWeaponLock, nextAvatarState.inventory.Equipments.OfType().Any(w => w.equipped)); + } + + [Theory] + [InlineData(4, 200)] + public void Execute_With_UpdateQuestList(int worldId, int stageId) + { + var state = _initialState; + + // Remove stageId from WorldQuestSheet + var worldQuestSheet = state.GetSheet(); + var targetRow = worldQuestSheet.OrderedList.FirstOrDefault(e => e.Goal == stageId); + Assert.NotNull(targetRow); + // Update new AvatarState + var avatarState = new AvatarState( + _avatarAddress, + _agentAddress, + 0, + state.GetAvatarSheets(), + state.GetGameConfigState(), + _rankingMapAddress) + { + level = 400, + exp = state.GetSheet().OrderedList.First(e => e.Level == 400).Exp, + worldInformation = new WorldInformation(0, state.GetSheet(), stageId), + }; + var equipments = Doomfist.GetAllParts(_tableSheets, avatarState.level); + foreach (var equipment in equipments) + { + avatarState.inventory.AddItem(equipment); + } + + state = state + .SetAvatarState(avatarState.address, avatarState); + Assert.Equal(400, avatarState.level); + Assert.True(avatarState.worldInformation.IsWorldUnlocked(worldId)); + Assert.True(avatarState.worldInformation.IsStageCleared(stageId)); + + var avatarWorldQuests = avatarState.questList.OfType().ToList(); + Assert.Equal(worldQuestSheet.Count, avatarWorldQuests.Count); + Assert.Empty(avatarState.questList.completedQuestIds); + Assert.Equal(equipments.Count, avatarState.inventory.Items.Count); + + // HackAndSlash + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = avatarState.address, + }; + + avatarState = state.GetAvatarState(avatarState.address); + avatarWorldQuests = avatarState.questList.OfType().ToList(); + Assert.DoesNotContain(avatarWorldQuests, e => e.Complete); + + state = state.SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + // Second Execute + state = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + + avatarState = state.GetAvatarState(avatarState.address); + avatarWorldQuests = avatarState.questList.OfType().ToList(); + Assert.Equal(worldQuestSheet.Count, avatarWorldQuests.Count); + Assert.Single(avatarWorldQuests, e => e.Goal == stageId && e.Complete); + } + + [Fact] + public void MaxLevelTest() + { + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + var maxLevel = _tableSheets.CharacterLevelSheet.Max(row => row.Value.Level); + var expRow = _tableSheets.CharacterLevelSheet[maxLevel]; + var maxLevelExp = expRow.Exp; + var requiredExp = expRow.ExpNeed; + + previousAvatarState.level = maxLevel; + previousAvatarState.exp = maxLevelExp + requiredExp - 1; + + var stageId = 0; + try + { + stageId = _tableSheets.StageSheet + .FirstOrDefault(row => + previousAvatarState.level - row.Value.Id <= StageRewardExpHelper.DifferLowerLimit || + previousAvatarState.level - row.Value.Id > StageRewardExpHelper.DifferUpperLimit) + .Value.Id; + } + catch + { + // There is no stage that a avatar state which level is max can earning exp. + return; + } + + var worldRow = _tableSheets.WorldSheet + .FirstOrDefault(row => stageId >= row.Value.StageBegin && + stageId <= row.Value.StageEnd); + var worldId = worldRow.Value.Id; + + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + Math.Max(_tableSheets.StageSheet.First?.Id ?? 1, stageId)); + + var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); + Assert.Equal(maxLevelExp + requiredExp - 1, nextAvatarState.exp); + Assert.Equal(previousAvatarState.level, nextAvatarState.level); + } + + [Theory] + [InlineData(ItemSubType.Weapon, GameConfig.MaxEquipmentSlotCount.Weapon)] + [InlineData(ItemSubType.Armor, GameConfig.MaxEquipmentSlotCount.Armor)] + [InlineData(ItemSubType.Belt, GameConfig.MaxEquipmentSlotCount.Belt)] + [InlineData(ItemSubType.Necklace, GameConfig.MaxEquipmentSlotCount.Necklace)] + [InlineData(ItemSubType.Ring, GameConfig.MaxEquipmentSlotCount.Ring)] + public void MultipleEquipmentTest(ItemSubType type, int maxCount) + { + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + var maxLevel = _tableSheets.CharacterLevelSheet.Max(row => row.Value.Level); + var expRow = _tableSheets.CharacterLevelSheet[maxLevel]; + var maxLevelExp = expRow.Exp; + + previousAvatarState.level = maxLevel; + previousAvatarState.exp = maxLevelExp; + + var weaponRows = _tableSheets + .EquipmentItemSheet + .Values + .Where(r => r.ItemSubType == type) + .Take(maxCount + 1); + + var equipments = new List(); + foreach (var row in weaponRows) + { + var equipment = ItemFactory.CreateItem( + _tableSheets.EquipmentItemSheet[row.Id], + new TestRandom()) + as Equipment; + + equipments.Add(equipment.ItemId); + previousAvatarState.inventory.AddItem(equipment); + } + + var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = equipments, + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Execute_Throw_FailedLoadStateException(bool empty) + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + IWorld state = empty + ? new World(new MockWorldState()) + : _initialState + .SetAvatarState(_avatarAddress, _avatarState) + .SetAccount(Addresses.Inventory, new Account(new MockAccountState())); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(0)] + [InlineData(51)] + public void ExecuteThrowSheetRowColumnException(int stageId) + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = _initialState, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowSheetRowNotFoundExceptionByStage() + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var state = _initialState; + state = state.SetLegacyState(Addresses.TableSheet.Derive(nameof(StageSheet)), "test".Serialize()); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowFailedAddWorldException() + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var state = _initialState; + var worldSheet = new WorldSheet(); + worldSheet.Set("test"); + var avatarState = new AvatarState(_avatarState) + { + worldInformation = new WorldInformation(0, worldSheet, false), + }; + state = state.SetAvatarState(_avatarAddress, avatarState); + + Assert.False(avatarState.worldInformation.IsStageCleared(0)); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + // Try challenge Mimisbrunnr. + [InlineData(GameConfig.MimisbrunnrWorldId, GameConfig.MimisbrunnrStartStageId, false)] + // Unlock CRYSTAL first. + [InlineData(2, 51, false)] + [InlineData(2, 51, true)] + public void Execute_Throw_InvalidWorldException(int worldId, int stageId, bool unlockedIdsExist) + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + IWorld state = _initialState; + if (unlockedIdsExist) + { + state = state.SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + } + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowInvalidStageException() + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 3, + AvatarAddress = _avatarAddress, + }; + + var avatarState = new AvatarState(_avatarState); + avatarState.worldInformation.ClearStage( + 1, + 1, + 0, + _tableSheets.WorldSheet, + _tableSheets.WorldUnlockSheet + ); + + avatarState.worldInformation.TryGetWorld(1, out var world); + + Assert.True(world.IsStageCleared); + Assert.True(avatarState.worldInformation.IsWorldUnlocked(1)); + + var state = _initialState; + state = state.SetAvatarState(_avatarAddress, avatarState); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowInvalidStageExceptionUnlockedWorld() + { + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 2, + AvatarAddress = _avatarAddress, + }; + + _avatarState.worldInformation.TryGetWorld(1, out var world); + Assert.False(world.IsStageCleared); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = _initialState, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(ItemSubType.Weapon)] + [InlineData(ItemSubType.Armor)] + [InlineData(ItemSubType.Belt)] + [InlineData(ItemSubType.Necklace)] + [InlineData(ItemSubType.Ring)] + public void ExecuteThrowInvalidEquipmentException(ItemSubType itemSubType) + { + var avatarState = new AvatarState(_avatarState); + var equipRow = _tableSheets.EquipmentItemSheet.Values.First(r => r.ItemSubType == itemSubType); + var equipment = ItemFactory.CreateItemUsable(equipRow, Guid.NewGuid(), 100); + avatarState.inventory.AddItem(equipment); + + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List + { + equipment.ItemId, + }, + RuneInfos = new List(), + Foods = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var state = _initialState.SetAvatarState(_avatarAddress, avatarState); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(ItemSubType.Weapon)] + [InlineData(ItemSubType.Armor)] + [InlineData(ItemSubType.Belt)] + [InlineData(ItemSubType.Necklace)] + [InlineData(ItemSubType.Ring)] + public void ExecuteThrowEquipmentSlotUnlockException(ItemSubType itemSubType) + { + var state = _initialState; + var avatarState = new AvatarState(_avatarState) + { + level = 0, + }; + state = state.SetAvatarState(_avatarAddress, avatarState); + + var equipRow = _tableSheets.EquipmentItemSheet.Values.First(r => r.ItemSubType == itemSubType); + var equipment = ItemFactory.CreateItemUsable(equipRow, Guid.NewGuid(), 0); + avatarState.inventory.AddItem(equipment); + state = state.SetAvatarState(_avatarAddress, avatarState); + + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List + { + equipment.ItemId, + }, + RuneInfos = new List(), + Foods = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(0)] + [InlineData(5, 2)] + [InlineData(120, 25)] + public void ExecuteThrowNotEnoughActionPointException(int ap, int playCount = 1) + { + var avatarState = new AvatarState(_avatarState) + { + actionPoint = ap, + }; + + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + TotalPlayCount = playCount, + }; + + var state = _initialState; + state = state.SetAvatarState(_avatarAddress, avatarState); + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteWithoutPlayCount() + { + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.level = 1; + var clearedStageId = 0; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var costumes = new List(); + var equipments = new List(); + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + IWorld state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments, + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarAddress, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = 1, + }); + + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); + Assert.True(nextAvatarState.worldInformation.IsStageCleared(1)); + } + + [Theory] + [InlineData(15)] + [InlineData(30)] + [InlineData(50)] + [InlineData(75)] + [InlineData(100)] + [InlineData(120)] + [InlineData(150)] + [InlineData(200)] + public void Execute_Throw_NotEnoughAvatarLevelException(int avatarLevel) + { + var avatarState = new AvatarState(_avatarState) + { + actionPoint = 99999999, + level = avatarLevel, + }; + + var state = _initialState; + var itemIds = new[] { GameConfig.DefaultAvatarWeaponId, 40100000 }; + foreach (var itemId in itemIds) + { + foreach (var requirementRow in _tableSheets.ItemRequirementSheet.OrderedList + .Where(e => e.ItemId >= itemId && e.Level > avatarState.level) + .Take(3)) + { + var costumes = new List(); + var equipments = new List(); + var random = new TestRandom(DateTimeOffset.Now.Millisecond); + if (_tableSheets.EquipmentItemSheet.TryGetValue(requirementRow.ItemId, out var row)) + { + var equipment = ItemFactory.CreateItem(row, random); + avatarState.inventory.AddItem(equipment); + equipments.Add(((INonFungibleItem)equipment).NonFungibleId); + } + else if (_tableSheets.CostumeItemSheet.TryGetValue(requirementRow.ItemId, out var row2)) + { + var costume = ItemFactory.CreateItem(row2, random); + avatarState.inventory.AddItem(costume); + costumes.Add(((INonFungibleItem)costume).NonFungibleId); + } + + state = state.SetAvatarState(avatarState.address, avatarState); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments, + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = avatarState.address, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = avatarState.agentAddress, + RandomSeed = random.Seed, + })); + + SerializeException(exec); + } + } + } + + [Fact] + public void ExecuteThrowInvalidItemCountException() + { + var avatarState = new AvatarState(_avatarState) + { + actionPoint = 99999999, + level = 1, + }; + + var state = _initialState; + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = avatarState.address, + TotalPlayCount = 24, + ApStoneCount = -1, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = avatarState.agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(-1, 1)] + public void ExecuteThrowPlayCountIsZeroException(int totalPlayCount, int apStoneCount) + { + var avatarState = new AvatarState(_avatarState) + { + actionPoint = 99999999, + level = 1, + }; + + var state = _initialState; + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = avatarState.address, + TotalPlayCount = totalPlayCount, + ApStoneCount = apStoneCount, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = avatarState.agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowUsageLimitExceedException() + { + var state = _initialState; + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarState.address, + TotalPlayCount = 1, + ApStoneCount = 11, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _avatarState.agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteThrowNotEnoughMaterialException() + { + var state = _initialState; + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = _avatarState.address, + TotalPlayCount = 1, + ApStoneCount = 1, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _avatarState.agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Theory] + [InlineData(1, 15)] + [InlineData(2, 55)] + [InlineData(3, 111)] + [InlineData(4, 189)] + public void CheckRewardItems(int worldId, int stageId) + { + Assert.True(_tableSheets.WorldSheet.TryGetValue(worldId, out var worldRow)); + Assert.True(stageId >= worldRow.StageBegin); + Assert.True(stageId <= worldRow.StageEnd); + Assert.True(_tableSheets.StageSheet.TryGetValue(stageId, out var stageRow)); + + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.actionPoint = 999999; + previousAvatarState.level = 400; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + stageId); + + var costumes = new List(); + var random = new TestRandom(); + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + previousAvatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + + var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + previousAvatarState.inventory.AddItem(equipment); + } + + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + IWorld state = _initialState + .SetAvatarState(_avatarAddress, previousAvatarState) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + Enumerable.Range(1, worldId).ToList().Select(i => i.Serialize()).Serialize()); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = 1, + }); + + var nextAvatarState = nextState.GetAvatarState(_avatarAddress); + Assert.True(nextAvatarState.worldInformation.IsStageCleared(stageId)); + Assert.Equal(30, nextAvatarState.mailBox.Count); + + var rewardItem = nextAvatarState.inventory.Items.Where( + x => x.item.ItemSubType != ItemSubType.FoodMaterial && + x.item is IFungibleItem ownedFungibleItem && + x.item.Id != 400000 && x.item.Id != 500000); + + var worldQuestSheet = state.GetSheet(); + var questRow = worldQuestSheet.OrderedList.FirstOrDefault(e => e.Goal == stageId); + var questRewardSheet = state.GetSheet(); + var rewardIds = questRewardSheet.First(x => x.Key == questRow.QuestRewardId).Value + .RewardIds; + var questItemRewardSheet = state.GetSheet(); + var materialItemSheet = state.GetSheet(); + var sortedMaterialItemSheet = materialItemSheet + .Where(x => + x.Value.ItemSubType == ItemSubType.EquipmentMaterial || + x.Value.ItemSubType == ItemSubType.MonsterPart).ToList(); + + var selectedIdn = new Dictionary(); + foreach (var row in questItemRewardSheet) + { + if (sortedMaterialItemSheet.Exists(x => x.Key.Equals(row.ItemId))) + { + selectedIdn.Add(row.Key, row.Count); + } + } + + var questSum = rewardIds.Where(rewardId => selectedIdn.ContainsKey(rewardId)) + .Sum(rewardId => selectedIdn[rewardId]); + var min = stageRow.Rewards.OrderBy(x => x.Min).First().Min; + var max = stageRow.Rewards.OrderBy(x => x.Max).First().Max; + var totalMin = min * stageRow.DropItemMin + questSum; + var totalMax = max * stageRow.DropItemMax + questSum; + var totalCount = rewardItem.Sum(x => x.count); + Assert.InRange(totalCount, totalMin, totalMax); + } + + [Theory] + [InlineData(false, false, false, false)] + [InlineData(false, true, true, false)] + [InlineData(false, true, true, true)] + [InlineData(false, true, false, false)] + [InlineData(true, false, false, false)] + [InlineData(true, true, false, false)] + [InlineData(true, true, true, false)] + [InlineData(true, true, true, true)] + public void CheckCrystalRandomSkillState( + bool clear, + bool skillStateExist, + bool useCrystalSkill, + bool setSkillByArgument) + { + const int worldId = 1; + const int stageId = 10; + const int clearedStageId = 9; + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.actionPoint = 999999; + previousAvatarState.level = clear ? 400 : 1; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var costumes = new List(); + var random = new TestRandom(); + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + previousAvatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + + var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + previousAvatarState.inventory.AddItem(equipment); + } + + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + + state = state.SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + var skillStateAddress = Addresses.GetSkillStateAddressFromAvatarAddress(_avatarAddress); + CrystalRandomSkillState skillState = null; + if (skillStateExist) + { + skillState = new CrystalRandomSkillState(skillStateAddress, stageId); + if (useCrystalSkill) + { + skillState.Update(int.MaxValue, _tableSheets.CrystalStageBuffGachaSheet); + skillState.Update(_tableSheets.CrystalRandomBuffSheet + .Select(pair => pair.Value.Id).ToList()); + } + + state = state.SetLegacyState(skillStateAddress, skillState.Serialize()); + } + + int? stageBuffId = null; + if (useCrystalSkill) + { + stageBuffId = skillState?.GetHighestRankSkill(_tableSheets.CrystalRandomBuffSheet); + Assert.NotNull(stageBuffId); + } + + if (clear) + { + previousAvatarState.EquipItems(costumes.Concat(equipments.Select(e => e.ItemId))); + } + + var action = new HackAndSlash + { + Costumes = clear ? costumes : new List(), + Equipments = clear + ? equipments.Select(e => e.NonFungibleId).ToList() + : new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + StageBuffId = setSkillByArgument + ? stageBuffId + : null, + }; + + var ctx = new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = 1, + }; + var nextState = action.Execute(ctx); + var skillsOnWaveStart = new List(); + if (useCrystalSkill) + { + var skill = _tableSheets + .SkillSheet + .FirstOrDefault(pair => pair.Key == _tableSheets + .CrystalRandomBuffSheet[stageBuffId.Value].SkillId); + if (skill.Value != null) + { + skillsOnWaveStart.Add(SkillFactory.GetV1(skill.Value, default, 100)); + } + } + + var contextRandom = new TestRandom(ctx.RandomSeed); + var simulator = new StageSimulatorV3( + contextRandom, + previousAvatarState, + new List(), + null, + skillsOnWaveStart, + worldId, + stageId, + _tableSheets.StageSheet[stageId], + _tableSheets.StageWaveSheet[stageId], + false, + StageRewardExpHelper.GetExp(previousAvatarState.level, stageId), + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulatorV3.GetWaveRewards( + contextRandom, + _tableSheets.StageSheet[stageId], + _tableSheets.MaterialItemSheet)); + simulator.Simulate(); + var log = simulator.Log; + var skillStateIValue = + nextState.GetLegacyState(skillStateAddress); + var serialized = skillStateIValue as List; + Assert.NotNull(serialized); + var nextSkillState = new CrystalRandomSkillState(skillStateAddress, serialized); + Assert.Equal(skillStateAddress, nextSkillState.Address); + if (log.IsClear) + { + Assert.Equal(stageId + 1, nextSkillState.StageId); + Assert.Equal(0, nextSkillState.StarCount); + } + else + { + Assert.Equal(stageId, nextSkillState.StageId); + skillState?.Update(log.clearedWaveNumber, _tableSheets.CrystalStageBuffGachaSheet); + Assert.Equal(skillState?.StarCount ?? log.clearedWaveNumber, nextSkillState.StarCount); + } + + Assert.Empty(nextSkillState.SkillIds); + } + + [Theory] + [InlineData(1, 24)] + [InlineData(2, 24)] + [InlineData(3, 30)] + [InlineData(4, 30)] + [InlineData(5, 40)] + public void CheckUsedApByStaking(int level, int playCount) + { + const int worldId = 1; + const int stageId = 5; + const int clearedStageId = 4; + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.actionPoint = 120; + previousAvatarState.level = 400; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var stakeStateAddress = StakeState.DeriveAddress(_agentAddress); + var stakeState = new StakeState(stakeStateAddress, 1); + var requiredGold = _tableSheets.StakeRegularRewardSheet.OrderedRows + .FirstOrDefault(r => r.Level == level)?.RequiredGold ?? 0; + var context = new ActionContext(); + var state = _initialState + .SetAvatarState(_avatarAddress, previousAvatarState) + .SetLegacyState(stakeStateAddress, stakeState.SerializeV2()) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize())) + .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); + + var expectedAp = previousAvatarState.actionPoint - + _tableSheets.StakeActionPointCoefficientSheet.GetActionPointByStaking( + _tableSheets.StageSheet[stageId].CostAP, playCount, level); + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + StageBuffId = null, + TotalPlayCount = playCount, + }; + + var ctx = new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = 1, + }; + var nextState = action.Execute(ctx); + var nextAvatar = nextState.GetAvatarState(_avatarAddress); + Assert.Equal(expectedAp, nextAvatar.actionPoint); + } + + [Theory] + [InlineData(1, 1, 24, 0)] + [InlineData(1, 1, 25, 5)] + [InlineData(2, 1, 24, 0)] + [InlineData(2, 1, 25, 5)] + [InlineData(3, 1, 30, 0)] + [InlineData(3, 1, 31, 4)] + [InlineData(4, 1, 30, 0)] + [InlineData(4, 1, 31, 4)] + [InlineData(5, 1, 40, 0)] + [InlineData(5, 1, 41, 3)] + public void CheckUsingApStoneWithStaking(int level, int apStoneCount, int totalRepeatCount, int expectedUsingAp) + { + const int worldId = 1; + const int stageId = 5; + const int clearedStageId = 4; + const int itemId = 303100; + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.actionPoint = expectedUsingAp; + previousAvatarState.level = 400; + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + var apStoneRow = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(apStoneRow); + previousAvatarState.inventory.AddItem(apStone, apStoneCount); + var stakeStateAddress = StakeState.DeriveAddress(_agentAddress); + var stakeState = new StakeState(stakeStateAddress, 1); + var requiredGold = _tableSheets.StakeRegularRewardSheet.OrderedRows + .FirstOrDefault(r => r.Level == level)?.RequiredGold ?? 0; + var context = new ActionContext(); + var state = _initialState + .SetAvatarState(_avatarAddress, previousAvatarState) + .SetLegacyState(stakeStateAddress, stakeState.SerializeV2()) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize())) + .MintAsset(context, stakeStateAddress, requiredGold * _initialState.GetGoldCurrency()); + + var itemCount = previousAvatarState.inventory.Items + .FirstOrDefault(i => i.item.Id == itemId)?.count ?? 0; + var expectedItemCount = itemCount + totalRepeatCount; + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + StageBuffId = null, + TotalPlayCount = totalRepeatCount, + ApStoneCount = apStoneCount, + }; + + var ctx = new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = 1, + }; + var nextState = action.Execute(ctx); + var nextAvatar = nextState.GetAvatarState(_avatarAddress); + Assert.Equal(expectedItemCount, nextAvatar.inventory.Items.First(i => i.item.Id == itemId).count); + Assert.False(nextAvatar.inventory.HasItem(apStoneRow.Id)); + Assert.Equal(0, nextAvatar.actionPoint); + } + + [Fact] + public void ExecuteThrowInvalidRepeatPlayException() + { + var avatarState = new AvatarState(_avatarState) + { + actionPoint = 99999999, + level = 1, + }; + + var apStoneRow = _tableSheets.MaterialItemSheet.Values.First(r => + r.ItemSubType == ItemSubType.ApStone); + var apStone = ItemFactory.CreateTradableMaterial(apStoneRow); + avatarState.inventory.AddItem(apStone); + var state = _initialState.SetAvatarState(_avatarAddress, avatarState); + var action = new HackAndSlash + { + Costumes = new List(), + Equipments = new List(), + Foods = new List(), + RuneInfos = new List(), + WorldId = 1, + StageId = 1, + AvatarAddress = avatarState.address, + TotalPlayCount = 1, + ApStoneCount = 1, + }; + + var exec = Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = state, + Signer = avatarState.agentAddress, + RandomSeed = 0, + })); + + SerializeException(exec); + } + + [Fact] + public void ExecuteTwoRepetitions() + { + var avatarLevel = 50; + var worldId = 1; + var stageId = 20; + Assert.True(_tableSheets.WorldSheet.TryGetValue(worldId, out var worldRow)); + Assert.True(stageId >= worldRow.StageBegin); + Assert.True(stageId <= worldRow.StageEnd); + Assert.True(_tableSheets.StageSheet.TryGetValue(stageId, out _)); + + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.level = avatarLevel; + var clearedStageId = _tableSheets.StageSheet.First?.Id ?? 0; + clearedStageId = Math.Max(clearedStageId, stageId - 1); + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var costumes = new List(); + IRandom random = new TestRandom(); + if (avatarLevel >= GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot) + { + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + previousAvatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + } + + var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + previousAvatarState.inventory.AddItem(equipment); + } + + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + var state = _initialState + .SetAvatarState(_avatarAddress, previousAvatarState) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize())); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List() + { + new RuneSlotInfo(0, 30001), + new RuneSlotInfo(1, 10002), + }, + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + var nextState = action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = ActionObsoleteConfig.V100301ExecutedBlockIndex, + }); + + var action2 = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List() + { + new RuneSlotInfo(0, 10002), + new RuneSlotInfo(1, 30001), + }, + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + action2.Execute(new ActionContext + { + PreviousState = nextState, + Signer = _agentAddress, + RandomSeed = 0, + BlockIndex = ActionObsoleteConfig.V100301ExecutedBlockIndex, + }); + } + + [Theory] + [InlineData(0, 30001, 1, 30001, typeof(DuplicatedRuneIdException))] + [InlineData(1, 10002, 1, 30001, typeof(DuplicatedRuneSlotIndexException))] + public void ExecuteDuplicatedException(int slotIndex, int runeId, int slotIndex2, int runeId2, Type exception) + { + var avatarLevel = 50; + var worldId = 1; + var stageId = 20; + Assert.True(_tableSheets.WorldSheet.TryGetValue(worldId, out var worldRow)); + Assert.True(stageId >= worldRow.StageBegin); + Assert.True(stageId <= worldRow.StageEnd); + Assert.True(_tableSheets.StageSheet.TryGetValue(stageId, out _)); + + var previousAvatarState = _initialState.GetAvatarState(_avatarAddress); + previousAvatarState.level = avatarLevel; + var clearedStageId = _tableSheets.StageSheet.First?.Id ?? 0; + clearedStageId = Math.Max(clearedStageId, stageId - 1); + previousAvatarState.worldInformation = new WorldInformation( + 0, + _tableSheets.WorldSheet, + clearedStageId); + + var costumes = new List(); + IRandom random = new TestRandom(); + if (avatarLevel >= GameConfig.RequireCharacterLevel.CharacterFullCostumeSlot) + { + var costumeId = _tableSheets + .CostumeItemSheet + .Values + .First(r => r.ItemSubType == ItemSubType.FullCostume) + .Id; + + var costume = (Costume)ItemFactory.CreateItem( + _tableSheets.ItemSheet[costumeId], random); + previousAvatarState.inventory.AddItem(costume); + costumes.Add(costume.ItemId); + } + + var equipments = Doomfist.GetAllParts(_tableSheets, previousAvatarState.level); + foreach (var equipment in equipments) + { + previousAvatarState.inventory.AddItem(equipment); + } + + var mailEquipmentRow = _tableSheets.EquipmentItemSheet.Values.First(); + var mailEquipment = ItemFactory.CreateItemUsable(mailEquipmentRow, default, 0); + var result = new CombinationConsumable5.ResultModel + { + id = default, + gold = 0, + actionPoint = 0, + recipeId = 1, + materials = new Dictionary(), + itemUsable = mailEquipment, + }; + for (var i = 0; i < 100; i++) + { + var mail = new CombinationMail(result, i, default, 0); + previousAvatarState.Update(mail); + } + + var context = new ActionContext(); + var state = _initialState.SetAvatarState(_avatarAddress, previousAvatarState); + + state = state.SetLegacyState( + _avatarAddress.Derive("world_ids"), + List.Empty.Add(worldId.Serialize()) + ); + + var ncgCurrency = state.GetGoldCurrency(); + state = state.MintAsset(context, _agentAddress, 99999 * ncgCurrency); + + var unlockRuneSlot = new UnlockRuneSlot() + { + AvatarAddress = _avatarAddress, + SlotIndex = 1, + }; + + state = unlockRuneSlot.Execute(new ActionContext + { + BlockIndex = 1, + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + }); + + var action = new HackAndSlash + { + Costumes = costumes, + Equipments = equipments.Select(e => e.NonFungibleId).ToList(), + Foods = new List(), + RuneInfos = new List() + { + new RuneSlotInfo(slotIndex, runeId), + new RuneSlotInfo(slotIndex2, runeId2), + }, + WorldId = worldId, + StageId = stageId, + AvatarAddress = _avatarAddress, + }; + + Assert.Throws(exception, () => action.Execute(new ActionContext + { + PreviousState = state, + Signer = _agentAddress, + RandomSeed = 0, + })); + } + + private static void SerializeException(Exception exec) + where T : Exception + { + var formatter = new BinaryFormatter(); + using var ms = new MemoryStream(); + formatter.Serialize(ms, exec); + + ms.Seek(0, SeekOrigin.Begin); + var deserialized = (T)formatter.Deserialize(ms); + + Assert.Equal(exec.Message, deserialized.Message); + } + } +} diff --git a/.Lib9c.Tests/Action/MarketValidationTest.cs b/.Lib9c.Tests/Action/MarketValidationTest.cs index df1c003ce0..4889ee45cf 100644 --- a/.Lib9c.Tests/Action/MarketValidationTest.cs +++ b/.Lib9c.Tests/Action/MarketValidationTest.cs @@ -203,7 +203,7 @@ public void Validate_RegisterInfo(params RegisterInfosMember[] validateMembers) { foreach (var registerInfo in validateMember.RegisterInfos) { - var registerProduct = new RegisterProduct2 + var registerProduct = new RegisterProduct { AvatarAddress = AvatarAddress, RegisterInfos = new[] { registerInfo }, @@ -237,7 +237,7 @@ public void Validate_ProductInfo(params ProductInfosMember[] validateMembers) { foreach (var productInfo in validateMember.ProductInfos) { - var buyProduct = new BuyProduct2 + var buyProduct = new BuyProduct { AvatarAddress = AvatarAddress, ProductInfos = new[] { productInfo }, diff --git a/.Lib9c.Tests/Action/MigrateAgentAvatarTest.cs b/.Lib9c.Tests/Action/MigrateAgentAvatarTest.cs new file mode 100644 index 0000000000..befb0fe72d --- /dev/null +++ b/.Lib9c.Tests/Action/MigrateAgentAvatarTest.cs @@ -0,0 +1,203 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using Bencodex.Types; + using Libplanet.Action; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Action.Loader; + using Nekoyume.Model.State; + using Nekoyume.Module; + using Nekoyume.TableData; + using Xunit; + using static Lib9c.SerializeKeys; + + public class MigrateAgentAvatarTest + { + private readonly TableSheets _tableSheets; + + public MigrateAgentAvatarTest() + { + var sheets = TableSheetsImporter.ImportSheets(); + sheets[nameof(CharacterSheet)] = string.Join( + Environment.NewLine, + "id,_name,size_type,elemental_type,hp,atk,def,cri,hit,spd,lv_hp,lv_atk,lv_def,lv_cri,lv_hit,lv_spd,attack_range,run_speed", + "100010,전사,S,0,300,20,10,10,90,70,12,0.8,0.4,0,3.6,2.8,2,3"); + _tableSheets = new TableSheets(sheets); + } + + [Theory] + [InlineData(1, false)] + [InlineData(1, true)] + [InlineData(2, false)] + [InlineData(2, true)] + public void MigrateAgentAvatar(int legacyAvatarVersion, bool alreadyMigrated) + { + var avatarIndex = 1; + var agentAddress = new PrivateKey().Address; + var agentState = new AgentState(agentAddress); + var avatarAddress = agentAddress.Derive(string.Format(CultureInfo.InvariantCulture, CreateAvatar.DeriveFormat, avatarIndex)); + agentState.avatarAddresses.Add(avatarIndex, avatarAddress); + + var inventoryAddress = avatarAddress.Derive(LegacyInventoryKey); + var questListAddress = avatarAddress.Derive(LegacyQuestListKey); + var worldInformationAddress = avatarAddress.Derive(LegacyWorldInformationKey); + + var weekly = new WeeklyArenaState(0); + var gameConfigState = new GameConfigState(); + gameConfigState.Set(_tableSheets.GameConfigSheet); + var currency = Currency.Legacy("NCG", 2, null); + + var avatarState = new AvatarState( + avatarAddress, + agentAddress, + 456, + _tableSheets.GetAvatarSheets(), + gameConfigState, + default); + + MockWorldState mock = new MockWorldState() + .SetState( + ReservedAddresses.LegacyAccount, + GoldCurrencyState.Address, + new GoldCurrencyState(currency, 0).Serialize()) + .SetState( + ReservedAddresses.LegacyAccount, + weekly.address, + weekly.Serialize()) + .SetState( + ReservedAddresses.LegacyAccount, + Addresses.GoldDistribution, + new List()) + .SetState( + ReservedAddresses.LegacyAccount, + gameConfigState.address, + gameConfigState.Serialize()); + + switch (legacyAvatarVersion) + { + case 1: + mock = mock + .SetState( + ReservedAddresses.LegacyAccount, + agentAddress, + SerializeLegacyAgent(agentState)) + .SetState( + ReservedAddresses.LegacyAccount, + avatarAddress, + MigrationAvatarState.LegacySerializeV1(avatarState)); + break; + case 2: + mock = mock + .SetState( + ReservedAddresses.LegacyAccount, + agentAddress, + SerializeLegacyAgent(agentState)) + .SetState( + ReservedAddresses.LegacyAccount, + avatarAddress, + MigrationAvatarState.LegacySerializeV2(avatarState)) + .SetState( + ReservedAddresses.LegacyAccount, + inventoryAddress, + avatarState.inventory.Serialize()) + .SetState( + ReservedAddresses.LegacyAccount, + worldInformationAddress, + avatarState.questList.Serialize()) + .SetState( + ReservedAddresses.LegacyAccount, + questListAddress, + avatarState.questList.Serialize()); + break; + default: + throw new ArgumentException($"Invalid legacy avatar version: {legacyAvatarVersion}"); + } + + if (alreadyMigrated) + { + mock = mock + .SetState( + Addresses.Agent, + agentAddress, + agentState.SerializeList()) + .SetState( + Addresses.Avatar, + avatarAddress, + avatarState.SerializeList()) + .SetState( + Addresses.Inventory, + avatarAddress, + avatarState.inventory.Serialize()) + .SetState( + Addresses.WorldInformation, + avatarAddress, + avatarState.worldInformation.Serialize()) + .SetState( + Addresses.QuestList, + avatarAddress, + avatarState.questList.Serialize()); + } + + IAction action = new MigrateAgentAvatar + { + AgentAddresses = new List
{ agentAddress }, + }; + + var plainValue = action.PlainValue; + var actionLoader = new NCActionLoader(); + action = actionLoader.LoadAction(123, plainValue); + + var states = new World(mock); + IWorld nextState = action.Execute( + new ActionContext() + { + PreviousState = states, + Miner = default, + Signer = new Address("e2D18a50472e93d3165c478DefA69fa149214E72"), + } + ); + + Assert.Null(nextState.GetLegacyState(agentAddress)); + Assert.Null(nextState.GetLegacyState(avatarAddress)); + Assert.Null(nextState.GetLegacyState(inventoryAddress)); + Assert.Null(nextState.GetLegacyState(worldInformationAddress)); + Assert.Null(nextState.GetLegacyState(questListAddress)); + + Assert.NotNull(nextState.GetAccount(Addresses.Agent).GetState(agentAddress)); + Assert.NotNull(nextState.GetAccount(Addresses.Avatar).GetState(avatarAddress)); + Assert.NotNull(nextState.GetAccount(Addresses.Inventory).GetState(avatarAddress)); + Assert.NotNull(nextState.GetAccount(Addresses.WorldInformation).GetState(avatarAddress)); + Assert.NotNull(nextState.GetAccount(Addresses.QuestList).GetState(avatarAddress)); + } + + private static IValue SerializeLegacyAgent(AgentState agentState) + { + var innerDict = new Dictionary + { + [(Text)"avatarAddresses"] = new Dictionary( + agentState.avatarAddresses.Select(kv => + new KeyValuePair( + new Binary(BitConverter.GetBytes(kv.Key)), + kv.Value.Serialize() + ) + ) + ), + [(Text)"unlockedOptions"] = new List(), + [(Text)LegacyAddressKey] = agentState.address.Serialize(), + }; + if (agentState.MonsterCollectionRound > 0) + { + innerDict.Add((Text)MonsterCollectionRoundKey, agentState.MonsterCollectionRound.Serialize()); + } + + return new Dictionary(innerDict); + } + } +} diff --git a/.Lib9c.Tests/Action/RedeemCode0Test.cs b/.Lib9c.Tests/Action/RedeemCode0Test.cs deleted file mode 100644 index 3fe4794f51..0000000000 --- a/.Lib9c.Tests/Action/RedeemCode0Test.cs +++ /dev/null @@ -1,115 +0,0 @@ -namespace Lib9c.Tests.Action -{ - using System.Collections.Generic; - using System.Linq; - using Libplanet.Action.State; - using Libplanet.Common; - using Libplanet.Crypto; - using Libplanet.Types.Assets; - using Nekoyume; - using Nekoyume.Action; - using Nekoyume.Model.State; - using Nekoyume.Module; - using Nekoyume.TableData; - using Xunit; - using static Nekoyume.Model.State.RedeemCodeState; - - public class RedeemCode0Test - { - private readonly Address _agentAddress = new Address(new byte[] - { - 0x10, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, - }); - - private readonly Address _avatarAddress = new Address(new byte[] - { - 0x10, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, - }); - - private readonly Dictionary _sheets; - private readonly TableSheets _tableSheets; - - public RedeemCode0Test() - { - _sheets = TableSheetsImporter.ImportSheets(); - _tableSheets = new TableSheets(_sheets); - } - - [Fact] - public void Execute() - { - var privateKey = new PrivateKey(); - PublicKey publicKey = privateKey.PublicKey; - var prevRedeemCodesState = new RedeemCodeState(new Dictionary() - { - [publicKey] = new Reward(1), - }); - var gameConfigState = new GameConfigState(); - var agentState = new AgentState(_agentAddress); - agentState.avatarAddresses[0] = _avatarAddress; - var avatarState = new AvatarState( - _avatarAddress, - _agentAddress, - 1, - _tableSheets.GetAvatarSheets(), - gameConfigState, - default - ); - -#pragma warning disable CS0618 - // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 - var goldState = new GoldCurrencyState(Currency.Legacy("NCG", 2, null)); -#pragma warning restore CS0618 - - var context = new ActionContext(); - var initialState = new World(new MockWorldState()) - .SetAgentState(_agentAddress, agentState) - .SetAvatarState(_avatarAddress, avatarState) - .SetLegacyState(RedeemCodeState.Address, prevRedeemCodesState.Serialize()) - .SetLegacyState(GoldCurrencyState.Address, goldState.Serialize()) - .MintAsset(context, GoldCurrencyState.Address, goldState.Currency * 100000000); - - foreach (var (key, value) in _sheets) - { - initialState = initialState.SetLegacyState( - Addresses.TableSheet.Derive(key), - value.Serialize() - ); - } - - var redeemCode = new RedeemCode0( - ByteUtil.Hex(privateKey.ByteArray), - _avatarAddress - ); - - IWorld nextState = redeemCode.Execute(new ActionContext() - { - BlockIndex = 1, - Miner = default, - PreviousState = initialState, - Signer = _agentAddress, - RandomSeed = 0, - }); - - // Check target avatar & agent - AvatarState nextAvatarState = nextState.GetAvatarState(_avatarAddress); - // See also Data/TableCSV/RedeemRewardSheet.csv - ItemSheet itemSheet = initialState.GetItemSheet(); - HashSet expectedItems = new[] { 302006, 302004, 302001, 302002 }.ToHashSet(); - Assert.Subset(nextAvatarState.inventory.Items.Select(i => i.item.Id).ToHashSet(), expectedItems); - - // Check the code redeemed properly - RedeemCodeState nextRedeemCodeState = nextState.GetRedeemCodeState(); - Assert.Throws(() => - { - nextRedeemCodeState.Redeem(redeemCode.Code, redeemCode.AvatarAddress); - }); - } - } -} diff --git a/.Lib9c.Tests/Action/RegisterProduct2Test.cs b/.Lib9c.Tests/Action/RegisterProductTest.cs similarity index 97% rename from .Lib9c.Tests/Action/RegisterProduct2Test.cs rename to .Lib9c.Tests/Action/RegisterProductTest.cs index 10e679fced..d9c92299dd 100644 --- a/.Lib9c.Tests/Action/RegisterProduct2Test.cs +++ b/.Lib9c.Tests/Action/RegisterProductTest.cs @@ -20,7 +20,7 @@ namespace Lib9c.Tests.Action using Nekoyume.TableData; using Xunit; - public class RegisterProduct2Test + public class RegisterProductTest { private static readonly Address AvatarAddress = new Address("47d082a115c63e7b58b1532d20e631538eafadde"); @@ -33,7 +33,7 @@ public class RegisterProduct2Test private readonly GameConfigState _gameConfigState; private IWorld _initialState; - public RegisterProduct2Test() + public RegisterProductTest() { _agentAddress = new PrivateKey().Address; var agentState = new AgentState(_agentAddress); @@ -210,7 +210,7 @@ public void Execute() _initialState = _initialState .SetAvatarState(AvatarAddress, _avatarState) .MintAsset(context, AvatarAddress, asset); - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = AvatarAddress, RegisterInfos = new List @@ -250,7 +250,7 @@ public void Execute() var nextAvatarState = nextState.GetAvatarState(AvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(AvatarAddress, marketState.AvatarAddresses); @@ -289,7 +289,7 @@ public void Execute_Validate_RegisterInfos(params ValidateMember[] validateMembe { foreach (var registerInfo in validateMember.RegisterInfos) { - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = AvatarAddress, RegisterInfos = new[] { registerInfo }, @@ -349,7 +349,7 @@ public void Execute_Throw_ItemDoesNotExistException(ProductType type, int itemCo } _initialState = _initialState.SetAvatarState(AvatarAddress, _avatarState); - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = AvatarAddress, RegisterInfos = new List @@ -378,12 +378,12 @@ public void Execute_Throw_ItemDoesNotExistException(ProductType type, int itemCo public void Execute_Throw_ArgumentOutOfRangeException() { var registerInfos = new List(); - for (int i = 0; i < RegisterProduct2.Capacity + 1; i++) + for (int i = 0; i < RegisterProduct.Capacity + 1; i++) { registerInfos.Add(new RegisterInfo()); } - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = _avatarState.address, RegisterInfos = registerInfos, diff --git a/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs b/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs new file mode 100644 index 0000000000..c0176c9d3f --- /dev/null +++ b/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs @@ -0,0 +1,163 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using Bencodex.Types; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Model.State; + using Nekoyume.Module; + using Nekoyume.TableData; + using Xunit; + + public class RetrieveAvatarAssetsTest + { + private static readonly Address _minter = new Address( + new byte[] + { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + } + ); + +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + private static readonly Currency _currency = Currency.Legacy("NCG", 2, _minter); +#pragma warning restore CS0618 + + private static readonly Dictionary + _csv = TableSheetsImporter.ImportSheets(); + + private readonly Address _signer; + private readonly IWorld _state; + + public RetrieveAvatarAssetsTest() + { + var ca = new CreateAvatar + { + index = 0, + hair = 2, + lens = 3, + ear = 4, + tail = 5, + name = "JohnDoe", + }; + _signer = new PrivateKey().Address; + IWorld state = new World(new MockWorldState()); + foreach (var (key, value) in _csv) + { + state = state.SetLegacyState(Addresses.GetSheetAddress(key), (Text)value); + } + + state = state + .SetLegacyState( + Addresses.GameConfig, + new GameConfigState(_csv[nameof(GameConfigSheet)]).Serialize()) + .SetLegacyState(Addresses.GoldCurrency, new GoldCurrencyState(_currency).Serialize()); + + _state = ca.Execute(new ActionContext + { + PreviousState = state, + BlockIndex = 0, + Signer = _signer, + RandomSeed = 0, + }); + } + + [Fact] + public void PlainValue() + { + var avatarAddress = new PrivateKey().Address; + var action = new RetrieveAvatarAssets(avatarAddress); + var plainValue = (Dictionary)action.PlainValue; + var values = (Dictionary)plainValue["values"]; + Assert.Equal((Text)RetrieveAvatarAssets.TypeIdentifier, plainValue["type_id"]); + Assert.Equal(avatarAddress, values["a"].ToAddress()); + } + + [Fact] + public void LoadPlainValue() + { + var avatarAddress = new PrivateKey().Address; + var expectedValue = Dictionary.Empty + .Add("type_id", RetrieveAvatarAssets.TypeIdentifier) + .Add("values", Dictionary.Empty.Add("a", avatarAddress.Serialize())); + + // Let's assume that serializedAction is the serialized representation of the action, which might be obtained through some other part of your tests + var action = new RetrieveAvatarAssets(); + action.LoadPlainValue(expectedValue); + + var plainValue = (Dictionary)action.PlainValue; + var values = (Dictionary)plainValue["values"]; + + Assert.Equal((Text)RetrieveAvatarAssets.TypeIdentifier, plainValue["type_id"]); + Assert.Equal(avatarAddress, values["a"].ToAddress()); + } + + [Fact] + public void Execute() + { + var agentState = _state.GetAgentState(_signer); + Assert.NotNull(agentState); + var avatarAddress = agentState.avatarAddresses[0]; + var prevState = _state.MintAsset( + new ActionContext + { + Signer = _minter, + }, + avatarAddress, + 1 * _currency + ); + Assert.Equal(1 * _currency, prevState.GetBalance(avatarAddress, _currency)); + + var action = new RetrieveAvatarAssets(avatarAddress); + var nextState = action.Execute(new ActionContext + { + PreviousState = prevState, + BlockIndex = 1L, + Signer = _signer, + RandomSeed = 0, + }); + Assert.Equal(0 * _currency, nextState.GetBalance(avatarAddress, _currency)); + Assert.Equal(1 * _currency, nextState.GetBalance(_signer, _currency)); + } + + [Fact] + public void Execute_Throw_FailedLoadStateException() + { + var avatarAddress = new PrivateKey().Address; + var action = new RetrieveAvatarAssets(avatarAddress); + + var context = new ActionContext() + { + BlockIndex = 0, + PreviousState = new World(new MockWorldState()), + RandomSeed = 0, + Signer = avatarAddress, + }; + + Assert.Throws(() => action.Execute(context)); + } + + [Fact] + public void Execute_Throw_ArgumentOutOfRangeException() + { + var agentState = _state.GetAgentState(_signer); + Assert.NotNull(agentState); + var avatarAddress = agentState.avatarAddresses[0]; + Assert.Equal(0 * _currency, _state.GetBalance(avatarAddress, _currency)); + + var action = new RetrieveAvatarAssets(avatarAddress); + Assert.Throws(() => action.Execute(new ActionContext + { + PreviousState = _state, + BlockIndex = 1L, + Signer = _signer, + RandomSeed = 0, + })); + } + } +} diff --git a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs index fa023be771..49382047cb 100644 --- a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs @@ -257,7 +257,8 @@ public void Arena() enemyArenaPlayerDigest, _tableSheets.GetArenaSimulatorSheets(), new List(), - new List()); + new List(), + _tableSheets.DeBuffLimitSheet); // Check player, enemy equip aura foreach (var spawn in log.OfType()) { @@ -313,7 +314,7 @@ public void Market() var previousState = _initialState.SetAvatarState(_avatarAddress, avatarState); - var register = new RegisterProduct2 + var register = new RegisterProduct { AvatarAddress = _avatarAddress, RegisterInfos = new List diff --git a/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs b/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs index 0c0b6dc22c..d1bf55d5ae 100644 --- a/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs +++ b/.Lib9c.Tests/Action/Scenario/ItemCraftTest.cs @@ -125,7 +125,7 @@ public void CraftEquipmentTest(int randomSeed, int[] targetItemIdList) ); // Do Combination Action - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = i, @@ -296,7 +296,7 @@ int[] targetItemIdList var eventRow = _tableSheets.EventScheduleSheet[eventScheduleId]; // Do combination action var recipe = recipeList[i]; - var action = new EventConsumableItemCrafts0 + var action = new EventConsumableItemCrafts { AvatarAddress = _avatarAddr, EventScheduleId = eventScheduleId, @@ -394,7 +394,7 @@ int[] targetItemIdList } } - var action = new EventMaterialItemCrafts0 + var action = new EventMaterialItemCrafts { AvatarAddress = _avatarAddr, EventScheduleId = eventScheduleId, diff --git a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs index 9ded253595..c89b190833 100644 --- a/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/MarketScenarioTest.cs @@ -136,7 +136,7 @@ public void Register_And_Buy() var random = new TestRandom(); var productInfoList = new List(); - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -169,7 +169,7 @@ public void Register_And_Buy() var nextState = action.Execute(ctx); var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); var productsState = new ProductsState((List)nextState.GetLegacyState(ProductsState.DeriveAddress(_sellerAvatarAddress))); @@ -212,7 +212,7 @@ public void Register_And_Buy() } } - var action2 = new RegisterProduct2 + var action2 = new RegisterProduct { AvatarAddress = _sellerAvatarAddress2, RegisterInfos = new List @@ -244,7 +244,7 @@ public void Register_And_Buy() var nextState2 = action2.Execute(ctx); var nextAvatarState2 = nextState2.GetAvatarState(_sellerAvatarAddress2); Assert.Empty(nextAvatarState2.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState2.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState2.actionPoint); var productList2 = new ProductsState((List)nextState2.GetLegacyState(ProductsState.DeriveAddress(_sellerAvatarAddress2))); @@ -287,7 +287,7 @@ public void Register_And_Buy() } } - var action3 = new BuyProduct2 + var action3 = new BuyProduct { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfoList, @@ -363,7 +363,7 @@ public void Register_And_Cancel() _initialState = _initialState .SetAvatarState(_sellerAvatarAddress, _sellerAvatarState) .MintAsset(context, _sellerAvatarAddress, 1 * RuneHelper.StakeRune); - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -403,7 +403,7 @@ public void Register_And_Cancel() var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -498,7 +498,7 @@ public void Register_And_Cancel() ); } - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp - CancelProductRegistration0.CostAp, latestAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp - CancelProductRegistration0.CostAp, latestAvatarState.actionPoint); var sellProductList = new ProductsState((List)latestState.GetLegacyState(productsStateAddress)); Assert.Empty(sellProductList.ProductIds); @@ -535,7 +535,7 @@ public void Register_And_ReRegister() _initialState = _initialState .MintAsset(context, _sellerAvatarAddress, 2 * RuneHelper.StakeRune) .SetAvatarState(_sellerAvatarAddress, _sellerAvatarState); - var action = new RegisterProduct2 + var action = new RegisterProduct { AvatarAddress = _sellerAvatarAddress, RegisterInfos = new List @@ -575,7 +575,7 @@ public void Register_And_ReRegister() var nextAvatarState = nextState.GetAvatarState(_sellerAvatarAddress); Assert.Empty(nextAvatarState.inventory.Items); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp, nextAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp, nextAvatarState.actionPoint); var marketState = new MarketState(nextState.GetLegacyState(Addresses.Market)); Assert.Contains(_sellerAvatarAddress, marketState.AvatarAddresses); @@ -699,7 +699,7 @@ public void Register_And_ReRegister() var latestState = action2.Execute(ctx); var latestAvatarState = latestState.GetAvatarState(_sellerAvatarAddress); - Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct2.CostAp - ReRegisterProduct.CostAp, latestAvatarState.actionPoint); + Assert.Equal(_gameConfigState.ActionPointMax - RegisterProduct.CostAp - ReRegisterProduct.CostAp, latestAvatarState.actionPoint); var inventoryItem = Assert.Single(latestAvatarState.inventory.Items); Assert.Equal(1, inventoryItem.count); Assert.IsType(inventoryItem.item); @@ -1062,7 +1062,7 @@ public void HardFork() })); //Buy - var buyAction = new BuyProduct2 + var buyAction = new BuyProduct { AvatarAddress = _buyerAvatarAddress, ProductInfos = productInfos, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs index 08d476347e..f095cf2cf3 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/AdditionalOptionRateByFixedValueTest.cs @@ -236,7 +236,7 @@ int petLevel ); // Do combination without pet - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 0, @@ -276,7 +276,7 @@ int petLevel random ); - var petAction = new CombinationEquipment16 + var petAction = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 1, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs index 2b4b82e48e..a326c01f1e 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/CommonTest.cs @@ -85,7 +85,7 @@ public void PetCannotBeUsedToTwoSlotsAtTheSameTime() ); // Combination1 - var action1 = new CombinationEquipment16 + var action1 = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 0, @@ -102,7 +102,7 @@ public void PetCannotBeUsedToTwoSlotsAtTheSameTime() }); // Combination2: Raises error - var action2 = new CombinationEquipment16 + var action2 = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 1, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs index 06a9c8cd8d..453881df1e 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/DiscountMaterialCostCrystalTest.cs @@ -111,7 +111,7 @@ public void CraftEquipmentTest( ); // Do combination - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs index 4257aa798e..9329982c9a 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/IncreaseBlockPerHourglassTest.cs @@ -146,7 +146,7 @@ public void RapidCombinationTest_Equipment( ); // Do combination - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs b/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs index b2478b400d..95c10b9b08 100644 --- a/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs +++ b/.Lib9c.Tests/Action/Scenario/Pet/ReduceRequiredBlockTest.cs @@ -101,7 +101,7 @@ public void CombinationEquipmentTest( ); // Do Combination - var action = new CombinationEquipment16 + var action = new CombinationEquipment { avatarAddress = _avatarAddr, slotIndex = 0, diff --git a/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs b/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs index 5ef3fcdb83..c158ccce4c 100644 --- a/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/WorldUnlockScenarioTest.cs @@ -3,9 +3,9 @@ namespace Lib9c.Tests.Action.Scenario using System; using System.Collections.Generic; using System.Linq; - using System.Text; using Libplanet.Action.State; using Libplanet.Crypto; + using Libplanet.Types.Assets; using Nekoyume; using Nekoyume.Action; using Nekoyume.Model; @@ -35,12 +35,13 @@ public WorldUnlockScenarioTest() _avatarAddress = _agentAddress.Derive("avatar"); _rankingMapAddress = _avatarAddress.Derive("ranking_map"); + var gameConfigState = new GameConfigState(sheets[nameof(GameConfigSheet)]); var avatarState = new AvatarState( _avatarAddress, _agentAddress, 0, _tableSheets.GetAvatarSheets(), - new GameConfigState(sheets[nameof(GameConfigSheet)]), + gameConfigState, _rankingMapAddress ) { @@ -50,11 +51,18 @@ public WorldUnlockScenarioTest() _weeklyArenaState = new WeeklyArenaState(0); +#pragma warning disable CS0618 + // Use of obsolete method Currency.Legacy(): https://github.com/planetarium/lib9c/discussions/1319 + var currency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + var goldCurrencyState = new GoldCurrencyState(currency); _initialState = new World(new MockWorldState()) + .SetLegacyState(Addresses.GoldCurrency, goldCurrencyState.Serialize()) .SetLegacyState(_weeklyArenaState.address, _weeklyArenaState.Serialize()) .SetAgentState(_agentAddress, agentState) .SetAvatarState(_avatarAddress, avatarState) - .SetLegacyState(_rankingMapAddress, new RankingMapState(_rankingMapAddress).Serialize()); + .SetLegacyState(_rankingMapAddress, new RankingMapState(_rankingMapAddress).Serialize()) + .SetLegacyState(gameConfigState.address, gameConfigState.Serialize()); foreach (var (key, value) in sheets) { @@ -90,17 +98,20 @@ public void UnlockWorldByHackAndSlashAfterPatchTableWithAddRow( var doomfist = Doomfist.GetOne(_tableSheets, avatarState.level, ItemSubType.Weapon); avatarState.inventory.AddItem(doomfist); - var nextState = _initialState.SetAvatarState(_avatarAddress, avatarState); - var hackAndSlash = new HackAndSlash3 + var nextState = _initialState + .SetAvatarState(_avatarAddress, avatarState) + .SetLegacyState( + _avatarAddress.Derive("world_ids"), + new Bencodex.Types.List(Enumerable.Range(1, worldIdToClear).Select(i => i.Serialize()))); + var hackAndSlash = new HackAndSlash { - worldId = worldIdToClear, - stageId = stageIdToClear, - avatarAddress = _avatarAddress, - costumes = new List(), - equipments = new List { doomfist.NonFungibleId }, - foods = new List(), - WeeklyArenaAddress = _weeklyArenaState.address, - RankingMapAddress = _rankingMapAddress, + WorldId = worldIdToClear, + StageId = stageIdToClear, + AvatarAddress = _avatarAddress, + Costumes = new List(), + Equipments = new List { doomfist.NonFungibleId }, + Foods = new List(), + RuneInfos = new List(), }; nextState = hackAndSlash.Execute(new ActionContext { @@ -108,7 +119,6 @@ public void UnlockWorldByHackAndSlashAfterPatchTableWithAddRow( Signer = _agentAddress, RandomSeed = 0, }); - Assert.True(hackAndSlash.Result.IsClear); avatarState = nextState.GetAvatarState(_avatarAddress); Assert.True(avatarState.worldInformation.IsStageCleared(stageIdToClear)); @@ -149,7 +159,6 @@ public void UnlockWorldByHackAndSlashAfterPatchTableWithAddRow( Signer = _agentAddress, RandomSeed = 0, }); - Assert.True(hackAndSlash.Result.IsClear); avatarState = nextState.GetAvatarState(_avatarAddress); Assert.True(avatarState.worldInformation.IsWorldUnlocked(worldIdToUnlock)); diff --git a/.Lib9c.Tests/Action/UnlockEquipmentRecipeTest.cs b/.Lib9c.Tests/Action/UnlockEquipmentRecipeTest.cs index f87805897d..d45563ae92 100644 --- a/.Lib9c.Tests/Action/UnlockEquipmentRecipeTest.cs +++ b/.Lib9c.Tests/Action/UnlockEquipmentRecipeTest.cs @@ -60,6 +60,10 @@ public UnlockEquipmentRecipeTest() } [Theory] + // Recipe 5 unlocks at stage 99 + // Recipe 6 unlocks at stage 27 + // Recipe 94 unlocks at stage 90 + // Recipe 133 unlocks at stage 17 [InlineData(new[] { 6, 5 }, true, false, false, true, true, null)] [InlineData(new[] { 6 }, true, false, false, true, true, null)] // Unlock Belt without Armor unlock. diff --git a/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs b/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs index 4b7c9f0c7a..4a01d49654 100644 --- a/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs +++ b/.Lib9c.Tests/Extensions/EquipmentExtensionsTest.cs @@ -137,7 +137,7 @@ private Equipment CreateEquipment( if (!(subRecipeRow is null)) { - CombinationEquipment16.AddAndUnlockOption( + CombinationEquipment.AddAndUnlockOption( new AgentState(new PrivateKey().Address), null, equipment, diff --git a/.Lib9c.Tests/Model/ArenaSimulatorTest.cs b/.Lib9c.Tests/Model/ArenaSimulatorTest.cs index 5bb6bfd4ac..0261c3e87f 100644 --- a/.Lib9c.Tests/Model/ArenaSimulatorTest.cs +++ b/.Lib9c.Tests/Model/ArenaSimulatorTest.cs @@ -78,7 +78,8 @@ public void Simulate() { new (StatType.DEF, StatModifier.OperationType.Add, 1), new (StatType.HP, StatModifier.OperationType.Add, 100), - } + }, + _tableSheets.DeBuffLimitSheet ); CharacterSheet.Row row = _tableSheets.CharacterSheet[GameConfig.DefaultAvatarCharacterId]; @@ -133,7 +134,7 @@ public void HpIncreasingModifier(int? modifier) var myDigest = new ArenaPlayerDigest(_avatarState1, _arenaAvatarState1); var enemyDigest = new ArenaPlayerDigest(_avatarState2, _arenaAvatarState2); var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); - var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List()); + var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List(), _tableSheets.DeBuffLimitSheet); var expectedHpModifier = modifier ?? 2; Assert.Equal(_random, simulator.Random); @@ -178,7 +179,7 @@ public void TestSpeedModifierBySkill() var myDigest = new ArenaPlayerDigest(_avatarState1, arenaAvatarState1); var enemyDigest = new ArenaPlayerDigest(_avatarState2, arenaAvatarState2); var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); - var unskilledLog = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List()); + var unskilledLog = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List(), _tableSheets.DeBuffLimitSheet); // foreach (var log in unskilledLog) // { // _testOutputHelper.WriteLine($"{log.Character.Id} :: {log}"); @@ -231,7 +232,7 @@ public void TestSpeedModifierBySkill() myDigest = new ArenaPlayerDigest(_avatarState1, arenaAvatarState1); enemyDigest = new ArenaPlayerDigest(_avatarState2, arenaAvatarState2); arenaSheets = _tableSheets.GetArenaSimulatorSheets(); - var skilledLog = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List()); + var skilledLog = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List(), _tableSheets.DeBuffLimitSheet); // foreach (var log in skilledLog) // { // _testOutputHelper.WriteLine($"{log.Character.Id} :: {log}"); @@ -273,7 +274,7 @@ public void Thorns() var myDigest = new ArenaPlayerDigest(avatarState1, arenaAvatarState1); var enemyDigest = new ArenaPlayerDigest(avatarState2, arenaAvatarState2); var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); - var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List(), true); + var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, new List(), new List(), _tableSheets.DeBuffLimitSheet, true); var ticks = log.Events .OfType() .ToList(); @@ -335,7 +336,7 @@ public void Bleed() var enemyDigest = new ArenaPlayerDigest(avatarState2, arenaAvatarState2); enemyDigest.Runes.Add(rune); var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); - var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, modifiers, modifiers, true); + var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets, modifiers, modifiers, _tableSheets.DeBuffLimitSheet, true); var spawns = log.Events.OfType().ToList(); Assert.All(spawns, spawn => Assert.Equal(totalAtk, spawn.Character.ATK)); var ticks = log.Events diff --git a/.Lib9c.Tests/Model/ArenaSimulatorV1Test.cs b/.Lib9c.Tests/Model/ArenaSimulatorV1Test.cs deleted file mode 100644 index a51644ccf8..0000000000 --- a/.Lib9c.Tests/Model/ArenaSimulatorV1Test.cs +++ /dev/null @@ -1,138 +0,0 @@ -namespace Lib9c.Tests -{ - using System.Collections.Generic; - using System.Linq; - using Lib9c.Tests.Action; - using Libplanet.Action; - using Nekoyume.Arena; - using Nekoyume.Model; - using Nekoyume.Model.BattleStatus.Arena; - using Nekoyume.Model.State; - using Xunit; - - public class ArenaSimulatorV1Test - { - private readonly TableSheets _tableSheets; - private readonly IRandom _random; - private readonly AvatarState _avatarState1; - private readonly AvatarState _avatarState2; - - private readonly ArenaAvatarState _arenaAvatarState1; - private readonly ArenaAvatarState _arenaAvatarState2; - - public ArenaSimulatorV1Test() - { - _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); - _random = new TestRandom(); - - _avatarState1 = new AvatarState( - default, - default, - 0, - _tableSheets.GetAvatarSheets(), - new GameConfigState(), - default - ); - - _avatarState2 = new AvatarState( - default, - default, - 0, - _tableSheets.GetAvatarSheets(), - new GameConfigState(), - default - ); - - _arenaAvatarState1 = new ArenaAvatarState(_avatarState1); - _arenaAvatarState2 = new ArenaAvatarState(_avatarState2); - } - - [Fact] - public void Simulate() - { - var simulator = new ArenaSimulatorV1(_random); - var myDigest = new ArenaPlayerDigest(_avatarState1, _arenaAvatarState1); - var enemyDigest = new ArenaPlayerDigest(_avatarState2, _arenaAvatarState2); - var arenaSheets = _tableSheets.GetArenaSimulatorSheetsV1(); - var log = simulator.Simulate(myDigest, enemyDigest, arenaSheets); - - Assert.Equal(_random, simulator.Random); - - var turn = log.Events.OfType().Count(); - Assert.Equal(simulator.Turn, turn); - - var players = log.Events.OfType(); - var arenaCharacters = new List(); - foreach (var player in players) - { - if (player.Character is ArenaCharacter arenaCharacter) - { - arenaCharacters.Add(arenaCharacter); - } - } - - Assert.Equal(2, players.Count()); - Assert.Equal(2, arenaCharacters.Count); - Assert.Equal(1, arenaCharacters.Count(x => x.IsEnemy)); - Assert.Equal(1, arenaCharacters.Count(x => !x.IsEnemy)); - - var dead = log.Events.OfType(); - Assert.Single(dead); - var deadCharacter = dead.First().Character; - Assert.True(deadCharacter.IsDead); - Assert.Equal(0, deadCharacter.CurrentHP); - if (log.Result == ArenaLog.ArenaResult.Win) - { - Assert.True(deadCharacter.IsEnemy); - } - else - { - Assert.False(deadCharacter.IsEnemy); - } - } - - [Fact] - public void SimulateV1() - { - var simulator = new ArenaSimulatorV1(_random); - var myDigest = new ArenaPlayerDigest(_avatarState1, _arenaAvatarState1); - var enemyDigest = new ArenaPlayerDigest(_avatarState2, _arenaAvatarState2); - var arenaSheets = _tableSheets.GetArenaSimulatorSheetsV1(); - var log = simulator.SimulateV1(myDigest, enemyDigest, arenaSheets); - - Assert.Equal(_random, simulator.Random); - - var turn = log.Events.OfType().Count(); - Assert.Equal(simulator.Turn, turn); - - var players = log.Events.OfType(); - var arenaCharacters = new List(); - foreach (var player in players) - { - if (player.Character is ArenaCharacter arenaCharacter) - { - arenaCharacters.Add(arenaCharacter); - } - } - - Assert.Equal(2, players.Count()); - Assert.Equal(2, arenaCharacters.Count); - Assert.Equal(1, arenaCharacters.Count(x => x.IsEnemy)); - Assert.Equal(1, arenaCharacters.Count(x => !x.IsEnemy)); - - var dead = log.Events.OfType(); - Assert.Single(dead); - var deadCharacter = dead.First().Character; - Assert.True(deadCharacter.IsDead); - Assert.Equal(0, deadCharacter.CurrentHP); - if (log.Result == ArenaLog.ArenaResult.Win) - { - Assert.True(deadCharacter.IsEnemy); - } - else - { - Assert.False(deadCharacter.IsEnemy); - } - } - } -} diff --git a/.Lib9c.Tests/Model/BattleLogTest.cs b/.Lib9c.Tests/Model/BattleLogTest.cs index e5311fbff0..24aac3acd3 100644 --- a/.Lib9c.Tests/Model/BattleLogTest.cs +++ b/.Lib9c.Tests/Model/BattleLogTest.cs @@ -53,7 +53,8 @@ public void IsClearBeforeSimulate() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); Assert.False(simulator.Log.IsClear); } diff --git a/.Lib9c.Tests/Model/CharacterStatsTest.cs b/.Lib9c.Tests/Model/CharacterStatsTest.cs new file mode 100644 index 0000000000..b6f5c45e9e --- /dev/null +++ b/.Lib9c.Tests/Model/CharacterStatsTest.cs @@ -0,0 +1,42 @@ +namespace Lib9c.Tests.Model +{ + using Nekoyume; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Stat; + using Nekoyume.TableData; + using Xunit; + + public class CharacterStatsTest + { + private readonly TableSheets _tableSheets; + + public CharacterStatsTest() + { + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + } + + [Fact] + public void DeBuffLimit() + { + var stats = + new CharacterStats( + _tableSheets.CharacterSheet[GameConfig.DefaultAvatarCharacterId], + 1); + var deBuffLimitSheet = new DeBuffLimitSheet(); + // -100% def but limit -50% stats + var deBuff = new StatBuff(_tableSheets.StatBuffSheet[503012]); + var groupId = deBuff.RowData.GroupId; + deBuffLimitSheet.Set($"group_id,percentage\n{groupId},-50"); + var def = stats.DEF; + stats.AddBuff(deBuff, deBuffLimitSheet: deBuffLimitSheet); + var modifier = deBuffLimitSheet[groupId].GetModifier(deBuff.RowData.StatType); + Assert.Equal(modifier.GetModifiedAll(def), stats.DEF); + + // -500% critical with no limit + var deBuff2 = new StatBuff(_tableSheets.StatBuffSheet[204003]); + Assert.True(stats.CRI > 0); + stats.AddBuff(deBuff2, deBuffLimitSheet: deBuffLimitSheet); + Assert.Equal(0, stats.CRI); + } + } +} diff --git a/.Lib9c.Tests/Model/PlayerTest.cs b/.Lib9c.Tests/Model/PlayerTest.cs index e920816712..b88c6158e8 100644 --- a/.Lib9c.Tests/Model/PlayerTest.cs +++ b/.Lib9c.Tests/Model/PlayerTest.cs @@ -61,7 +61,8 @@ public void TickAlive() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; var enemy = new Enemy(player, _tableSheets.CharacterSheet.Values.First(), 1); @@ -95,7 +96,8 @@ public void TickDead() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; var enemy = new Enemy(player, _tableSheets.CharacterSheet.Values.First(), 1); @@ -146,7 +148,8 @@ public void UseDoubleAttack(SkillCategory skillCategory) _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; @@ -197,7 +200,8 @@ public void UseAuraSkill() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + new DeBuffLimitSheet() ); var player = simulator.Player; var enemy = new Enemy(player, _tableSheets.CharacterSheet.Values.First(), 1); @@ -252,7 +256,8 @@ public void UseAuraBuffWithFood() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + new DeBuffLimitSheet() ); var player = simulator.Player; var enemy = new Enemy(player, _tableSheets.CharacterSheet.Values.First(), 1); @@ -380,7 +385,8 @@ public void GetExpV3(int nextLevel, bool log) _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; Assert.Empty(player.eventMap); @@ -459,7 +465,8 @@ public void GetStun() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; var enemy = new Enemy(player, _tableSheets.CharacterSheet.Values.First(), 1); @@ -528,7 +535,8 @@ public void GiveStun() _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var skill = SkillFactory.Get(_tableSheets.SkillSheet[700004], 0, 100, 0, StatType.NONE); skill.CustomField = new SkillCustomField { BuffDuration = 2 }; @@ -611,7 +619,8 @@ public void Vampiric(int duration, int percent) _random, _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; var enemy = new Enemy( @@ -687,59 +696,45 @@ public void StatsLayerTest() _tableSheets.CostumeStatSheet.Values.First(r => r.StatType == StatType.ATK); var costumeId = costumeStatRow.CostumeId; var costume = ItemFactory.CreateCostume(_tableSheets.CostumeItemSheet[costumeId], Guid.NewGuid()); - costume.equipped = true; - _avatarState.inventory.AddItem(costume); + // costume.equipped = true; + // _avatarState.inventory.AddItem(costume); var foodRow = _tableSheets.ConsumableItemSheet.Values.First(r => r.Stats.Any(s => s.StatType == StatType.ATK)); var food = (Consumable)ItemFactory.CreateItem(foodRow, _random); _avatarState.inventory.AddItem(food); - - // Update equipment stats - var player = new Player( - _avatarState, - _tableSheets.CharacterSheet, - _tableSheets.CharacterLevelSheet, - _tableSheets.EquipmentItemSetEffectSheet - ); - Assert.Equal(player.ATK, player.Stats.BaseATK + player.Stats.EquipmentStats.ATK); - // BaseAtk 20, EquipmentStats 1 - // Assert.Equal(21, player.ATK); - var equipmentLayerAtk = player.ATK; - - // Update consumable stats - player.Use(new List - { - food.ItemId, - }); - Assert.Equal(player.ATK, equipmentLayerAtk + food.Stats.Where(s => s.StatType == StatType.ATK).Sum(s => s.BaseValueAsLong)); - // ConsumableStats 18 - // Assert.Equal(39, player.ATK); - var consumableLayerAtk = player.ATK; - - // Update rune stat var runeId = 10002; var runeState = new RuneState(runeId); runeState.LevelUp(); Assert.Equal(1, runeState.Level); - var runeStates = new List { runeState, }; - player.SetRuneStats(runeStates, _tableSheets.RuneOptionSheet); - var runeOptionRow = _tableSheets.RuneOptionSheet.Values.First(r => r.RuneId == runeId); - var runeAtk = runeOptionRow.LevelOptionMap[1].Stats.Sum(r => r.stat.BaseValueAsLong); - Assert.Equal(player.ATK, consumableLayerAtk + runeAtk); - // RuneStats 235 - // Assert.Equal(274, player.ATK); - var runeLayerAtk = player.ATK; - // Update costume stats - player.SetCostumeStat(_tableSheets.CostumeStatSheet); - Assert.Equal(player.ATK, runeLayerAtk + costumeStatRow.Stat); - // CostumeStats 1829 - // Assert.Equal(2103, player.ATK); + var simulator = new StageSimulator( + _random, + _avatarState, + new List() + { + food.ItemId, + }, + runeStates, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + new CostumeStatSheet(), + new List(), + new List(), + _tableSheets.DeBuffLimitSheet + ); + var player = simulator.Player; var costumeLayerAtk = player.ATK; // Update collection stat @@ -808,7 +803,7 @@ public void StatsLayerTest() var addBuffAtk = addBuffModifier.GetModifiedValue(costumeLayerAtk); Assert.Equal(10, addBuffAtk); - player.Stats.SetBuffs(statBuffs); + player.Stats.SetBuffs(statBuffs, _tableSheets.DeBuffLimitSheet); Assert.Equal(player.ATK, collectionLayerAtk + addBuffAtk + percentageBuffAtk); // 20 + 1 + 18 + 1829 + 235 + 100 + 1662 // Assert.Equal(3865, player.ATK); @@ -912,7 +907,7 @@ public void IncreaseHpForArena() statBuffs.Add(percentageBuff); var percentageModifier = percentageBuff.GetModifier(); var percentageBuffAtk = (long)percentageModifier.GetModifiedValue(arenaHp); - player.Stats.SetBuffs(statBuffs); + player.Stats.SetBuffs(statBuffs, _tableSheets.DeBuffLimitSheet); Assert.Equal(arenaHp + percentageBuffAtk, player.HP); Assert.Equal(arenaHp, player.Stats.StatWithoutBuffs.HP); } diff --git a/.Lib9c.Tests/Model/RaidSimulatorV3Test.cs b/.Lib9c.Tests/Model/RaidSimulatorV3Test.cs index 73e70edb92..3eed9b0c3c 100644 --- a/.Lib9c.Tests/Model/RaidSimulatorV3Test.cs +++ b/.Lib9c.Tests/Model/RaidSimulatorV3Test.cs @@ -55,7 +55,8 @@ public void Simulate() new List { new (StatType.DEF, StatModifier.OperationType.Percentage, 100), - }); + }, + _tableSheets.DeBuffLimitSheet); Assert.Equal(_random, simulator.Random); Assert.Equal(simulator.Player.Stats.BaseStats.DEF * 2, simulator.Player.Stats.DEF); Assert.Equal(simulator.Player.Stats.BaseStats.DEF, simulator.Player.Stats.CollectionStats.DEF); @@ -96,7 +97,8 @@ public void TestSpeedMultiplierBySkill() null, _tableSheets.GetRaidSimulatorSheets(), _tableSheets.CostumeStatSheet, - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; var unskilledLogs = simulator.Simulate(); @@ -136,7 +138,8 @@ public void TestSpeedMultiplierBySkill() null, _tableSheets.GetRaidSimulatorSheets(), _tableSheets.CostumeStatSheet, - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); player = simulator.Player; var skilledLogs = simulator.Simulate(); diff --git a/.Lib9c.Tests/Model/Skill/CombatTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/CombatTest.cs similarity index 50% rename from .Lib9c.Tests/Model/Skill/CombatTest.cs rename to .Lib9c.Tests/Model/Skill/Adventure/CombatTest.cs index 45ae780ce0..aa9554b53a 100644 --- a/.Lib9c.Tests/Model/Skill/CombatTest.cs +++ b/.Lib9c.Tests/Model/Skill/Adventure/CombatTest.cs @@ -1,4 +1,4 @@ -namespace Lib9c.Tests.Model.Skill +namespace Lib9c.Tests.Model.Skill.Adventure { using System.Collections.Generic; using System.Linq; @@ -16,6 +16,7 @@ namespace Lib9c.Tests.Model.Skill public class CombatTest { private readonly TableSheets _tableSheets; + private readonly AvatarState _avatarState; private readonly Player _player; private readonly Enemy _enemy; @@ -25,7 +26,7 @@ public CombatTest() _tableSheets = new TableSheets(csv); var gameConfigState = new GameConfigState(csv[nameof(GameConfigSheet)]); - var avatarState = new AvatarState( + _avatarState = new AvatarState( default, default, 0, @@ -40,7 +41,7 @@ public CombatTest() _tableSheets.EquipmentItemSetEffectSheet); var simulator = new TestSimulator( new TestRandom(), - avatarState, + _avatarState, new List(), _tableSheets.GetSimulatorSheets()); _player.Simulator = simulator; @@ -137,6 +138,173 @@ public void Bleed() Assert.Equal(currentHP, skillInfo.Target!.CurrentHP); } + [Theory] + [InlineData(700009, 50, 3, new[] { 600001 }, new[] { 600001, 707000 })] + [InlineData(700009, 100, 0, new[] { 600001 }, new[] { 707000 })] + [InlineData(700010, 100, 0, new[] { 600001, 704000 }, new[] { 707000 })] + public void DispelOnUse(int dispelId, int chance, int seed, int[] debuffIdList, int[] expectedResult) + { + var simulator = new TestSimulator( + new TestRandom(seed), + _avatarState, + new List(), + _tableSheets.GetSimulatorSheets()); + _player.Simulator = simulator; + var actionBuffSheet = _tableSheets.ActionBuffSheet; + + // Add Debuff + foreach (var debuffId in debuffIdList) + { + var debuff = + actionBuffSheet.Values.First(bf => bf.Id == debuffId); // 600001 is bleed + _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, debuff)); + } + + Assert.Equal(debuffIdList.Length, _player.Buffs.Count()); + + // Use Dispel + var actionBuffRow = new ActionBuffSheet.Row(); + actionBuffRow.Set(new[] { "707000", "707000", chance.ToString(), "0", "Self", "Dispel", "Normal", "0" }); + var dispelRow = _tableSheets.SkillSheet.Values.First(bf => bf.Id == dispelId); + var dispel = new BuffSkill(dispelRow, 0, chance, 0, StatType.NONE); + var battleStatus = dispel.Use( + _player, + 0, + new List() { new Dispel(actionBuffRow) }, + false); + Assert.NotNull(battleStatus); + // Remove Bleed, add Dispel + Assert.Equal(expectedResult.Length, _player.Buffs.Count); + Assert.Equal(expectedResult, _player.Buffs.Values.Select(bf => bf.BuffInfo.GroupId).ToArray()); + } + + [Fact] + public void DispelOnDuration_Block() + { + const int actionBuffId = 708000; // Dispel with duration + var actionBuffSheet = _tableSheets.ActionBuffSheet; + + // Use Dispel first + var dispel = actionBuffSheet.Values.First(bf => bf.Id == actionBuffId); + _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, dispel)); + Assert.Single(_player.Buffs); + + // Use Bleed + var debuffRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 600001); // 600001 is bleed + var debuff = new BuffSkill(debuffRow, 100, 100, 0, StatType.NONE); + var battleStatus = debuff.Use( + _enemy, + 0, + BuffFactory.GetBuffs( + _enemy.Stats, + debuff, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ), + false); + + // Bleed should be blocked + Assert.NotNull(battleStatus); + // Remove Bleed, add Dispel + Assert.Single(_player.Buffs); + Assert.False(battleStatus.SkillInfos.First().Affected); + } + + [Fact] + public void DispelOnDuration_Affect() + { + const int actionBuffId = 708000; // Dispel with duration + var actionBuffSheet = _tableSheets.ActionBuffSheet; + + // Use Dispel first + var dispel = actionBuffSheet.Values.First(bf => bf.Id == actionBuffId); + _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, dispel)); + Assert.Single(_player.Buffs); + + // Use Focus + var buffRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 700007); // 700007 is Focus + var buff = new BuffSkill(buffRow, 100, 100, 0, StatType.NONE); + var battleStatus = buff.Use( + _player, + 0, + BuffFactory.GetBuffs( + _player.Stats, + buff, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ), + false); + + // Bleed should be blocked + Assert.NotNull(battleStatus); + // Add Focus without block + Assert.Equal(2, _player.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + } + + [Fact] + public void DispelOnDuration_Nothing() + { + const int actionBuffId = 708000; // Dispel with duration + var actionBuffSheet = _tableSheets.ActionBuffSheet; + + // Use Dispel first + var dispel = actionBuffSheet.Values.First(bf => bf.Id == actionBuffId); + _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, dispel)); + Assert.Single(_player.Buffs); + + // Add Bleed + var bleed = actionBuffSheet.Values.First(bf => bf.Id == 600001); + _player.AddBuff(BuffFactory.GetActionBuff(_player.Stats, bleed)); + + // Attack + _enemy.Targets.Add(_player); + var skillRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 100000); + var attack = new NormalAttack(skillRow, 100, 100, default, StatType.NONE); + var battleStatus = attack.Use( + _enemy, + 0, + BuffFactory.GetBuffs( + _enemy.Stats, + attack, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ), + false); + + // keep debuff + Assert.Equal(2, _player.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + + // Attack + _player.Targets.Add(_enemy); + battleStatus = attack.Use( + _player, + 0, + BuffFactory.GetBuffs( + _player.Stats, + attack, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ), + false); + + // keep debuff + Assert.Equal(2, _player.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + } + [Theory] // Buff [InlineData(SkillType.Buff, true)] diff --git a/.Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs new file mode 100644 index 0000000000..4ccbbfadab --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs @@ -0,0 +1,126 @@ +namespace Lib9c.Tests.Model.Skill.Adventure +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Libplanet.Crypto; + using Nekoyume.Battle; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class DoubleAttackTest + { + private readonly TableSheets _tableSheets = new (TableSheetsImporter.ImportSheets()); + + [Theory] + // DoubleAttack: Does not consider attack count + [InlineData(100003, 1, 0, 0, true)] + [InlineData(100003, 1, 0, 0, false)] + [InlineData(100003, 1, 2, 2, true)] + [InlineData(100003, 1, 2, 2, false)] + + // TwinAttack : Increase attack count per each attack + // Attack count returns to 1 when exceeds max attack(combo) count. + // lvl. 1 ~ 10 : Max 2 combo + [InlineData(700008, 1, 0, 2, true)] + [InlineData(700008, 1, 0, 2, false)] + [InlineData(700008, 1, 1, 1, true)] + [InlineData(700008, 1, 1, 1, false)] + [InlineData(700008, 1, 2, 2, true)] + [InlineData(700008, 1, 2, 2, false)] + // lvl. 11 ~ 99 : max 3 combo + [InlineData(700008, 11, 1, 3, true)] + [InlineData(700008, 11, 1, 3, false)] + [InlineData(700008, 11, 2, 1, true)] + [InlineData(700008, 11, 2, 1, false)] + [InlineData(700008, 11, 3, 2, true)] + [InlineData(700008, 11, 3, 2, false)] + // lvl. 100 ~ 249 : max 4 combo + [InlineData(700008, 100, 2, 4, true)] + [InlineData(700008, 100, 2, 4, false)] + [InlineData(700008, 100, 3, 1, true)] + [InlineData(700008, 100, 3, 1, false)] + [InlineData(700008, 100, 4, 2, true)] + [InlineData(700008, 100, 4, 2, false)] + // lvl. 250 ~ : max 5 combo + [InlineData(700008, 250, 3, 5, true)] + [InlineData(700008, 250, 3, 5, false)] + [InlineData(700008, 250, 4, 1, true)] + [InlineData(700008, 250, 4, 1, false)] + [InlineData(700008, 250, 5, 2, true)] + [InlineData(700008, 250, 5, 2, false)] + public void DoubleAttackStage( + int skillId, + int level, + int initialAttackCount, + int expectedAttackCount, + bool copyCharacter + ) + { + Assert.True(_tableSheets.SkillSheet.TryGetValue(skillId, out var skillRow)); + var twinAttack = new DoubleAttack(skillRow, 100, 100, default, StatType.NONE); + var avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address + ) + { + level = level, + }; + var worldRow = _tableSheets.WorldSheet.First; + Assert.NotNull(worldRow); + + var simulator = new StageSimulator( + new TestRandom(), + avatarState, + new List(), + null, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulator.GetWaveRewards( + new TestRandom(), + _tableSheets.StageSheet[1], + _tableSheets.MaterialItemSheet), + new List(), + _tableSheets.DeBuffLimitSheet, + copyCharacter + ); + var player = new Player(avatarState, simulator) + { + AttackCount = initialAttackCount, + }; + + var enemyRow = _tableSheets.CharacterSheet.OrderedList + .FirstOrDefault(e => e.Id > 200000); + Assert.NotNull(enemyRow); + + var enemy = new Enemy(player, enemyRow, 1); + + player.Targets.Add(enemy); + + Assert.Equal(initialAttackCount, player.AttackCount); + var battleStatus = twinAttack.Use(player, 0, new List(), copyCharacter); + + Assert.NotNull(battleStatus); + Assert.Equal(!copyCharacter, battleStatus.Character is null); + Assert.Equal(2, battleStatus.SkillInfos.Count()); + Assert.Equal(expectedAttackCount, player.AttackCount); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs new file mode 100644 index 0000000000..2c3667a196 --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs @@ -0,0 +1,207 @@ +namespace Lib9c.Tests.Model.Skill.Adventure +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Libplanet.Crypto; + using Nekoyume.Battle; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Serilog; + using Xunit; + using Xunit.Abstractions; + + public class NormalAttackTest + { + private readonly TableSheets _tableSheets = new (TableSheetsImporter.ImportSheets()); + + public NormalAttackTest(ITestOutputHelper outputHelper) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.TestOutput(outputHelper) + .CreateLogger(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Use(bool copyCharacter) + { + Assert.True(_tableSheets.SkillSheet.TryGetValue(100000, out var skillRow)); + var normalAttack = new NormalAttack(skillRow, 100, 100, default, StatType.NONE); + + var avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address); + + var worldRow = _tableSheets.WorldSheet.First; + Assert.NotNull(worldRow); + + var random = new TestRandom(); + var simulator = new StageSimulator( + random, + avatarState, + new List(), + null, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulator.GetWaveRewards( + random, + _tableSheets.StageSheet[1], + _tableSheets.MaterialItemSheet), + new List(), + _tableSheets.DeBuffLimitSheet, + copyCharacter + ); + var player = new Player(avatarState, simulator); + + var enemyRow = _tableSheets.CharacterSheet.OrderedList + .FirstOrDefault(e => e.Id > 200000); + Assert.NotNull(enemyRow); + + var enemy = new Enemy(player, enemyRow, 1); + + player.Targets.Add(enemy); + var battleStatusSkill = normalAttack.Use( + player, + 0, + new List(), + copyCharacter); + Assert.NotNull(battleStatusSkill); + Assert.Equal(!copyCharacter, battleStatusSkill.Character is null); + var skillInfo = Assert.Single(battleStatusSkill.SkillInfos); + Assert.Equal(enemy.Id, skillInfo.CharacterId); + Assert.Equal(!copyCharacter, skillInfo.Target is null); + } + + [Fact] + public void FocusSkill() + { + const int seed = 10; // This seed fails to attack enemy with NormalAttack + + // Without Focus buff + Assert.True(_tableSheets.SkillSheet.TryGetValue(100000, out var skillRow)); + // Set chance to 0 to minimize attack success probability + var normalAttack = new NormalAttack(skillRow, 100, 0, default, StatType.NONE); + + var avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address); + + var worldRow = _tableSheets.WorldSheet.First; + Assert.NotNull(worldRow); + + var simulator = new StageSimulator( + new TestRandom(seed), + avatarState, + new List(), + null, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulator.GetWaveRewards( + new TestRandom(seed), + _tableSheets.StageSheet[1], + _tableSheets.MaterialItemSheet), + new List(), + _tableSheets.DeBuffLimitSheet + ); + var player = new Player(avatarState, simulator); + + var enemyRow = _tableSheets.CharacterSheet.OrderedList + .FirstOrDefault(e => e.Id > 200000); + Assert.NotNull(enemyRow); + + var enemy = new Enemy(player, enemyRow, 1); + + player.Targets.Add(enemy); + var battleStatusSkill = normalAttack.Use( + player, + 0, + new List(), + true + ); + Assert.NotNull(battleStatusSkill); + Assert.Equal(0, player.AttackCount); + + // With Focus buff + avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address); + + simulator = new StageSimulator( + new TestRandom(seed), + avatarState, + new List(), + null, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulator.GetWaveRewards( + new TestRandom(seed), + _tableSheets.StageSheet[1], + _tableSheets.MaterialItemSheet), + new List(), + _tableSheets.DeBuffLimitSheet + ); + player = new Player(avatarState, simulator); + player.AddBuff(new Focus(_tableSheets.ActionBuffSheet.OrderedList.First(s => s.ActionBuffType == ActionBuffType.Focus))); + Assert.Single(player.ActionBuffs); + + enemyRow = _tableSheets.CharacterSheet.OrderedList + .FirstOrDefault(e => e.Id > 200000); + Assert.NotNull(enemyRow); + + enemy = new Enemy(player, enemyRow, 1); + + player.Targets.Add(enemy); + battleStatusSkill = normalAttack.Use( + player, + 0, + new List(), + true + ); + Assert.NotNull(battleStatusSkill); + Assert.Equal(1, player.AttackCount); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs new file mode 100644 index 0000000000..29a80523ef --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs @@ -0,0 +1,105 @@ +namespace Lib9c.Tests.Model.Skill.Adventure +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Bencodex.Types; + using Lib9c.Tests.Action; + using Libplanet.Crypto; + using Nekoyume.Battle; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class ShatterStrikeTest + { + private readonly TableSheets _tableSheets = new (TableSheetsImporter.ImportSheets()); + + [Theory] + // 1bp == 0.01% + [InlineData(100, 10000, false, true)] + [InlineData(100, 10000, false, false)] + [InlineData(100, 1000, false, true)] + [InlineData(100, 1000, false, false)] + [InlineData(100, 3700, false, true)] + [InlineData(100, 3700, false, false)] + [InlineData(100, 100_000, true, true)] + [InlineData(100, 100_000, true, false)] + [InlineData(1_000_000, 100_000, false, true)] + [InlineData(1_000_000, 100_000, false, false)] + public void Use(int enemyHp, int ratioBp, bool expectedEnemyDead, bool copyCharacter) + { + var gameConfigState = + new GameConfigState((Text)_tableSheets.GameConfigSheet.Serialize()); + Assert.True( + _tableSheets.SkillSheet.TryGetValue(700011, out var skillRow) + ); // 700011 is ShatterStrike + var shatterStrike = new ShatterStrike(skillRow, 0, 0, ratioBp, StatType.NONE); + + var avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address + ); + var worldRow = _tableSheets.WorldSheet.First; + Assert.NotNull(worldRow); + + var random = new TestRandom(); + var simulator = new StageSimulator( + random, + avatarState, + new List(), + null, + new List(), + 1, + 1, + _tableSheets.StageSheet[1], + _tableSheets.StageWaveSheet[1], + false, + 20, + _tableSheets.GetSimulatorSheets(), + _tableSheets.EnemySkillSheet, + _tableSheets.CostumeStatSheet, + StageSimulator.GetWaveRewards( + random, + _tableSheets.StageSheet[1], + _tableSheets.MaterialItemSheet), + new List(), + _tableSheets.DeBuffLimitSheet, + copyCharacter, + shatterStrikeMaxDamage: gameConfigState.ShatterStrikeMaxDamage + ); + var player = new Player(avatarState, simulator); + var enemyRow = _tableSheets.CharacterSheet.OrderedList + .FirstOrDefault(e => e.Id > 200000); + enemyRow.Set(new[] + { + "201000", "XS", "2", enemyHp.ToString(), "16", "6", "4", "90", "15", "3.2", "0.64", + "0.24", "0", "3.6", "0.6", "0.8", "1.2", + }); + Assert.NotNull(enemyRow); + + var enemy = new Enemy(player, enemyRow, 1); + + player.Targets.Add(enemy); + var used = shatterStrike.Use(player, 0, new List(), copyCharacter); + Assert.NotNull(used); + var skillInfo = Assert.Single(used.SkillInfos); + Assert.Equal( + Math.Clamp( + enemy.HP * ratioBp / 10000m - enemy.DEF + player.ArmorPenetration, + 1, + gameConfigState.ShatterStrikeMaxDamage + ), + skillInfo.Effect + ); + Assert.Equal(expectedEnemyDead, skillInfo.IsDead); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs new file mode 100644 index 0000000000..04e42a5aaf --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs @@ -0,0 +1,272 @@ +namespace Lib9c.Tests.Model.Skill.Arena +{ + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Nekoyume.Arena; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill.Arena; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class ArenaCombatTest + { + private const int ActionBuffId = 708000; // Dispel with duration + + private readonly TableSheets _tableSheets; + private readonly AvatarState _avatar1; + private readonly AvatarState _avatar2; + + private readonly ArenaAvatarState _arenaAvatar1; + private readonly ArenaAvatarState _arenaAvatar2; + + public ArenaCombatTest() + { + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + _avatar1 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + _avatar2 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + _arenaAvatar1 = new ArenaAvatarState(_avatar1); + _arenaAvatar2 = new ArenaAvatarState(_avatar2); + } + + [Theory] + [InlineData(700009, new[] { 600001 })] + [InlineData(700010, new[] { 600001, 704000 })] + public void DispelOnUse(int dispelId, int[] debuffIdList) + { + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var simulator = new ArenaSimulator(new TestRandom()); + var challenger = new ArenaCharacter( + simulator, + myDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + var enemy = new ArenaCharacter( + simulator, + enemyDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + + // Add Debuff to avatar1 + foreach (var debuffId in debuffIdList) + { + var debuffRow = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == debuffId); + challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, debuffRow)); + } + + Assert.Equal(debuffIdList.Length, challenger.Buffs.Count); + + // Use Dispel + var skillRow = _tableSheets.SkillSheet.Values.First(bf => bf.Id == dispelId); + var dispelRow = _tableSheets.ActionBuffSheet.Values.First( + bf => bf.Id == _tableSheets.SkillActionBuffSheet.OrderedList.First( + abf => abf.SkillId == dispelId) + .BuffIds.First() + ); + var dispel = new ArenaBuffSkill(skillRow, 0, 100, 0, StatType.NONE); + dispel.Use(challenger, challenger, simulator.Turn, BuffFactory.GetBuffs( + challenger.Stats, + dispel, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + )); + Assert.Single(challenger.Buffs); + Assert.Equal(dispelRow.GroupId, challenger.Buffs.First().Value.BuffInfo.GroupId); + } + + [Fact] + public void DispelOnDuration_Block() + { + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var simulator = new ArenaSimulator(new TestRandom()); + var challenger = new ArenaCharacter( + simulator, + myDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + var enemy = new ArenaCharacter( + simulator, + enemyDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + + // Use Dispel first + var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == ActionBuffId); + challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, dispel)); + Assert.Single(challenger.Buffs); + + // Use Bleed + var debuffRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 600001); // 600001 is bleed + var debuff = new ArenaBuffSkill(debuffRow, 100, 100, 0, StatType.NONE); + var battleStatus = debuff.Use( + enemy, + challenger, + simulator.Turn, + BuffFactory.GetBuffs( + challenger.Stats, + debuff, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ) + ); + Assert.Single(challenger.Buffs); + Assert.False(battleStatus.SkillInfos.First().Affected); + } + + [Fact] + public void DispelOnDuration_Affect() + { + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var simulator = new ArenaSimulator(new TestRandom()); + var challenger = new ArenaCharacter( + simulator, + myDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + var enemy = new ArenaCharacter( + simulator, + enemyDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + + // Use Dispel first + var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == ActionBuffId); + challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, dispel)); + Assert.Single(challenger.Buffs); + + // Use Focus + var buffRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 700007); // 700007 is Focus + var buff = new ArenaBuffSkill(buffRow, 100, 100, 0, StatType.NONE); + var battleStatus = buff.Use( + challenger, + challenger, + simulator.Turn, + BuffFactory.GetBuffs( + challenger.Stats, + buff, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ) + ); + Assert.Equal(2, challenger.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + } + + [Fact] + public void DispelOnDuration_Nothing() + { + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var simulator = new ArenaSimulator(new TestRandom()); + var challenger = new ArenaCharacter( + simulator, + myDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + var enemy = new ArenaCharacter( + simulator, + enemyDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + + // Use Dispel first + var dispel = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == ActionBuffId); + challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, dispel)); + Assert.Single(challenger.Buffs); + + // Use Bleed + var bleed = _tableSheets.ActionBuffSheet.Values.First(bf => bf.Id == 600001); + challenger.AddBuff(BuffFactory.GetActionBuff(challenger.Stats, bleed)); + Assert.Equal(2, challenger.Buffs.Count); + + // NormalAttack to test. This must not remove debuff by dispel + var attackRow = + _tableSheets.SkillSheet.Values.First(bf => bf.Id == 100000); + var attack = new ArenaNormalAttack(attackRow, 100, 100, 0, StatType.NONE); + + // Hit + var battleStatus = attack.Use( + enemy, + challenger, + simulator.Turn, + BuffFactory.GetBuffs( + challenger.Stats, + attack, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ) + ); + Assert.Equal(2, challenger.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + Assert.Contains(600001, challenger.Buffs.Values.Select(bf => bf.BuffInfo.Id)); + + // Attack + battleStatus = attack.Use( + challenger, + enemy, + simulator.Turn, + BuffFactory.GetBuffs( + challenger.Stats, + attack, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet + ) + ); + Assert.Equal(2, challenger.Buffs.Count); + Assert.True(battleStatus.SkillInfos.First().Affected); + Assert.Contains(600001, challenger.Buffs.Values.Select(bf => bf.BuffInfo.Id)); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaDoubleAttackTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaDoubleAttackTest.cs new file mode 100644 index 0000000000..7f80215c14 --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaDoubleAttackTest.cs @@ -0,0 +1,68 @@ +namespace Lib9c.Tests.Model.Skill.Arena +{ + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Nekoyume.Arena; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill.Arena; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class ArenaDoubleAttackTest + { + private readonly TableSheets _tableSheets; + private readonly AvatarState _avatar1; + private readonly AvatarState _avatar2; + private readonly ArenaAvatarState _arenaAvatar1; + private readonly ArenaAvatarState _arenaAvatar2; + + public ArenaDoubleAttackTest() + { + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + _avatar1 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + _avatar2 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + _arenaAvatar1 = new ArenaAvatarState(_avatar1); + _arenaAvatar2 = new ArenaAvatarState(_avatar2); + } + + [Theory] + [InlineData(100003, 0)] + [InlineData(700008, 2)] + public void DoubleAttackTest(int skillId, int expectedAttackCount) + { + var simulator = new ArenaSimulator(new TestRandom()); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var challenger = + new ArenaCharacter(simulator, myDigest, arenaSheets, simulator.HpModifier, new List()); + var enemy = + new ArenaCharacter(simulator, enemyDigest, arenaSheets, simulator.HpModifier, new List()); + + var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == skillId); + var skill = new ArenaDoubleAttack(skillRow, 100, 100, 0, StatType.NONE); + var used = skill.Use(challenger, enemy, simulator.Turn, new List()); + Assert.Equal(expectedAttackCount, challenger.AttackCount); + Assert.Equal(2, used.SkillInfos.Count()); + Assert.True(used.SkillInfos.First().Effect <= used.SkillInfos.Last().Effect); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaNormalAttackTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaNormalAttackTest.cs new file mode 100644 index 0000000000..07bf79aace --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaNormalAttackTest.cs @@ -0,0 +1,103 @@ +namespace Lib9c.Tests.Model.Skill.Arena +{ + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Nekoyume.Arena; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill; + using Nekoyume.Model.Skill.Arena; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class ArenaNormalAttackTest + { + private readonly TableSheets _tableSheets; + private readonly AvatarState _avatar1; + private readonly AvatarState _avatar2; + + private readonly ArenaAvatarState _arenaAvatar1; + private readonly ArenaAvatarState _arenaAvatar2; + + public ArenaNormalAttackTest() + { + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + _avatar1 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + _avatar2 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + _arenaAvatar1 = new ArenaAvatarState(_avatar1); + _arenaAvatar2 = new ArenaAvatarState(_avatar2); + } + + [Fact] + public void NormalAttack() + { + var simulator = new ArenaSimulator(new TestRandom()); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var challenger = + new ArenaCharacter(simulator, myDigest, arenaSheets, simulator.HpModifier, new List()); + var enemy = + new ArenaCharacter(simulator, enemyDigest, arenaSheets, simulator.HpModifier, new List()); + + var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == 100000); + var skill = new ArenaNormalAttack(skillRow, 100, 100, 0, StatType.NONE); + var used = skill.Use(challenger, enemy, simulator.Turn, new List()); + Assert.Single(used.SkillInfos); + Assert.Equal(1, challenger.AttackCount); + } + + [Fact] + public void FocusSkill() + { + const int seed = 10; + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + + // Without Focus buff + var simulator = new ArenaSimulator(new TestRandom(seed)); + var challenger = + new ArenaCharacter(simulator, myDigest, arenaSheets, simulator.HpModifier, new List()); + var enemy = + new ArenaCharacter(simulator, enemyDigest, arenaSheets, simulator.HpModifier, new List()); + var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == 100000); + var skill = new ArenaNormalAttack(skillRow, 100, 100, 0, StatType.NONE); + var used = skill.Use(challenger, enemy, simulator.Turn, new List()); + Assert.Equal(0, challenger.AttackCount); + + // With Focus Buff + simulator = new ArenaSimulator(new TestRandom(seed)); + challenger = new ArenaCharacter(simulator, myDigest, arenaSheets, simulator.HpModifier, new List()); + enemy = new ArenaCharacter(simulator, enemyDigest, arenaSheets, simulator.HpModifier, new List()); + + challenger.AddBuff(new Focus( + _tableSheets.ActionBuffSheet.OrderedList.First(s => + s.ActionBuffType == ActionBuffType.Focus) + )); + Assert.Single(challenger.ActionBuffs); + + skill = new ArenaNormalAttack(skillRow, 100, 100, 0, StatType.NONE); + used = skill.Use(challenger, enemy, simulator.Turn, new List()); + Assert.Single(used.SkillInfos); + Assert.Equal(1, challenger.AttackCount); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs new file mode 100644 index 0000000000..545852abc8 --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs @@ -0,0 +1,99 @@ +namespace Lib9c.Tests.Model.Skill.Arena +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Bencodex.Types; + using Lib9c.Tests.Action; + using Nekoyume.Arena; + using Nekoyume.Model; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Skill.Arena; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Xunit; + + public class ArenaShatterStrikeTest + { + private readonly TableSheets _tableSheets; + private readonly AvatarState _avatar1; + private readonly AvatarState _avatar2; + + private readonly ArenaAvatarState _arenaAvatar1; + private readonly ArenaAvatarState _arenaAvatar2; + + public ArenaShatterStrikeTest() + { + _tableSheets = new TableSheets(TableSheetsImporter.ImportSheets()); + _avatar1 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + _avatar2 = new AvatarState( + default, + default, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + default + ); + + _arenaAvatar1 = new ArenaAvatarState(_avatar1); + _arenaAvatar2 = new ArenaAvatarState(_avatar2); + } + + [Theory] + // 1bp == 0.01% + [InlineData(2, 1000, false)] + [InlineData(2, 3700, false)] + [InlineData(2, 100_000, true)] + [InlineData(100_000, 100_000, false)] + public void Use(int hpModifier, int ratioBp, bool expectedEnemyDead) + { + var gameConfigState = + new GameConfigState((Text)_tableSheets.GameConfigSheet.Serialize()); + var simulator = new ArenaSimulator( + new TestRandom(), + hpModifier: hpModifier, + shatterStrikeMaxDamage: gameConfigState.ShatterStrikeMaxDamage + ); + var myDigest = new ArenaPlayerDigest(_avatar1, _arenaAvatar1); + var enemyDigest = new ArenaPlayerDigest(_avatar2, _arenaAvatar2); + var arenaSheets = _tableSheets.GetArenaSimulatorSheets(); + var challenger = + new ArenaCharacter( + simulator, + myDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + var enemy = + new ArenaCharacter( + simulator, + enemyDigest, + arenaSheets, + simulator.HpModifier, + new List() + ); + + var skillRow = _tableSheets.SkillSheet.OrderedList.First(s => s.Id == 700011); + var shatterStrike = new ArenaShatterStrike(skillRow, 0, 0, ratioBp, StatType.NONE); + var used = shatterStrike.Use(challenger, enemy, simulator.Turn, new List()); + Assert.Single(used.SkillInfos); + Assert.Equal( + Math.Clamp( + enemy.HP * ratioBp / 10000m - enemy.DEF + challenger.ArmorPenetration, + 1, + gameConfigState.ShatterStrikeMaxDamage + ), + used.SkillInfos.First().Effect + ); + Assert.Equal(expectedEnemyDead, enemy.IsDead); + } + } +} diff --git a/.Lib9c.Tests/Model/Skill/BuffFactoryTest.cs b/.Lib9c.Tests/Model/Skill/BuffFactoryTest.cs index f5fad4e550..6b03467d54 100644 --- a/.Lib9c.Tests/Model/Skill/BuffFactoryTest.cs +++ b/.Lib9c.Tests/Model/Skill/BuffFactoryTest.cs @@ -149,5 +149,67 @@ public void Thorns() Assert.NotNull(buff3.CustomField); Assert.True(buff3.CustomField.Value.BuffValue > buff2.CustomField.Value.BuffValue); } + + [Theory] + [InlineData(204003, false)] + [InlineData(206002, true)] + public void IsDebuff(int buffId, bool hasCustom) + { + var player = new Player( + level: 1, + _tableSheets.CharacterSheet, + _tableSheets.CharacterLevelSheet, + _tableSheets.EquipmentItemSetEffectSheet); + var skillId = _tableSheets.SkillBuffSheet.Values.First(r => r.BuffIds.Contains(buffId)).SkillId; + var skillRow = _tableSheets.SkillSheet[skillId]; + int power = hasCustom ? 0 : 100; + int statPower = hasCustom ? 250 : 0; + StatType referencedStat = hasCustom ? StatType.HP : StatType.NONE; + var skill = SkillFactory.Get(skillRow, power, 100, statPower, referencedStat); + var buffs = BuffFactory.GetBuffs( + player.Stats, + skill, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet, + hasCustom + ); + var buff = Assert.IsType(buffs.Single(r => r.BuffInfo.Id == buffId)); + Assert.Equal(buff.CustomField is not null, hasCustom); + Assert.False(buff.IsBuff()); + Assert.True(buff.IsDebuff()); + } + + [Theory] + [InlineData(102001, false)] + [InlineData(102003, true)] + public void IsBuff(int buffId, bool hasCustom) + { + var player = new Player( + level: 1, + _tableSheets.CharacterSheet, + _tableSheets.CharacterLevelSheet, + _tableSheets.EquipmentItemSetEffectSheet); + var skillId = _tableSheets.SkillBuffSheet.Values.First(r => r.BuffIds.Contains(buffId)).SkillId; + var skillRow = _tableSheets.SkillSheet[skillId]; + int power = hasCustom ? 0 : 100; + int statPower = hasCustom ? 250 : 0; + StatType referencedStat = hasCustom ? StatType.ATK : StatType.NONE; + var skill = SkillFactory.Get(skillRow, power, 100, statPower, referencedStat); + var buffs = BuffFactory.GetBuffs( + player.Stats, + skill, + _tableSheets.SkillBuffSheet, + _tableSheets.StatBuffSheet, + _tableSheets.SkillActionBuffSheet, + _tableSheets.ActionBuffSheet, + hasCustom + ); + var buff = Assert.IsType(buffs.Single(r => r.BuffInfo.Id == buffId)); + Assert.NotNull(buff.CustomField); + Assert.True(buff.IsBuff()); + Assert.False(buff.IsDebuff()); + } } } diff --git a/.Lib9c.Tests/Model/Skill/NormalAttackTest.cs b/.Lib9c.Tests/Model/Skill/NormalAttackTest.cs deleted file mode 100644 index daeb855469..0000000000 --- a/.Lib9c.Tests/Model/Skill/NormalAttackTest.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace Lib9c.Tests.Model.Skill -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Lib9c.Tests.Action; - using Libplanet.Crypto; - using Nekoyume.Battle; - using Nekoyume.Model; - using Nekoyume.Model.Buff; - using Nekoyume.Model.Skill; - using Nekoyume.Model.Stat; - using Nekoyume.Model.State; - using Serilog; - using Xunit; - using Xunit.Abstractions; - - public class NormalAttackTest - { - public NormalAttackTest(ITestOutputHelper outputHelper) - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Verbose() - .WriteTo.TestOutput(outputHelper) - .CreateLogger(); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Use(bool copyCharacter) - { - var sheets = TableSheetsImporter.ImportSheets(); - var tableSheets = new TableSheets(sheets); - - Assert.True(tableSheets.SkillSheet.TryGetValue(100000, out var skillRow)); - var normalAttack = new NormalAttack(skillRow, 100, 100, default, StatType.NONE); - - var avatarState = new AvatarState( - new PrivateKey().Address, - new PrivateKey().Address, - 0, - tableSheets.GetAvatarSheets(), - new GameConfigState(), - new PrivateKey().Address); - - var worldRow = tableSheets.WorldSheet.First; - Assert.NotNull(worldRow); - - var random = new TestRandom(); - var simulator = new StageSimulator( - random, - avatarState, - new List(), - null, - new List(), - 1, - 1, - tableSheets.StageSheet[1], - tableSheets.StageWaveSheet[1], - false, - 20, - tableSheets.GetSimulatorSheets(), - tableSheets.EnemySkillSheet, - tableSheets.CostumeStatSheet, - StageSimulator.GetWaveRewards( - random, - tableSheets.StageSheet[1], - tableSheets.MaterialItemSheet), - new List(), - copyCharacter - ); - var player = new Player(avatarState, simulator); - - var enemyRow = tableSheets.CharacterSheet.OrderedList - .FirstOrDefault(e => e.Id > 200000); - Assert.NotNull(enemyRow); - - var enemy = new Enemy(player, enemyRow, 1); - - player.Targets.Add(enemy); - var battleStatusSkill = normalAttack.Use( - player, - 0, - new List(), - copyCharacter); - Assert.NotNull(battleStatusSkill); - Assert.Equal(!copyCharacter, battleStatusSkill.Character is null); - var skillInfo = Assert.Single(battleStatusSkill.SkillInfos); - Assert.Equal(enemy.Id, skillInfo.CharacterId); - Assert.Equal(!copyCharacter, skillInfo.Target is null); - } - } -} diff --git a/.Lib9c.Tests/Model/Skill/Raid/NormalAttackTest.cs b/.Lib9c.Tests/Model/Skill/Raid/NormalAttackTest.cs new file mode 100644 index 0000000000..51adc36f2f --- /dev/null +++ b/.Lib9c.Tests/Model/Skill/Raid/NormalAttackTest.cs @@ -0,0 +1,68 @@ +namespace Lib9c.Tests.Model.Skill.Raid +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Lib9c.Tests.Action; + using Libplanet.Crypto; + using Nekoyume.Battle; + using Nekoyume.Model.BattleStatus; + using Nekoyume.Model.Buff; + using Nekoyume.Model.Stat; + using Nekoyume.Model.State; + using Nekoyume.TableData; + using Xunit; + + public class NormalAttackTest + { + private readonly TableSheets _tableSheets = new (TableSheetsImporter.ImportSheets()); + + [Fact] + public void FocusSkill() + { + const int seed = 10; // This seed fails to attack enemy with NormalAttack + + // With Focus buff + var avatarState = new AvatarState( + new PrivateKey().Address, + new PrivateKey().Address, + 0, + _tableSheets.GetAvatarSheets(), + new GameConfigState(), + new PrivateKey().Address); + avatarState.level = 400; + + var simulator = new RaidSimulator( + _tableSheets.WorldBossListSheet.First().Value.BossId, + new TestRandom(seed), + avatarState, + new List(), + null, + _tableSheets.GetRaidSimulatorSheets(), + _tableSheets.CostumeStatSheet, + new List + { + new (StatType.DEF, StatModifier.OperationType.Percentage, 100), + }, + _tableSheets.DeBuffLimitSheet + ); + var player = simulator.Player; + var buffRow = new ActionBuffSheet.Row(); + buffRow.Set( + new List + { "706000", "706000", "100", "9999", "Self", "Focus", "Normal", "0" } + ); + player.AddBuff(new Focus(buffRow)); + + var logs = simulator.Simulate(); + var playerLog = logs.Where(lg => lg.Character?.Id == player.Id); + foreach (var log in playerLog) + { + if (log is NormalAttack or BlowAttack or DoubleAttack) + { + Assert.True(((Skill)log).SkillInfos.First().Effect > 0); + } + } + } + } +} diff --git a/.Lib9c.Tests/Model/StageSimulatorTest.cs b/.Lib9c.Tests/Model/StageSimulatorTest.cs index 6c8132a2ec..1eb8173aa7 100644 --- a/.Lib9c.Tests/Model/StageSimulatorTest.cs +++ b/.Lib9c.Tests/Model/StageSimulatorTest.cs @@ -69,7 +69,8 @@ public void Simulate() new List { new (StatType.ATK, StatModifier.OperationType.Add, 100), - } + }, + _tableSheets.DeBuffLimitSheet ); var player = simulator.Player; @@ -127,7 +128,8 @@ public void TestSpeedModifierBySkill() new TestRandom(1), _tableSheets.StageSheet[3], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var unskilledPlayer = simulator.Player; Assert.Contains(item, unskilledPlayer.Inventory.Equipments); @@ -179,7 +181,8 @@ public void TestSpeedModifierBySkill() new TestRandom(1), _tableSheets.StageSheet[3], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var skilledPlayer = simulator.Player; Assert.Contains(skilledItem, skilledPlayer.Inventory.Equipments); diff --git a/.Lib9c.Tests/TableSheets.cs b/.Lib9c.Tests/TableSheets.cs index 67d5497355..df043b3519 100644 --- a/.Lib9c.Tests/TableSheets.cs +++ b/.Lib9c.Tests/TableSheets.cs @@ -248,6 +248,8 @@ public StakeActionPointCoefficientSheet StakeActionPointCoefficientSheet public CollectionSheet CollectionSheet { get; private set; } + public DeBuffLimitSheet DeBuffLimitSheet { get; set; } + public void ItemSheetInitialize() { ItemSheet ??= new ItemSheet(); diff --git a/.github/workflows/lib9c_plugin_build_and_push_s3.yaml b/.github/workflows/lib9c_plugin_build_and_push_s3.yaml index 041d349dfc..5a9961292d 100644 --- a/.github/workflows/lib9c_plugin_build_and_push_s3.yaml +++ b/.github/workflows/lib9c_plugin_build_and_push_s3.yaml @@ -4,7 +4,7 @@ on: push: branches: - development - - main + - main workflow_dispatch: jobs: @@ -27,7 +27,7 @@ jobs: run: zip -r ../${{ matrix.runtime }}.zip . working-directory: ./out - name: Upload S3 - run: aws s3 cp ${{ matrix.runtime }}.zip s3://9c-dx/Lib9c.Plugin/${{ github.sha }}/ + run: aws s3 cp ${{ matrix.runtime }}.zip s3://${{ vars.LIB9C_PLUGIN_S3_PATH }}/${{ github.sha }}/ env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs b/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs index bae6d208ad..df72c5c107 100644 --- a/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs +++ b/Lib9c.DevExtensions/Action/CreateOrReplaceAvatar.cs @@ -478,7 +478,7 @@ public IWorld Execute( var recipe = recipeSheet.OrderedList! .First(e => e.ResultEquipmentId == equipmentId); var subRecipe = subRecipeSheetV2[recipe.SubRecipeIds[1]]; - CombinationEquipment16.AddAndUnlockOption( + CombinationEquipment.AddAndUnlockOption( agent, null, equipment, diff --git a/Lib9c.DevExtensions/Data/TestbedCreateAvatar.json b/Lib9c.DevExtensions/Data/TestbedCreateAvatar.json index 198279aa91..0d522af818 100644 --- a/Lib9c.DevExtensions/Data/TestbedCreateAvatar.json +++ b/Lib9c.DevExtensions/Data/TestbedCreateAvatar.json @@ -1,5 +1,5 @@ { - "Level": 300, + "Level": 400, "TradableMaterialCount": 6000, "MaterialCount": 5000, "RuneStoneCount": 4000, @@ -12,7 +12,13 @@ 103, 128, 148, - 152 + 152, + 170, + 165, + 160, + 157, + 71, + 30 ], "CustomEquipmentItems": [ { @@ -329,12 +335,39 @@ ] }, { - "ID": 10540000, - "Level": 12, + "ID": 10254001, + "Level": 30, + "OptionIds": [ + 1045400214, + 1055400013, + 1055400013 + ] + }, + { + "ID": 10354001, + "Level": 30, + "OptionIds": [ + 1055400033, + 1055400033, + 1065000612 + ] + }, + { + "ID": 10354001, + "Level": 17, + "OptionIds": [ + 1015200124, + 1055400033, + 1065000612 + ] + }, + { + "ID": 10354001, + "Level": 17, "OptionIds": [ - 1054000011, - 1054000012, - 1054000013 + 1055400125, + 1055400033, + 1065000612 ] } ] diff --git a/Lib9c.MessagePack/Action/NCActionEvaluation.cs b/Lib9c.MessagePack/Action/NCActionEvaluation.cs index 35f2b94a68..f7df7c63bd 100644 --- a/Lib9c.MessagePack/Action/NCActionEvaluation.cs +++ b/Lib9c.MessagePack/Action/NCActionEvaluation.cs @@ -49,7 +49,9 @@ public struct NCActionEvaluation [Key(8)] [MessagePackFormatter(typeof(TxIdFormatter))] +#pragma warning disable MsgPack003 public TxId? TxId { get; set; } +#pragma warning restore MsgPack003 [SerializationConstructor] public NCActionEvaluation( diff --git a/Lib9c.Policy/Policy/BlockPolicySource.cs b/Lib9c.Policy/Policy/BlockPolicySource.cs index edd16ab940..5d31b60a2d 100644 --- a/Lib9c.Policy/Policy/BlockPolicySource.cs +++ b/Lib9c.Policy/Policy/BlockPolicySource.cs @@ -31,15 +31,22 @@ namespace Nekoyume.Blockchain.Policy { public partial class BlockPolicySource { - public const int MaxTransactionsPerBlock = 200; + public static int MaxTransactionsPerBlock; + + public const int DefaultMaxTransactionsPerBlock = 200; public static readonly TimeSpan BlockInterval = TimeSpan.FromSeconds(8); private readonly IActionLoader _actionLoader; - public BlockPolicySource(IActionLoader? actionLoader = null) + public BlockPolicySource( + IActionLoader? actionLoader = null, + int? maxTransactionPerBlock = null) { _actionLoader = actionLoader ?? new NCActionLoader(); + MaxTransactionsPerBlock = Math.Min( + maxTransactionPerBlock ?? DefaultMaxTransactionsPerBlock, + DefaultMaxTransactionsPerBlock); } /// diff --git a/Lib9c/Action/ActivateCollection.cs b/Lib9c/Action/ActivateCollection.cs index 10c3505077..a1fc454ff9 100644 --- a/Lib9c/Action/ActivateCollection.cs +++ b/Lib9c/Action/ActivateCollection.cs @@ -49,6 +49,10 @@ public override IWorld Execute(IActionContext context) var itemSheet = sheets.GetItemSheet(); foreach (var (collectionId, collectionMaterials) in CollectionData) { + if (collectionState.Ids.Contains(collectionId)) + { + throw new AlreadyActivatedException($"{collectionId} already activated."); + } var row = collectionSheet[collectionId]; foreach (var requiredMaterial in row.Materials) { diff --git a/Lib9c/Action/AddActivatedAccount0.cs b/Lib9c/Action/AddActivatedAccount0.cs deleted file mode 100644 index 75af86b758..0000000000 --- a/Lib9c/Action/AddActivatedAccount0.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Model.State; -using Nekoyume.Module; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("add_activated_account")] - public class AddActivatedAccount0 : ActionBase, IAddActivatedAccountV1 - { - public AddActivatedAccount0(Address address) - { - Address = address; - } - - public AddActivatedAccount0() - { - } - - public Address Address { get; private set; } - - Address IAddActivatedAccountV1.Address => Address; - - public override IValue PlainValue => Dictionary.Empty - .Add("type_id", "add_activated_account") - .Add("values", new Dictionary( - new[] - { - new KeyValuePair((Text)"address", Address.Serialize()), - } - )); - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IWorld state = context.PreviousState; - - if (!state.TryGetLegacyState(ActivatedAccountsState.Address, out Dictionary accountsAsDict)) - { - throw new ActivatedAccountsDoesNotExistsException(); - } - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - CheckPermission(context); - - var accounts = new ActivatedAccountsState(accountsAsDict); - return state.SetLegacyState( - ActivatedAccountsState.Address, - accounts.AddAccount(Address).Serialize() - ); - } - - public override void LoadPlainValue(IValue plainValue) - { - var asDict = (Dictionary)((Dictionary)plainValue)["values"]; - Address = asDict["address"].ToAddress(); - } - } -} diff --git a/Lib9c/Action/AuraSummon.cs b/Lib9c/Action/AuraSummon.cs index a0843b74ce..62c1b846db 100644 --- a/Lib9c/Action/AuraSummon.cs +++ b/Lib9c/Action/AuraSummon.cs @@ -300,16 +300,14 @@ SkillSheet skillSheet var stat = CombinationEquipment5.GetStat(optionRow, random); equipment.StatsMap.AddStatAdditionalValue(stat.StatType, stat.BaseValue); equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); } else { - var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); + var skill = CombinationEquipment.GetSkill(optionRow, skillSheet, random); if (skill is null) continue; equipment.Skills.Add(skill); equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); } } } diff --git a/Lib9c/Action/BattleArena.cs b/Lib9c/Action/BattleArena.cs index 407d99f5d5..b7f958979f 100644 --- a/Lib9c/Action/BattleArena.cs +++ b/Lib9c/Action/BattleArena.cs @@ -130,6 +130,7 @@ public override IWorld Execute(IActionContext context) typeof(EquipmentItemOptionSheet), typeof(MaterialItemSheet), typeof(RuneListSheet), + typeof(DeBuffLimitSheet), }; if (collectionExist) { @@ -379,6 +380,7 @@ public override IWorld Execute(IActionContext context) enemyRuneStates); var previousMyScore = myArenaScore.Score; var arenaSheets = sheets.GetArenaSimulatorSheets(); + var deBuffLimitSheet = sheets.GetSheet(); var winCount = 0; var defeatCount = 0; var rewards = new List(); @@ -404,13 +406,15 @@ public override IWorld Execute(IActionContext context) } for (var i = 0; i < ticket; i++) { - var simulator = new ArenaSimulator(random, HpIncreasingModifier); + var simulator = new ArenaSimulator(random, HpIncreasingModifier, + gameConfigState.ShatterStrikeMaxDamage); var log = simulator.Simulate( myArenaPlayerDigest, enemyArenaPlayerDigest, arenaSheets, modifiers[myAvatarAddress], modifiers[enemyAvatarAddress], + deBuffLimitSheet, true); if (log.Result.Equals(ArenaLog.ArenaResult.Win)) { diff --git a/Lib9c/Action/BuyProduct0.cs b/Lib9c/Action/BuyProduct0.cs deleted file mode 100644 index cbb2ed8e49..0000000000 --- a/Lib9c/Action/BuyProduct0.cs +++ /dev/null @@ -1,343 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Bencodex.Types; -using Lib9c.Model.Order; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using Nekoyume.Battle; -using Nekoyume.Model.EnumType; -using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; -using Nekoyume.Model.Market; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using static Lib9c.SerializeKeys; -using Log = Serilog.Log; - -namespace Nekoyume.Action -{ - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType("buy_product")] - public class BuyProduct0 : GameAction - { - // Capacity from Buy limits in NineChronicles - // https://github.com/planetarium/NineChronicles/blob/v100372-1/nekoyume/Assets/_Scripts/UI/Shop/BuyView.cs#L127 - public const int Capacity = 20; - public Address AvatarAddress; - public IEnumerable ProductInfos; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IWorld states = context.PreviousState; - - var sw = new Stopwatch(); - sw.Start(); - var started = DateTimeOffset.UtcNow; - Log.Debug("BuyProduct exec started"); - - if (!ProductInfos.Any()) - { - throw new ListEmptyException("ProductInfos was empty."); - } - - if (ProductInfos.Count() > Capacity) - { - throw new ArgumentOutOfRangeException($"{nameof(ProductInfos)} must be less than or equal {Capacity}."); - - } - - if (ProductInfos.Any(p => p.AgentAddress == context.Signer || - p.AvatarAddress == AvatarAddress)) - { - throw new InvalidAddressException(); - } - - foreach (var productInfo in ProductInfos) - { - productInfo.ValidateType(); - } - - if (!states.TryGetAvatarState(context.Signer, AvatarAddress, out var buyerAvatarState)) - { - throw new FailedLoadStateException("failed load to buyer avatar state."); - } - - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - - if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(addressesHex, - GameConfig.RequireClearedStageLevel.ActionsInShop, current); - } - - var materialSheet = states.GetSheet(); - foreach (var productInfo in ProductInfos.OrderBy(p => p.ProductId).ThenBy(p =>p.Price)) - { - var sellerAgentAddress = productInfo.AgentAddress; - var sellerAvatarAddress = productInfo.AvatarAddress; - if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) - { - throw new FailedLoadStateException($"failed load to seller avatar state."); - } - - if (productInfo is ItemProductInfo {Legacy: true} itemProductInfo) - { - var purchaseInfo = new PurchaseInfo(itemProductInfo.ProductId, itemProductInfo.TradableId, - sellerAgentAddress, sellerAvatarAddress, itemProductInfo.ItemSubType, - productInfo.Price); - states = Buy_Order(purchaseInfo, context, states, buyerAvatarState, materialSheet, sellerAvatarState); - } - else - { - states = Buy(context, productInfo, sellerAvatarAddress, states, sellerAgentAddress, buyerAvatarState, sellerAvatarState, materialSheet); - } - } - - states = states.SetAvatarState(AvatarAddress, buyerAvatarState); - - var ended = DateTimeOffset.UtcNow; - Log.Debug("BuyProduct Total Executed Time: {Elapsed}", ended - started); - return states; - } - - private IWorld Buy(IActionContext context, IProductInfo productInfo, Address sellerAvatarAddress, - IWorld states, Address sellerAgentAddress, AvatarState buyerAvatarState, AvatarState sellerAvatarState, - MaterialItemSheet materialSheet) - { - var productId = productInfo.ProductId; - var productsStateAddress = ProductsState.DeriveAddress(sellerAvatarAddress); - var productsState = new ProductsState((List) states.GetLegacyState(productsStateAddress)); - if (!productsState.ProductIds.Contains(productId)) - { - // sold out or canceled product. - throw new ProductNotFoundException($"can't find product {productId}"); - } - - productsState.ProductIds.Remove(productId); - - var productAddress = Product.DeriveAddress(productId); - var product = ProductFactory.DeserializeProduct((List) states.GetLegacyState(productAddress)); - product.Validate(productInfo); - - switch (product) - { - case FavProduct favProduct: - states = states.TransferAsset(context, productAddress, AvatarAddress, favProduct.Asset); - break; - case ItemProduct itemProduct: - { - switch (itemProduct.TradableItem) - { - case Costume costume: - buyerAvatarState.UpdateFromAddCostume(costume, false); - break; - case ItemUsable itemUsable: - buyerAvatarState.UpdateFromAddItem(itemUsable, false); - break; - case TradableMaterial tradableMaterial: - { - buyerAvatarState.UpdateFromAddItem(tradableMaterial, itemProduct.ItemCount, false); - break; - } - } - } - break; - default: - throw new InvalidProductTypeException($"{product} is not support type."); - } - - var sellerMail = new ProductSellerMail(context.BlockIndex, productId, - context.BlockIndex, productId); - sellerAvatarState.Update(sellerMail); - sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, product.Price); - sellerAvatarState.UpdateQuestRewards(materialSheet); - - var buyerMail = new ProductBuyerMail(context.BlockIndex, productId, - context.BlockIndex, productId - ); - buyerAvatarState.Update(buyerMail); - buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, product.Price); - buyerAvatarState.UpdateQuestRewards(materialSheet); - - // Transfer tax. - var arenaSheet = states.GetSheet(); - var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); - var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); - var tax = product.Price.DivRem(100, out _) * Action.Buy.TaxRate; - var taxedPrice = product.Price - tax; - - // Receipt - var receipt = new ProductReceipt(productId, sellerAvatarAddress, buyerAvatarState.address, product.Price, - context.BlockIndex); - states = states - .SetLegacyState(productAddress, Null.Value) - .SetLegacyState(productsStateAddress, productsState.Serialize()) - .SetLegacyState(ProductReceipt.DeriveAddress(productId), receipt.Serialize()) - .SetAvatarState(sellerAvatarAddress, sellerAvatarState, true, true, true, true) - .TransferAsset(context, context.Signer, feeStoreAddress, tax) - .TransferAsset(context, context.Signer, sellerAgentAddress, taxedPrice); - - return states; - } - - - // backward compatibility for order - shared shop state. - // TODO DELETE THIS METHOD AFTER PRODUCT MIGRATION END. - private static IWorld Buy_Order(PurchaseInfo purchaseInfo, IActionContext context, IWorld states, AvatarState buyerAvatarState, MaterialItemSheet materialSheet, AvatarState sellerAvatarState) - { - Address shardedShopAddress = - ShardedShopStateV2.DeriveAddress(purchaseInfo.ItemSubType, purchaseInfo.OrderId); - Address sellerAgentAddress = purchaseInfo.SellerAgentAddress; - Address sellerAvatarAddress = purchaseInfo.SellerAvatarAddress; - Address sellerInventoryAddress = sellerAvatarAddress.Derive(LegacyInventoryKey); - var sellerWorldInformationAddress = sellerAvatarAddress.Derive(LegacyWorldInformationKey); - Address sellerQuestListAddress = sellerAvatarAddress.Derive(LegacyQuestListKey); - Guid orderId = purchaseInfo.OrderId; - Address orderAddress = Order.DeriveAddress(orderId); - Address digestListAddress = OrderDigestListState.DeriveAddress(sellerAvatarAddress); - - if (!states.TryGetLegacyState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) - { - throw new FailedLoadStateException("failed to load shop state"); - } - - if (!states.TryGetLegacyState(orderAddress, out Dictionary rawOrder)) - { - throw new OrderIdDoesNotExistException($"{orderId}"); - } - - Order order = OrderFactory.Deserialize(rawOrder); - - var shardedShopState = new ShardedShopStateV2(shopStateDict); - shardedShopState.Remove(order, context.BlockIndex); - - if (!states.TryGetLegacyState(digestListAddress, out Dictionary rawDigestList)) - { - throw new FailedLoadStateException($"{orderId}"); - } - var digestList = new OrderDigestListState(rawDigestList); - - // migration method - sellerAvatarState.inventory.UnlockInvalidSlot(digestList, sellerAgentAddress, sellerAvatarAddress); - sellerAvatarState.inventory.ReconfigureFungibleItem(digestList, order.TradableId); - sellerAvatarState.inventory.LockByReferringToDigestList(digestList, order.TradableId, context.BlockIndex); - - digestList.Remove(orderId); - - var errorCode = order.ValidateTransfer(sellerAvatarState, purchaseInfo.TradableId, purchaseInfo.Price, context.BlockIndex); - switch (errorCode) - { - case Action.Buy.ErrorCodeInvalidAddress: - throw new InvalidAddressException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidTradableId: - throw new InvalidTradableIdException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidPrice: - throw new InvalidPriceException($"{orderId}"); - case Action.Buy.ErrorCodeShopItemExpired: - throw new ShopItemExpiredException($"{orderId}"); - case Action.Buy.ErrorCodeItemDoesNotExist: - throw new ItemDoesNotExistException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidItemType: - throw new InvalidItemTypeException($"{orderId}"); - } - - // Check Balance. - FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); - if (buyerBalance < order.Price) - { - throw new InsufficientBalanceException($"{orderId}", buyerAvatarState.address, - buyerBalance); - } - - var orderReceipt = order.Transfer(sellerAvatarState, buyerAvatarState, context.BlockIndex); - - Address orderReceiptAddress = OrderReceipt.DeriveAddress(orderId); - if (!(states.GetLegacyState(orderReceiptAddress) is null)) - { - throw new DuplicateOrderIdException($"{orderId}"); - } - - var expirationMail = sellerAvatarState.mailBox.OfType() - .FirstOrDefault(m => m.OrderId.Equals(orderId)); - if (!(expirationMail is null)) - { - sellerAvatarState.mailBox.Remove(expirationMail); - } - - var orderSellerMail = new OrderSellerMail( - context.BlockIndex, - orderId, - context.BlockIndex, - orderId - ); - var orderBuyerMail = new OrderBuyerMail( - context.BlockIndex, - orderId, - context.BlockIndex, - orderId - ); - - buyerAvatarState.Update(orderBuyerMail); - sellerAvatarState.Update(orderSellerMail); - - // // Update quest. - buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, order.Price); - sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, order.Price); - - sellerAvatarState.updatedAt = context.BlockIndex; - sellerAvatarState.blockIndex = context.BlockIndex; - - buyerAvatarState.UpdateQuestRewards(materialSheet); - sellerAvatarState.UpdateQuestRewards(materialSheet); - - FungibleAssetValue tax = order.GetTax(); - var taxedPrice = order.Price - tax; - - // Transfer tax. - var arenaSheet = states.GetSheet(); - var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); - var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); - states = states.TransferAsset( - context, - context.Signer, - feeStoreAddress, - tax); - - // Transfer seller. - states = states.TransferAsset( - context, - context.Signer, - sellerAgentAddress, - taxedPrice - ); - - states = states - .SetLegacyState(digestListAddress, digestList.Serialize()) - .SetLegacyState(orderReceiptAddress, orderReceipt.Serialize()) - .SetAvatarState(sellerAvatarAddress, sellerAvatarState, true, true, true, true); - states = states.SetLegacyState(shardedShopAddress, shardedShopState.Serialize()); - return states; - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["a"] = AvatarAddress.Serialize(), - ["p"] = new List(ProductInfos.Select(p => p.Serialize())), - }.ToImmutableDictionary(); - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - AvatarAddress = plainValue["a"].ToAddress(); - var serialized = (List) plainValue["p"]; - ProductInfos = serialized.Cast().Select(ProductFactory.DeserializeProductInfo).ToList(); - } - } -} diff --git a/Lib9c/Action/BuyProduct2.cs b/Lib9c/Action/BuyProduct2.cs deleted file mode 100644 index 57b7b2168f..0000000000 --- a/Lib9c/Action/BuyProduct2.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Bencodex.Types; -using Lib9c.Model.Order; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using Nekoyume.Battle; -using Nekoyume.Model.EnumType; -using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; -using Nekoyume.Model.Market; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using static Lib9c.SerializeKeys; -using Log = Serilog.Log; - -namespace Nekoyume.Action -{ - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType("buy_product2")] - public class BuyProduct2 : GameAction - { - // Capacity from Buy limits in NineChronicles - // https://github.com/planetarium/NineChronicles/blob/v100372-1/nekoyume/Assets/_Scripts/UI/Shop/BuyView.cs#L127 - public const int Capacity = 20; - public Address AvatarAddress; - public IEnumerable ProductInfos; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IWorld states = context.PreviousState; - - var sw = new Stopwatch(); - sw.Start(); - var started = DateTimeOffset.UtcNow; - Log.Debug("BuyProduct exec started"); - - if (!ProductInfos.Any()) - { - throw new ListEmptyException("ProductInfos was empty."); - } - - if (ProductInfos.Count() > Capacity) - { - throw new ArgumentOutOfRangeException($"{nameof(ProductInfos)} must be less than or equal {Capacity}."); - - } - - if (ProductInfos.Any(p => p.AgentAddress == context.Signer || - p.AvatarAddress == AvatarAddress)) - { - throw new InvalidAddressException(); - } - - foreach (var productInfo in ProductInfos) - { - productInfo.ValidateType(); - } - - if (!states.TryGetAvatarState(context.Signer, AvatarAddress, out var buyerAvatarState)) - { - throw new FailedLoadStateException("failed load to buyer avatar state."); - } - - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - - if (!buyerAvatarState.worldInformation.IsStageCleared(GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - buyerAvatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(addressesHex, - GameConfig.RequireClearedStageLevel.ActionsInShop, current); - } - - var materialSheet = states.GetSheet(); - foreach (var productInfo in ProductInfos.OrderBy(p => p.ProductId).ThenBy(p =>p.Price)) - { - var sellerAgentAddress = productInfo.AgentAddress; - var sellerAvatarAddress = productInfo.AvatarAddress; - if (!states.TryGetAvatarState(sellerAgentAddress, sellerAvatarAddress, out var sellerAvatarState)) - { - throw new FailedLoadStateException($"failed load to seller avatar state."); - } - - if (productInfo is ItemProductInfo {Legacy: true} itemProductInfo) - { - var purchaseInfo = new PurchaseInfo(itemProductInfo.ProductId, itemProductInfo.TradableId, - sellerAgentAddress, sellerAvatarAddress, itemProductInfo.ItemSubType, - productInfo.Price); - states = Buy_Order(purchaseInfo, context, states, buyerAvatarState, materialSheet, sellerAvatarState); - } - else - { - states = Buy(context, productInfo, sellerAvatarAddress, states, sellerAgentAddress, buyerAvatarState, sellerAvatarState, materialSheet); - } - } - - states = states.SetAvatarState(AvatarAddress, buyerAvatarState); - - var ended = DateTimeOffset.UtcNow; - Log.Debug("BuyProduct Total Executed Time: {Elapsed}", ended - started); - return states; - } - - private IWorld Buy(IActionContext context, IProductInfo productInfo, Address sellerAvatarAddress, - IWorld states, Address sellerAgentAddress, AvatarState buyerAvatarState, AvatarState sellerAvatarState, - MaterialItemSheet materialSheet) - { - var productId = productInfo.ProductId; - var productsStateAddress = ProductsState.DeriveAddress(sellerAvatarAddress); - var productsState = new ProductsState((List) states.GetLegacyState(productsStateAddress)); - if (!productsState.ProductIds.Contains(productId)) - { - // sold out or canceled product. - throw new ProductNotFoundException($"can't find product {productId}"); - } - - productsState.ProductIds.Remove(productId); - - var productAddress = Product.DeriveAddress(productId); - var product = ProductFactory.DeserializeProduct((List) states.GetLegacyState(productAddress)); - product.Validate(productInfo); - - switch (product) - { - case FavProduct favProduct: - states = states.TransferAsset(context, productAddress, AvatarAddress, favProduct.Asset); - break; - case ItemProduct itemProduct: - { - switch (itemProduct.TradableItem) - { - case Costume costume: - // Fix RequiredBlockIndex from RegisterProduct0 - if (costume.RequiredBlockIndex > context.BlockIndex) - { - costume.RequiredBlockIndex = context.BlockIndex; - } - buyerAvatarState.UpdateFromAddCostume(costume, false); - break; - case ItemUsable itemUsable: - // Fix RequiredBlockIndex from RegisterProduct0 - if (itemUsable.RequiredBlockIndex > context.BlockIndex) - { - itemUsable.RequiredBlockIndex = context.BlockIndex; - } - buyerAvatarState.UpdateFromAddItem(itemUsable, false); - break; - case TradableMaterial tradableMaterial: - { - buyerAvatarState.UpdateFromAddItem(tradableMaterial, itemProduct.ItemCount, false); - break; - } - } - } - break; - default: - throw new InvalidProductTypeException($"{product} is not support type."); - } - - var sellerMail = new ProductSellerMail(context.BlockIndex, productId, - context.BlockIndex, productId); - sellerAvatarState.Update(sellerMail); - sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, product.Price); - sellerAvatarState.UpdateQuestRewards(materialSheet); - - var buyerMail = new ProductBuyerMail(context.BlockIndex, productId, - context.BlockIndex, productId - ); - buyerAvatarState.Update(buyerMail); - buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, product.Price); - buyerAvatarState.UpdateQuestRewards(materialSheet); - - // Transfer tax. - var arenaSheet = states.GetSheet(); - var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); - var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); - var tax = product.Price.DivRem(100, out _) * Action.Buy.TaxRate; - var taxedPrice = product.Price - tax; - - // Receipt - var receipt = new ProductReceipt(productId, sellerAvatarAddress, buyerAvatarState.address, product.Price, - context.BlockIndex); - states = states - .SetLegacyState(productAddress, Null.Value) - .SetLegacyState(productsStateAddress, productsState.Serialize()) - .SetAvatarState(sellerAvatarAddress, sellerAvatarState) - .SetLegacyState(ProductReceipt.DeriveAddress(productId), receipt.Serialize()) - .TransferAsset(context, context.Signer, feeStoreAddress, tax) - .TransferAsset(context, context.Signer, sellerAgentAddress, taxedPrice); - - return states; - } - - - // backward compatibility for order - shared shop state. - // TODO DELETE THIS METHOD AFTER PRODUCT MIGRATION END. - private static IWorld Buy_Order(PurchaseInfo purchaseInfo, IActionContext context, IWorld states, AvatarState buyerAvatarState, MaterialItemSheet materialSheet, AvatarState sellerAvatarState) - { - Address shardedShopAddress = - ShardedShopStateV2.DeriveAddress(purchaseInfo.ItemSubType, purchaseInfo.OrderId); - Address sellerAgentAddress = purchaseInfo.SellerAgentAddress; - Address sellerAvatarAddress = purchaseInfo.SellerAvatarAddress; - Guid orderId = purchaseInfo.OrderId; - Address orderAddress = Order.DeriveAddress(orderId); - Address digestListAddress = OrderDigestListState.DeriveAddress(sellerAvatarAddress); - - if (!states.TryGetLegacyState(shardedShopAddress, out Bencodex.Types.Dictionary shopStateDict)) - { - throw new FailedLoadStateException("failed to load shop state"); - } - - if (!states.TryGetLegacyState(orderAddress, out Dictionary rawOrder)) - { - throw new OrderIdDoesNotExistException($"{orderId}"); - } - - Order order = OrderFactory.Deserialize(rawOrder); - - var shardedShopState = new ShardedShopStateV2(shopStateDict); - shardedShopState.Remove(order, context.BlockIndex); - - if (!states.TryGetLegacyState(digestListAddress, out Dictionary rawDigestList)) - { - throw new FailedLoadStateException($"{orderId}"); - } - var digestList = new OrderDigestListState(rawDigestList); - - // migration method - sellerAvatarState.inventory.UnlockInvalidSlot(digestList, sellerAgentAddress, sellerAvatarAddress); - sellerAvatarState.inventory.ReconfigureFungibleItem(digestList, order.TradableId); - sellerAvatarState.inventory.LockByReferringToDigestList(digestList, order.TradableId, context.BlockIndex); - - digestList.Remove(orderId); - - var errorCode = order.ValidateTransfer(sellerAvatarState, purchaseInfo.TradableId, purchaseInfo.Price, context.BlockIndex); - switch (errorCode) - { - case Action.Buy.ErrorCodeInvalidAddress: - throw new InvalidAddressException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidTradableId: - throw new InvalidTradableIdException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidPrice: - throw new InvalidPriceException($"{orderId}"); - case Action.Buy.ErrorCodeShopItemExpired: - throw new ShopItemExpiredException($"{orderId}"); - case Action.Buy.ErrorCodeItemDoesNotExist: - throw new ItemDoesNotExistException($"{orderId}"); - case Action.Buy.ErrorCodeInvalidItemType: - throw new InvalidItemTypeException($"{orderId}"); - } - - // Check Balance. - FungibleAssetValue buyerBalance = states.GetBalance(context.Signer, states.GetGoldCurrency()); - if (buyerBalance < order.Price) - { - throw new InsufficientBalanceException($"{orderId}", buyerAvatarState.address, - buyerBalance); - } - - var orderReceipt = order.Transfer(sellerAvatarState, buyerAvatarState, context.BlockIndex); - - Address orderReceiptAddress = OrderReceipt.DeriveAddress(orderId); - if (!(states.GetLegacyState(orderReceiptAddress) is null)) - { - throw new DuplicateOrderIdException($"{orderId}"); - } - - var expirationMail = sellerAvatarState.mailBox.OfType() - .FirstOrDefault(m => m.OrderId.Equals(orderId)); - if (!(expirationMail is null)) - { - sellerAvatarState.mailBox.Remove(expirationMail); - } - - var orderSellerMail = new OrderSellerMail( - context.BlockIndex, - orderId, - context.BlockIndex, - orderId - ); - var orderBuyerMail = new OrderBuyerMail( - context.BlockIndex, - orderId, - context.BlockIndex, - orderId - ); - - buyerAvatarState.Update(orderBuyerMail); - sellerAvatarState.Update(orderSellerMail); - - // // Update quest. - buyerAvatarState.questList.UpdateTradeQuest(TradeType.Buy, order.Price); - sellerAvatarState.questList.UpdateTradeQuest(TradeType.Sell, order.Price); - - sellerAvatarState.updatedAt = context.BlockIndex; - sellerAvatarState.blockIndex = context.BlockIndex; - - buyerAvatarState.UpdateQuestRewards(materialSheet); - sellerAvatarState.UpdateQuestRewards(materialSheet); - - FungibleAssetValue tax = order.GetTax(); - var taxedPrice = order.Price - tax; - - // Transfer tax. - var arenaSheet = states.GetSheet(); - var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); - var feeStoreAddress = Addresses.GetShopFeeAddress(arenaData.ChampionshipId, arenaData.Round); - states = states.TransferAsset( - context, - context.Signer, - feeStoreAddress, - tax); - - // Transfer seller. - states = states.TransferAsset( - context, - context.Signer, - sellerAgentAddress, - taxedPrice - ); - - states = states - .SetLegacyState(digestListAddress, digestList.Serialize()) - .SetLegacyState(orderReceiptAddress, orderReceipt.Serialize()) - .SetAvatarState(sellerAvatarAddress, sellerAvatarState); - states = states.SetLegacyState(shardedShopAddress, shardedShopState.Serialize()); - return states; - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["a"] = AvatarAddress.Serialize(), - ["p"] = new List(ProductInfos.Select(p => p.Serialize())), - }.ToImmutableDictionary(); - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - AvatarAddress = plainValue["a"].ToAddress(); - var serialized = (List) plainValue["p"]; - ProductInfos = serialized.Cast().Select(ProductFactory.DeserializeProductInfo).ToList(); - } - } -} diff --git a/Lib9c/Action/ChargeActionPoint0.cs b/Lib9c/Action/ChargeActionPoint0.cs deleted file mode 100644 index 0e17e3941a..0000000000 --- a/Lib9c/Action/ChargeActionPoint0.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Model.Item; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Serilog; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("charge_action_point")] - public class ChargeActionPoint0 : GameAction, IChargeActionPointV1 - { - public Address avatarAddress; - - Address IChargeActionPointV1.AvatarAddress => avatarAddress; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - - var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); - - if (states.GetAgentState(context.Signer) is null) - { - return states; - } - - if (!states.TryGetAvatarState(context.Signer, avatarAddress, out var avatarState)) - { - return states; - } - - var row = states.GetSheet().Values.FirstOrDefault(r => r.ItemSubType == ItemSubType.ApStone); - var apStone = ItemFactory.CreateMaterial(row); - if (!avatarState.inventory.RemoveFungibleItem2(apStone)) - { - Log.Error("{AddressesHex}Not enough item {ApStone}", addressesHex, apStone); - return states; - } - - var gameConfigState = states.GetGameConfigState(); - if (gameConfigState is null) - { - return states; - } - - avatarState.actionPoint = gameConfigState.ActionPointMax; - return states.SetAvatarState(avatarAddress, avatarState); - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["avatarAddress"] = avatarAddress.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - avatarAddress = plainValue["avatarAddress"].ToAddress(); - } - } -} diff --git a/Lib9c/Action/ChargeActionPoint2.cs b/Lib9c/Action/ChargeActionPoint2.cs deleted file mode 100644 index 15772fd1bb..0000000000 --- a/Lib9c/Action/ChargeActionPoint2.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Model.Item; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("charge_action_point2")] - public class ChargeActionPoint2 : GameAction, IChargeActionPointV1 - { - public Address avatarAddress; - - Address IChargeActionPointV1.AvatarAddress => avatarAddress; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - - var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); - - if (!states.TryGetAvatarState(context.Signer, avatarAddress, out var avatarState)) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); - } - - var row = states.GetSheet().Values.First(r => r.ItemSubType == ItemSubType.ApStone); - if (!avatarState.inventory.RemoveFungibleItem(row.ItemId, context.BlockIndex)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({row.Id})"); - } - - var gameConfigState = states.GetGameConfigState(); - if (gameConfigState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the game config state was failed to load."); - } - - avatarState.actionPoint = gameConfigState.ActionPointMax; - return states.SetAvatarState(avatarAddress, avatarState); - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["avatarAddress"] = avatarAddress.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - avatarAddress = plainValue["avatarAddress"].ToAddress(); - } - } -} diff --git a/Lib9c/Action/CombinationEquipment.cs b/Lib9c/Action/CombinationEquipment.cs index a2a1bde18d..0b9f038191 100644 --- a/Lib9c/Action/CombinationEquipment.cs +++ b/Lib9c/Action/CombinationEquipment.cs @@ -685,17 +685,15 @@ SkillSheet skillSheet equipment.StatsMap.AddStatAdditionalValue(stat.StatType, stat.BaseValue); equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); } else { - var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); + var skill = CombinationEquipment.GetSkill(optionRow, skillSheet, random); if (!(skill is null)) { equipment.Skills.Add(skill); equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); } } } @@ -746,7 +744,6 @@ SkillSheet skillSheet equipment.Skills.Add(skill); equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); } } } diff --git a/Lib9c/Action/CombinationEquipment16.cs b/Lib9c/Action/CombinationEquipment16.cs deleted file mode 100644 index 72949aa1a1..0000000000 --- a/Lib9c/Action/CombinationEquipment16.cs +++ /dev/null @@ -1,767 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Extensions; -using Nekoyume.Helper; -using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; -using Nekoyume.Model.Skill; -using Nekoyume.Model.Stat; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Nekoyume.TableData.Crystal; -using Nekoyume.TableData.Pet; -using Serilog; -using static Lib9c.SerializeKeys; - -namespace Nekoyume.Action -{ - /// - /// Hard forked at https://github.com/planetarium/lib9c/pull/1711 - /// - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType("combination_equipment16")] - public class CombinationEquipment16 : GameAction, ICombinationEquipmentV4 - { - public const string AvatarAddressKey = "a"; - public Address avatarAddress; - - public const string SlotIndexKey = "s"; - public int slotIndex; - - public const string RecipeIdKey = "r"; - public int recipeId; - - public const string SubRecipeIdKey = "i"; - public int? subRecipeId; - public const string PayByCrystalKey = "p"; - public bool payByCrystal; - public const string UseHammerPointKey = "h"; - public bool useHammerPoint; - public const string PetIdKey = "pid"; - public int? petId; - - public const int BasicSubRecipeHammerPoint = 1; - public const int SpecialSubRecipeHammerPoint = 2; - - Address ICombinationEquipmentV4.AvatarAddress => avatarAddress; - int ICombinationEquipmentV4.RecipeId => recipeId; - int ICombinationEquipmentV4.SlotIndex => slotIndex; - int? ICombinationEquipmentV4.SubRecipeId => subRecipeId; - bool ICombinationEquipmentV4.PayByCrystal => payByCrystal; - bool ICombinationEquipmentV4.UseHammerPoint => useHammerPoint; - int? ICombinationEquipmentV4.PetId => petId; - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - [AvatarAddressKey] = avatarAddress.Serialize(), - [SlotIndexKey] = slotIndex.Serialize(), - [RecipeIdKey] = recipeId.Serialize(), - [SubRecipeIdKey] = subRecipeId.Serialize(), - [PayByCrystalKey] = payByCrystal.Serialize(), - [UseHammerPointKey] = useHammerPoint.Serialize(), - [PetIdKey] = petId.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal( - IImmutableDictionary plainValue) - { - avatarAddress = plainValue[AvatarAddressKey].ToAddress(); - slotIndex = plainValue[SlotIndexKey].ToInteger(); - recipeId = plainValue[RecipeIdKey].ToInteger(); - subRecipeId = plainValue[SubRecipeIdKey].ToNullableInteger(); - payByCrystal = plainValue[PayByCrystalKey].ToBoolean(); - useHammerPoint = plainValue[UseHammerPointKey].ToBoolean(); - petId = plainValue[PetIdKey].ToNullableInteger(); - } - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - var random = context.GetRandom(); - var slotAddress = avatarAddress.Derive( - string.Format( - CultureInfo.InvariantCulture, - CombinationSlotState.DeriveFormat, - slotIndex - ) - ); - - var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); - var started = DateTimeOffset.UtcNow; - Log.Debug("{AddressesHex}CombinationEquipment exec started", addressesHex); - - var agentState = states.GetAgentState(context.Signer); - if (agentState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the agent state of the signer was failed to load."); - } - - if (!states.TryGetAvatarState(context.Signer, avatarAddress, out var avatarState)) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the avatar state of the signer was failed to load."); - } - - // Validate Required Cleared Tutorial Stage - if (!avatarState.worldInformation.IsStageCleared( - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - addressesHex, - GameConfig.RequireClearedStageLevel.CombinationEquipmentAction, - current); - } - // ~Validate Required Cleared Tutorial Stage - - // Validate SlotIndex - var slotState = states.GetCombinationSlotState(avatarAddress, slotIndex); - if (slotState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the slot state is failed to load: # {slotIndex}"); - } - - if (!slotState.Validate(avatarState, context.BlockIndex)) - { - throw new CombinationSlotUnlockException( - $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {slotIndex}"); - } - // ~Validate SlotIndex - - // Validate PetState - PetState petState = null; - if (petId.HasValue) - { - var petStateAddress = PetState.DeriveAddress(avatarAddress, petId.Value); - if (!states.TryGetLegacyState(petStateAddress, out List rawState)) - { - throw new FailedLoadStateException($"{addressesHex}Aborted as the {nameof(PetState)} was failed to load."); - } - petState = new PetState(rawState); - - if (!petState.Validate(context.BlockIndex)) - { - throw new PetIsLockedException($"{addressesHex}Aborted as the pet is already in use."); - } - } - // ~Validate PetState - - // Validate Work - var costActionPoint = 0; - var costNcg = 0L; - var endBlockIndex = context.BlockIndex; - var requiredFungibleItems = new Dictionary(); - - Dictionary sheets = states.GetSheets(sheetTypes: new[] - { - typeof(EquipmentItemRecipeSheet), - typeof(EquipmentItemSheet), - typeof(MaterialItemSheet), - typeof(EquipmentItemSubRecipeSheetV2), - typeof(EquipmentItemOptionSheet), - typeof(SkillSheet), - typeof(CrystalMaterialCostSheet), - typeof(CrystalFluctuationSheet), - typeof(CrystalHammerPointSheet), - typeof(ConsumableItemRecipeSheet), - }); - - // Validate RecipeId - var equipmentItemRecipeSheet = sheets.GetSheet(); - if (!equipmentItemRecipeSheet.TryGetValue(recipeId, out var recipeRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(EquipmentItemRecipeSheet), - recipeId); - } - // ~Validate RecipeId - - // Validate Recipe ResultEquipmentId - var equipmentItemSheet = sheets.GetSheet(); - if (!equipmentItemSheet.TryGetValue(recipeRow.ResultEquipmentId, out var equipmentRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(equipmentItemSheet), - recipeRow.ResultEquipmentId); - } - // ~Validate Recipe ResultEquipmentId - - // Validate Recipe Material - var materialItemSheet = sheets.GetSheet(); - if (!materialItemSheet.TryGetValue(recipeRow.MaterialId, out var materialRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(MaterialItemSheet), - recipeRow.MaterialId); - } - - if (requiredFungibleItems.ContainsKey(materialRow.Id)) - { - requiredFungibleItems[materialRow.Id] += recipeRow.MaterialCount; - } - else - { - requiredFungibleItems[materialRow.Id] = recipeRow.MaterialCount; - } - // ~Validate Recipe Material - - // Validate Recipe Unlocked. - if (equipmentItemRecipeSheet[recipeId].CRYSTAL != 0) - { - var unlockedRecipeIdsAddress = avatarAddress.Derive("recipe_ids"); - if (!states.TryGetLegacyState(unlockedRecipeIdsAddress, out List rawIds)) - { - throw new FailedLoadStateException("can't find UnlockedRecipeList."); - } - - var unlockedIds = rawIds.ToList(StateExtensions.ToInteger); - if (!unlockedIds.Contains(recipeId)) - { - throw new InvalidRecipeIdException($"unlock {recipeId} first."); - } - - if (!avatarState.worldInformation.IsStageCleared(recipeRow.UnlockStage)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - addressesHex, - recipeRow.UnlockStage, - current); - } - } - // ~Validate Recipe Unlocked - - // Validate SubRecipeId - EquipmentItemSubRecipeSheetV2.Row subRecipeRow = null; - if (subRecipeId.HasValue) - { - if (!recipeRow.SubRecipeIds.Contains(subRecipeId.Value)) - { - throw new SheetRowColumnException( - $"{addressesHex}Aborted as the sub recipe {subRecipeId.Value} was failed to load from the sheet." - ); - } - - var equipmentItemSubRecipeSheetV2 = sheets.GetSheet(); - if (!equipmentItemSubRecipeSheetV2.TryGetValue(subRecipeId.Value, out subRecipeRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(EquipmentItemSubRecipeSheetV2), - subRecipeId.Value); - } - - // Validate SubRecipe Material - for (var i = subRecipeRow.Materials.Count; i > 0; i--) - { - var materialInfo = subRecipeRow.Materials[i - 1]; - if (!materialItemSheet.TryGetValue(materialInfo.Id, out materialRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(MaterialItemSheet), - materialInfo.Id); - } - - if (requiredFungibleItems.ContainsKey(materialRow.Id)) - { - requiredFungibleItems[materialRow.Id] += materialInfo.Count; - } - else - { - requiredFungibleItems[materialRow.Id] = materialInfo.Count; - } - } - // ~Validate SubRecipe Material - - costActionPoint += subRecipeRow.RequiredActionPoint; - costNcg += subRecipeRow.RequiredGold; - endBlockIndex += subRecipeRow.RequiredBlockIndex; - } - // ~Validate SubRecipeId - - costActionPoint += recipeRow.RequiredActionPoint; - costNcg += recipeRow.RequiredGold; - endBlockIndex += recipeRow.RequiredBlockIndex; - // ~Validate Work - - var existHammerPointSheet = - sheets.TryGetSheet(out CrystalHammerPointSheet hammerPointSheet); - var hammerPointAddress = - Addresses.GetHammerPointStateAddress(avatarAddress, recipeId); - var hammerPointState = new HammerPointState(hammerPointAddress, recipeId); - CrystalHammerPointSheet.Row hammerPointRow = null; - if (existHammerPointSheet) - { - if (states.TryGetLegacyState(hammerPointAddress, out List serialized)) - { - hammerPointState = - new HammerPointState(hammerPointAddress, serialized); - } - - // Validate HammerPointSheet by recipeId - if (!hammerPointSheet.TryGetValue(recipeId, out hammerPointRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(CrystalHammerPointSheet), - recipeId); - } - } - - var isMimisbrunnrSubRecipe = subRecipeRow?.IsMimisbrunnrSubRecipe ?? - subRecipeId.HasValue && recipeRow.SubRecipeIds[2] == subRecipeId.Value; - var petOptionSheet = states.GetSheet(); - if (useHammerPoint) - { - if (!existHammerPointSheet) - { - throw new FailedLoadSheetException(typeof(CrystalHammerPointSheet)); - } - - if (isMimisbrunnrSubRecipe) - { - throw new ArgumentException( - $"Can not super craft with mimisbrunnr recipe. Subrecipe id: {subRecipeId}"); - } - - if (hammerPointState.HammerPoint < hammerPointRow.MaxPoint) - { - throw new NotEnoughHammerPointException( - $"Not enough hammer points. Need : {hammerPointRow.MaxPoint}, own : {hammerPointState.HammerPoint}"); - } - - states = UseAssetsBySuperCraft( - states, - context, - hammerPointRow, - hammerPointState); - } - else - { - states = UseAssetsByNormalCombination( - states, - context, - avatarState, - hammerPointState, - petState, - sheets, - materialItemSheet, - hammerPointSheet, - petOptionSheet, - recipeRow, - subRecipeRow, - requiredFungibleItems, - addressesHex); - } - - // Subtract Required ActionPoint - if (costActionPoint > 0) - { - if (avatarState.actionPoint < costActionPoint) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" - ); - } - - avatarState.actionPoint -= costActionPoint; - } - // ~Subtract Required ActionPoint - - // Transfer Required NCG - if (costNcg > 0L) - { - var arenaSheet = states.GetSheet(); - var arenaData = arenaSheet.GetRoundByBlockIndex(context.BlockIndex); - var feeStoreAddress = Addresses.GetBlacksmithFeeAddress(arenaData.ChampionshipId, arenaData.Round); - - states = states.TransferAsset( - context, - context.Signer, - feeStoreAddress, - states.GetGoldCurrency() * costNcg - ); - } - // ~Transfer Required NCG - - // Create Equipment - var equipment = (Equipment) ItemFactory.CreateItemUsable( - equipmentRow, - random.GenerateRandomGuid(), - endBlockIndex, - madeWithMimisbrunnrRecipe: isMimisbrunnrSubRecipe - ); - - if (!(subRecipeRow is null)) - { - AddAndUnlockOption( - agentState, - petState, - equipment, - random, - subRecipeRow, - sheets.GetSheet(), - petOptionSheet, - sheets.GetSheet() - ); - endBlockIndex = equipment.RequiredBlockIndex; - - if (useHammerPoint) - { - if (!equipment.Skills.Any()) - { - AddSkillOption( - agentState, - equipment, - random, - subRecipeRow, - sheets.GetSheet(), - sheets.GetSheet() - ); - } - - var firstFoodRow = sheets.GetSheet() - .First; - if (firstFoodRow is null) - { - throw new SheetRowNotFoundException( - $"{nameof(ConsumableItemRecipeSheet)}'s first row is null.", 0); - } - - endBlockIndex = equipment.RequiredBlockIndex = - context.BlockIndex + firstFoodRow.RequiredBlockIndex; - } - } - // ~Create Equipment - - // Apply block time discount - if (!(petState is null)) - { - var requiredBlockIndex = endBlockIndex - context.BlockIndex; - var gameConfigState = states.GetGameConfigState(); - requiredBlockIndex = PetHelper.CalculateReducedBlockOnCraft( - requiredBlockIndex, - gameConfigState.RequiredAppraiseBlock, - petState, - petOptionSheet); - endBlockIndex = context.BlockIndex + requiredBlockIndex; - equipment.Update(endBlockIndex); - } - - // Add or Update Equipment - avatarState.blockIndex = context.BlockIndex; - avatarState.updatedAt = context.BlockIndex; - avatarState.questList.UpdateCombinationEquipmentQuest(recipeId); - avatarState.UpdateFromCombination(equipment); - avatarState.UpdateQuestRewards(materialItemSheet); - // ~Add or Update Equipment - - // Update Slot - var mailId = random.GenerateRandomGuid(); - var attachmentResult = new CombinationConsumable5.ResultModel - { - id = mailId, - actionPoint = costActionPoint, - gold = costNcg, - materials = requiredFungibleItems.ToDictionary( - e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), - e => e.Value), - itemUsable = equipment, - recipeId = recipeId, - subRecipeId = subRecipeId, - }; - slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex, petId); - // ~Update Slot - - // Update Pet - if (!(petState is null)) - { - petState.Update(endBlockIndex); - var petStateAddress = PetState.DeriveAddress(avatarAddress, petState.PetId); - states = states.SetLegacyState(petStateAddress, petState.Serialize()); - } - // ~Update Pet - - // Create Mail - var mail = new CombinationMail( - attachmentResult, - context.BlockIndex, - mailId, - endBlockIndex); - avatarState.Update(mail); - // ~Create Mail - - var ended = DateTimeOffset.UtcNow; - Log.Debug("{AddressesHex}CombinationEquipment Total Executed Time: {Elapsed}", addressesHex, ended - started); - return states - .SetAvatarState(avatarAddress, avatarState) - .SetLegacyState(slotAddress, slotState.Serialize()) - .SetLegacyState(hammerPointAddress,hammerPointState.Serialize()) - .SetAgentState(context.Signer, agentState); - } - - private IWorld UseAssetsBySuperCraft( - IWorld states, - IActionContext context, - CrystalHammerPointSheet.Row row, - HammerPointState hammerPointState) - { - var crystalBalance = states.GetBalance(context.Signer, CrystalCalculator.CRYSTAL); - var hammerPointCost = CrystalCalculator.CRYSTAL * row.CRYSTAL; - if (crystalBalance < hammerPointCost) - { - throw new NotEnoughFungibleAssetValueException($"required {hammerPointCost}, but balance is {crystalBalance}"); - } - - hammerPointState.ResetHammerPoint(); - return states.TransferAsset( - context, - context.Signer, - Addresses.SuperCraft, - hammerPointCost); - } - - private IWorld UseAssetsByNormalCombination( - IWorld states, - IActionContext context, - AvatarState avatarState, - HammerPointState hammerPointState, - PetState petState, - Dictionary sheets, - MaterialItemSheet materialItemSheet, - CrystalHammerPointSheet hammerPointSheet, - PetOptionSheet petOptionSheet, - EquipmentItemRecipeSheet.Row recipeRow, - EquipmentItemSubRecipeSheetV2.Row subRecipeRow, - Dictionary requiredFungibleItems, - string addressesHex) - { - // Remove Required Materials - var inventory = avatarState.inventory; - var crystalMaterialSheet = sheets.GetSheet(); - var costCrystal = CrystalCalculator.CRYSTAL * 0; - foreach (var pair in requiredFungibleItems.OrderBy(pair => pair.Key)) - { - var itemId = pair.Key; - var requiredCount = pair.Value; - if (materialItemSheet.TryGetValue(itemId, out var materialRow)) - { - int itemCount = inventory.TryGetItem(itemId, out Inventory.Item item) - ? item.count - : 0; - if (itemCount < requiredCount && payByCrystal) - { - costCrystal += CrystalCalculator.CalculateMaterialCost( - itemId, - requiredCount - itemCount, - crystalMaterialSheet); - requiredCount = itemCount; - } - - if (requiredCount > 0 && !inventory.RemoveFungibleItem(materialRow.ItemId, - context.BlockIndex, - requiredCount)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); - } - } - else - { - throw new SheetRowNotFoundException(nameof(MaterialItemSheet), itemId); - } - } - - // ~Remove Required Materials - if (costCrystal > 0 * CrystalCalculator.CRYSTAL) - { - var crystalFluctuationSheet = sheets.GetSheet(); - var row = crystalFluctuationSheet.Values - .First(r => r.Type == CrystalFluctuationSheet.ServiceType.Combination); - var (dailyCostState, weeklyCostState, _, _) = - states.GetCrystalCostStates(context.BlockIndex, row.BlockInterval); - - // 1x fixed crystal cost. - costCrystal = CrystalCalculator.CalculateCombinationCost( - costCrystal, - row: row, - prevWeeklyCostState: null, - beforePrevWeeklyCostState: null); - - // Apply pet discount if possible. - if (!(petState is null)) - { - costCrystal = PetHelper.CalculateDiscountedMaterialCost( - costCrystal, - petState, - petOptionSheet); - } - - // Update Daily Formula. - dailyCostState.Count++; - dailyCostState.CRYSTAL += costCrystal; - // Update Weekly Formula. - weeklyCostState.Count++; - weeklyCostState.CRYSTAL += costCrystal; - - var crystalBalance = - states.GetBalance(context.Signer, CrystalCalculator.CRYSTAL); - if (costCrystal > crystalBalance) - { - throw new NotEnoughFungibleAssetValueException( - $"required {costCrystal}, but balance is {crystalBalance}"); - } - - states = states - .SetLegacyState(dailyCostState.Address, dailyCostState.Serialize()) - .SetLegacyState(weeklyCostState.Address, weeklyCostState.Serialize()) - .TransferAsset(context, context.Signer, Addresses.MaterialCost, costCrystal); - } - - int hammerPoint; - if (subRecipeRow?.RewardHammerPoint.HasValue ?? false) - { - hammerPoint = subRecipeRow.RewardHammerPoint.Value; - } - else - { - var isBasicSubRecipe = !subRecipeId.HasValue || - recipeRow.SubRecipeIds[0] == subRecipeId.Value; - hammerPoint = isBasicSubRecipe - ? BasicSubRecipeHammerPoint - : SpecialSubRecipeHammerPoint; - } - - hammerPointState.AddHammerPoint(hammerPoint, hammerPointSheet); - return states; - } - - public static void AddAndUnlockOption( - AgentState agentState, - PetState petState, - Equipment equipment, - IRandom random, - EquipmentItemSubRecipeSheetV2.Row subRecipe, - EquipmentItemOptionSheet optionSheet, - PetOptionSheet petOptionSheet, - SkillSheet skillSheet - ) - { - foreach (var optionInfo in subRecipe.Options - .OrderByDescending(e => e.Ratio) - .ThenBy(e => e.RequiredBlockIndex) - .ThenBy(e => e.Id)) - { - if (!optionSheet.TryGetValue(optionInfo.Id, out var optionRow)) - { - continue; - } - - var value = random.Next(1, GameConfig.MaximumProbability + 1); - var ratio = optionInfo.Ratio; - - // Apply pet bonus if possible - if (!(petState is null)) - { - ratio = PetHelper.GetBonusOptionProbability( - ratio, - petState, - petOptionSheet); - } - - if (value > ratio) - { - continue; - } - - if (optionRow.StatType != StatType.NONE) - { - var stat = CombinationEquipment5.GetStat(optionRow, random); - equipment.StatsMap.AddStatAdditionalValue(stat.StatType, stat.BaseValue); - equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); - equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); - } - else - { - var skill = CombinationEquipment16.GetSkill(optionRow, skillSheet, random); - if (!(skill is null)) - { - equipment.Skills.Add(skill); - equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); - equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); - } - } - } - } - - public static Skill GetSkill( - EquipmentItemOptionSheet.Row row, - SkillSheet skillSheet, - IRandom random) - { - var skillRow = skillSheet.OrderedList.FirstOrDefault(r => r.Id == row.SkillId); - if (skillRow == null) - { - return null; - } - - var dmg = random.Next(row.SkillDamageMin, row.SkillDamageMax + 1); - var chance = random.Next(row.SkillChanceMin, row.SkillChanceMax + 1); - - var hasStatDamageRatio = row.StatDamageRatioMin != default && row.StatDamageRatioMax != default; - var statDamageRatio = hasStatDamageRatio ? - random.Next(row.StatDamageRatioMin, row.StatDamageRatioMax + 1) : default; - var refStatType = hasStatDamageRatio ? row.ReferencedStatType : StatType.NONE; - - var skill = SkillFactory.Get(skillRow, dmg, chance, statDamageRatio, refStatType); - return skill; - } - - public static void AddSkillOption( - AgentState agentState, - Equipment equipment, - IRandom random, - EquipmentItemSubRecipeSheetV2.Row subRecipe, - EquipmentItemOptionSheet optionSheet, - SkillSheet skillSheet - ) - { - foreach (var optionInfo in subRecipe.Options) - { - if (!optionSheet.TryGetValue(optionInfo.Id, out var optionRow)) - { - continue; - } - - var skill = GetSkill(optionRow, skillSheet, random); - if (!(skill is null)) - { - equipment.Skills.Add(skill); - equipment.Update(equipment.RequiredBlockIndex + optionInfo.RequiredBlockIndex); - equipment.optionCountFromCombination++; - agentState.unlockedOptions.Add(optionRow.Id); - } - } - } - } -} diff --git a/Lib9c/Action/CombinationEquipment5.cs b/Lib9c/Action/CombinationEquipment5.cs index c4e4b3bf8f..48488e37ab 100644 --- a/Lib9c/Action/CombinationEquipment5.cs +++ b/Lib9c/Action/CombinationEquipment5.cs @@ -1,262 +1,20 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Globalization; using System.Linq; -using System.Numerics; -using Bencodex.Types; -using Lib9c.Abstractions; using Libplanet.Action; -using Libplanet.Action.State; using Libplanet.Crypto; -using Libplanet.Types.Assets; using Nekoyume.Battle; using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; using Nekoyume.Model.Skill; using Nekoyume.Model.Stat; -using Nekoyume.Model.State; -using Nekoyume.Module; using Nekoyume.TableData; namespace Nekoyume.Action { - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("combination_equipment5")] - public class CombinationEquipment5 : GameAction, ICombinationEquipmentV1 + public static class CombinationEquipment5 { public static readonly Address BlacksmithAddress = ItemEnhancement9.BlacksmithAddress; - public Address AvatarAddress; - public int RecipeId; - public int SlotIndex; - public int? SubRecipeId; - - Address ICombinationEquipmentV1.AvatarAddress => AvatarAddress; - int ICombinationEquipmentV1.RecipeId => RecipeId; - int ICombinationEquipmentV1.SlotIndex => SlotIndex; - int? ICombinationEquipmentV1.SubRecipeId => SubRecipeId; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IActionContext ctx = context; - var states = ctx.PreviousState; - var slotAddress = AvatarAddress.Derive( - string.Format( - CultureInfo.InvariantCulture, - CombinationSlotState.DeriveFormat, - SlotIndex - ) - ); - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - - var agentState = states.GetAgentState(ctx.Signer); - if (agentState is null) - { - throw new FailedLoadStateException($"{addressesHex}Aborted as the agent state of the signer was failed to load."); - } - - if (!states.TryGetAvatarState(ctx.Signer, AvatarAddress, out var avatarState)) - { - throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); - } - - var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); - if (slotState is null) - { - throw new FailedLoadStateException($"{addressesHex}Aborted as the slot state is failed to load"); - } - - if (!slotState.Validate(avatarState, ctx.BlockIndex)) - { - throw new CombinationSlotUnlockException( - $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); - } - - var recipeSheet = states.GetSheet(); - var materialSheet = states.GetSheet(); - var materials = new Dictionary(); - - // Validate recipe. - if (!recipeSheet.TryGetValue(RecipeId, out var recipe)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemRecipeSheet), RecipeId); - } - - if (!(SubRecipeId is null)) - { - if (!recipe.SubRecipeIds.Contains((int) SubRecipeId)) - { - throw new SheetRowColumnException( - $"{addressesHex}Aborted as the sub recipe {SubRecipeId} was failed to load from the sheet." - ); - } - } - - // Validate main recipe is unlocked. - if (!avatarState.worldInformation.IsStageCleared(recipe.UnlockStage)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException(addressesHex, recipe.UnlockStage, current); - } - - if (!materialSheet.TryGetValue(recipe.MaterialId, out var material)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), recipe.MaterialId); - } - - if (!avatarState.inventory.RemoveFungibleItem2(material.ItemId, recipe.MaterialCount)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({material} * {recipe.MaterialCount})" - ); - } - - var equipmentMaterial = ItemFactory.CreateMaterial(materialSheet, material.Id); - materials[equipmentMaterial] = recipe.MaterialCount; - - BigInteger requiredGold = recipe.RequiredGold; - var requiredActionPoint = recipe.RequiredActionPoint; - var equipmentItemSheet = states.GetSheet(); - - // Validate equipment id. - if (!equipmentItemSheet.TryGetValue(recipe.ResultEquipmentId, out var equipRow)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(equipmentItemSheet), recipe.ResultEquipmentId); - } - - var requiredBlockIndex = ctx.BlockIndex + recipe.RequiredBlockIndex; - var random = context.GetRandom(); - var equipment = (Equipment) ItemFactory.CreateItemUsable( - equipRow, - random.GenerateRandomGuid(), - requiredBlockIndex - ); - - // Validate sub recipe. - HashSet optionIds = null; - if (SubRecipeId.HasValue) - { - var subSheet = states.GetSheet(); - var subId = (int) SubRecipeId; - if (!subSheet.TryGetValue(subId, out var subRecipe)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(EquipmentItemSubRecipeSheet), subId); - } - - requiredBlockIndex += subRecipe.RequiredBlockIndex; - requiredGold += subRecipe.RequiredGold; - requiredActionPoint += subRecipe.RequiredActionPoint; - - foreach (var materialInfo in subRecipe.Materials) - { - if (!materialSheet.TryGetValue(materialInfo.Id, out var subMaterialRow)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(MaterialItemSheet), materialInfo.Id); - } - - if (!avatarState.inventory.RemoveFungibleItem2(subMaterialRow.ItemId, - materialInfo.Count)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({subMaterialRow} * {materialInfo.Count})" - ); - } - - var subMaterial = ItemFactory.CreateMaterial(materialSheet, materialInfo.Id); - materials[subMaterial] = materialInfo.Count; - } - - optionIds = SelectOption(states.GetSheet(), states.GetSheet(), - subRecipe, random, equipment); - equipment.Update(requiredBlockIndex); - } - - // Validate NCG. - FungibleAssetValue agentBalance = states.GetBalance(ctx.Signer, states.GetGoldCurrency()); - if (agentBalance < states.GetGoldCurrency() * requiredGold) - { - throw new InsufficientBalanceException( - $"{addressesHex}Aborted as the agent ({ctx.Signer}) has no sufficient gold: {agentBalance} < {requiredGold}", - ctx.Signer, - agentBalance - ); - } - - if (avatarState.actionPoint < requiredActionPoint) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {requiredActionPoint}" - ); - } - - avatarState.actionPoint -= requiredActionPoint; - if (!(optionIds is null)) - { - foreach (var id in optionIds.OrderBy(id => id)) - { - agentState.unlockedOptions.Add(id); - } - } - - // FIXME: BlacksmithAddress just accumulate NCG. we need plan how to circulate this. - if (requiredGold > 0) - { - states = states.TransferAsset( - ctx, - ctx.Signer, - BlacksmithAddress, - states.GetGoldCurrency() * requiredGold - ); - } - - var result = new CombinationConsumable5.ResultModel - { - actionPoint = requiredActionPoint, - gold = requiredGold, - materials = materials, - itemUsable = equipment, - recipeId = RecipeId, - subRecipeId = SubRecipeId, - itemType = ItemType.Equipment, - }; - slotState.Update(result, ctx.BlockIndex, requiredBlockIndex); - var mail = new CombinationMail(result, ctx.BlockIndex, random.GenerateRandomGuid(), - requiredBlockIndex); - result.id = mail.id; - avatarState.Update(mail); - avatarState.questList.UpdateCombinationEquipmentQuest(RecipeId); - avatarState.UpdateFromCombination2(equipment); - avatarState.UpdateQuestRewards2(materialSheet); - return states - .SetAvatarState(AvatarAddress, avatarState) - .SetLegacyState(slotAddress, slotState.Serialize()) - .SetAgentState(ctx.Signer, agentState); - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["avatarAddress"] = AvatarAddress.Serialize(), - ["recipeId"] = RecipeId.Serialize(), - ["subRecipeId"] = SubRecipeId.Serialize(), - ["slotIndex"] = SlotIndex.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal( - IImmutableDictionary plainValue) - { - AvatarAddress = plainValue["avatarAddress"].ToAddress(); - RecipeId = plainValue["recipeId"].ToInteger(); - SubRecipeId = plainValue["subRecipeId"].ToNullableInteger(); - SlotIndex = plainValue["slotIndex"].ToInteger(); - } - public static DecimalStat GetStat(EquipmentItemOptionSheet.Row row, IRandom random) { var value = random.Next(row.StatMin, row.StatMax + 1); @@ -271,7 +29,13 @@ public static Skill GetSkill(EquipmentItemOptionSheet.Row row, SkillSheet skillS var skillRow = skillSheet.OrderedList.First(r => r.Id == row.SkillId); var dmg = random.Next(row.SkillDamageMin, row.SkillDamageMax + 1); var chance = random.Next(row.SkillChanceMin, row.SkillChanceMax + 1); - var skill = SkillFactory.GetV1(skillRow, dmg, chance); + + var hasStatDamageRatio = row.StatDamageRatioMin != default && row.StatDamageRatioMax != default; + var statDamageRatio = hasStatDamageRatio ? + random.Next(row.StatDamageRatioMin, row.StatDamageRatioMax + 1) : default; + var refStatType = hasStatDamageRatio ? row.ReferencedStatType : StatType.NONE; + + var skill = SkillFactory.Get(skillRow, dmg, chance, statDamageRatio, refStatType); return skill; } catch (InvalidOperationException) diff --git a/Lib9c/Action/EventConsumableItemCrafts0.cs b/Lib9c/Action/EventConsumableItemCrafts0.cs deleted file mode 100644 index 1b7391781b..0000000000 --- a/Lib9c/Action/EventConsumableItemCrafts0.cs +++ /dev/null @@ -1,305 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Extensions; -using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Nekoyume.TableData.Event; -using Serilog; -using static Lib9c.SerializeKeys; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType(ActionTypeText)] - public class EventConsumableItemCrafts0 : GameAction, IEventConsumableItemCraftsV1 - { - private const string ActionTypeText = "event_consumable_item_crafts"; - - public Address AvatarAddress; - public int EventScheduleId; - public int EventConsumableItemRecipeId; - public int SlotIndex; - - Address IEventConsumableItemCraftsV1.AvatarAddress => AvatarAddress; - int IEventConsumableItemCraftsV1.EventScheduleId => EventScheduleId; - int IEventConsumableItemCraftsV1.EventConsumableItemRecipeId => EventConsumableItemRecipeId; - int IEventConsumableItemCraftsV1.SlotIndex => SlotIndex; - - protected override IImmutableDictionary PlainValueInternal - { - get - { - var list = Bencodex.Types.List.Empty - .Add(AvatarAddress.Serialize()) - .Add(EventScheduleId.Serialize()) - .Add(EventConsumableItemRecipeId.Serialize()) - .Add(SlotIndex.Serialize()); - - return new Dictionary - { - { "l", list }, - }.ToImmutableDictionary(); - } - } - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - if (!plainValue.TryGetValue("l", out var serialized)) - { - throw new ArgumentException("plainValue must contain 'l'"); - } - - if (!(serialized is Bencodex.Types.List list)) - { - throw new ArgumentException("'l' must be a bencodex list"); - } - - if (list.Count < 4) - { - throw new ArgumentException("'l' must contain at least 4 items"); - } - - AvatarAddress = list[0].ToAddress(); - EventScheduleId = list[1].ToInteger(); - EventConsumableItemRecipeId = list[2].ToInteger(); - SlotIndex = list[3].ToInteger(); - } - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - var random = context.GetRandom(); - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - var started = DateTimeOffset.UtcNow; - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Execute() start", - ActionTypeText, - addressesHex); - - var sw = new Stopwatch(); - // Get AvatarState - sw.Start(); - if (!states.TryGetAvatarState( - context.Signer, - AvatarAddress, - out var avatarState)) - { - throw new FailedLoadStateException( - ActionTypeText, - addressesHex, - typeof(AvatarState), - AvatarAddress); - } - - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] TryGetAvatarStateV2: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Get AvatarState - - // Get sheets - sw.Restart(); - var sheets = states.GetSheets( - sheetTypes: new[] - { - typeof(EventScheduleSheet), - typeof(EventConsumableItemRecipeSheet), - }); - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Get sheets: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Get sheets - - // Validate Requirements. - sw.Restart(); - avatarState.worldInformation.ValidateFromAction( - GameConfig.RequireClearedStageLevel.CombinationConsumableAction, - ActionTypeText, - addressesHex); - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Requirements. - - // Validate fields. - sw.Restart(); - var scheduleSheet = sheets.GetSheet(); - scheduleSheet.ValidateFromActionForRecipe( - context.BlockIndex, - EventScheduleId, - EventConsumableItemRecipeId, - ActionTypeText, - addressesHex); - - var recipeSheet = sheets.GetSheet(); - var recipeRow = recipeSheet.ValidateFromAction( - EventConsumableItemRecipeId, - ActionTypeText, - addressesHex); - - var slotState = states.GetCombinationSlotState(AvatarAddress, SlotIndex); - if (slotState is null) - { - throw new FailedLoadStateException( - $"{addressesHex}Aborted as the slot state is failed to load: # {SlotIndex}"); - } - - if (!slotState.Validate(avatarState, context.BlockIndex)) - { - throw new CombinationSlotUnlockException( - $"{addressesHex}Aborted as the slot state is invalid: {slotState} @ {SlotIndex}"); - } - - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate fields. - - // Validate Work - sw.Restart(); - var costActionPoint = 0; - var endBlockIndex = context.BlockIndex; - var requiredFungibleItems = new Dictionary(); - - // Validate Recipe ResultEquipmentId - var consumableItemSheet = states.GetSheet(); - if (!consumableItemSheet.TryGetValue( - recipeRow.ResultConsumableItemId, - out var consumableRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(consumableItemSheet), - recipeRow.ResultConsumableItemId); - } - // ~Validate Recipe ResultEquipmentId - - // Validate Recipe Material - var materialItemSheet = states.GetSheet(); - materialItemSheet.ValidateFromAction( - recipeRow.Materials, - requiredFungibleItems, - addressesHex); - // ~Validate Recipe Material - - costActionPoint += recipeRow.RequiredActionPoint; - endBlockIndex += recipeRow.RequiredBlockIndex; - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate work: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Work - - // Remove Required Materials - var inventory = avatarState.inventory; -#pragma warning disable LAA1002 - foreach (var pair in requiredFungibleItems) -#pragma warning restore LAA1002 - { - if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || - !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); - } - } - // ~Remove Required Materials - - // Subtract Required ActionPoint - if (costActionPoint > 0) - { - if (avatarState.actionPoint < costActionPoint) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: {avatarState.actionPoint} < {costActionPoint}" - ); - } - - avatarState.actionPoint -= costActionPoint; - } - // ~Subtract Required ActionPoint - - // Create and Add Consumable - var consumable = ItemFactory.CreateItemUsable( - consumableRow, - random.GenerateRandomGuid(), - endBlockIndex - ); - avatarState.inventory.AddItem(consumable); - // ~Create and Add Consumable - - // Update Slot - var mailId = random.GenerateRandomGuid(); - var attachmentResult = new CombinationConsumable5.ResultModel - { - id = mailId, - actionPoint = costActionPoint, - materials = requiredFungibleItems.ToDictionary( - e => ItemFactory.CreateMaterial(materialItemSheet, e.Key), - e => e.Value), - itemUsable = consumable, - recipeId = EventConsumableItemRecipeId, - }; - slotState.Update(attachmentResult, context.BlockIndex, endBlockIndex); - // ~Update Slot - - // Create Mail - var mail = new CombinationMail( - attachmentResult, - context.BlockIndex, - mailId, - endBlockIndex); - avatarState.Update(mail); - // ~Create Mail - - // Set states - states = states - .SetAvatarState(AvatarAddress, avatarState) - .SetLegacyState( - CombinationSlotState.DeriveAddress(AvatarAddress, SlotIndex), - slotState.Serialize()); - - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Set states: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Set states - - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Total elapsed: {Elapsed}", - ActionTypeText, - addressesHex, - DateTimeOffset.UtcNow - started); - - return states; - } - } -} diff --git a/Lib9c/Action/EventDungeonBattle.cs b/Lib9c/Action/EventDungeonBattle.cs index 978f542cf9..8e75818e83 100644 --- a/Lib9c/Action/EventDungeonBattle.cs +++ b/Lib9c/Action/EventDungeonBattle.cs @@ -163,6 +163,7 @@ public override IWorld Execute(IActionContext context) typeof(CostumeStatSheet), typeof(MaterialItemSheet), typeof(RuneListSheet), + typeof(DeBuffLimitSheet), }; if (collectionExist) { @@ -346,6 +347,7 @@ is Bencodex.Types.List serializedEventDungeonInfoList } } + var deBuffLimitSheet = sheets.GetSheet(); var simulator = new StageSimulator( random, avatarState, @@ -366,7 +368,9 @@ is Bencodex.Types.List serializedEventDungeonInfoList stageRow, sheets.GetSheet(), PlayCount), - collectionModifiers); + collectionModifiers, + deBuffLimitSheet, + shatterStrikeMaxDamage: gameConfigState.ShatterStrikeMaxDamage); simulator.Simulate(); sw.Stop(); Log.Verbose( diff --git a/Lib9c/Action/EventMaterialItemCrafts0.cs b/Lib9c/Action/EventMaterialItemCrafts0.cs deleted file mode 100644 index 7ded80a1f6..0000000000 --- a/Lib9c/Action/EventMaterialItemCrafts0.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Extensions; -using Nekoyume.Model.Item; -using Nekoyume.Model.Mail; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Nekoyume.TableData.Event; -using Serilog; -using static Lib9c.SerializeKeys; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType(ActionTypeText)] - public class EventMaterialItemCrafts0 : GameAction, IEventMaterialItemCraftsV1 - { - private const string ActionTypeText = "event_material_item_crafts"; - public Address AvatarAddress; - public int EventScheduleId; - public int EventMaterialItemRecipeId; - public Dictionary MaterialsToUse; - - Address IEventMaterialItemCraftsV1.AvatarAddress => AvatarAddress; - int IEventMaterialItemCraftsV1.EventScheduleId => EventScheduleId; - int IEventMaterialItemCraftsV1.EventMaterialItemRecipeId => EventMaterialItemRecipeId; - IReadOnlyDictionary IEventMaterialItemCraftsV1.MaterialsToUse => MaterialsToUse; - - protected override IImmutableDictionary PlainValueInternal - { - get - { - var serialized = new Dictionary(MaterialsToUse - .OrderBy(pair => pair.Key) - .Select(pair => - new KeyValuePair( - (IKey)pair.Key.Serialize(), pair.Value.Serialize() - ) - )); - var list = List.Empty - .Add(AvatarAddress.Serialize()) - .Add(EventScheduleId.Serialize()) - .Add(EventMaterialItemRecipeId.Serialize()) - .Add(serialized); - - return new Dictionary - { - { "l", list }, - }.ToImmutableDictionary(); - } - } - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - if (!plainValue.TryGetValue("l", out var serialized)) - { - throw new ArgumentException("plainValue must contain 'l'"); - } - - if (!(serialized is List list)) - { - throw new ArgumentException("'l' must be a bencodex list"); - } - - if (list.Count < 4) - { - throw new ArgumentException("'l' must contain at least 4 items"); - } - - AvatarAddress = list[0].ToAddress(); - EventScheduleId = list[1].ToInteger(); - EventMaterialItemRecipeId = list[2].ToInteger(); - var deserialized = ((Dictionary)list[3]).ToDictionary(pair => - pair.Key.ToInteger(), pair => pair.Value.ToInteger()); - MaterialsToUse = deserialized; - } - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - var started = DateTimeOffset.UtcNow; - Log.Debug( - "[{ActionTypeString}][{AddressesHex}] Execute() start", - ActionTypeText, - addressesHex); - - var sw = new Stopwatch(); - - // Get AvatarState - sw.Start(); - if (!states.TryGetAvatarState( - context.Signer, - AvatarAddress, - out var avatarState)) - { - throw new FailedLoadStateException( - ActionTypeText, - addressesHex, - typeof(AvatarState), - AvatarAddress); - } - sw.Stop(); - - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] TryGetAvatarStateV2: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Get AvatarState - - // Get sheets - sw.Restart(); - var sheets = states.GetSheets( - sheetTypes: new[] - { - typeof(EventScheduleSheet), - typeof(EventMaterialItemRecipeSheet), - }); - sw.Stop(); - - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Get sheets: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Get sheets - - // Validate Requirements - sw.Restart(); - avatarState.worldInformation.ValidateFromAction( - GameConfig.RequireClearedStageLevel.CombinationConsumableAction, - ActionTypeText, - addressesHex); - sw.Stop(); - - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate requirements: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Requirements - - // Validate fields - sw.Restart(); - var scheduleSheet = sheets.GetSheet(); - scheduleSheet.ValidateFromActionForRecipe( - context.BlockIndex, - EventScheduleId, - EventMaterialItemRecipeId, - ActionTypeText, - addressesHex); - - var recipeSheet = sheets.GetSheet(); - var recipeRow = recipeSheet.ValidateFromAction( - EventMaterialItemRecipeId, - ActionTypeText, - addressesHex); - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate fields: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate fields - - // Validate Work - sw.Restart(); - - // Validate Recipe ResultMaterialItemId - var materialItemSheet = states.GetSheet(); - if (!materialItemSheet.TryGetValue( - recipeRow.ResultMaterialItemId, - out var resultMaterialRow)) - { - throw new SheetRowNotFoundException( - addressesHex, - nameof(materialItemSheet), - recipeRow.ResultMaterialItemId); - } - // ~Validate Recipe ResultEquipmentId - - // Validate Recipe Material - recipeRow.ValidateFromAction( - materialItemSheet, - MaterialsToUse, - ActionTypeText, - addressesHex); - // ~Validate Recipe Material - - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Validate work: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Validate Work - - // Remove Required Materials - var inventory = avatarState.inventory; -#pragma warning disable LAA1002 - foreach (var pair in MaterialsToUse) -#pragma warning restore LAA1002 - { - if (!materialItemSheet.TryGetValue(pair.Key, out var materialRow) || - !inventory.RemoveFungibleItem(materialRow.ItemId, context.BlockIndex, pair.Value)) - { - throw new NotEnoughMaterialException( - $"{addressesHex}Aborted as the player has no enough material ({pair.Key} * {pair.Value})"); - } - } - // ~Remove Required Materials - - // Create Material - var materialResult = ItemFactory.CreateMaterial(resultMaterialRow); - avatarState.inventory.AddItem(materialResult, recipeRow.ResultMaterialItemCount); - // ~Create Material - - // Create Mail - var mail = new MaterialCraftMail( - context.BlockIndex, - Id, - context.BlockIndex, - recipeRow.ResultMaterialItemCount, - materialResult.Id); - avatarState.Update(mail); - // ~Create Mail - - // Set states - sw.Restart(); - states = states - .SetAvatarState(AvatarAddress, avatarState); - sw.Stop(); - Log.Verbose( - "[{ActionTypeString}][{AddressesHex}] Set states: {Elapsed}", - ActionTypeText, - addressesHex, - sw.Elapsed); - // ~Set states - - Log.Debug( - "[{ActionTypeString}][{AddressesHex}] Total elapsed: {Elapsed}", - ActionTypeText, - addressesHex, - DateTimeOffset.UtcNow - started); - - return states; - } - - } -} diff --git a/Lib9c/Action/HackAndSlash.cs b/Lib9c/Action/HackAndSlash.cs index a2b05c2592..b2545d3a7c 100644 --- a/Lib9c/Action/HackAndSlash.cs +++ b/Lib9c/Action/HackAndSlash.cs @@ -177,6 +177,7 @@ public IWorld Execute( typeof(CrystalRandomBuffSheet), typeof(StakeActionPointCoefficientSheet), typeof(RuneListSheet), + typeof(DeBuffLimitSheet), }; if (collectionExist) { @@ -471,6 +472,8 @@ public IWorld Execute( collectionModifiers.AddRange(collectionSheet[collectionId].StatModifiers); } } + + var deBuffLimitSheet = sheets.GetSheet(); for (var i = 0; i < TotalPlayCount; i++) { var rewards = StageSimulator.GetWaveRewards(random, stageRow, materialItemSheet); @@ -494,7 +497,9 @@ public IWorld Execute( costumeStatSheet, rewards, collectionModifiers, - false); + deBuffLimitSheet, + false, + gameConfigState.ShatterStrikeMaxDamage); sw.Stop(); Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", addressesHex, source, "Initialize Simulator", blockIndex, sw.Elapsed.TotalMilliseconds); @@ -508,17 +513,14 @@ public IWorld Execute( sw.Restart(); if (simulator.Log.IsClear) { - if (!stageCleared) - { - avatarState.worldInformation.ClearStage( - WorldId, - StageId, - blockIndex, - worldSheet, - worldUnlockSheet - ); - stageCleared = true; - } + avatarState.worldInformation.ClearStage( + WorldId, + StageId, + blockIndex, + worldSheet, + worldUnlockSheet + ); + stageCleared = true; sw.Stop(); Log.Verbose("{AddressesHex} {Source} HAS {Process} from #{BlockIndex}: {Elapsed}", addressesHex, source, "ClearStage", blockIndex, sw.Elapsed.TotalMilliseconds); diff --git a/Lib9c/Action/HackAndSlash3.cs b/Lib9c/Action/HackAndSlash3.cs deleted file mode 100644 index bf122cd283..0000000000 --- a/Lib9c/Action/HackAndSlash3.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Battle; -using Nekoyume.Model.BattleStatus; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Serilog; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("hack_and_slash3")] - public class HackAndSlash3 : GameAction, IHackAndSlashV1 - { - public List costumes; - public List equipments; - public List foods; - public int worldId; - public int stageId; - public Address avatarAddress; - public Address WeeklyArenaAddress; - public Address RankingMapAddress; - public BattleLog Result { get; private set; } - - IEnumerable IHackAndSlashV1.Costumes => costumes; - IEnumerable IHackAndSlashV1.Equipments => equipments; - IEnumerable IHackAndSlashV1.Foods => foods; - int IHackAndSlashV1.WorldId => worldId; - int IHackAndSlashV1.StageId => stageId; - Address IHackAndSlashV1.AvatarAddress => avatarAddress; - Address IHackAndSlashV1.WeeklyArenaAddress => WeeklyArenaAddress; - Address IHackAndSlashV1.RankingMapAddress => RankingMapAddress; - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["costumes"] = new List(costumes.OrderBy(i => i).Select(e => e.Serialize())), - ["equipments"] = new List(equipments.OrderBy(i => i).Select(e => e.Serialize())), - ["foods"] = new List(foods.OrderBy(i => i).Select(e => e.Serialize())), - ["worldId"] = worldId.Serialize(), - ["stageId"] = stageId.Serialize(), - ["avatarAddress"] = avatarAddress.Serialize(), - ["weeklyArenaAddress"] = WeeklyArenaAddress.Serialize(), - ["rankingMapAddress"] = RankingMapAddress.Serialize(), - }.ToImmutableDictionary(); - - - protected override void LoadPlainValueInternal( - IImmutableDictionary plainValue) - { - costumes = ((List) plainValue["costumes"]).Select(e => e.ToInteger()).ToList(); - equipments = ((List) plainValue["equipments"]).Select(e => e.ToGuid()).ToList(); - foods = ((List) plainValue["foods"]).Select(e => e.ToGuid()).ToList(); - worldId = plainValue["worldId"].ToInteger(); - stageId = plainValue["stageId"].ToInteger(); - avatarAddress = plainValue["avatarAddress"].ToAddress(); - WeeklyArenaAddress = plainValue["weeklyArenaAddress"].ToAddress(); - RankingMapAddress = plainValue["rankingMapAddress"].ToAddress(); - } - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - IActionContext ctx = context; - var states = ctx.PreviousState; - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - - var addressesHex = GetSignerAndOtherAddressesHex(context, avatarAddress); - - var sw = new Stopwatch(); - sw.Start(); - var started = DateTimeOffset.UtcNow; - Log.Verbose("{AddressesHex}HAS exec started", addressesHex); - - if (!states.TryGetAvatarState(ctx.Signer, avatarAddress, out AvatarState avatarState)) - { - throw new FailedLoadStateException($"{addressesHex}Aborted as the avatar state of the signer was failed to load."); - } - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Get AgentAvatarStates: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - - if (avatarState.RankingMapAddress != RankingMapAddress) - { - throw new InvalidAddressException($"{addressesHex}Invalid ranking map address"); - } - - // worldId와 stageId가 유효한지 확인합니다. - var worldSheet = states.GetSheet(); - - if (!worldSheet.TryGetValue(worldId, out var worldRow, false)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(WorldSheet), worldId); - } - - if (stageId < worldRow.StageBegin || - stageId > worldRow.StageEnd) - { - throw new SheetRowColumnException( - $"{addressesHex}{worldId} world is not contains {worldRow.Id} stage: " + - $"{worldRow.StageBegin}-{worldRow.StageEnd}"); - } - - var stageSheet = states.GetSheet(); - if (!stageSheet.TryGetValue(stageId, out var stageRow)) - { - throw new SheetRowNotFoundException(addressesHex, nameof(StageSheet), stageId); - } - - var worldInformation = avatarState.worldInformation; - if (!worldInformation.TryGetWorld(worldId, out var world)) - { - // NOTE: Add new World from WorldSheet - worldInformation.AddAndUnlockNewWorld(worldRow, ctx.BlockIndex, worldSheet); - } - - if (!world.IsUnlocked) - { - throw new InvalidWorldException($"{addressesHex}{worldId} is locked."); - } - - if (world.StageBegin != worldRow.StageBegin || - world.StageEnd != worldRow.StageEnd) - { - worldInformation.UpdateWorld(worldRow); - } - - if (world.IsStageCleared && stageId > world.StageClearedId + 1 || - !world.IsStageCleared && stageId != world.StageBegin) - { - throw new InvalidStageException( - $"{addressesHex}Aborted as the stage ({worldId}/{stageId}) is not cleared; " + - $"cleared stage: {world.StageClearedId}" - ); - } - - avatarState.ValidateEquipments(equipments, context.BlockIndex); - avatarState.ValidateConsumable(foods, context.BlockIndex); - avatarState.ValidateCostume(new HashSet(costumes)); - - var costumeStatSheet = states.GetSheet(); - - sw.Restart(); - if (avatarState.actionPoint < stageRow.CostAP) - { - throw new NotEnoughActionPointException( - $"{addressesHex}Aborted due to insufficient action point: " + - $"{avatarState.actionPoint} < {stageRow.CostAP}" - ); - } - - avatarState.actionPoint -= stageRow.CostAP; - - avatarState.EquipCostumes(new HashSet(costumes)); - - avatarState.EquipEquipments(equipments); - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Unequip items: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - var characterSheet = states.GetSheet(); - var random = ctx.GetRandom(); - var simulator = new StageSimulatorV1( - random, - avatarState, - foods, - worldId, - stageId, - states.GetStageSimulatorSheetsV1(), - costumeStatSheet - ); - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Initialize Simulator: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - simulator.SimulateV1(); - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Simulator.Simulate(): {Elapsed}", addressesHex, sw.Elapsed); - - Log.Verbose( - "{AddressesHex}Execute HackAndSlash({AvatarAddress}); worldId: {WorldId}, stageId: {StageId}, result: {Result}, " + - "clearWave: {ClearWave}, totalWave: {TotalWave}", - addressesHex, - avatarAddress, - worldId, - stageId, - simulator.Log.result, - simulator.Log.clearedWaveNumber, - simulator.Log.waveCount - ); - - sw.Restart(); - if (simulator.Log.IsClear) - { - var worldUnlockSheet = states.GetSheet(); - simulator.Player.worldInformation.ClearStage( - worldId, - stageId, - ctx.BlockIndex, - worldSheet, - worldUnlockSheet - ); - } - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS ClearStage: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - avatarState.Update(simulator); - - var materialSheet = states.GetSheet(); - avatarState.UpdateQuestRewards2(materialSheet); - - avatarState.updatedAt = ctx.BlockIndex; - avatarState.mailBox.CleanUpV1(); - states = states.SetAvatarState(avatarAddress, avatarState); - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Set AvatarState: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - if (states.TryGetLegacyState(RankingMapAddress, out Dictionary d) && simulator.Log.IsClear) - { - var ranking = new RankingMapState(d); - ranking.Update(avatarState); - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Update RankingState: {Elapsed}", addressesHex, sw.Elapsed); - sw.Restart(); - - var serialized = ranking.Serialize(); - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Serialize RankingState: {Elapsed}", addressesHex, sw.Elapsed); - sw.Restart(); - states = states.SetLegacyState(RankingMapAddress, serialized); - } - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Set RankingState: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - if (simulator.Log.stageId >= GameConfig.RequireClearedStageLevel.ActionsInRankingBoard && - simulator.Log.IsClear && - states.TryGetLegacyState(WeeklyArenaAddress, out Dictionary weeklyDict)) - { - var weekly = new WeeklyArenaState(weeklyDict); - if (!weekly.Ended) - { - if (weekly.ContainsKey(avatarAddress)) - { - var info = weekly[avatarAddress]; - info.UpdateV2(avatarState, characterSheet, costumeStatSheet); - weekly.Update(info); - } - else - { - weekly.SetV2(avatarState, characterSheet, costumeStatSheet); - } - - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Update WeeklyArenaState: {Elapsed}", addressesHex, sw.Elapsed); - - sw.Restart(); - var weeklySerialized = weekly.Serialize(); - sw.Stop(); - Log.Verbose("{AddressesHex}HAS Serialize RankingState: {Elapsed}", addressesHex, sw.Elapsed); - - states = states.SetLegacyState(weekly.address, weeklySerialized); - } - } - - Result = simulator.Log; - - var ended = DateTimeOffset.UtcNow; - Log.Verbose("{AddressesHex}HAS Total Executed Time: {Elapsed}", addressesHex, ended - started); - return states; - } - } -} diff --git a/Lib9c/Action/HackAndSlashSweep.cs b/Lib9c/Action/HackAndSlashSweep.cs index 94e5289a57..2284f26be8 100644 --- a/Lib9c/Action/HackAndSlashSweep.cs +++ b/Lib9c/Action/HackAndSlashSweep.cs @@ -28,7 +28,7 @@ namespace Nekoyume.Action [ActionType("hack_and_slash_sweep10")] public class HackAndSlashSweep : GameAction, IHackAndSlashSweepV3 { - public const int UsableApStoneCount = 10; + public const int UsableApStoneCount = 20; public List costumes; public List equipments; diff --git a/Lib9c/Action/MigrateAgentAvatar.cs b/Lib9c/Action/MigrateAgentAvatar.cs new file mode 100644 index 0000000000..e4dff011c9 --- /dev/null +++ b/Lib9c/Action/MigrateAgentAvatar.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Module; +using Serilog; +using static Lib9c.SerializeKeys; + +namespace Nekoyume.Action +{ + [ActionType(TypeIdentifier)] + public class MigrateAgentAvatar : ActionBase + { + public const string TypeIdentifier = "migrate_agent_avatar"; + + private static readonly Address Operator = + new Address("e2D18a50472e93d3165c478DefA69fa149214E72"); + + public List
AgentAddresses; + + public MigrateAgentAvatar() + { + } + + public override IValue PlainValue => Dictionary.Empty + .Add("type_id", TypeIdentifier) + .Add( + "values", + Dictionary.Empty.Add( + "agent_addresses", + new List(AgentAddresses.Select(address => address.Bencoded)))); + + public override void LoadPlainValue(IValue plainValue) + { + var asDict = (Dictionary)((Dictionary)plainValue)["values"]; + AgentAddresses = ((List)asDict["agent_addresses"]).Select(v => new Address(v)).ToList(); + } + + public override IWorld Execute(IActionContext context) + { + context.UseGas(1); + +#if !LIB9C_DEV_EXTENSIONS && !UNITY_EDITOR + if (context.Signer != Operator) + { + throw new Exception("Migration action must be signed by given operator."); + } +#endif + + var states = context.PreviousState; + var migrationStarted = DateTimeOffset.UtcNow; + Log.Debug("Migrating agent/avatar states in block index #{Index} started", context.BlockIndex); + + const int maxAvatarCount = 3; + var avatarAddresses = Enumerable + .Range(0, AgentAddresses.Count * maxAvatarCount) + .Select(i => AgentAddresses[i / maxAvatarCount] + .Derive( + string.Format(CultureInfo.InvariantCulture, CreateAvatar.DeriveFormat, i % maxAvatarCount))) + .ToList(); + + foreach (var address in AgentAddresses) + { + // Try migrating if not already migrated + var started = DateTimeOffset.UtcNow; + Log.Debug("Migrating agent {Address}", address); + if (states.GetAccountState(Addresses.Agent).GetState(address) is null) + { + Log.Debug("Getting agent {Address}", address); + var agentState = states.GetAgentState(address); + if (agentState is null) continue; + Log.Debug("Setting agent {Address} to modern account", address); + states = states.SetAgentState(address, agentState); + } + + // Delete AgentState in Legacy + Log.Debug("Deleting agent {Address} from legacy account", address); + states = states.SetLegacyState(address, null); + Log.Debug( + "Migrating agent {Address} finished in: {Elapsed} ms", + address, + (DateTimeOffset.UtcNow - started).Milliseconds); + } + + foreach (var address in avatarAddresses) + { + var started = DateTimeOffset.UtcNow; + Log.Debug("Migrating avatar {Address}", address); + // Try migrating if not already migrated + if (states.GetAccountState(Addresses.Avatar).GetState(address) is null) + { + Log.Debug("Getting avatar {Address}", address); + var avatarState = states.GetAvatarState(address); + if (avatarState is null) continue; + Log.Debug("Setting avatar {Address} to modern account", address); + states = states.SetAvatarState(address, avatarState); + } + + // Delete AvatarState in Legacy + Log.Debug("Deleting avatar {Address} from legacy account", address); + states = states.SetLegacyState(address, null); + states = states.SetLegacyState(address.Derive(LegacyInventoryKey), null); + states = states.SetLegacyState(address.Derive(LegacyQuestListKey), null); + states = states.SetLegacyState(address.Derive(LegacyWorldInformationKey), null); + Log.Debug( + "Migrating avatar {Address} finished in: {Elapsed} ms", + address, + (DateTimeOffset.UtcNow - started).Milliseconds); + } + + Log.Debug( + "Migrating {Count} agents in block index #{Index} finished in: {Elapsed} ms", + AgentAddresses.Count, + context.BlockIndex, + (DateTimeOffset.UtcNow - migrationStarted).Milliseconds); + return states; + } + } +} diff --git a/Lib9c/Action/Raid.cs b/Lib9c/Action/Raid.cs index 0001f8276d..8c742a96bd 100644 --- a/Lib9c/Action/Raid.cs +++ b/Lib9c/Action/Raid.cs @@ -83,6 +83,7 @@ public override IWorld Execute(IActionContext context) typeof(WorldBossKillRewardSheet), typeof(RuneSheet), typeof(RuneListSheet), + typeof(DeBuffLimitSheet), }; if (collectionExist) { @@ -231,8 +232,10 @@ public override IWorld Execute(IActionContext context) runeStates, raidSimulatorSheets, sheets.GetSheet(), - collectionModifiers - ); + collectionModifiers, + sheets.GetSheet(), + shatterStrikeMaxDamage: gameConfigState.ShatterStrikeMaxDamage + ); simulator.Simulate(); avatarState.inventory = simulator.Player.Inventory; diff --git a/Lib9c/Action/ReRegisterProduct.cs b/Lib9c/Action/ReRegisterProduct.cs index 7949f13bb1..7ed74ff2af 100644 --- a/Lib9c/Action/ReRegisterProduct.cs +++ b/Lib9c/Action/ReRegisterProduct.cs @@ -157,7 +157,7 @@ public override IWorld Execute(IActionContext context) states, avatarState, context); } - states = RegisterProduct2.Register(context, info, avatarState, productsState, states, random); + states = RegisterProduct.Register(context, info, avatarState, productsState, states, random); } states = states diff --git a/Lib9c/Action/RedeemCode0.cs b/Lib9c/Action/RedeemCode0.cs deleted file mode 100644 index 0ef8bd4ac1..0000000000 --- a/Lib9c/Action/RedeemCode0.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Bencodex.Types; -using Lib9c.Abstractions; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Nekoyume.Model.Item; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using Serilog; - -namespace Nekoyume.Action -{ - [Serializable] - [ActionObsolete(ActionObsoleteConfig.V200020AccidentObsoleteIndex)] - [ActionType("redeem_code")] - public class RedeemCode0 : GameAction, IRedeemCodeV1 - { - public string Code { get; internal set; } - - public Address AvatarAddress {get; internal set; } - - string IRedeemCodeV1.Code => Code; - Address IRedeemCodeV1.AvatarAddress => AvatarAddress; - - public RedeemCode0() - { - } - - public RedeemCode0(string code, Address avatarAddress) - { - Code = code; - AvatarAddress = avatarAddress; - } - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - - CheckObsolete(ActionObsoleteConfig.V100080ObsoleteIndex, context); - - var addressesHex = GetSignerAndOtherAddressesHex(context, AvatarAddress); - - if (!states.TryGetAvatarState(context.Signer, AvatarAddress, out AvatarState avatarState)) - { - return states; - } - - var redeemState = states.GetRedeemCodeState(); - if (redeemState is null) - { - return states; - } - - int redeemId; - try - { - redeemId = redeemState.Redeem(Code, AvatarAddress); - } - catch (InvalidRedeemCodeException) - { - Log.Error("{AddressesHex}Invalid Code", addressesHex); - throw; - } - catch (DuplicateRedeemException e) - { - Log.Warning("{AddressesHex}{Message}", addressesHex, e.Message); - throw; - } - - var row = states.GetSheet().Values.First(r => r.Id == redeemId); - var itemSheets = states.GetItemSheet(); - - var random = context.GetRandom(); - foreach (RedeemRewardSheet.RewardInfo info in row.Rewards) - { - switch (info.Type) - { - case RewardType.Item: - for (var i = 0; i < info.Quantity; i++) - { - if (info.ItemId is int itemId) - { - ItemBase item = ItemFactory.CreateItem(itemSheets[itemId], random); - // We should fix count as 1 because ItemFactory.CreateItem - // will create a new item every time. - avatarState.inventory.AddItem2(item, count: 1); - } - } - break; - case RewardType.Gold: - states = states.TransferAsset( - context, - GoldCurrencyState.Address, - context.Signer, - states.GetGoldCurrency() * info.Quantity - ); - break; - default: - // FIXME: We should raise exception here. - break; - } - } - - states = states.SetAvatarState(AvatarAddress, avatarState); - states = states.SetLegacyState(RedeemCodeState.Address, redeemState.Serialize()); - return states; - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - [nameof(Code)] = Code.Serialize(), - [nameof(AvatarAddress)] = AvatarAddress.Serialize() - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - Code = (Text) plainValue[nameof(Code)]; - AvatarAddress = plainValue[nameof(AvatarAddress)].ToAddress(); - } - } -} diff --git a/Lib9c/Action/RegisterProduct2.cs b/Lib9c/Action/RegisterProduct2.cs deleted file mode 100644 index 51dac79d8c..0000000000 --- a/Lib9c/Action/RegisterProduct2.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Action.State; -using Libplanet.Crypto; -using Libplanet.Types.Assets; -using Nekoyume.Battle; -using Nekoyume.Model.Item; -using Nekoyume.Model.Market; -using Nekoyume.Model.State; -using Nekoyume.Module; -using Nekoyume.TableData; -using static Lib9c.SerializeKeys; - -namespace Nekoyume.Action -{ - [ActionObsolete(ActionObsoleteConfig.V200092ObsoleteIndex)] - [ActionType("register_product2")] - public class RegisterProduct2 : GameAction - { - public const int CostAp = 5; - public const int Capacity = 100; - public Address AvatarAddress; - public IEnumerable RegisterInfos; - public bool ChargeAp; - - public override IWorld Execute(IActionContext context) - { - context.UseGas(1); - var states = context.PreviousState; - var random = context.GetRandom(); - - if (!RegisterInfos.Any()) - { - throw new ListEmptyException("RegisterInfos was empty"); - } - - if (RegisterInfos.Count() > Capacity) - { - throw new ArgumentOutOfRangeException($"{nameof(RegisterInfos)} must be less than or equal {Capacity}."); - } - - var ncg = states.GetGoldCurrency(); - foreach (var registerInfo in RegisterInfos) - { - registerInfo.ValidateAddress(AvatarAddress); - registerInfo.ValidatePrice(ncg); - registerInfo.Validate(); - } - - if (!states.TryGetAvatarState(context.Signer, AvatarAddress, out var avatarState)) - { - throw new FailedLoadStateException("failed to load avatar state."); - } - - if (!avatarState.worldInformation.IsStageCleared( - GameConfig.RequireClearedStageLevel.ActionsInShop)) - { - avatarState.worldInformation.TryGetLastClearedStageId(out var current); - throw new NotEnoughClearedStageLevelException( - AvatarAddress.ToHex(), - GameConfig.RequireClearedStageLevel.ActionsInShop, - current); - } - - avatarState.UseAp(CostAp, ChargeAp, states.GetSheet(), context.BlockIndex, states.GetGameConfigState()); - var productsStateAddress = ProductsState.DeriveAddress(AvatarAddress); - ProductsState productsState; - if (states.TryGetLegacyState(productsStateAddress, out List rawProducts)) - { - productsState = new ProductsState(rawProducts); - } - else - { - productsState = new ProductsState(); - var marketState = states.TryGetLegacyState(Addresses.Market, out List rawMarketList) - ? rawMarketList - : List.Empty; - marketState = marketState.Add(AvatarAddress.Serialize()); - states = states.SetLegacyState(Addresses.Market, marketState); - } - foreach (var info in RegisterInfos.OrderBy(r => r.Type).ThenBy(r => r.Price)) - { - states = Register(context, info, avatarState, productsState, states, random); - } - - states = states - .SetAvatarState(AvatarAddress, avatarState) - .SetLegacyState(productsStateAddress, productsState.Serialize()); - - return states; - } - - public static IWorld Register(IActionContext context, IRegisterInfo info, AvatarState avatarState, - ProductsState productsState, IWorld states, IRandom random) - { - switch (info) - { - case RegisterInfo registerInfo: - switch (info.Type) - { - case ProductType.Fungible: - case ProductType.NonFungible: - { - var tradableId = registerInfo.TradableId; - var itemCount = registerInfo.ItemCount; - var type = registerInfo.Type; - ITradableItem tradableItem = null; - switch (type) - { - case ProductType.Fungible: - { - if (avatarState.inventory.TryGetTradableItems(tradableId, - context.BlockIndex, itemCount, out var items)) - { - int totalCount = itemCount; - tradableItem = (ITradableItem) items.First().item; - foreach (var inventoryItem in items) - { - int removeCount = Math.Min(totalCount, - inventoryItem.count); - ITradableFungibleItem tradableFungibleItem = - (ITradableFungibleItem) inventoryItem.item; - if (!avatarState.inventory.RemoveTradableItem( - tradableId, - tradableFungibleItem.RequiredBlockIndex, - removeCount)) - { - throw new ItemDoesNotExistException( - $"failed to remove tradable material {tradableId}/{itemCount}"); - } - - totalCount -= removeCount; - if (totalCount < 1) - { - break; - } - } - - if (totalCount != 0) - { - throw new InvalidItemCountException(); - } - } - - break; - } - case ProductType.NonFungible: - { - if (avatarState.inventory.TryGetNonFungibleItem(tradableId, - out var item) && - avatarState.inventory.RemoveNonFungibleItem(tradableId)) - { - tradableItem = item.item as ITradableItem; - } - - break; - } - } - - if (tradableItem is null || tradableItem.RequiredBlockIndex > context.BlockIndex) - { - throw new ItemDoesNotExistException($"can't find item: {tradableId}"); - } - - Guid productId = random.GenerateRandomGuid(); - var product = new ItemProduct - { - ProductId = productId, - Price = registerInfo.Price, - TradableItem = tradableItem, - ItemCount = itemCount, - RegisteredBlockIndex = context.BlockIndex, - Type = registerInfo.Type, - SellerAgentAddress = context.Signer, - SellerAvatarAddress = registerInfo.AvatarAddress, - }; - productsState.ProductIds.Add(productId); - states = states.SetLegacyState(Product.DeriveAddress(productId), - product.Serialize()); - break; - } - } - - break; - case AssetInfo assetInfo: - { - Guid productId = random.GenerateRandomGuid(); - Address productAddress = Product.DeriveAddress(productId); - FungibleAssetValue asset = assetInfo.Asset; - var product = new FavProduct - { - ProductId = productId, - Price = assetInfo.Price, - Asset = asset, - RegisteredBlockIndex = context.BlockIndex, - Type = assetInfo.Type, - SellerAgentAddress = context.Signer, - SellerAvatarAddress = assetInfo.AvatarAddress, - }; - states = states - .TransferAsset(context, avatarState.address, productAddress, asset) - .SetLegacyState(productAddress, product.Serialize()); - productsState.ProductIds.Add(productId); - break; - } - } - - return states; - } - - protected override IImmutableDictionary PlainValueInternal => - new Dictionary - { - ["r"] = new List(RegisterInfos.Select(r => r.Serialize())), - ["a"] = AvatarAddress.Serialize(), - ["c"] = ChargeAp.Serialize(), - }.ToImmutableDictionary(); - - protected override void LoadPlainValueInternal(IImmutableDictionary plainValue) - { - var serialized = (List) plainValue["r"]; - RegisterInfos = serialized.Cast() - .Select(ProductFactory.DeserializeRegisterInfo).ToList(); - AvatarAddress = plainValue["a"].ToAddress(); - ChargeAp = plainValue["c"].ToBoolean(); - } - } -} diff --git a/Lib9c/Action/RetrieveAvatarAssets.cs b/Lib9c/Action/RetrieveAvatarAssets.cs new file mode 100644 index 0000000000..719a3068da --- /dev/null +++ b/Lib9c/Action/RetrieveAvatarAssets.cs @@ -0,0 +1,53 @@ +using System; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Nekoyume.Model.State; +using Nekoyume.Module; + +namespace Nekoyume.Action +{ + [Serializable] + [ActionType(TypeIdentifier)] + public class RetrieveAvatarAssets: ActionBase + { + public const string TypeIdentifier = "retrieve_avatar_assets"; + public Address AvatarAddress; + + public RetrieveAvatarAssets() + { + } + + public RetrieveAvatarAssets(Address avatarAddress) + { + AvatarAddress = avatarAddress; + } + + public override IValue PlainValue => Dictionary.Empty + .Add("type_id", TypeIdentifier) + .Add("values", Dictionary.Empty.Add("a", AvatarAddress.Serialize())); + + public override void LoadPlainValue(IValue plainValue) + { + var asDict = (Dictionary)((Dictionary)plainValue)["values"]; + AvatarAddress = asDict["a"].ToAddress(); + } + + public override IWorld Execute(IActionContext context) + { + context.UseGas(1); + Address signer = context.Signer; + var state = context.PreviousState; + var agentState = state.GetAgentState(signer); + if (agentState is not null && agentState.avatarAddresses.ContainsValue(AvatarAddress)) + { + var currency = state.GetGoldCurrency(); + var balance = state.GetBalance(AvatarAddress, currency); + return state.TransferAsset(context, AvatarAddress, signer, balance); + } + + throw new FailedLoadStateException($"signer({signer}) does not contains avatar address({AvatarAddress})."); + } + } +} diff --git a/Lib9c/Arena/ArenaSimulator.cs b/Lib9c/Arena/ArenaSimulator.cs index d2dd618869..8dd985c762 100644 --- a/Lib9c/Arena/ArenaSimulator.cs +++ b/Lib9c/Arena/ArenaSimulator.cs @@ -22,11 +22,18 @@ public class ArenaSimulator : IArenaSimulator public ArenaLog Log { get; private set; } public int HpModifier { get; } - public ArenaSimulator(IRandom random, int hpModifier = 2) + public long ShatterStrikeMaxDamage { get; private set; } + public DeBuffLimitSheet DeBuffLimitSheet { get; private set; } + + public ArenaSimulator(IRandom random, + int hpModifier = 2, + long shatterStrikeMaxDamage = 400_000 // 400K is initial limit of ShatterStrike. Use this as default. + ) { Random = random; Turn = 1; HpModifier = hpModifier; + ShatterStrikeMaxDamage = shatterStrikeMaxDamage; } public ArenaLog Simulate( @@ -35,9 +42,11 @@ public ArenaLog Simulate( ArenaSimulatorSheets sheets, List challengerCollectionModifiers, List enemyCollectionModifiers, + DeBuffLimitSheet deBuffLimitSheet, bool setExtraValueBuffBeforeGetBuffs = false) { Log = new ArenaLog(); + DeBuffLimitSheet = deBuffLimitSheet; var players = SpawnPlayers(this, challenger, enemy, sheets, Log, challengerCollectionModifiers, enemyCollectionModifiers, setExtraValueBuffBeforeGetBuffs); Turn = 1; diff --git a/Lib9c/Arena/ArenaSimulatorV1.cs b/Lib9c/Arena/ArenaSimulatorV1.cs deleted file mode 100644 index e61d0e5094..0000000000 --- a/Lib9c/Arena/ArenaSimulatorV1.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Libplanet.Action; -using Nekoyume.Model; -using Nekoyume.Model.BattleStatus.Arena; -using Nekoyume.TableData; -using Priority_Queue; - -namespace Nekoyume.Arena -{ - /// - /// Introduced at https://github.com/planetarium/lib9c/pull/1135 - /// - public class ArenaSimulatorV1 : IArenaSimulator - { - private const decimal TurnPriority = 100m; - private const int MaxTurn = 200; - - public IRandom Random { get; } - public int Turn { get; private set; } - public ArenaLog Log { get; private set; } - - public ArenaSimulatorV1(IRandom random) - { - Random = random; - Turn = 1; - } - - public ArenaLog Simulate( - ArenaPlayerDigest challenger, - ArenaPlayerDigest enemy, - ArenaSimulatorSheetsV1 sheets) - { - Log = new ArenaLog(); - var players = SpawnPlayers(this, challenger, enemy, sheets, Log); - Turn = 1; - - while (true) - { - if (Turn > MaxTurn) - { - // todo : 턴오버일경우 정책 필요함 일단 Lose - Log.Result = ArenaLog.ArenaResult.Lose; - break; - } - - if (!players.TryDequeue(out var selectedPlayer)) - { - break; - } - - selectedPlayer.Tick(); - var clone = (ArenaCharacter)selectedPlayer.Clone(); - Log.Add(clone.SkillLog); - - var deadPlayers = players.Where(x => x.IsDead); - var arenaCharacters = deadPlayers as ArenaCharacter[] ?? deadPlayers.ToArray(); - if (arenaCharacters.Any()) - { - var (deadPlayer, result) = GetBattleResult(arenaCharacters); - Log.Result = result; - Log.Add(new ArenaDead((ArenaCharacter)deadPlayer.Clone())); - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - break; - } - - if (!selectedPlayer.IsEnemy) - { - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - Turn++; - } - - foreach (var other in players) - { - var current = players.GetPriority(other); - var speed = current * 0.6m; - players.UpdatePriority(other, speed); - } - - players.Enqueue(selectedPlayer, TurnPriority / selectedPlayer.SPD); - } - - return Log; - } - - [Obsolete("Use Simulate")] - public ArenaLog SimulateV1( - ArenaPlayerDigest challenger, - ArenaPlayerDigest enemy, - ArenaSimulatorSheetsV1 sheets) - { - var log = new ArenaLog(); - var players = SpawnPlayersV1(this, challenger, enemy, sheets, log); - Turn = 1; - - while (true) - { - if (Turn > MaxTurn) - { - // todo : 턴오버일경우 정책 필요함 일단 Lose - log.Result = ArenaLog.ArenaResult.Lose; - break; - } - - if (!players.TryDequeue(out var selectedPlayer)) - { - break; - } - - selectedPlayer.Tick(); - var clone = (ArenaCharacter)selectedPlayer.Clone(); - log.Add(clone.SkillLog); - - var deadPlayers = players.Where(x => x.IsDead); - var arenaCharacters = deadPlayers as ArenaCharacter[] ?? deadPlayers.ToArray(); - if (arenaCharacters.Any()) - { - var (deadPlayer, result) = GetBattleResult(arenaCharacters); - log.Result = result; - log.Add(new ArenaDead((ArenaCharacter)deadPlayer.Clone())); - log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - break; - } - - if (!selectedPlayer.IsEnemy) - { - log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - Turn++; - } - - foreach (var other in players) - { - var current = players.GetPriority(other); - var speed = current * 0.6m; - players.UpdatePriority(other, speed); - } - - players.Enqueue(selectedPlayer, TurnPriority / selectedPlayer.SPD); - } - - return log; - } - - private static (ArenaCharacter, ArenaLog.ArenaResult) GetBattleResult( - IReadOnlyCollection deadPlayers) - { - if (deadPlayers.Count > 1) - { - var enemy = deadPlayers.First(x => x.IsEnemy); - return (enemy, ArenaLog.ArenaResult.Win); - } - - var player = deadPlayers.First(); - return (player, player.IsEnemy ? ArenaLog.ArenaResult.Win : ArenaLog.ArenaResult.Lose); - } - - - private static SimplePriorityQueue SpawnPlayers( - ArenaSimulatorV1 simulator, - ArenaPlayerDigest challengerDigest, - ArenaPlayerDigest enemyDigest, - ArenaSimulatorSheetsV1 simulatorSheets, - ArenaLog log) - { - var challenger = new ArenaCharacter(simulator, challengerDigest, simulatorSheets); - var enemy = new ArenaCharacter(simulator, enemyDigest, simulatorSheets, true); - - challenger.SpawnV2(enemy); - enemy.SpawnV2(challenger); - - log.Add(new ArenaSpawnCharacter((ArenaCharacter)challenger.Clone())); - log.Add(new ArenaSpawnCharacter((ArenaCharacter)enemy.Clone())); - - var players = new SimplePriorityQueue(); - players.Enqueue(challenger, TurnPriority / challenger.SPD); - players.Enqueue(enemy, TurnPriority / enemy.SPD); - return players; - } - - [Obsolete("Use SpawnPlayers")] - private static SimplePriorityQueue SpawnPlayersV1( - ArenaSimulatorV1 simulator, - ArenaPlayerDigest challengerDigest, - ArenaPlayerDigest enemyDigest, - ArenaSimulatorSheetsV1 simulatorSheets, - ArenaLog log) - { - var challenger = new ArenaCharacter(simulator, challengerDigest, simulatorSheets); - var enemy = new ArenaCharacter(simulator, enemyDigest, simulatorSheets, true); - - challenger.SpawnV1(enemy); - enemy.SpawnV1(challenger); - - log.Add(new ArenaSpawnCharacter((ArenaCharacter)challenger.Clone())); - log.Add(new ArenaSpawnCharacter((ArenaCharacter)enemy.Clone())); - - var players = new SimplePriorityQueue(); - players.Enqueue(challenger, TurnPriority / challenger.SPD); - players.Enqueue(enemy, TurnPriority / enemy.SPD); - return players; - } - } -} diff --git a/Lib9c/Arena/ArenaSimulatorV2.cs b/Lib9c/Arena/ArenaSimulatorV2.cs deleted file mode 100644 index 6e0c9bba74..0000000000 --- a/Lib9c/Arena/ArenaSimulatorV2.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Libplanet.Action; -using Nekoyume.Model; -using Nekoyume.Model.BattleStatus.Arena; -using Nekoyume.TableData; -using Priority_Queue; - -namespace Nekoyume.Arena -{ - /// - /// Introduced at https://github.com/planetarium/lib9c/pull/1501 - /// - public class ArenaSimulatorV2 : IArenaSimulator - { - private const decimal TurnPriority = 100m; - private const int MaxTurn = 200; - - public IRandom Random { get; } - public int Turn { get; private set; } - public ArenaLog Log { get; private set; } - - public ArenaSimulatorV2(IRandom random) - { - Random = random; - Turn = 1; - } - - public ArenaLog Simulate( - ArenaPlayerDigest challenger, - ArenaPlayerDigest enemy, - ArenaSimulatorSheets sheets) - { - Log = new ArenaLog(); - var players = SpawnPlayers(this, challenger, enemy, sheets, Log); - Turn = 1; - - while (true) - { - if (Turn > MaxTurn) - { - // todo : 턴오버일경우 정책 필요함 일단 Lose - Log.Result = ArenaLog.ArenaResult.Lose; - break; - } - - if (!players.TryDequeue(out var selectedPlayer)) - { - break; - } - - selectedPlayer.Tick(); - - var deadPlayers = players.Where(x => x.IsDead); - var arenaCharacters = deadPlayers as ArenaCharacter[] ?? deadPlayers.ToArray(); - if (arenaCharacters.Any()) - { - var (deadPlayer, result) = GetBattleResult(arenaCharacters); - Log.Result = result; - Log.Add(new ArenaDead((ArenaCharacter)deadPlayer.Clone())); - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - break; - } - - if (!selectedPlayer.IsEnemy) - { - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - Turn++; - } - - foreach (var other in players) - { - var current = players.GetPriority(other); - var speed = current * 0.6m; - players.UpdatePriority(other, speed); - } - - players.Enqueue(selectedPlayer, TurnPriority / selectedPlayer.SPD); - } - - return Log; - } - - private static (ArenaCharacter, ArenaLog.ArenaResult) GetBattleResult( - IReadOnlyCollection deadPlayers) - { - if (deadPlayers.Count > 1) - { - var enemy = deadPlayers.First(x => x.IsEnemy); - return (enemy, ArenaLog.ArenaResult.Win); - } - - var player = deadPlayers.First(); - return (player, player.IsEnemy ? ArenaLog.ArenaResult.Win : ArenaLog.ArenaResult.Lose); - } - - - private static SimplePriorityQueue SpawnPlayers( - ArenaSimulatorV2 simulator, - ArenaPlayerDigest challengerDigest, - ArenaPlayerDigest enemyDigest, - ArenaSimulatorSheets simulatorSheets, - ArenaLog log) - { - var challenger = new ArenaCharacter(simulator, challengerDigest, simulatorSheets); - if (challengerDigest.Runes != null) - { - challenger.SetRuneV1( - challengerDigest.Runes, - simulatorSheets.RuneOptionSheet, - simulatorSheets.SkillSheet); - } - - var enemy = new ArenaCharacter(simulator, enemyDigest, simulatorSheets, true); - if (enemyDigest.Runes != null) - { - enemy.SetRuneV1( - enemyDigest.Runes, - simulatorSheets.RuneOptionSheet, - simulatorSheets.SkillSheet); - } - - challenger.Spawn(enemy); - enemy.Spawn(challenger); - - log.Add(new ArenaSpawnCharacter((ArenaCharacter)challenger.Clone())); - log.Add(new ArenaSpawnCharacter((ArenaCharacter)enemy.Clone())); - - var players = new SimplePriorityQueue(); - players.Enqueue(challenger, TurnPriority / challenger.SPD); - players.Enqueue(enemy, TurnPriority / enemy.SPD); - return players; - } - } -} diff --git a/Lib9c/Arena/ArenaSimulatorV3.cs b/Lib9c/Arena/ArenaSimulatorV3.cs deleted file mode 100644 index 8c89f73e9d..0000000000 --- a/Lib9c/Arena/ArenaSimulatorV3.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Libplanet.Action; -using Nekoyume.Model; -using Nekoyume.Model.BattleStatus.Arena; -using Nekoyume.TableData; -using Priority_Queue; - -namespace Nekoyume.Arena -{ - /// - /// Introduced at https://github.com/planetarium/lib9c/pull/1679 - /// - public class ArenaSimulatorV3 : IArenaSimulator - { - private const decimal TurnPriority = 100m; - private const int MaxTurn = 200; - - public IRandom Random { get; } - public int Turn { get; private set; } - public ArenaLog Log { get; private set; } - - public ArenaSimulatorV3(IRandom random) - { - Random = random; - Turn = 1; - } - - public ArenaLog Simulate( - ArenaPlayerDigest challenger, - ArenaPlayerDigest enemy, - ArenaSimulatorSheets sheets) - { - Log = new ArenaLog(); - var players = SpawnPlayers(this, challenger, enemy, sheets, Log); - Turn = 1; - - while (true) - { - if (Turn > MaxTurn) - { - // todo : 턴오버일경우 정책 필요함 일단 Lose - Log.Result = ArenaLog.ArenaResult.Lose; - break; - } - - if (!players.TryDequeue(out var selectedPlayer)) - { - break; - } - - selectedPlayer.Tick(); - - var deadPlayers = players.Where(x => x.IsDead); - var arenaCharacters = deadPlayers as ArenaCharacter[] ?? deadPlayers.ToArray(); - if (arenaCharacters.Any()) - { - var (deadPlayer, result) = GetBattleResult(arenaCharacters); - Log.Result = result; - Log.Add(new ArenaDead((ArenaCharacter)deadPlayer.Clone())); - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - break; - } - - if (!selectedPlayer.IsEnemy) - { - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - Turn++; - } - - foreach (var other in players) - { - var current = players.GetPriority(other); - var speed = current * 0.6m; - players.UpdatePriority(other, speed); - } - - players.Enqueue(selectedPlayer, TurnPriority / selectedPlayer.SPD); - } - - return Log; - } - - private static (ArenaCharacter, ArenaLog.ArenaResult) GetBattleResult( - IReadOnlyCollection deadPlayers) - { - if (deadPlayers.Count > 1) - { - var enemy = deadPlayers.First(x => x.IsEnemy); - return (enemy, ArenaLog.ArenaResult.Win); - } - - var player = deadPlayers.First(); - return (player, player.IsEnemy ? ArenaLog.ArenaResult.Win : ArenaLog.ArenaResult.Lose); - } - - - private static SimplePriorityQueue SpawnPlayers( - ArenaSimulatorV3 simulator, - ArenaPlayerDigest challengerDigest, - ArenaPlayerDigest enemyDigest, - ArenaSimulatorSheets simulatorSheets, - ArenaLog log) - { - var challenger = new ArenaCharacter(simulator, challengerDigest, simulatorSheets); - if (challengerDigest.Runes != null) - { - challenger.SetRuneV2( - challengerDigest.Runes, - simulatorSheets.RuneOptionSheet, - simulatorSheets.SkillSheet); - } - - var enemy = new ArenaCharacter(simulator, enemyDigest, simulatorSheets, true); - if (enemyDigest.Runes != null) - { - enemy.SetRuneV2( - enemyDigest.Runes, - simulatorSheets.RuneOptionSheet, - simulatorSheets.SkillSheet); - } - - challenger.Spawn(enemy); - enemy.Spawn(challenger); - - log.Add(new ArenaSpawnCharacter((ArenaCharacter)challenger.Clone())); - log.Add(new ArenaSpawnCharacter((ArenaCharacter)enemy.Clone())); - - var players = new SimplePriorityQueue(); - players.Enqueue(challenger, TurnPriority / challenger.SPD); - players.Enqueue(enemy, TurnPriority / enemy.SPD); - return players; - } - } -} diff --git a/Lib9c/Arena/ArenaSimulatorV4.cs b/Lib9c/Arena/ArenaSimulatorV4.cs deleted file mode 100644 index 01db3b1d03..0000000000 --- a/Lib9c/Arena/ArenaSimulatorV4.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Libplanet.Action; -using Nekoyume.Model; -using Nekoyume.Model.BattleStatus.Arena; -using Nekoyume.TableData; -using Priority_Queue; - -namespace Nekoyume.Arena -{ - /// - /// Introduced at https://github.com/planetarium/lib9c/pull/1930 - /// - public class ArenaSimulatorV4 : IArenaSimulator - { - private const decimal TurnPriority = 100m; - private const int MaxTurn = 200; - - public IRandom Random { get; } - public int Turn { get; private set; } - public ArenaLog Log { get; private set; } - - public ArenaSimulatorV4(IRandom random) - { - Random = random; - Turn = 1; - } - - public ArenaLog Simulate( - ArenaPlayerDigest challenger, - ArenaPlayerDigest enemy, - ArenaSimulatorSheets sheets) - { - Log = new ArenaLog(); - var players = SpawnPlayers(this, challenger, enemy, sheets, Log); - Turn = 1; - - while (true) - { - if (Turn > MaxTurn) - { - // todo : 턴오버일경우 정책 필요함 일단 Lose - Log.Result = ArenaLog.ArenaResult.Lose; - break; - } - - if (!players.TryDequeue(out var selectedPlayer)) - { - break; - } - - selectedPlayer.Tick(); - - var deadPlayers = players.Where(x => x.IsDead); - var arenaCharacters = deadPlayers as ArenaCharacter[] ?? deadPlayers.ToArray(); - if (arenaCharacters.Any()) - { - var (deadPlayer, result) = GetBattleResult(arenaCharacters); - Log.Result = result; - Log.Add(new ArenaDead((ArenaCharacter)deadPlayer.Clone())); - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - break; - } - - if (!selectedPlayer.IsEnemy) - { - Log.Add(new ArenaTurnEnd((ArenaCharacter)selectedPlayer.Clone(), Turn)); - Turn++; - } - - foreach (var other in players) - { - var current = players.GetPriority(other); - var speed = current * 0.6m; - players.UpdatePriority(other, speed); - } - - players.Enqueue(selectedPlayer, TurnPriority / selectedPlayer.SPD); - } - - return Log; - } - - private static (ArenaCharacter, ArenaLog.ArenaResult) GetBattleResult( - IReadOnlyCollection deadPlayers) - { - if (deadPlayers.Count > 1) - { - var enemy = deadPlayers.First(x => x.IsEnemy); - return (enemy, ArenaLog.ArenaResult.Win); - } - - var player = deadPlayers.First(); - return (player, player.IsEnemy ? ArenaLog.ArenaResult.Win : ArenaLog.ArenaResult.Lose); - } - - - private static SimplePriorityQueue SpawnPlayers( - ArenaSimulatorV4 simulator, - ArenaPlayerDigest challengerDigest, - ArenaPlayerDigest enemyDigest, - ArenaSimulatorSheets simulatorSheets, - ArenaLog log) - { - var challenger = new ArenaCharacter(simulator, challengerDigest, simulatorSheets); - if (challengerDigest.Runes != null) - { - challenger.SetRuneStats(challengerDigest.Runes, simulatorSheets.RuneOptionSheet); - challenger.SetRuneSkills(challengerDigest.Runes, simulatorSheets.RuneOptionSheet, simulatorSheets.SkillSheet); - } - - var enemy = new ArenaCharacter(simulator, enemyDigest, simulatorSheets, true); - if (enemyDigest.Runes != null) - { - enemy.SetRuneStats(enemyDigest.Runes, simulatorSheets.RuneOptionSheet); - enemy.SetRuneSkills(enemyDigest.Runes, simulatorSheets.RuneOptionSheet, simulatorSheets.SkillSheet); - } - - challenger.Spawn(enemy); - enemy.Spawn(challenger); - - log.Add(new ArenaSpawnCharacter((ArenaCharacter)challenger.Clone())); - log.Add(new ArenaSpawnCharacter((ArenaCharacter)enemy.Clone())); - - var players = new SimplePriorityQueue(); - players.Enqueue(challenger, TurnPriority / challenger.SPD); - players.Enqueue(enemy, TurnPriority / enemy.SPD); - return players; - } - } -} diff --git a/Lib9c/Arena/IArenaSimulator.cs b/Lib9c/Arena/IArenaSimulator.cs index b8804bedea..370ab5a189 100644 --- a/Lib9c/Arena/IArenaSimulator.cs +++ b/Lib9c/Arena/IArenaSimulator.cs @@ -1,6 +1,7 @@ using Libplanet.Action; using Nekoyume.Model.BattleStatus.Arena; +using Nekoyume.TableData; namespace Nekoyume.Arena { @@ -9,5 +10,7 @@ public interface IArenaSimulator public ArenaLog Log { get; } public IRandom Random { get; } public int Turn { get; } + public long ShatterStrikeMaxDamage { get; } + public DeBuffLimitSheet DeBuffLimitSheet { get; } } } diff --git a/Lib9c/Battle/HitHelper.cs b/Lib9c/Battle/HitHelper.cs index f4b8c7c751..1082a6a9eb 100644 --- a/Lib9c/Battle/HitHelper.cs +++ b/Lib9c/Battle/HitHelper.cs @@ -2,6 +2,11 @@ using System; +#if TEST_LOG +using System.Text; +using UnityEngine; +#endif + namespace Nekoyume.Battle { public static class HitHelper diff --git a/Lib9c/Battle/RaidBoss.cs b/Lib9c/Battle/RaidBoss.cs index a94ec52fba..d682841b90 100644 --- a/Lib9c/Battle/RaidBoss.cs +++ b/Lib9c/Battle/RaidBoss.cs @@ -118,6 +118,11 @@ public override bool IsHit(CharacterBase caster) return base.IsHit(caster); } + if (caster.ActionBuffs.Any(buff => buff is Focus)) + { + return true; + } + var isHit = HitHelper.IsHitWithoutLevelCorrection( caster.Level, caster.HIT, diff --git a/Lib9c/Battle/RaidSimulator.cs b/Lib9c/Battle/RaidSimulator.cs index bb4b27137c..fd1a60574c 100644 --- a/Lib9c/Battle/RaidSimulator.cs +++ b/Lib9c/Battle/RaidSimulator.cs @@ -36,8 +36,13 @@ public RaidSimulator( List runeStates, RaidSimulatorSheets simulatorSheets, CostumeStatSheet costumeStatSheet, - List collectionModifiers) : base(random, avatarState, foods, simulatorSheets) + List collectionModifiers, + DeBuffLimitSheet deBuffLimitSheet, + long shatterStrikeMaxDamage = 400_000 + ) : base(random, avatarState, foods, simulatorSheets, + shatterStrikeMaxDamage: shatterStrikeMaxDamage) { + DeBuffLimitSheet = deBuffLimitSheet; var runeOptionSheet = simulatorSheets.RuneOptionSheet; var skillSheet = simulatorSheets.SkillSheet; Player.ConfigureStats(costumeStatSheet, runeStates, runeOptionSheet, skillSheet, diff --git a/Lib9c/Battle/Simulator.cs b/Lib9c/Battle/Simulator.cs index 7d43a46bcd..cca3300271 100644 --- a/Lib9c/Battle/Simulator.cs +++ b/Lib9c/Battle/Simulator.cs @@ -28,6 +28,10 @@ public abstract class Simulator : ISimulator public readonly CharacterSheet CharacterSheet; public readonly CharacterLevelSheet CharacterLevelSheet; public readonly EquipmentItemSetEffectSheet EquipmentItemSetEffectSheet; + public DeBuffLimitSheet DeBuffLimitSheet { get; protected set; } + + public long ShatterStrikeMaxDamage { get; private set; } + protected const int MaxTurn = 200; public int TurnNumber; public int WaveNumber { get; protected set; } @@ -43,6 +47,19 @@ protected Simulator(IRandom random, LogEvent = logEvent; } + protected Simulator(IRandom random, + AvatarState avatarState, + List foods, + SimulatorSheets simulatorSheets, + bool logEvent = true, + long shatterStrikeMaxDamage = 400_000 // 400k is initial limit of ShatterStrike. Use this as default + ) + : this(random, new Player(avatarState, simulatorSheets), foods, simulatorSheets) + { + LogEvent = logEvent; + ShatterStrikeMaxDamage = shatterStrikeMaxDamage; + } + protected Simulator( IRandom random, Player player, @@ -67,6 +84,30 @@ SimulatorSheetsV1 simulatorSheets Player.ResetCurrentHP(); } + protected Simulator( + IRandom random, + Player player, + List foods, + SimulatorSheets simulatorSheets + ) + { + Random = random; + MaterialItemSheet = simulatorSheets.MaterialItemSheet; + SkillSheet = simulatorSheets.SkillSheet; + SkillBuffSheet = simulatorSheets.SkillBuffSheet; + StatBuffSheet = simulatorSheets.StatBuffSheet; + SkillActionBuffSheet = simulatorSheets.SkillActionBuffSheet; + ActionBuffSheet = simulatorSheets.ActionBuffSheet; + CharacterSheet = simulatorSheets.CharacterSheet; + CharacterLevelSheet = simulatorSheets.CharacterLevelSheet; + EquipmentItemSetEffectSheet = simulatorSheets.EquipmentItemSetEffectSheet; + Log = new BattleLog(); + player.Simulator = this; + Player = player; + Player.Use(foods); + Player.ResetCurrentHP(); + } + public static List SetReward( WeightedSelector itemSelector, int maxCount, diff --git a/Lib9c/Battle/StageSimulator.cs b/Lib9c/Battle/StageSimulator.cs index 6222cf839e..0097170cdd 100644 --- a/Lib9c/Battle/StageSimulator.cs +++ b/Lib9c/Battle/StageSimulator.cs @@ -49,14 +49,20 @@ public StageSimulator(IRandom random, CostumeStatSheet costumeStatSheet, List waveRewards, List collectionModifiers, - bool logEvent = true) + DeBuffLimitSheet deBuffLimitSheet, + bool logEvent = true, + long shatterStrikeMaxDamage = 400_000 + ) : base( random, avatarState, foods, simulatorSheets, - logEvent) + logEvent, + shatterStrikeMaxDamage + ) { + DeBuffLimitSheet = deBuffLimitSheet; var runeOptionSheet = simulatorSheets.RuneOptionSheet; var skillSheet = simulatorSheets.SkillSheet; Player.ConfigureStats(costumeStatSheet, runeStates, runeOptionSheet, skillSheet, diff --git a/Lib9c/Model/BattleStatus/Arena/ArenaDoubleAttackWithCombo.cs b/Lib9c/Model/BattleStatus/Arena/ArenaDoubleAttackWithCombo.cs new file mode 100644 index 0000000000..eff8febae6 --- /dev/null +++ b/Lib9c/Model/BattleStatus/Arena/ArenaDoubleAttackWithCombo.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nekoyume.Model.BattleStatus.Arena +{ + [Serializable] + public class ArenaDoubleAttackWithCombo : ArenaSkill + { + public ArenaDoubleAttackWithCombo( + ArenaCharacter character, + IEnumerable skillInfos, + IEnumerable buffInfos) + : base(character, skillInfos, buffInfos) + { + } + + public override IEnumerator CoExecute(IArena arena) + { + yield return arena.CoDoubleAttackWithCombo(Character, SkillInfos, BuffInfos); + } + } +} diff --git a/Lib9c/Model/BattleStatus/Arena/ArenaShatterStrike.cs b/Lib9c/Model/BattleStatus/Arena/ArenaShatterStrike.cs new file mode 100644 index 0000000000..5ee0189c58 --- /dev/null +++ b/Lib9c/Model/BattleStatus/Arena/ArenaShatterStrike.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nekoyume.Model.BattleStatus.Arena +{ + [Serializable] + public class ArenaShatterStrike : ArenaSkill + { + public ArenaShatterStrike( + ArenaCharacter character, + IEnumerable skillInfos, + IEnumerable buffInfos + ) : base(character, skillInfos, buffInfos) + { + } + + public override IEnumerator CoExecute(IArena arena) + { + yield return arena.CoShatterStrike(Character, SkillInfos, BuffInfos); + } + } +} diff --git a/Lib9c/Model/BattleStatus/Arena/ArenaSkill.cs b/Lib9c/Model/BattleStatus/Arena/ArenaSkill.cs index f4a4d1aae3..896420b710 100644 --- a/Lib9c/Model/BattleStatus/Arena/ArenaSkill.cs +++ b/Lib9c/Model/BattleStatus/Arena/ArenaSkill.cs @@ -19,13 +19,16 @@ public class ArenaSkillInfo public readonly ElementalType ElementalType; public readonly SkillTargetType SkillTargetType; public readonly int Turn; - + public readonly bool Affected; + public readonly IEnumerable? DispelList; public readonly Model.Buff.Buff? Buff; public ArenaSkillInfo(ArenaCharacter character, long effect, bool critical, SkillCategory skillCategory, int turn, ElementalType elementalType = ElementalType.Normal, - SkillTargetType targetType = SkillTargetType.Enemy, Model.Buff.Buff? buff = null) + SkillTargetType targetType = SkillTargetType.Enemy, Model.Buff.Buff? buff = null, + bool affected = true, + IEnumerable? dispelList = null) { Target = character; Effect = effect; @@ -35,6 +38,8 @@ public ArenaSkillInfo(ArenaCharacter character, long effect, bool critical, Skil SkillTargetType = targetType; Buff = buff; Turn = turn; + Affected = affected; + DispelList = dispelList; } } diff --git a/Lib9c/Model/BattleStatus/DoubleAttackWithCombo.cs b/Lib9c/Model/BattleStatus/DoubleAttackWithCombo.cs new file mode 100644 index 0000000000..e4e4146730 --- /dev/null +++ b/Lib9c/Model/BattleStatus/DoubleAttackWithCombo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nekoyume.Model.BattleStatus +{ + [Serializable] + public class DoubleAttackWithCombo : Skill + { + public DoubleAttackWithCombo(int skillId, CharacterBase character, IEnumerable skillInfos, IEnumerable buffInfos) + : base(skillId, character, skillInfos, buffInfos) + { + } + + public override IEnumerator CoExecute(IStage stage) + { + yield return stage.CoDoubleAttackWithCombo(Character, SkillId, SkillInfos, BuffInfos); + } + } +} diff --git a/Lib9c/Model/BattleStatus/ShatterStrike.cs b/Lib9c/Model/BattleStatus/ShatterStrike.cs new file mode 100644 index 0000000000..b3d992a8e5 --- /dev/null +++ b/Lib9c/Model/BattleStatus/ShatterStrike.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Nekoyume.Model.BattleStatus +{ + [Serializable] + public class ShatterStrike : Skill + { + public ShatterStrike(int skillId, CharacterBase character, + IEnumerable skillInfos, IEnumerable buffInfos) + : base(skillId, character, skillInfos, buffInfos) + { + } + + public override IEnumerator CoExecute(IStage stage) + { + yield return stage.CoShatterStrike(Character, SkillId, SkillInfos, BuffInfos); + } + } +} diff --git a/Lib9c/Model/BattleStatus/Skill.cs b/Lib9c/Model/BattleStatus/Skill.cs index 17240eab63..a6746da450 100644 --- a/Lib9c/Model/BattleStatus/Skill.cs +++ b/Lib9c/Model/BattleStatus/Skill.cs @@ -22,13 +22,18 @@ public class SkillInfo public readonly long Thorn; public readonly bool IsDead; public readonly Guid CharacterId; - + public readonly IEnumerable? DispelList; + public readonly bool Affected; public readonly Model.Buff.Buff? Buff; - public SkillInfo(Guid characterId, bool isDead, long thorn, long effect, bool critical, SkillCategory skillCategory, + public SkillInfo(Guid characterId, bool isDead, long thorn, long effect, bool critical, + SkillCategory skillCategory, int waveTurn, ElementalType elementalType = ElementalType.Normal, - SkillTargetType targetType = SkillTargetType.Enemy, Model.Buff.Buff? buff = null, CharacterBase? target = null) + SkillTargetType targetType = SkillTargetType.Enemy, Model.Buff.Buff? buff = null, + CharacterBase? target = null, + bool affected = true, + IEnumerable? dispelList = null) { CharacterId = characterId; IsDead = isDead; @@ -41,6 +46,8 @@ public SkillInfo(Guid characterId, bool isDead, long thorn, long effect, bool cr Buff = buff; WaveTurn = waveTurn; Target = target; + Affected = affected; + DispelList = dispelList; } } diff --git a/Lib9c/Model/Buff/ActionBuff.cs b/Lib9c/Model/Buff/ActionBuff.cs index 91ecac20fd..b6cab331f0 100644 --- a/Lib9c/Model/Buff/ActionBuff.cs +++ b/Lib9c/Model/Buff/ActionBuff.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Nekoyume.Model.Skill; using Nekoyume.TableData; @@ -7,6 +9,12 @@ namespace Nekoyume.Model.Buff [Serializable] public abstract class ActionBuff : Buff { + private readonly IEnumerable _debuffTypes = new List + { + ActionBuffType.Bleed, + ActionBuffType.Stun, + }; + public ActionBuffSheet.Row RowData { get; } public SkillCustomField? CustomField { get; } @@ -27,5 +35,15 @@ protected ActionBuff(ActionBuff value) : base(value) RowData = value.RowData; CustomField = value.CustomField; } + + public override bool IsBuff() + { + return !IsDebuff(); + } + + public override bool IsDebuff() + { + return _debuffTypes.Contains(RowData.ActionBuffType); + } } } diff --git a/Lib9c/Model/Buff/Buff.cs b/Lib9c/Model/Buff/Buff.cs index b057503e47..3659f5e7af 100644 --- a/Lib9c/Model/Buff/Buff.cs +++ b/Lib9c/Model/Buff/Buff.cs @@ -29,6 +29,9 @@ public virtual IEnumerable GetTarget(CharacterBase caster) return BuffInfo.SkillTargetType.GetTarget(caster); } + public abstract bool IsBuff(); + public abstract bool IsDebuff(); + public abstract object Clone(); } diff --git a/Lib9c/Model/Buff/BuffFactory.cs b/Lib9c/Model/Buff/BuffFactory.cs index 331714c18e..68ac0c4405 100644 --- a/Lib9c/Model/Buff/BuffFactory.cs +++ b/Lib9c/Model/Buff/BuffFactory.cs @@ -29,6 +29,10 @@ public static ActionBuff GetActionBuff(Stats stat, ActionBuffSheet.Row row) return new Stun(row); case ActionBuffType.Vampiric: return new Vampiric(row, 0); + case ActionBuffType.Focus: + return new Focus(row); + case ActionBuffType.Dispel: + return new Dispel(row); default: throw new ArgumentOutOfRangeException(); } @@ -44,6 +48,10 @@ public static ActionBuff GetCustomActionBuff(SkillCustomField customField, Actio return new Stun(customField, row); case ActionBuffType.Vampiric: return new Vampiric(customField, row); + case ActionBuffType.Focus: + return new Focus(customField, row); + case ActionBuffType.Dispel: + return new Dispel(customField, row); default: throw new ArgumentOutOfRangeException(); } diff --git a/Lib9c/Model/Buff/Dispel.cs b/Lib9c/Model/Buff/Dispel.cs new file mode 100644 index 0000000000..bd76463faa --- /dev/null +++ b/Lib9c/Model/Buff/Dispel.cs @@ -0,0 +1,28 @@ +using System; +using Nekoyume.Model.Skill; +using Nekoyume.TableData; + +namespace Nekoyume.Model.Buff +{ + [Serializable] + public class Dispel : ActionBuff + { + public Dispel(ActionBuffSheet.Row row) : base(row) + { + } + + public Dispel(SkillCustomField customField, ActionBuffSheet.Row row) + : base(customField, row) + { + } + + protected Dispel(ActionBuff value) : base(value) + { + } + + public override object Clone() + { + return new Dispel(this); + } + } +} diff --git a/Lib9c/Model/Buff/Focus.cs b/Lib9c/Model/Buff/Focus.cs new file mode 100644 index 0000000000..bc1aaf523e --- /dev/null +++ b/Lib9c/Model/Buff/Focus.cs @@ -0,0 +1,28 @@ +using System; +using Nekoyume.Model.Skill; +using Nekoyume.TableData; + +namespace Nekoyume.Model.Buff +{ + [Serializable] + public class Focus : ActionBuff + { + public Focus(ActionBuffSheet.Row row) : base(row) + { + } + + public Focus(SkillCustomField customField, ActionBuffSheet.Row row) : + base(customField, row) + { + } + + protected Focus(Focus value) : base(value) + { + } + + public override object Clone() + { + return new Focus(this); + } + } +} diff --git a/Lib9c/Model/Buff/StatBuff.cs b/Lib9c/Model/Buff/StatBuff.cs index 4af2e726db..71b3ebe573 100644 --- a/Lib9c/Model/Buff/StatBuff.cs +++ b/Lib9c/Model/Buff/StatBuff.cs @@ -42,6 +42,16 @@ public StatModifier GetModifier() value); } + public override bool IsBuff() + { + return !IsDebuff(); + } + + public override bool IsDebuff() + { + return RowData.Value < 0 || CustomField?.BuffValue < 0; + } + public override object Clone() { return new StatBuff(this); diff --git a/Lib9c/Model/Character/ArenaCharacter.cs b/Lib9c/Model/Character/ArenaCharacter.cs index e695f32726..6c8e8d36fc 100644 --- a/Lib9c/Model/Character/ArenaCharacter.cs +++ b/Lib9c/Model/Character/ArenaCharacter.cs @@ -28,16 +28,16 @@ public class ArenaCharacter : ICloneable private readonly StatBuffSheet _statBuffSheet; private readonly SkillActionBuffSheet _skillActionBuffSheet; private readonly ActionBuffSheet _actionBuffSheet; - private readonly IArenaSimulator _simulator; private readonly ArenaSkills _skills; + public readonly IArenaSimulator Simulator; public readonly ArenaSkills _runeSkills = new ArenaSkills(); public readonly Dictionary RuneSkillCooldownMap = new Dictionary(); private readonly int _attackCountMax; private ArenaCharacter _target; - private int _attackCount; + public int AttackCount { get; private set; } public ArenaSkill usedSkill; public Guid Id { get; } = Guid.NewGuid(); @@ -87,39 +87,6 @@ public int Level public object Clone() => new ArenaCharacter(this); - [Obsolete("It using at ArenaSimulatorV1.")] - public ArenaCharacter( - ArenaSimulatorV1 simulator, - ArenaPlayerDigest digest, - ArenaSimulatorSheetsV1 sheets, - bool isEnemy = false) - { - OffensiveElementalType = GetElementalType(digest.Equipments, ItemSubType.Weapon); - DefenseElementalType = GetElementalType(digest.Equipments, ItemSubType.Armor); - var row = CharacterRow(digest.CharacterId, sheets); - SizeType = row?.SizeType ?? SizeType.S; - RunSpeed = row?.RunSpeed ?? 1f; - AttackRange = row?.AttackRange ?? 1f; - CharacterId = digest.CharacterId; - IsEnemy = isEnemy; - - _skillSheet = sheets.SkillSheet; - _skillBuffSheet = sheets.SkillBuffSheet; - _statBuffSheet = sheets.StatBuffSheet; - _skillActionBuffSheet = sheets.SkillActionBuffSheet; - _actionBuffSheet = sheets.ActionBuffSheet; - - _simulator = simulator; - Stats = GetStatV1( - digest, - row, - sheets.EquipmentItemSetEffectSheet, - sheets.CostumeStatSheet); - _skills = GetSkills(digest.Equipments, sheets.SkillSheet); - _attackCountMax = AttackCountHelper.GetCountMax(digest.Level); - ResetCurrentHP(); - } - public ArenaCharacter( IArenaSimulator simulator, ArenaPlayerDigest digest, @@ -141,7 +108,7 @@ public ArenaCharacter( _skillActionBuffSheet = sheets.SkillActionBuffSheet; _actionBuffSheet = sheets.ActionBuffSheet; - _simulator = simulator; + Simulator = simulator; Stats = GetStatV1( digest, row, @@ -177,7 +144,7 @@ public ArenaCharacter( _skillActionBuffSheet = sheets.SkillActionBuffSheet; _actionBuffSheet = sheets.ActionBuffSheet; - _simulator = simulator; + Simulator = simulator; Stats = GetStat( digest, row, @@ -221,7 +188,7 @@ private ArenaCharacter(ArenaCharacter value) _skillActionBuffSheet = value._skillActionBuffSheet; _actionBuffSheet = value._actionBuffSheet; - _simulator = value._simulator; + Simulator = value.Simulator; Stats = new CharacterStats(value.Stats); _skills = value._skills; Buffs = new Dictionary(); @@ -232,8 +199,8 @@ private ArenaCharacter(ArenaCharacter value) Buffs.Add(pair.Key, (Buff.Buff) pair.Value.Clone()); } - _attackCountMax = value._attackCount; - _attackCount = value._attackCount; + _attackCountMax = value.AttackCount; + AttackCount = value.AttackCount; _target = value._target; CurrentHP = value.CurrentHP; } @@ -573,15 +540,6 @@ private void InitAIV2() ); } - [Obsolete("Use InitAI")] - private void InitAIV1() - { - _root = new Root(); - _root.OpenBranch( - BT.Call(ActV1) - ); - } - private void Act() { if (IsDead) @@ -594,7 +552,7 @@ private void Act() if (OnPreSkill()) { usedSkill = new ArenaTick((ArenaCharacter)Clone()); - _simulator.Log.Add(usedSkill); + Simulator.Log.Add(usedSkill); } else { @@ -608,20 +566,6 @@ private void Act() RemoveBuffs(); } - [Obsolete("Use Act")] - private void ActV1() - { - if (IsDead) - { - return; - } - - ReduceDurationOfBuffs(); - ReduceSkillCooldown(); - UseSkillV1(); - RemoveBuffsV1(); - } - [Obsolete("Use Act")] private void ActV2() { @@ -658,9 +602,9 @@ or SkillCategory.AreaAttack { foreach (var effect in attackSkills .Select(skillInfo => - vampiric.GiveEffectForArena(this, skillInfo, _simulator.Turn))) + vampiric.GiveEffectForArena(this, skillInfo, Simulator.Turn))) { - _simulator.Log.Add(effect); + Simulator.Log.Add(effect); } } } @@ -668,8 +612,8 @@ or SkillCategory.AreaAttack var bleeds = Buffs.Values.OfType().OrderBy(x => x.BuffInfo.Id); foreach (var bleed in bleeds) { - var effect = bleed.GiveEffectForArena(this, _simulator.Turn); - _simulator.Log.Add(effect); + var effect = bleed.GiveEffectForArena(this, Simulator.Turn); + Simulator.Log.Add(effect); } // Apply thorn damage if target has thorn @@ -678,7 +622,7 @@ or SkillCategory.AreaAttack if (skillInfo.Target.Thorn > 0) { var effect = GiveThornDamage(skillInfo.Target.Thorn); - _simulator.Log.Add(effect); + Simulator.Log.Add(effect); } } } @@ -696,7 +640,7 @@ private ArenaSkill GiveThornDamage(long targetThorn) thornDamage, false, SkillCategory.TickDamage, - _simulator.Turn, + Simulator.Turn, ElementalType.Normal, SkillTargetType.Enemy) }; @@ -727,13 +671,12 @@ private void ReduceSkillCooldown() private BattleStatus.Arena.ArenaSkill UseSkill() { - var selectedRuneSkill = _runeSkills.SelectWithoutDefaultAttack(_simulator.Random); - var selectedSkill = selectedRuneSkill ?? - _skills.Select(_simulator.Random); + var selectedRuneSkill = _runeSkills.SelectWithoutDefaultAttack(Simulator.Random); + var selectedSkill = selectedRuneSkill ?? _skills.Select(Simulator.Random); usedSkill = selectedSkill.Use( this, _target, - _simulator.Turn, + Simulator.Turn, BuffFactory.GetBuffs( Stats, selectedSkill, @@ -763,36 +706,10 @@ selectedSkill is ArenaBuffSkill && _runeSkills.SetCooldown(selectedSkill.SkillRow.Id, row.Cooldown); } - _simulator.Log.Add(usedSkill); + Simulator.Log.Add(usedSkill); return usedSkill; } - [Obsolete("Use UseSkill")] - private void UseSkillV1() - { - var selectedSkill = _skills.Select(_simulator.Random); - SkillLog = selectedSkill.UseV1( - this, - _target, - _simulator.Turn, - BuffFactory.GetBuffs( - Stats, - selectedSkill, - _skillBuffSheet, - _statBuffSheet, - _skillActionBuffSheet, - _actionBuffSheet) - ); - - if (!_skillSheet.TryGetValue(selectedSkill.SkillRow.Id, out var row)) - { - throw new KeyNotFoundException( - selectedSkill.SkillRow.Id.ToString(CultureInfo.InvariantCulture)); - } - - _skills.SetCooldown(selectedSkill.SkillRow.Id, row.Cooldown); - } - private void RemoveBuffs() { var isBuffRemoved = false; @@ -811,7 +728,7 @@ private void RemoveBuffs() if (!isBuffRemoved) return; - Stats.SetBuffs(StatBuffs); + Stats.SetBuffs(StatBuffs, Simulator.DeBuffLimitSheet); } [Obsolete("Use RemoveBuffs")] @@ -833,7 +750,7 @@ private void RemoveBuffsV1() if (isApply) { - Stats.SetBuffs(StatBuffs); + Stats.SetBuffs(StatBuffs, Simulator.DeBuffLimitSheet); } } @@ -849,13 +766,6 @@ public void Spawn(ArenaCharacter target) InitAI(); } - [Obsolete("Use Spawn")] - public void SpawnV1(ArenaCharacter target) - { - _target = target; - InitAIV1(); - } - [Obsolete("Use Spawn")] public void SpawnV2(ArenaCharacter target) { @@ -863,35 +773,81 @@ public void SpawnV2(ArenaCharacter target) InitAIV2(); } - public void AddBuff(Buff.Buff buff, bool updateImmediate = true) + public IEnumerable AddBuff(Buff.Buff buff, bool updateImmediate = true) { if (Buffs.TryGetValue(buff.BuffInfo.GroupId, out var outBuff) && outBuff.BuffInfo.Id > buff.BuffInfo.Id) - return; + return null; - if (buff is StatBuff stat) + var dispelList = new List(); + switch (buff) { - var clone = (StatBuff)stat.Clone(); - Buffs[stat.RowData.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); - } - else if (buff is ActionBuff action) - { - var clone = (ActionBuff)action.Clone(); - Buffs[action.RowData.GroupId] = clone; + // StatBuff Modifies stats + case StatBuff stat: + { + var clone = (StatBuff)stat.Clone(); + Buffs[stat.RowData.GroupId] = clone; + Stats.AddBuff(clone, Simulator.DeBuffLimitSheet, updateImmediate); + break; + } + case ActionBuff action: + { + var clone = (ActionBuff)action.Clone(); + + switch (action) + { + // Stun freezes target + case Stun stun: + { + Buffs[stun.BuffInfo.GroupId] = clone; + break; + } + // Dispel removes debuffs + case Dispel dispel: + { + Buffs[dispel.BuffInfo.GroupId] = clone; + + dispelList = Buffs.Values.Where( + bff => bff.IsDebuff() && + Simulator.Random.Next(0, 100) < + action.RowData.Chance).ToList(); + + foreach (var bff in dispelList) + { + switch (bff) + { + case StatBuff statBuff: + RemoveStatBuff(statBuff); + break; + case ActionBuff actionBuff: + RemoveActionBuff(actionBuff); + break; + } + } + + break; + } + default: + Buffs[action.RowData.GroupId] = clone; + break; + } + + break; + } } + + return dispelList; } - [Obsolete("Use AddBuff")] - public void AddBuffV1(Buff.Buff buff, bool updateImmediate = true) + public void RemoveActionBuff(ActionBuff removedBuff) { - if (Buffs.TryGetValue(buff.BuffInfo.GroupId, out var outBuff) && - outBuff.BuffInfo.Id > buff.BuffInfo.Id) - return; + Buffs.Remove(removedBuff.RowData.GroupId); + } - var clone = (Buff.StatBuff) buff.Clone(); - Buffs[buff.BuffInfo.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); + public void RemoveStatBuff(StatBuff removedBuff) + { + Stats.RemoveBuff(removedBuff); + Buffs.Remove(removedBuff.RowData.GroupId); } public void RemoveRecentStatBuff() @@ -924,8 +880,7 @@ public void RemoveRecentStatBuff() if (removedBuff != null) { - Stats.RemoveBuff(removedBuff); - Buffs.Remove(removedBuff.RowData.GroupId); + RemoveStatBuff(removedBuff); } } @@ -936,26 +891,31 @@ public void Heal(long heal) public bool IsCritical(bool considerAttackCount = true) { - var chance = _simulator.Random.Next(0, 100); + var chance = Simulator.Random.Next(0, 100); if (!considerAttackCount) return CRI >= chance; var additionalCriticalChance = - AttackCountHelper.GetAdditionalCriticalChance(_attackCount, _attackCountMax); + AttackCountHelper.GetAdditionalCriticalChance(AttackCount, _attackCountMax); return CRI + additionalCriticalChance >= chance; } public virtual bool IsHit(ArenaCharacter caster) { + if (caster.ActionBuffs.Any(buff => buff is Focus)) + { + return true; + } + var isHit = HitHelper.IsHitWithoutLevelCorrection( caster.Level, caster.HIT, Level, HIT, - _simulator.Random.Next(0, 100)); + Simulator.Random.Next(0, 100)); if (!isHit) { - caster._attackCount = 0; + caster.AttackCount = 0; } return isHit; @@ -966,13 +926,13 @@ public long GetDamage(long damage, bool considerAttackCount = true) if (!considerAttackCount) return damage; - _attackCount++; - if (_attackCount > _attackCountMax) + AttackCount++; + if (AttackCount > _attackCountMax) { - _attackCount = 1; + AttackCount = 1; } - var damageMultiplier = AttackCountHelper.GetDamageMultiplier(_attackCount, _attackCountMax); + var damageMultiplier = AttackCountHelper.GetDamageMultiplier(AttackCount, _attackCountMax); damage *= damageMultiplier; return damage; } diff --git a/Lib9c/Model/Character/CharacterBase.cs b/Lib9c/Model/Character/CharacterBase.cs index 8e63828924..a3eca37b33 100644 --- a/Lib9c/Model/Character/CharacterBase.cs +++ b/Lib9c/Model/Character/CharacterBase.cs @@ -2,6 +2,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +#if TEST_LOG +using System.Text; +using UnityEngine; +#endif using BTAI; using Nekoyume.Battle; using Nekoyume.Model.BattleStatus; @@ -334,7 +338,7 @@ private void RemoveBuffs() if (!isBuffRemoved) return; - Stats.SetBuffs(StatBuffs); + Stats.SetBuffs(StatBuffs, Simulator.DeBuffLimitSheet); if (Simulator.LogEvent) { Simulator.Log.Add(new RemoveBuffs((CharacterBase) Clone())); @@ -352,28 +356,89 @@ protected virtual void EndTurn() #region Buff - public void AddBuff(Buff.Buff buff, bool updateImmediate = true) + /// + /// Add buff/debuff to target; it means buff/debuff is used by caster. + /// When `Dispel` is used, it can remove prev. debuffs on target. + /// All the removed debuffs will be returned and saved in battle log. + /// + /// + /// + /// An enumerable of removed debuffs from target. `null` will be returned if nothing eliminated. + public IEnumerable AddBuff(Buff.Buff buff, bool updateImmediate = true) { if (Buffs.TryGetValue(buff.BuffInfo.GroupId, out var outBuff) && outBuff.BuffInfo.Id > buff.BuffInfo.Id) - return; + return null; - if (buff is StatBuff stat) - { - var clone = (StatBuff)stat.Clone(); - Buffs[stat.RowData.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); - } - else if (buff is ActionBuff action) - { - var clone = (ActionBuff)action.Clone(); - Buffs[action.RowData.GroupId] = clone; - } - else if (buff is Stun stun) + var dispelList = new List(); + switch (buff) { - var clone = (Stun)stun.Clone(); - Buffs[stun.BuffInfo.GroupId] = clone; + // StatBuff Modifies stats + case StatBuff stat: + { + var clone = (StatBuff)stat.Clone(); + Buffs[stat.RowData.GroupId] = clone; + Stats.AddBuff(clone, Simulator.DeBuffLimitSheet, updateImmediate); + break; + } + case ActionBuff action: + { + var clone = (ActionBuff)action.Clone(); + + switch (action) + { + // Stun freezes target + case Stun stun: + { + Buffs[stun.BuffInfo.GroupId] = clone; + break; + } + // Dispel removes debuffs + case Dispel dispel: + { + Buffs[dispel.BuffInfo.GroupId] = clone; + + dispelList = Buffs.Values.Where( + bff => bff.IsDebuff() && + Simulator.Random.Next(0, 100) < + dispel.BuffInfo.Chance).ToList(); + + foreach (var bff in dispelList) + { + switch (bff) + { + case StatBuff statBuff: + RemoveStatBuff(statBuff); + break; + case ActionBuff actionBuff: + RemoveActionBuff(actionBuff); + break; + } + } + + break; + } + default: + Buffs[action.RowData.GroupId] = clone; + break; + } + + break; + } } + + return dispelList; + } + + public void RemoveActionBuff(ActionBuff removedBuff) + { + Buffs.Remove(removedBuff.RowData.GroupId); + } + + public void RemoveStatBuff(StatBuff removedBuff) + { + Stats.RemoveBuff(removedBuff); + Buffs.Remove(removedBuff.RowData.GroupId); } public void RemoveRecentStatBuff() @@ -406,8 +471,7 @@ public void RemoveRecentStatBuff() if (removedBuff != null) { - Stats.RemoveBuff(removedBuff); - Buffs.Remove(removedBuff.RowData.GroupId); + RemoveStatBuff(removedBuff); } } @@ -438,6 +502,11 @@ public bool IsHit(ElementalResult result) public virtual bool IsHit(CharacterBase caster) { + if (caster.ActionBuffs.Any(buff => buff is Focus)) + { + return true; + } + var isHit = HitHelper.IsHit(caster.Level, caster.HIT, Level, HIT, Simulator.Random.Next(0, 100)); if (!isHit) { diff --git a/Lib9c/Model/EnumType/Grade.cs b/Lib9c/Model/EnumType/Grade.cs index dab43467d7..5512e2d237 100644 --- a/Lib9c/Model/EnumType/Grade.cs +++ b/Lib9c/Model/EnumType/Grade.cs @@ -7,5 +7,6 @@ public enum Grade Epic = 3, Unique = 4, Legendary = 5, + Divinity = 6, } } diff --git a/Lib9c/Model/IArena.cs b/Lib9c/Model/IArena.cs index 71f96b6caa..e9fc0d4b1e 100644 --- a/Lib9c/Model/IArena.cs +++ b/Lib9c/Model/IArena.cs @@ -11,8 +11,10 @@ public interface IArena IEnumerator CoNormalAttack(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBlowAttack(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoDoubleAttack(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); + IEnumerator CoDoubleAttackWithCombo(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoAreaAttack(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBuffRemovalAttack(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); + IEnumerator CoShatterStrike(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoHeal(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBuff(ArenaCharacter caster, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoTickDamage(ArenaCharacter affectedCharacter, IEnumerable skillInfos); diff --git a/Lib9c/Model/IStage.cs b/Lib9c/Model/IStage.cs index 4a8f6c79d5..747665b615 100644 --- a/Lib9c/Model/IStage.cs +++ b/Lib9c/Model/IStage.cs @@ -15,12 +15,13 @@ public interface IStage IEnumerator CoNormalAttack(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBlowAttack(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoDoubleAttack(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); + IEnumerator CoDoubleAttackWithCombo(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoAreaAttack(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBuffRemovalAttack(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoHeal(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoBuff(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); IEnumerator CoTickDamage(CharacterBase affectedCharacter, int skillId, IEnumerable skillInfos); - + IEnumerator CoShatterStrike(CharacterBase caster, int skillId, IEnumerable skillInfos, IEnumerable buffInfos); #endregion IEnumerator CoRemoveBuffs(CharacterBase caster); diff --git a/Lib9c/Model/Skill/ActionBuffType.cs b/Lib9c/Model/Skill/ActionBuffType.cs index 64662ef89b..41007ef9db 100644 --- a/Lib9c/Model/Skill/ActionBuffType.cs +++ b/Lib9c/Model/Skill/ActionBuffType.cs @@ -6,5 +6,7 @@ public enum ActionBuffType Bleed, Stun, Vampiric, + Focus, + Dispel, // Erase/defence debuffs on me } } diff --git a/Lib9c/Model/Skill/Arena/ArenaAreaAttack.cs b/Lib9c/Model/Skill/Arena/ArenaAreaAttack.cs index 584364c862..c2d57cd052 100644 --- a/Lib9c/Model/Skill/Arena/ArenaAreaAttack.cs +++ b/Lib9c/Model/Skill/Arena/ArenaAreaAttack.cs @@ -29,19 +29,5 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaAreaAttack(clone, damage, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var damage = ProcessDamage(caster, target, turn); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaAreaAttack(clone, damage, buff); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaAttackSkill.cs b/Lib9c/Model/Skill/Arena/ArenaAttackSkill.cs index 2acb4a85c7..0cd7cfc7b9 100644 --- a/Lib9c/Model/Skill/Arena/ArenaAttackSkill.cs +++ b/Lib9c/Model/Skill/Arena/ArenaAttackSkill.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Nekoyume.Arena; using Nekoyume.Battle; using Nekoyume.Model.Elemental; using Nekoyume.Model.Stat; @@ -19,7 +20,7 @@ protected ArenaAttackSkill( { } - protected IEnumerable ProcessDamage( + protected IEnumerable ProcessDamage( ArenaCharacter caster, ArenaCharacter target, int simulatorWaveTurn, @@ -42,11 +43,14 @@ protected ArenaAttackSkill( if (target.IsHit(caster)) { - damage = caster.ATK + Power + statAdditionalPower; - damage = (long) (damage * multiplier); - damage = caster.GetDamage(damage, isNormalAttack); + damage = (long)(SkillRow.SkillCategory is SkillCategory.ShatterStrike + ? target.HP * powerMultiplier + : caster.ATK + Power + statAdditionalPower); + damage = (long)(damage * multiplier); + damage = caster.GetDamage(damage, isNormalAttack || SkillRow.Combo); damage = elementalType.GetDamage(target.DefenseElementalType, damage); - isCritical = caster.IsCritical(isNormalAttack); + isCritical = SkillRow.SkillCategory is not SkillCategory.ShatterStrike && + caster.IsCritical(isNormalAttack || SkillRow.Combo); if (isCritical) { damage = CriticalHelper.GetCriticalDamageForArena(caster, damage); @@ -57,6 +61,14 @@ protected ArenaAttackSkill( damage = Math.Max(damage - finalDEF, 1); // Apply damage reduce damage = (int)((damage - target.DRV) * (1 - target.DRR / 10000m)); + + // ShatterStrike has max damage limitation + if (SkillRow.SkillCategory is SkillCategory.ShatterStrike) + { + damage = Math.Clamp(damage, + 1, caster.Simulator.ShatterStrikeMaxDamage); + } + target.CurrentHP -= damage; // double attack must be shown as critical attack @@ -76,21 +88,21 @@ protected ArenaAttackSkill( return infos; } - private static decimal[] GetMultiplier(int hitCount, decimal totalDamage) - { - if (hitCount == 1) return new[] {totalDamage}; - var multiplier = new List(); - var avg = totalDamage / hitCount; - var lastDamage = avg * 1.3m; - var lastHitIndex = hitCount - 1; - var eachDamage = (totalDamage - lastDamage) / lastHitIndex; - for (var i = 0; i < hitCount; i++) - { - var result = i == lastHitIndex ? lastDamage : eachDamage; - multiplier.Add(result); - } + private static decimal[] GetMultiplier(int hitCount, decimal totalDamage) + { + if (hitCount == 1) return new[] { totalDamage }; + var multiplier = new List(); + var avg = totalDamage / hitCount; + var lastDamage = avg * 1.3m; + var lastHitIndex = hitCount - 1; + var eachDamage = (totalDamage - lastDamage) / lastHitIndex; + for (var i = 0; i < hitCount; i++) + { + var result = i == lastHitIndex ? lastDamage : eachDamage; + multiplier.Add(result); + } - return multiplier.ToArray(); - } + return multiplier.ToArray(); + } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaBlowAttack.cs b/Lib9c/Model/Skill/Arena/ArenaBlowAttack.cs index d10dccbc99..3189f56157 100644 --- a/Lib9c/Model/Skill/Arena/ArenaBlowAttack.cs +++ b/Lib9c/Model/Skill/Arena/ArenaBlowAttack.cs @@ -29,19 +29,5 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaBlowAttack(clone, damage, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var damage = ProcessDamage(caster, target, turn); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaBlowAttack(clone, damage, buff); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaBuffRemovalAttack.cs b/Lib9c/Model/Skill/Arena/ArenaBuffRemovalAttack.cs index 43ff7695d0..cb2641d958 100644 --- a/Lib9c/Model/Skill/Arena/ArenaBuffRemovalAttack.cs +++ b/Lib9c/Model/Skill/Arena/ArenaBuffRemovalAttack.cs @@ -30,20 +30,5 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaBuffRemovalAttack(clone, damage, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var damage = ProcessDamage(caster, target, turn); - var buff = ProcessBuffV1(caster, target, turn, buffs); - target.RemoveRecentStatBuff(); - - return new BattleStatus.Arena.ArenaBuffRemovalAttack(clone, damage, buff); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaBuffSkill.cs b/Lib9c/Model/Skill/Arena/ArenaBuffSkill.cs index d4bae7ddbc..5512fe9930 100644 --- a/Lib9c/Model/Skill/Arena/ArenaBuffSkill.cs +++ b/Lib9c/Model/Skill/Arena/ArenaBuffSkill.cs @@ -28,18 +28,5 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaBuff(clone, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaBuff(clone, buff); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaDoubleAttack.cs b/Lib9c/Model/Skill/Arena/ArenaDoubleAttack.cs index 01f5b5109e..7f884a83b5 100644 --- a/Lib9c/Model/Skill/Arena/ArenaDoubleAttack.cs +++ b/Lib9c/Model/Skill/Arena/ArenaDoubleAttack.cs @@ -13,7 +13,8 @@ public ArenaDoubleAttack( long power, int chance, int statPowerRatio, - StatType referencedStatType) : base(skillRow, power, chance, statPowerRatio, referencedStatType) + StatType referencedStatType) : base(skillRow, power, chance, statPowerRatio, + referencedStatType) { } @@ -23,25 +24,18 @@ public override BattleStatus.Arena.ArenaSkill Use( int turn, IEnumerable buffs) { - var clone = (ArenaCharacter)caster.Clone(); + var clone = (ArenaCharacter) caster.Clone(); var damage = ProcessDamage(caster, target, turn); var buff = ProcessBuff(caster, target, turn, buffs); - return new BattleStatus.Arena.ArenaDoubleAttack(clone, damage, buff); - } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var damage = ProcessDamage(caster, target, turn); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaDoubleAttack(clone, damage, buff); + if (SkillRow.Combo) + { + return new BattleStatus.Arena.ArenaDoubleAttackWithCombo(clone, damage, buff); + } + else + { + return new BattleStatus.Arena.ArenaDoubleAttack(clone, damage, buff); + } } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaHealSkill.cs b/Lib9c/Model/Skill/Arena/ArenaHealSkill.cs index 442d62262e..fdb4aedca1 100644 --- a/Lib9c/Model/Skill/Arena/ArenaHealSkill.cs +++ b/Lib9c/Model/Skill/Arena/ArenaHealSkill.cs @@ -30,20 +30,6 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaHeal(clone, heal, buff); } - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var heal = ProcessHeal(caster, turn); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaHeal(clone, heal, buff); - } - private IEnumerable ProcessHeal( ArenaCharacter caster, int turn) diff --git a/Lib9c/Model/Skill/Arena/ArenaNormalAttack.cs b/Lib9c/Model/Skill/Arena/ArenaNormalAttack.cs index 44fe6d8e7c..1d80931c66 100644 --- a/Lib9c/Model/Skill/Arena/ArenaNormalAttack.cs +++ b/Lib9c/Model/Skill/Arena/ArenaNormalAttack.cs @@ -29,19 +29,5 @@ public override BattleStatus.Arena.ArenaSkill Use( return new BattleStatus.Arena.ArenaNormalAttack(clone, damage, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs) - { - var clone = (ArenaCharacter)caster.Clone(); - var damage = ProcessDamage(caster, target, turn, true); - var buff = ProcessBuffV1(caster, target, turn, buffs); - - return new BattleStatus.Arena.ArenaNormalAttack(clone, damage, buff); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaShatterStrike.cs b/Lib9c/Model/Skill/Arena/ArenaShatterStrike.cs new file mode 100644 index 0000000000..cab04595c0 --- /dev/null +++ b/Lib9c/Model/Skill/Arena/ArenaShatterStrike.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Nekoyume.Model.Stat; +using Nekoyume.TableData; + +namespace Nekoyume.Model.Skill.Arena +{ + [Serializable] + public class ArenaShatterStrike: ArenaAttackSkill + { + public ArenaShatterStrike(SkillSheet.Row skillRow, long power, int chance, int statPowerRatio, StatType referencedStatType) : base(skillRow, power, chance, statPowerRatio, referencedStatType) + { + } + + public override BattleStatus.Arena.ArenaSkill Use(ArenaCharacter caster, ArenaCharacter target, int turn, IEnumerable buffs) + { + var clone = (ArenaCharacter)caster.Clone(); + var damage = ProcessDamage(caster, target, turn); + var buff = ProcessBuff(caster, target, turn, buffs); + + return new BattleStatus.Arena.ArenaShatterStrike(clone, damage, buff); + } + } +} diff --git a/Lib9c/Model/Skill/Arena/ArenaSkill.cs b/Lib9c/Model/Skill/Arena/ArenaSkill.cs index 59c75216c9..b6eb4b58de 100644 --- a/Lib9c/Model/Skill/Arena/ArenaSkill.cs +++ b/Lib9c/Model/Skill/Arena/ArenaSkill.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using Nekoyume.Model.Buff; using Nekoyume.Model.Elemental; using Nekoyume.Model.Stat; using Nekoyume.TableData; @@ -40,14 +42,6 @@ public abstract BattleStatus.Arena.ArenaSkill Use( IEnumerable buffs ); - [Obsolete("Use Use")] - public abstract BattleStatus.Arena.ArenaSkill UseV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs - ); - protected bool Equals(Skill other) { return SkillRow.Equals(other.SkillRow) && Power == other.Power && Chance.Equals(other.Chance); @@ -84,50 +78,34 @@ public override int GetHashCode() var infos = new List(); foreach (var buff in buffs) { + IEnumerable dispelList = null; switch (buff.BuffInfo.SkillTargetType) { case SkillTargetType.Enemy: case SkillTargetType.Enemies: - target.AddBuff(buff); - infos.Add(GetSkillInfo(target, turn, buff)); - break; - - case SkillTargetType.Self: - case SkillTargetType.Ally: - caster.AddBuff(buff); - infos.Add(GetSkillInfo(caster, turn, buff)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - return infos; - } - - [Obsolete("Use ProcessBuff")] - protected IEnumerable ProcessBuffV1( - ArenaCharacter caster, - ArenaCharacter target, - int turn, - IEnumerable buffs - ) - { - var infos = new List(); - foreach (var buff in buffs) - { - switch (buff.BuffInfo.SkillTargetType) - { - case SkillTargetType.Enemy: - case SkillTargetType.Enemies: - target.AddBuffV1(buff); - infos.Add(GetSkillInfo(target, turn, buff)); + var affected = true; + var dispel = target.Buffs.Values.FirstOrDefault(bf => bf is Dispel); + if (dispel is not null && buff.IsDebuff()) + { + if (target.Simulator.Random.Next(0, 100) < dispel.BuffInfo.Chance) + { + affected = false; + } + } + + if (affected) + { + dispelList = target.AddBuff(buff); + } + + infos.Add(GetSkillInfo(target, turn, buff, affected: affected, + dispelList: dispelList)); break; case SkillTargetType.Self: case SkillTargetType.Ally: - caster.AddBuffV1(buff); - infos.Add(GetSkillInfo(caster, turn, buff)); + dispelList = caster.AddBuff(buff); + infos.Add(GetSkillInfo(caster, turn, buff, dispelList: dispelList)); break; default: throw new ArgumentOutOfRangeException(); @@ -137,17 +115,22 @@ public override int GetHashCode() return infos; } - private BattleStatus.Arena.ArenaSkill.ArenaSkillInfo GetSkillInfo(ICloneable target, int turn, Buff.Buff buff) + private BattleStatus.Arena.ArenaSkill.ArenaSkillInfo GetSkillInfo(ICloneable target, + int turn, Buff.Buff buff, bool affected = true, + IEnumerable dispelList = null) { return new BattleStatus.Arena.ArenaSkill.ArenaSkillInfo( - (ArenaCharacter) target.Clone(), + (ArenaCharacter)target.Clone(), 0, false, SkillRow.SkillCategory, turn, ElementalType.Normal, SkillRow.SkillTargetType, - buff); + buff, + affected, + dispelList + ); } @@ -157,5 +140,15 @@ public void Update(int chance, long power, int statPowerRatio) Power = power; StatPowerRatio = statPowerRatio; } + + public bool IsBuff() + { + return SkillRow.SkillType is SkillType.Buff; + } + + public bool IsDebuff() + { + return SkillRow.SkillType is SkillType.Debuff; + } } } diff --git a/Lib9c/Model/Skill/AttackSkill.cs b/Lib9c/Model/Skill/AttackSkill.cs index 18bd604a22..f2c66fbf66 100644 --- a/Lib9c/Model/Skill/AttackSkill.cs +++ b/Lib9c/Model/Skill/AttackSkill.cs @@ -49,6 +49,11 @@ protected AttackSkill( foreach (var target in targets) { + if (SkillRow.SkillCategory is SkillCategory.ShatterStrike) + { + totalDamage = (long)(target.HP * powerMultiplier); + } + long damage = 0; var isCritical = false; // Skill or when normal attack hit. @@ -70,18 +75,30 @@ protected AttackSkill( else { // 모션 배율 적용. - damage = caster.GetDamage(damage, isNormalAttack); + damage = caster.GetDamage( + damage, + isNormalAttack || SkillRow.Combo + ); // 속성 적용. damage = elementalType.GetDamage(target.defElementType, damage); // 치명 적용. - isCritical = caster.IsCritical(isNormalAttack); + isCritical = + SkillRow.SkillCategory is not SkillCategory.ShatterStrike && + caster.IsCritical(isNormalAttack || SkillRow.Combo); if (isCritical) { damage = CriticalHelper.GetCriticalDamage(caster, damage); } // double attack must be shown as critical attack - isCritical |= SkillRow.SkillCategory == SkillCategory.DoubleAttack; + isCritical |= SkillRow.SkillCategory is SkillCategory.DoubleAttack; + + // ShatterStrike has max damage limitation + if (SkillRow.SkillCategory is SkillCategory.ShatterStrike) + { + damage = Math.Clamp(damage, + 1, caster.Simulator.ShatterStrikeMaxDamage); + } } target.CurrentHP -= damage; diff --git a/Lib9c/Model/Skill/DoubleAttack.cs b/Lib9c/Model/Skill/DoubleAttack.cs index c2661d28e4..f9c4e39f63 100644 --- a/Lib9c/Model/Skill/DoubleAttack.cs +++ b/Lib9c/Model/Skill/DoubleAttack.cs @@ -24,8 +24,14 @@ public override BattleStatus.Skill Use(CharacterBase caster, var clone = copyCharacter ? (CharacterBase) caster.Clone() : null; var damage = ProcessDamage(caster, simulatorWaveTurn, copyCharacter: copyCharacter); var buff = ProcessBuff(caster, simulatorWaveTurn, buffs, copyCharacter); - - return new Model.BattleStatus.DoubleAttack(SkillRow.Id, clone, damage, buff); + if (SkillRow.Combo) + { + return new Model.BattleStatus.DoubleAttackWithCombo(SkillRow.Id, clone, damage, buff); + } + else + { + return new Model.BattleStatus.DoubleAttack(SkillRow.Id, clone, damage, buff); + } } } } diff --git a/Lib9c/Model/Skill/ISkill.cs b/Lib9c/Model/Skill/ISkill.cs index 59972fe7a7..725756a8c0 100644 --- a/Lib9c/Model/Skill/ISkill.cs +++ b/Lib9c/Model/Skill/ISkill.cs @@ -16,5 +16,8 @@ public interface ISkill public StatType ReferencedStatType { get; } public SkillCustomField? CustomField { get; } public void Update(int chance, long power, int statPowerRatio); + + public bool IsBuff(); + public bool IsDebuff(); } } diff --git a/Lib9c/Model/Skill/ShatterStrike.cs b/Lib9c/Model/Skill/ShatterStrike.cs new file mode 100644 index 0000000000..d7e4f89ee0 --- /dev/null +++ b/Lib9c/Model/Skill/ShatterStrike.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using Nekoyume.Model.Stat; +using Nekoyume.TableData; + +namespace Nekoyume.Model.Skill +{ + [Serializable] + public class ShatterStrike : AttackSkill + { + public ShatterStrike( + SkillSheet.Row skillRow, + long power, int chance, + int statPowerRatio, StatType referencedStatType + ) : base(skillRow, power, chance, statPowerRatio, referencedStatType) + { + } + + public override BattleStatus.Skill Use(CharacterBase caster, int simulatorWaveTurn, + IEnumerable buffs, bool copyCharacter) + { + var clone = copyCharacter ? (CharacterBase) caster.Clone() : null; + var damage = ProcessDamage(caster, simulatorWaveTurn, copyCharacter: copyCharacter); + var buff = ProcessBuff(caster, simulatorWaveTurn, buffs, copyCharacter); + return new Model.BattleStatus.ShatterStrike(SkillRow.Id, clone, damage, buff); + } + } +} diff --git a/Lib9c/Model/Skill/Skill.cs b/Lib9c/Model/Skill/Skill.cs index 65a5599383..ba35590cd1 100644 --- a/Lib9c/Model/Skill/Skill.cs +++ b/Lib9c/Model/Skill/Skill.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Bencodex.Types; +using Nekoyume.Model.Buff; using Nekoyume.Model.Elemental; using Nekoyume.Model.Stat; using Nekoyume.Model.State; @@ -76,12 +77,32 @@ public override int GetHashCode() foreach (var buff in buffs) { var targets = buff.GetTarget(caster); - foreach (var target in targets.Where(target => target.GetChance(buff.BuffInfo.Chance))) + foreach (var target in targets.Where(target => + target.GetChance(buff.BuffInfo.Chance))) { - target.AddBuff(buff); - infos.Add(new Model.BattleStatus.Skill.SkillInfo(target.Id, target.IsDead, target.Thorn, 0, false, - SkillRow.SkillCategory, simulatorWaveTurn, ElementalType.Normal, SkillRow.SkillTargetType, - buff, copyCharacter ? (CharacterBase)target.Clone() : target)); + var affected = true; + IEnumerable dispelList = null; + var dispel = target.Buffs.Values.FirstOrDefault(bf => bf is Dispel); + // Defence debuff if target has dispel + if (dispel is not null && buff.IsDebuff()) + { + if (target.Simulator.Random.Next(0, 100) < dispel.BuffInfo.Chance) + { + affected = false; + } + } + + if (affected) + { + dispelList = target.AddBuff(buff); + } + + infos.Add(new Model.BattleStatus.Skill.SkillInfo(target.Id, target.IsDead, + target.Thorn, 0, false, + SkillRow.SkillCategory, simulatorWaveTurn, ElementalType.Normal, + SkillRow.SkillTargetType, + buff, copyCharacter ? (CharacterBase)target.Clone() : target, + affected: affected, dispelList: dispelList)); } } @@ -95,6 +116,16 @@ public void Update(int chance, long power, int statPowerRatio) StatPowerRatio = statPowerRatio; } + public bool IsBuff() + { + return SkillRow.SkillType is SkillType.Buff; + } + + public bool IsDebuff() + { + return SkillRow.SkillType is SkillType.Debuff; + } + public IValue Serialize() { var dict = new Bencodex.Types.Dictionary(new Dictionary diff --git a/Lib9c/Model/Skill/SkillCategory.cs b/Lib9c/Model/Skill/SkillCategory.cs index 617be7d1cc..f0c7404526 100644 --- a/Lib9c/Model/Skill/SkillCategory.cs +++ b/Lib9c/Model/Skill/SkillCategory.cs @@ -4,9 +4,10 @@ public enum SkillCategory { NormalAttack, BlowAttack, - DoubleAttack, + DoubleAttack, // Attack enemy two times AreaAttack, BuffRemovalAttack, + ShatterStrike, // Damage based on enemy's full HP Heal, @@ -22,5 +23,7 @@ public enum SkillCategory Buff, Debuff, TickDamage, + Focus, // Always hit enemy. + Dispel, // Remove/defence debuffs } } diff --git a/Lib9c/Model/Skill/SkillFactory.cs b/Lib9c/Model/Skill/SkillFactory.cs index 5a99433217..15e9d67b47 100644 --- a/Lib9c/Model/Skill/SkillFactory.cs +++ b/Lib9c/Model/Skill/SkillFactory.cs @@ -31,6 +31,9 @@ public static Skill Get( return new AreaAttack(skillRow, power, chance, statPowerRatio, referencedStatType); case SkillCategory.BuffRemovalAttack: return new BuffRemovalAttack(skillRow, power, chance, statPowerRatio, referencedStatType); + case SkillCategory.ShatterStrike: + return new ShatterStrike(skillRow, power, chance, statPowerRatio, + referencedStatType); default: return new NormalAttack(skillRow, power, chance, statPowerRatio, referencedStatType); } diff --git a/Lib9c/Model/Stat/CharacterStats.cs b/Lib9c/Model/Stat/CharacterStats.cs index 56e1af1377..c48d40324c 100644 --- a/Lib9c/Model/Stat/CharacterStats.cs +++ b/Lib9c/Model/Stat/CharacterStats.cs @@ -263,17 +263,18 @@ public CharacterStats SetRunes(IEnumerable value, bool updateImmed /// Set stats based on buffs. ///
/// + /// /// /// public CharacterStats SetBuffs(IEnumerable value, - bool updateImmediate = true) + DeBuffLimitSheet deBuffLimitSheet, bool updateImmediate = true) { _buffStatModifiers.Clear(); if (!(value is null)) { foreach (var buff in value) { - AddBuff(buff, false); + AddBuff(buff, deBuffLimitSheet, false); } } @@ -322,9 +323,10 @@ public CharacterStats SetCollections(IEnumerable statModifiers, return this; } - public void AddBuff(Buff.StatBuff buff, bool updateImmediate = true) + public void AddBuff(Buff.StatBuff buff, DeBuffLimitSheet deBuffLimitSheet, bool updateImmediate = true) { - _buffStatModifiers[buff.RowData.GroupId] = buff.GetModifier(); + var modifier = GetBuffModifier(buff, deBuffLimitSheet); + _buffStatModifiers[buff.RowData.GroupId] = modifier; if (updateImmediate) { @@ -610,5 +612,31 @@ public void ConfigureStats( SetCollections(collectionStatModifiers); } + + private StatModifier GetBuffModifier(Buff.StatBuff buff, DeBuffLimitSheet deBuffLimitSheet) + { + var modifier = buff.GetModifier(); + if (buff.IsDebuff()) + { + try + { + var statType = modifier.StatType; + var limitModifier = deBuffLimitSheet[buff.RowData.GroupId].GetModifier(statType); + var stat = _statMap.GetStatAsLong(statType); + var buffModified = modifier.GetModifiedValue(stat); + var maxModified = (long)limitModifier.GetModifiedValue(stat); + if (maxModified > buffModified) + { + return limitModifier; + } + } + catch (KeyNotFoundException) + { + // pass + } + } + + return modifier; + } } } diff --git a/Lib9c/Model/State/AgentState.cs b/Lib9c/Model/State/AgentState.cs index 37e017d01c..2a9c590f22 100644 --- a/Lib9c/Model/State/AgentState.cs +++ b/Lib9c/Model/State/AgentState.cs @@ -17,7 +17,6 @@ public class AgentState : State, ICloneable public readonly Dictionary avatarAddresses; - public HashSet unlockedOptions; public int MonsterCollectionRound { get; private set; } public int Version { get; private set; } @@ -26,7 +25,6 @@ public AgentState(Address address) : base(address) { Version = CurrentVersion; avatarAddresses = new Dictionary(); - unlockedOptions = new HashSet(); } public AgentState(Dictionary serialized) @@ -41,9 +39,6 @@ public AgentState(Dictionary serialized) kv => kv.Value.ToAddress() ); #pragma warning restore LAA1002 - unlockedOptions = serialized.ContainsKey((IKey)(Text) "unlockedOptions") - ? serialized["unlockedOptions"].ToHashSet(StateExtensions.ToInteger) - : new HashSet(); MonsterCollectionRound = serialized.ContainsKey((IKey) (Text) MonsterCollectionRoundKey) ? serialized[MonsterCollectionRoundKey].ToInteger() : 0; @@ -61,7 +56,7 @@ public AgentState(List serialized) kv => kv.Value.ToAddress() ); #pragma warning restore LAA1002 - unlockedOptions = serialized[3].ToHashSet(StateExtensions.ToInteger); + // serialized[3] is unused and ignored. MonsterCollectionRound = serialized[4].ToInteger(); } @@ -97,7 +92,7 @@ public override IValue SerializeList() new KeyValuePair( new Binary(BitConverter.GetBytes(kv.Key)), kv.Value.Serialize()))), - unlockedOptions.Select(i => i.Serialize()).Serialize(), + new List(), // A placeholder list for now removed unlockedOptions property. #pragma warning restore LAA1002 MonsterCollectionRound.Serialize()); } diff --git a/Lib9c/Model/State/AvatarState.cs b/Lib9c/Model/State/AvatarState.cs index 327c96e09c..a76e738afd 100644 --- a/Lib9c/Model/State/AvatarState.cs +++ b/Lib9c/Model/State/AvatarState.cs @@ -79,7 +79,7 @@ public AvatarState(Address address, level = 1; exp = 0; inventory = new Inventory(); - worldInformation = new WorldInformation(blockIndex, avatarSheets.WorldSheet, GameConfig.IsEditor); + worldInformation = new WorldInformation(blockIndex, avatarSheets.WorldSheet, GameConfig.IsEditor, name); updatedAt = blockIndex; this.agentAddress = agentAddress; questList = new QuestList( diff --git a/Lib9c/Model/State/GameConfigState.cs b/Lib9c/Model/State/GameConfigState.cs index a30d9deb69..5c5435c74c 100644 --- a/Lib9c/Model/State/GameConfigState.cs +++ b/Lib9c/Model/State/GameConfigState.cs @@ -53,6 +53,7 @@ public class GameConfigState : State public int RequireCharacterLevel_ConsumableSlot3 { get; private set; } public int RequireCharacterLevel_ConsumableSlot4 { get; private set; } public int RequireCharacterLevel_ConsumableSlot5 { get; private set; } + public long ShatterStrikeMaxDamage { get; private set; } public GameConfigState() : base(Address) { @@ -243,6 +244,11 @@ public GameConfigState(Dictionary serialized) : base(serialized) { RuneSkillSlotCrystalUnlockCost = (Integer)rscc2; } + + if (serialized.TryGetValue((Text)"shatter_strike_max_damage", out var ssmd)) + { + ShatterStrikeMaxDamage = ssmd.ToLong(); + } } public GameConfigState(string csv) : base(Address) @@ -471,6 +477,14 @@ public override IValue Serialize() (Integer)RuneStatSlotCrystalUnlockCost); } + if (ShatterStrikeMaxDamage > 0) + { + values.Add( + (Text)"shatter_strike_max_damage", + ShatterStrikeMaxDamage.Serialize() + ); + } + #pragma warning disable LAA1002 return new Dictionary(values.Union((Dictionary) base.Serialize())); #pragma warning restore LAA1002 @@ -630,6 +644,9 @@ public void Update(GameConfigSheet.Row row) RequireCharacterLevel_ConsumableSlot5 = TableExtensions.ParseInt(row.Value); break; + case "shatter_strike_max_damage": + ShatterStrikeMaxDamage = TableExtensions.ParseLong(row.Value); + break; } } } diff --git a/Lib9c/Model/WorldInformation.cs b/Lib9c/Model/WorldInformation.cs index c0db140a72..edde6d1d05 100644 --- a/Lib9c/Model/WorldInformation.cs +++ b/Lib9c/Model/WorldInformation.cs @@ -141,7 +141,7 @@ private IDictionary worlds public WorldInformation( long blockIndex, WorldSheet worldSheet, - bool openAllOfWorldsAndStages = false) + bool openAllOfWorldsAndStages = false, string avatarName = "") { if (worldSheet is null) { @@ -153,9 +153,31 @@ public WorldInformation( if (openAllOfWorldsAndStages) { - foreach (var row in orderedSheet) + if(avatarName == "8World") + { + foreach (var row in orderedSheet) + { + //for test world 8 + if(row.Id == 7) + { + _worlds.Add(row.Id, new World(row, blockIndex, blockIndex, row.StageEnd - 1)); + } + else if(row.Id == 8) + { + _worlds.Add(row.Id, new World(row)); + } + else + { + _worlds.Add(row.Id, new World(row, blockIndex, blockIndex, row.StageEnd)); + } + } + } + else { - _worlds.Add(row.Id, new World(row, blockIndex, blockIndex, row.StageEnd)); + foreach (var row in orderedSheet) + { + _worlds.Add(row.Id, new World(row, blockIndex, blockIndex, row.StageEnd)); + } } } else diff --git a/Lib9c/TableCSV/Character/CharacterSheet.csv b/Lib9c/TableCSV/Character/CharacterSheet.csv index ae7c31afb5..d1edea9d10 100644 --- a/Lib9c/TableCSV/Character/CharacterSheet.csv +++ b/Lib9c/TableCSV/Character/CharacterSheet.csv @@ -73,4 +73,12 @@ id,_name,size_type,elemental_type,hp,atk,def,cri,hit,spd,lv_hp,lv_atk,lv_def,lv_ 208004,얼음 석상,L,2,4500,75,40,4,90,6,180,3,1.6,0,3.6,0.24,1.65,0.4 208005,라이딩 전사,L,2,3500,55,25,4,90,14,140,2.2,1,0,3.6,0.56,1.8,0.6 208006,영혼 지배자,L,2,3000,63,30,4,90,16,120,2.52,1.2,0,3.6,0.64,2.4,0.3 -208007,요르문간드,XL,2,15000,130,50,6,90,10,600,5.2,2,0,3.6,0.4,4,0.3 \ No newline at end of file +208007,요르문간드,XL,2,15000,130,50,6,90,10,600,5.2,2,0,3.6,0.4,4,0.3 +209000,강글라티,M,1,210,35,20,4,90,25,8.4,1.4,0.8,0,3.6,1,1.7,1.5 +209001,강글로트,M,1,500,35,40,4,90,5,20,1.4,1.6,0,3.6,0.2,1.7,0.8 +209002,헬헤스트,L,1,360,47,30,4,90,10,14.4,1.88,1.2,0,3.6,0.4,2.4,1 +209003,흐림,L,1,1800,50,10,4,90,10,72,2,0.4,0,3.6,0.4,2.1,0.8 +209004,모드구드,L,1,4500,75,40,4,90,6,180,3,1.6,0,3.6,0.24,2.7,0.4 +209005,흐림그림니르,XL,1,3500,55,25,4,90,14,140,2.2,1,0,3.6,0.56,3,0.6 +209006,가름,XL,1,3000,63,30,4,90,16,120,2.52,1.2,0,3.6,0.64,3.2,0.3 +209007,헤라,XL,1,15000,130,50,6,90,10,600,5.2,2,0,3.6,0.4,4,0.3 \ No newline at end of file diff --git a/Lib9c/TableCSV/Crystal/CrystalEquipmentGrindingSheet.csv b/Lib9c/TableCSV/Crystal/CrystalEquipmentGrindingSheet.csv index d5ff0ca2e3..4a361f169f 100644 --- a/Lib9c/TableCSV/Crystal/CrystalEquipmentGrindingSheet.csv +++ b/Lib9c/TableCSV/Crystal/CrystalEquipmentGrindingSheet.csv @@ -35,11 +35,11 @@ id,enchant_base_id,gain_crystal 10142001,10140001,332820 10143001,10140001,332820 10144001,10140001,337380 -10150001,10150001,1123800 -10151001,10150001,1164660 -10152001,10150001,1175220 -10153001,10150001,1194960 -10154001,10150001,1204140 +10150001,10151001,1 +10151001,10151001,1 +10152001,10151001,1 +10153001,10151001,1 +10154001,10151001,1 10155000,10155000,1200600 10200000,10200000,10 10210000,10210000,10 @@ -72,11 +72,11 @@ id,enchant_base_id,gain_crystal 10242001,10240001,286620 10243001,10240001,286620 10244001,10240001,289800 -10250000,10250000,807840 -10251000,10250000,831600 -10252000,10250000,841320 -10253000,10250000,850500 -10254000,10250000,888300 +10250000,10251000,1 +10251000,10251000,1 +10252000,10251000,1 +10253000,10251000,1 +10254000,10251000,1 10250001,10251001,2000000 10251001,10251001,1000000 10252001,10251001,1000000 @@ -177,19 +177,34 @@ id,enchant_base_id,gain_crystal 10610000,10610000,1000 10620000,10620000,3000 10630000,10630000,50000 -10620001,10620001,1000000 -10630001,10630001,2000000 -10640001,10640001,10000000 -10650001,10650001,20000000 -10650002,10650002,20000000 +10620001,10620000,500000 +10630001,10630000,1000000 +10640001,10640001,5000000 +10650001,10650001,10000000 +10650002,10650002,10000000 10130002,10130002,1000 -10620002,10620002,1000000 -10630002,10630002,2000000 -10640002,10640002,10000000 -10650003,10650003,20000000 -10650004,10650004,20000000 -10620003,10620003,1000000 -10630003,10630003,2000000 -10640003,10640003,10000000 -10650005,10650005,20000000 -10650006,10650006,20000000 \ No newline at end of file +10620002,10620000,500000 +10630002,10630000,1000000 +10640002,10640002,5000000 +10650003,10650003,10000000 +10650004,10650004,10000000 +10620003,10620000,500000 +10630003,10630000,1000000 +10640003,10640003,5000000 +10650005,10650005,10000000 +10650006,10650006,10000000 +10350002,10351002,1 +10351002,10351002,1 +10352002,10351002,1 +10353002,10351002,1 +10354002,10351002,1 +10450002,10451002,1 +10451002,10451002,1 +10452002,10451002,1 +10453002,10451002,1 +10454002,10451002,1 +10550001,10551001,1 +10551001,10551001,1 +10552001,10551001,1 +10553001,10551001,1 +10554001,10551001,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/Crystal/CrystalHammerPointSheet.csv b/Lib9c/TableCSV/Crystal/CrystalHammerPointSheet.csv index 8ead4368a6..c9721075ed 100644 --- a/Lib9c/TableCSV/Crystal/CrystalHammerPointSheet.csv +++ b/Lib9c/TableCSV/Crystal/CrystalHammerPointSheet.csv @@ -168,4 +168,29 @@ recipe_ID,max_hammer_count,crystal_cost 167,32,2000000 168,32,2000000 169,32,2000000 -170,32,2000000 \ No newline at end of file +170,32,2000000 +189,1,1 +190,1,1 +191,1,1 +192,1,1 +193,1,1 +194,1,1 +195,1,1 +196,1,1 +197,1,1 +198,1,1 +199,1,1 +200,1,1 +201,1,1 +202,1,1 +203,1,1 +204,1,1 +205,1,1 +206,1,1 +207,1,1 +208,1,1 +209,1,1 +210,1,1 +211,1,1 +212,1,1 +213,1,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/Crystal/CrystalMaterialCostSheet.csv b/Lib9c/TableCSV/Crystal/CrystalMaterialCostSheet.csv index 4bbb793986..21ad336070 100644 --- a/Lib9c/TableCSV/Crystal/CrystalMaterialCostSheet.csv +++ b/Lib9c/TableCSV/Crystal/CrystalMaterialCostSheet.csv @@ -102,4 +102,19 @@ item_id,crystal 306081,45000 306082,45000 306083,45000 -306084,45000 \ No newline at end of file +306084,45000 +306085,1 +306086,1 +306087,1 +306088,1 +306089,1 +306090,1 +306091,1 +306092,1 +306093,1 +306094,1 +306095,1 +306096,1 +306097,1 +306098,1 +306099,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/Crystal/CrystalStageBuffGachaSheet.csv b/Lib9c/TableCSV/Crystal/CrystalStageBuffGachaSheet.csv index 2b11ca7c50..63df33fd48 100644 --- a/Lib9c/TableCSV/Crystal/CrystalStageBuffGachaSheet.csv +++ b/Lib9c/TableCSV/Crystal/CrystalStageBuffGachaSheet.csv @@ -348,4 +348,54 @@ stage_id,max_star,crystal_cost_normal,crystal_cost_advanced 347,400,602045,1204090 348,400,605520,1211040 349,400,609005,1218010 -350,400,612500,1225000 \ No newline at end of file +350,400,612500,1225000 +351,1,1,1 +352,1,1,1 +353,1,1,1 +354,1,1,1 +355,1,1,1 +356,1,1,1 +357,1,1,1 +358,1,1,1 +359,1,1,1 +360,1,1,1 +361,1,1,1 +362,1,1,1 +363,1,1,1 +364,1,1,1 +365,1,1,1 +366,1,1,1 +367,1,1,1 +368,1,1,1 +369,1,1,1 +370,1,1,1 +371,1,1,1 +372,1,1,1 +373,1,1,1 +374,1,1,1 +375,1,1,1 +376,1,1,1 +377,1,1,1 +378,1,1,1 +379,1,1,1 +380,1,1,1 +381,1,1,1 +382,1,1,1 +383,1,1,1 +384,1,1,1 +385,1,1,1 +386,1,1,1 +387,1,1,1 +388,1,1,1 +389,1,1,1 +390,1,1,1 +391,1,1,1 +392,1,1,1 +393,1,1,1 +394,1,1,1 +395,1,1,1 +396,1,1,1 +397,1,1,1 +398,1,1,1 +399,1,1,1 +400,1,1,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/GameConfigSheet.csv b/Lib9c/TableCSV/GameConfigSheet.csv index 29cd53f513..1f0d237d79 100644 --- a/Lib9c/TableCSV/GameConfigSheet.csv +++ b/Lib9c/TableCSV/GameConfigSheet.csv @@ -36,3 +36,4 @@ character_consumable_slot_2,35 character_consumable_slot_3,100 character_consumable_slot_4,200 character_consumable_slot_5,350 +shatter_strike_max_damage,400000 diff --git a/Lib9c/TableCSV/Item/ConsumableItemRecipeSheet.csv b/Lib9c/TableCSV/Item/ConsumableItemRecipeSheet.csv index fda7a2cff7..35e8cf3029 100644 --- a/Lib9c/TableCSV/Item/ConsumableItemRecipeSheet.csv +++ b/Lib9c/TableCSV/Item/ConsumableItemRecipeSheet.csv @@ -32,4 +32,11 @@ id,required_block_index,required_ap,required_gold,material_item_id_1,material_it 100030,20,0,0,302023,4,302024,3,302026,4,302027,2,201030 100031,20,0,0,302023,2,302025,1,,,,,201031 100032,20,0,0,302023,3,302025,2,302026,2,,,201032 -100033,20,0,0,302023,4,302025,3,302026,4,302027,2,201033 \ No newline at end of file +100033,20,0,0,302023,4,302025,3,302026,4,302027,2,201033 +100034,20,0,0,800202,5,,,,,,,201034 +100035,20,0,0,302028,2,302029,1,,,,,201035 +100036,20,0,0,302028,3,302029,2,302031,2,,,201036 +100037,20,0,0,302028,4,302029,3,302031,4,302032,2,201037 +100038,20,0,0,302028,2,302030,1,,,,,201038 +100039,20,0,0,302028,3,302030,2,302031,2,,,201039 +100040,20,0,0,302028,4,302030,3,302031,4,302032,2,201040 \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/ConsumableItemSheet.csv b/Lib9c/TableCSV/Item/ConsumableItemSheet.csv index 3f04c48c43..d6ce8071be 100644 --- a/Lib9c/TableCSV/Item/ConsumableItemSheet.csv +++ b/Lib9c/TableCSV/Item/ConsumableItemSheet.csv @@ -37,6 +37,13 @@ id,_name,item_sub_type,grade,elemental_type,stat_type_1,stat_value_1,stat_type_2 201031,얼음 잎사귀 샐러드,Food,3,Normal,DEF,386,, 201032,얼음 잎사귀 샐러드,Food,4,Normal,DEF,522,, 201033,얼음 잎사귀 샐러드,Food,5,Normal,DEF,954,, +201034,황금 스테이크,Food,5,Normal,DEF,999,, +201035,붉은 와인,Food,3,Normal,ATK,1,, +201036,붉은 와인,Food,4,Normal,ATK,1,, +201037,붉은 와인,Food,5,Normal,ATK,1,, +201038,홍색 만두,Food,3,Normal,SPD,1,, +201039,홍색 만두,Food,4,Normal,SPD,1,, +201040,홍색 만두,Food,5,Normal,SPD,1,, 900101,몬스터펀치,Food,3,Normal,HP,834,ATK,46 900102,시원한 몬스터펀치,Food,4,Normal,HP,1799,ATK,98 900103,위대한 몬스터펀치,Food,5,Normal,HP,3629,ATK,202 diff --git a/Lib9c/TableCSV/Item/EquipmentItemOptionSheet.csv b/Lib9c/TableCSV/Item/EquipmentItemOptionSheet.csv index 2af253154e..c3cfb65888 100644 --- a/Lib9c/TableCSV/Item/EquipmentItemOptionSheet.csv +++ b/Lib9c/TableCSV/Item/EquipmentItemOptionSheet.csv @@ -1869,4 +1869,205 @@ id,stat_type,stat_min,stat_max,skill_id,skill_damage_min,skill_damage_max,skill_ 1065000513,,,,110008,0,0,30,30,25000,25000,ATK 1065000611,ATK,5125,5125,,,,,,,, 1065000612,DEF,3216,3216,,,,,,,, -1065000613,,,,110008,0,0,30,30,25000,25000,ATK \ No newline at end of file +1065000613,,,,110008,0,0,30,30,25000,25000,ATK +1015000111,ATK,1,1,,,,,,,, +1015000112,HIT,1,1,,,,,,,, +1015000113,ATK,1,1,,,,,,,, +1015000114,,,,700008,80000,120000,28,28,,, +1015000121,ATK,1,1,,,,,,,, +1015000122,HIT,1,1,,,,,,,, +1015000123,DEF,1,1,,,,,,,, +1015000124,,,,700010,50,80,28,28,,, +1015100111,ATK,1,1,,,,,,,, +1015100112,HIT,1,1,,,,,,,, +1015100113,ATK,1,1,,,,,,,, +1015100114,,,,700008,80000,120000,28,28,,, +1015100121,ATK,1,1,,,,,,,, +1015100122,HIT,1,1,,,,,,,, +1015100123,DEF,1,1,,,,,,,, +1015100124,,,,700010,50,80,28,28,,, +1015200111,ATK,1,1,,,,,,,, +1015200112,HIT,1,1,,,,,,,, +1015200113,ATK,1,1,,,,,,,, +1015200114,,,,700008,80000,120000,28,28,,, +1015200121,ATK,1,1,,,,,,,, +1015200122,HIT,1,1,,,,,,,, +1015200123,DEF,1,1,,,,,,,, +1015200124,,,,700010,50,80,28,28,,, +1015300111,ATK,1,1,,,,,,,, +1015300112,HIT,1,1,,,,,,,, +1015300113,ATK,1,1,,,,,,,, +1015300114,,,,700008,80000,120000,28,28,,, +1015300121,ATK,1,1,,,,,,,, +1015300122,HIT,1,1,,,,,,,, +1015300123,DEF,1,1,,,,,,,, +1015300124,,,,700010,50,80,28,28,,, +1015400111,ATK,1,1,,,,,,,, +1015400112,HIT,1,1,,,,,,,, +1015400113,ATK,1,1,,,,,,,, +1015400114,,,,700008,80000,120000,28,28,,, +1015400121,ATK,1,1,,,,,,,, +1015400122,HIT,1,1,,,,,,,, +1015400123,DEF,1,1,,,,,,,, +1015400124,,,,700010,50,80,28,28,,, +1025000011,HP,1,1,,,,,,,, +1025000012,HIT,1,1,,,,,,,, +1025000013,ATK,1,1,,,,,,,, +1025000014,,,,700007,,,12,12,,, +1025000021,HP,1,1,,,,,,,, +1025000022,HIT,1,1,,,,,,,, +1025000023,DEF,1,1,,,,,,,, +1025000024,,,,700009,,,12,12,,, +1025100011,HP,1,1,,,,,,,, +1025100012,HIT,1,1,,,,,,,, +1025100013,ATK,1,1,,,,,,,, +1025100014,,,,700007,,,12,12,,, +1025100021,HP,1,1,,,,,,,, +1025100022,HIT,1,1,,,,,,,, +1025100023,DEF,1,1,,,,,,,, +1025100024,,,,700009,,,12,12,,, +1025200011,HP,1,1,,,,,,,, +1025200012,HIT,1,1,,,,,,,, +1025200013,ATK,1,1,,,,,,,, +1025200014,,,,700007,,,12,12,,, +1025200021,HP,1,1,,,,,,,, +1025200022,HIT,1,1,,,,,,,, +1025200023,DEF,1,1,,,,,,,, +1025200024,,,,700009,,,12,12,,, +1025300011,HP,1,1,,,,,,,, +1025300012,HIT,1,1,,,,,,,, +1025300013,ATK,1,1,,,,,,,, +1025300014,,,,700007,,,12,12,,, +1025300021,HP,1,1,,,,,,,, +1025300022,HIT,1,1,,,,,,,, +1025300023,DEF,1,1,,,,,,,, +1025300024,,,,700009,,,12,12,,, +1025400011,HP,1,1,,,,,,,, +1025400012,HIT,1,1,,,,,,,, +1025400013,ATK,1,1,,,,,,,, +1025400014,,,,700007,,,12,12,,, +1025400021,HP,1,1,,,,,,,, +1025400022,HIT,1,1,,,,,,,, +1025400023,DEF,1,1,,,,,,,, +1025400024,,,,700009,,,12,12,,, +1035000211,SPD,1,1,,,,,,,, +1035000212,HIT,1,1,,,,,,,, +1035000213,ATK,1,1,,,,,,,, +1035000214,,,,700008,80000,120000,28,28,,, +1035000221,SPD,1,1,,,,,,,, +1035000222,HIT,1,1,,,,,,,, +1035000223,DEF,1,1,,,,,,,, +1035000224,,,,700010,50,80,28,28,,, +1035100211,SPD,1,1,,,,,,,, +1035100212,HIT,1,1,,,,,,,, +1035100213,ATK,1,1,,,,,,,, +1035100214,,,,700008,80000,120000,28,28,,, +1035100221,SPD,1,1,,,,,,,, +1035100222,HIT,1,1,,,,,,,, +1035100223,DEF,1,1,,,,,,,, +1035100224,,,,700010,50,80,28,28,,, +1035200211,SPD,1,1,,,,,,,, +1035200212,HIT,1,1,,,,,,,, +1035200213,ATK,1,1,,,,,,,, +1035200214,,,,700008,80000,120000,28,28,,, +1035200221,SPD,1,1,,,,,,,, +1035200222,HIT,1,1,,,,,,,, +1035200223,DEF,1,1,,,,,,,, +1035200224,,,,700010,50,80,28,28,,, +1035300211,SPD,1,1,,,,,,,, +1035300212,HIT,1,1,,,,,,,, +1035300213,ATK,1,1,,,,,,,, +1035300214,,,,700008,80000,120000,28,28,,, +1035300221,SPD,1,1,,,,,,,, +1035300222,HIT,1,1,,,,,,,, +1035300223,DEF,1,1,,,,,,,, +1035300224,,,,700010,50,80,28,28,,, +1035400211,SPD,1,1,,,,,,,, +1035400212,HIT,1,1,,,,,,,, +1035400213,ATK,1,1,,,,,,,, +1035400214,,,,700008,80000,120000,28,28,,, +1035400221,SPD,1,1,,,,,,,, +1035400222,HIT,1,1,,,,,,,, +1035400223,DEF,1,1,,,,,,,, +1035400224,,,,700010,50,80,28,28,,, +1045000211,HIT,1,1,,,,,,,, +1045000212,HIT,1,1,,,,,,,, +1045000213,ATK,1,1,,,,,,,, +1045000214,,,,700007,,,12,12,,, +1045000221,HIT,1,1,,,,,,,, +1045000222,HIT,1,1,,,,,,,, +1045000223,DEF,1,1,,,,,,,, +1045000224,,,,700009,,,12,12,,, +1045100211,HIT,1,1,,,,,,,, +1045100212,HIT,1,1,,,,,,,, +1045100213,ATK,1,1,,,,,,,, +1045100214,,,,700007,,,12,12,,, +1045100221,HIT,1,1,,,,,,,, +1045100222,HIT,1,1,,,,,,,, +1045100223,DEF,1,1,,,,,,,, +1045100224,,,,700009,,,12,12,,, +1045200211,HIT,1,1,,,,,,,, +1045200212,HIT,1,1,,,,,,,, +1045200213,ATK,1,1,,,,,,,, +1045200214,,,,700007,,,12,12,,, +1045200221,HIT,1,1,,,,,,,, +1045200222,HIT,1,1,,,,,,,, +1045200223,DEF,1,1,,,,,,,, +1045200224,,,,700009,,,12,12,,, +1045300211,HIT,1,1,,,,,,,, +1045300212,HIT,1,1,,,,,,,, +1045300213,ATK,1,1,,,,,,,, +1045300214,,,,700007,,,12,12,,, +1045300221,HIT,1,1,,,,,,,, +1045300222,HIT,1,1,,,,,,,, +1045300223,DEF,1,1,,,,,,,, +1045300224,,,,700009,,,12,12,,, +1045400211,HIT,1,1,,,,,,,, +1045400212,HIT,1,1,,,,,,,, +1045400213,ATK,1,1,,,,,,,, +1045400214,,,,700007,,,12,12,,, +1045400221,HIT,1,1,,,,,,,, +1045400222,HIT,1,1,,,,,,,, +1045400223,DEF,1,1,,,,,,,, +1045400224,,,,700009,,,12,12,,, +1055000111,DEF,1,1,,,,,,,, +1055000112,HIT,1,1,,,,,,,, +1055000113,ATK,1,1,,,,,,,, +1055000114,,,,700008,80000,120000,28,28,,, +1055000121,DEF,1,1,,,,,,,, +1055000122,HIT,1,1,,,,,,,, +1055000123,DEF,1,1,,,,,,,, +1055000124,,,,700010,50,80,28,28,,, +1055100111,DEF,1,1,,,,,,,, +1055100112,HIT,1,1,,,,,,,, +1055100113,ATK,1,1,,,,,,,, +1055100114,,,,700008,80000,120000,28,28,,, +1055100121,DEF,1,1,,,,,,,, +1055100122,HIT,1,1,,,,,,,, +1055100123,DEF,1,1,,,,,,,, +1055100124,,,,700010,50,80,28,28,,, +1055200111,DEF,1,1,,,,,,,, +1055200112,HIT,1,1,,,,,,,, +1055200113,ATK,1,1,,,,,,,, +1055200114,,,,700008,80000,120000,28,28,,, +1055200121,DEF,1,1,,,,,,,, +1055200122,HIT,1,1,,,,,,,, +1055200123,DEF,1,1,,,,,,,, +1055200124,,,,700010,50,80,28,28,,, +1055300111,DEF,1,1,,,,,,,, +1055300112,HIT,1,1,,,,,,,, +1055300113,ATK,1,1,,,,,,,, +1055300114,,,,700008,80000,120000,28,28,,, +1055300121,DEF,1,1,,,,,,,, +1055300122,HIT,1,1,,,,,,,, +1055300123,DEF,1,1,,,,,,,, +1055300124,,,,700010,50,80,28,28,,, +1055400111,DEF,1,1,,,,,,,, +1055400112,HIT,1,1,,,,,,,, +1055400113,ATK,1,1,,,,,,,, +1055400114,,,,700008,80000,120000,28,28,,, +1055400121,DEF,1,1,,,,,,,, +1055400122,HIT,1,1,,,,,,,, +1055400123,DEF,1,1,,,,,,,, +1055400124,,,,700010,50,80,28,28,,, +1055400125,,,,700011,0,0,28,28,2300,2300,HP \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/EquipmentItemRecipeSheet.csv b/Lib9c/TableCSV/Item/EquipmentItemRecipeSheet.csv index 370dda6723..254bf4e082 100644 --- a/Lib9c/TableCSV/Item/EquipmentItemRecipeSheet.csv +++ b/Lib9c/TableCSV/Item/EquipmentItemRecipeSheet.csv @@ -1,111 +1,136 @@ -id,result_equipment_id,material_id,material_count,required_action_point,required_gold,required_block_index,unlock_stage,sub_recipe_id,sub_recipe_id_2,sub_recipe_id_3,required_crystal,item_sub_type -1,10110000,303000,2,0,0,5,3,373,374,,0,Weapon -5,10114000,303000,12,0,0,477,99,10,11,,64,Weapon -6,10120000,303001,4,0,0,37,27,376,377,,8,Weapon -10,10124000,303001,24,0,0,7155,174,22,23,,22560,Weapon -14,10133000,303002,27,0,0,9807,190,31,32,,41040,Weapon -15,10134000,303002,47,0,0,11585,204,34,35,,167040,Weapon -18,10132001,303002,21,0,0,5190,154,40,41,,32040,Weapon -19,10133001,303002,61,0,0,18050,220,43,44,,216180,Weapon -20,10134001,303002,73,0,0,24302,236,46,47,,495900,Weapon -21,10140000,303003,250,0,0,25000,299,101400001,101400002,,1404000,Weapon -22,10141000,303003,120,0,0,10000,255,101410001,101410002,,1532100,Weapon -23,10142000,303003,120,0,0,14000,265,101420001,101420002,,1532100,Weapon -24,10143000,303003,180,0,0,18000,277,101430001,101430002,,1532100,Weapon -25,10144000,303003,180,0,0,22000,290,101440001,101440002,,1532100,Weapon -26,10150000,303004,360,0,0,3000,330,101500001,101500002,,500000,Weapon -27,10151000,303004,360,0,0,3000,305,101510001,101510002,,500000,Weapon -28,10152000,303004,360,0,0,3000,305,101520001,101520002,101520003,500000,Weapon -29,10153000,303004,360,0,0,3000,330,101530001,101530002,,500000,Weapon -30,10154000,303004,360,0,0,3000,330,101540001,101540002,101540003,500000,Weapon -43,10211000,303100,2,0,0,5,13,97,98,,2,Armor -46,10214000,303100,14,0,0,1076,108,106,107,,742,Armor -49,10222000,303101,7,0,0,120,45,112,113,,11,Armor -51,10224000,303101,26,0,0,8361,182,118,119,,23920,Armor -55,10233000,303102,40,0,0,10426,201,127,128,,140400,Armor -56,10234000,303102,52,0,0,13991,208,130,131,,182520,Armor -57,10230001,303102,20,0,0,1824,146,409,410,,9600,Armor -58,10231001,303102,22,0,0,5463,158,133,134,,33000,Armor -59,10232001,303102,23,0,0,6189,166,136,137,,35040,Armor -60,10233001,303102,67,0,0,11056,228,139,140,,455400,Armor -62,10240000,303103,250,0,0,25000,294,102400001,102400002,,1166700,Armor -63,10241000,303103,120,0,0,10000,261,102410001,102410002,,1173660,Armor -64,10242000,303103,120,0,0,14000,270,102420001,102420002,,1188720,Armor -65,10243000,303103,180,0,0,18000,280,102430001,102430002,,1345500,Armor -66,10244000,303103,180,0,0,22000,287,102440001,102440002,,1345500,Armor -67,10250001,303104,360,0,0,3000,335,102500011,102500012,,500000,Armor -68,10251001,303104,360,0,0,3000,315,102510011,102510012,,500000,Armor -69,10252001,303104,360,0,0,3000,315,102520011,102520012,102520013,1000000,Armor -70,10253001,303104,360,0,0,3000,335,102530011,102530012,,1000000,Armor -71,10254001,303104,360,0,0,3000,335,102540011,102540012,102540013,1000000,Armor -87,10314000,303200,18,0,0,1560,134,202,203,,954,Belt -91,10323000,303201,22,0,0,5796,162,211,212,,2992,Belt -92,10324000,303201,45,0,0,10472,203,214,215,,122220,Belt -94,10331000,303202,11,0,0,410,90,217,218,,387,Belt -97,10334000,303202,150,0,0,20000,251,226,227,,737100,Belt -101,10343000,303203,76,0,0,26015,240,235,236,,1128600,Belt -103,10350000,303203,250,0,0,25000,296,103500001,103500002,,4177800,Belt -104,10351000,303203,120,0,0,10000,260,103510001,103510002,,4347000,Belt -105,10352000,303203,120,0,0,14000,269,103520001,103520002,,4395600,Belt -106,10353000,303203,180,0,0,18000,278,103530001,103530002,,4441500,Belt -107,10354000,303203,180,0,0,22000,287,103540001,103540002,,4638900,Belt -111,10413000,303300,15,0,0,368,84,259,260,,32,Necklace -116,10423000,303301,25,0,0,7728,178,271,272,,3472,Necklace -117,10424000,303301,150,0,0,13419,202,274,275,,116880,Necklace -121,10433000,303302,55,0,0,15284,212,283,284,,195120,Necklace -122,10434000,303302,70,0,0,22649,232,286,287,,472500,Necklace -123,10440000,303303,77,0,0,9093,249,451,452,,729000,Necklace -128,10450000,303303,250,0,0,25000,293,104500001,104500002,,4361400,Necklace -129,10451000,303303,120,0,0,10000,255,104510001,104510002,,4536000,Necklace -130,10452000,303303,120,0,0,14000,264,104520001,104520002,,4584600,Necklace -131,10453000,303303,180,0,0,18000,274,104530001,104530002,,4630500,Necklace -132,10454000,303303,180,0,0,22000,284,104540001,104540002,,4889100,Necklace -133,10510000,303400,2,0,0,5,17,457,458,,1,Ring -137,10514000,303400,24,0,0,6642,170,322,323,,2544,Ring -139,10521000,303401,9,0,0,195,57,325,326,,22,Ring -141,10523000,303401,28,0,0,10493,198,331,332,,3808,Ring -142,10524000,303401,49,0,0,12758,205,334,335,,132900,Ring -144,10531000,303402,16,0,0,1278,120,337,338,,5568,Ring -146,10533000,303402,64,0,0,19523,224,343,344,,748800,Ring -147,10534000,303402,76,0,0,26075,243,346,347,,889200,Ring -148,10540000,303403,250,0,0,25000,298,105400001,105400002,,1170000,Ring -149,10541000,303403,120,0,0,10000,258,105410001,105410002,,1160400,Ring -150,10542000,303403,120,0,0,14000,268,105420001,105420002,,1160760,Ring -151,10543000,303403,180,0,0,18000,275,105430001,105430002,,1340700,Ring -152,10544000,303403,180,0,0,22000,285,105440001,105440002,,1340700,Ring -153,10550000,303404,360,0,0,3000,340,105500001,105500002,,500000,Ring -154,10551000,303404,360,0,0,3000,325,105510001,105510002,,500000,Ring -155,10552000,303404,360,0,0,3000,325,105520001,105520002,105520003,1000000,Ring -156,10553000,303404,360,0,0,3000,340,105530001,105530002,,1000000,Ring -157,10554000,303404,360,0,0,3000,340,105540001,105540002,105540003,1000000,Ring -158,12001001,600101,100,0,0,10,999,120010011,120010012,,0,Belt -159,12001002,600101,100,0,0,10,999,120010021,120010022,,0,Necklace -160,12001003,600101,50,0,0,10,999,120010031,120010032,,0,Ring -161,10350001,303204,360,0,0,3000,330,103500011,103500012,,500000,Belt -162,10351001,303204,360,0,0,3000,310,103510011,103510012,,500000,Belt -163,10352001,303204,360,0,0,3000,310,103520011,103520012,103520013,1000000,Belt -164,10353001,303204,360,0,0,3000,330,103530011,103530012,,1000000,Belt -165,10354001,303204,360,0,0,3000,330,103540011,103540012,103540013,1000000,Belt -166,10450001,303304,360,0,0,3000,335,104500011,104500012,,500000,Necklace -167,10451001,303304,360,0,0,3000,320,104510011,104510012,,500000,Necklace -168,10452001,303304,360,0,0,3000,320,104520011,104520012,104520013,1000000,Necklace -169,10453001,303304,360,0,0,3000,335,104530011,104530012,,1000000,Necklace -170,10454001,303304,360,0,0,3000,335,104540011,104540012,104540013,1000000,Necklace -171,10610000,0,0,0,0,10000000,999,106100001,,,0,Aura -172,10620000,0,0,0,0,10000000,999,106200001,,,0,Aura -173,10630000,0,0,0,0,10000000,999,106300001,,,0,Aura -174,10620001,0,0,0,0,10000000,999,106200011,,,0,Aura -175,10630001,0,0,0,0,10000000,999,106300011,,,0,Aura -176,10640001,0,0,0,0,10000000,999,106400011,,,0,Aura -177,10650001,0,0,0,0,10000000,999,106500011,,,0,Aura -178,10650002,0,0,0,0,10000000,999,106500021,,,0,Aura -179,10620002,0,0,0,0,10000000,999,106200021,,,0,Aura -180,10630002,0,0,0,0,10000000,999,106300021,,,0,Aura -181,10640002,0,0,0,0,10000000,999,106400021,,,0,Aura -182,10650003,0,0,0,0,10000000,999,106500031,,,0,Aura -183,10650004,0,0,0,0,10000000,999,106500041,,,0,Aura -184,10620003,0,0,0,0,10000000,999,106200031,,,0,Aura -185,10630003,0,0,0,0,10000000,999,106300031,,,0,Aura -186,10640003,0,0,0,0,10000000,999,106400031,,,0,Aura -187,10650005,0,0,0,0,10000000,999,106500051,,,0,Aura -188,10650006,0,0,0,0,10000000,999,106500061,,,0,Aura \ No newline at end of file +id,result_equipment_id,material_id,material_count,required_action_point,required_gold,required_block_index,unlock_stage,sub_recipe_id,sub_recipe_id_2,sub_recipe_id_3,required_crystal,item_sub_type +1,10110000,303000,2,0,0,5,3,373,374,,0,Weapon +5,10114000,303000,12,0,0,477,99,10,11,,64,Weapon +6,10120000,303001,4,0,0,37,27,376,377,,8,Weapon +10,10124000,303001,24,0,0,7155,174,22,23,,22560,Weapon +14,10133000,303002,27,0,0,9807,190,31,32,,41040,Weapon +15,10134000,303002,47,0,0,11585,204,34,35,,167040,Weapon +18,10132001,303002,21,0,0,5190,154,40,41,,32040,Weapon +19,10133001,303002,61,0,0,18050,220,43,44,,216180,Weapon +20,10134001,303002,73,0,0,24302,236,46,47,,495900,Weapon +21,10140000,303003,250,0,0,25000,299,101400001,101400002,,1404000,Weapon +22,10141000,303003,120,0,0,10000,255,101410001,101410002,,1532100,Weapon +23,10142000,303003,120,0,0,14000,265,101420001,101420002,,1532100,Weapon +24,10143000,303003,180,0,0,18000,277,101430001,101430002,,1532100,Weapon +25,10144000,303003,180,0,0,22000,290,101440001,101440002,,1532100,Weapon +26,10150000,303004,360,0,0,3000,330,101500001,101500002,,500000,Weapon +27,10151000,303004,360,0,0,3000,305,101510001,101510002,,500000,Weapon +28,10152000,303004,360,0,0,3000,305,101520001,101520002,101520003,500000,Weapon +29,10153000,303004,360,0,0,3000,330,101530001,101530002,,500000,Weapon +30,10154000,303004,360,0,0,3000,330,101540001,101540002,101540003,500000,Weapon +43,10211000,303100,2,0,0,5,13,97,98,,2,Armor +46,10214000,303100,14,0,0,1076,108,106,107,,742,Armor +49,10222000,303101,7,0,0,120,45,112,113,,11,Armor +51,10224000,303101,26,0,0,8361,182,118,119,,23920,Armor +55,10233000,303102,40,0,0,10426,201,127,128,,140400,Armor +56,10234000,303102,52,0,0,13991,208,130,131,,182520,Armor +57,10230001,303102,20,0,0,1824,146,409,410,,9600,Armor +58,10231001,303102,22,0,0,5463,158,133,134,,33000,Armor +59,10232001,303102,23,0,0,6189,166,136,137,,35040,Armor +60,10233001,303102,67,0,0,11056,228,139,140,,455400,Armor +62,10240000,303103,250,0,0,25000,294,102400001,102400002,,1166700,Armor +63,10241000,303103,120,0,0,10000,261,102410001,102410002,,1173660,Armor +64,10242000,303103,120,0,0,14000,270,102420001,102420002,,1188720,Armor +65,10243000,303103,180,0,0,18000,280,102430001,102430002,,1345500,Armor +66,10244000,303103,180,0,0,22000,287,102440001,102440002,,1345500,Armor +67,10250001,303104,360,0,0,3000,335,102500011,102500012,,500000,Armor +68,10251001,303104,360,0,0,3000,315,102510011,102510012,,500000,Armor +69,10252001,303104,360,0,0,3000,315,102520011,102520012,102520013,1000000,Armor +70,10253001,303104,360,0,0,3000,335,102530011,102530012,,1000000,Armor +71,10254001,303104,360,0,0,3000,335,102540011,102540012,102540013,1000000,Armor +87,10314000,303200,18,0,0,1560,134,202,203,,954,Belt +91,10323000,303201,22,0,0,5796,162,211,212,,2992,Belt +92,10324000,303201,45,0,0,10472,203,214,215,,122220,Belt +94,10331000,303202,11,0,0,410,90,217,218,,387,Belt +97,10334000,303202,150,0,0,20000,251,226,227,,737100,Belt +101,10343000,303203,76,0,0,26015,240,235,236,,1128600,Belt +103,10350000,303203,250,0,0,25000,296,103500001,103500002,,4177800,Belt +104,10351000,303203,120,0,0,10000,260,103510001,103510002,,4347000,Belt +105,10352000,303203,120,0,0,14000,269,103520001,103520002,,4395600,Belt +106,10353000,303203,180,0,0,18000,278,103530001,103530002,,4441500,Belt +107,10354000,303203,180,0,0,22000,287,103540001,103540002,,4638900,Belt +111,10413000,303300,15,0,0,368,84,259,260,,32,Necklace +116,10423000,303301,25,0,0,7728,178,271,272,,3472,Necklace +117,10424000,303301,150,0,0,13419,202,274,275,,116880,Necklace +121,10433000,303302,55,0,0,15284,212,283,284,,195120,Necklace +122,10434000,303302,70,0,0,22649,232,286,287,,472500,Necklace +123,10440000,303303,77,0,0,9093,249,451,452,,729000,Necklace +128,10450000,303303,250,0,0,25000,293,104500001,104500002,,4361400,Necklace +129,10451000,303303,120,0,0,10000,255,104510001,104510002,,4536000,Necklace +130,10452000,303303,120,0,0,14000,264,104520001,104520002,,4584600,Necklace +131,10453000,303303,180,0,0,18000,274,104530001,104530002,,4630500,Necklace +132,10454000,303303,180,0,0,22000,284,104540001,104540002,,4889100,Necklace +133,10510000,303400,2,0,0,5,17,457,458,,1,Ring +137,10514000,303400,24,0,0,6642,170,322,323,,2544,Ring +139,10521000,303401,9,0,0,195,57,325,326,,22,Ring +141,10523000,303401,28,0,0,10493,198,331,332,,3808,Ring +142,10524000,303401,49,0,0,12758,205,334,335,,132900,Ring +144,10531000,303402,16,0,0,1278,120,337,338,,5568,Ring +146,10533000,303402,64,0,0,19523,224,343,344,,748800,Ring +147,10534000,303402,76,0,0,26075,243,346,347,,889200,Ring +148,10540000,303403,250,0,0,25000,298,105400001,105400002,,1170000,Ring +149,10541000,303403,120,0,0,10000,258,105410001,105410002,,1160400,Ring +150,10542000,303403,120,0,0,14000,268,105420001,105420002,,1160760,Ring +151,10543000,303403,180,0,0,18000,275,105430001,105430002,,1340700,Ring +152,10544000,303403,180,0,0,22000,285,105440001,105440002,,1340700,Ring +153,10550000,303404,360,0,0,3000,340,105500001,105500002,,500000,Ring +154,10551000,303404,360,0,0,3000,325,105510001,105510002,,500000,Ring +155,10552000,303404,360,0,0,3000,325,105520001,105520002,105520003,1000000,Ring +156,10553000,303404,360,0,0,3000,340,105530001,105530002,,1000000,Ring +157,10554000,303404,360,0,0,3000,340,105540001,105540002,105540003,1000000,Ring +158,12001001,600101,100,0,0,10,999,120010011,120010012,,0,Belt +159,12001002,600101,100,0,0,10,999,120010021,120010022,,0,Necklace +160,12001003,600101,50,0,0,10,999,120010031,120010032,,0,Ring +161,10350001,303204,360,0,0,3000,330,103500011,103500012,,500000,Belt +162,10351001,303204,360,0,0,3000,310,103510011,103510012,,500000,Belt +163,10352001,303204,360,0,0,3000,310,103520011,103520012,103520013,1000000,Belt +164,10353001,303204,360,0,0,3000,330,103530011,103530012,,1000000,Belt +165,10354001,303204,360,0,0,3000,330,103540011,103540012,103540013,1000000,Belt +166,10450001,303304,360,0,0,3000,335,104500011,104500012,,500000,Necklace +167,10451001,303304,360,0,0,3000,320,104510011,104510012,,500000,Necklace +168,10452001,303304,360,0,0,3000,320,104520011,104520012,104520013,1000000,Necklace +169,10453001,303304,360,0,0,3000,335,104530011,104530012,,1000000,Necklace +170,10454001,303304,360,0,0,3000,335,104540011,104540012,104540013,1000000,Necklace +171,10610000,0,0,0,0,10000000,999,106100001,,,0,Aura +172,10620000,0,0,0,0,10000000,999,106200001,,,0,Aura +173,10630000,0,0,0,0,10000000,999,106300001,,,0,Aura +174,10620001,0,0,0,0,10000000,999,106200011,,,0,Aura +175,10630001,0,0,0,0,10000000,999,106300011,,,0,Aura +176,10640001,0,0,0,0,10000000,999,106400011,,,0,Aura +177,10650001,0,0,0,0,10000000,999,106500011,,,0,Aura +178,10650002,0,0,0,0,10000000,999,106500021,,,0,Aura +179,10620002,0,0,0,0,10000000,999,106200021,,,0,Aura +180,10630002,0,0,0,0,10000000,999,106300021,,,0,Aura +181,10640002,0,0,0,0,10000000,999,106400021,,,0,Aura +182,10650003,0,0,0,0,10000000,999,106500031,,,0,Aura +183,10650004,0,0,0,0,10000000,999,106500041,,,0,Aura +184,10620003,0,0,0,0,10000000,999,106200031,,,0,Aura +185,10630003,0,0,0,0,10000000,999,106300031,,,0,Aura +186,10640003,0,0,0,0,10000000,999,106400031,,,0,Aura +187,10650005,0,0,0,0,10000000,999,106500051,,,0,Aura +188,10650006,0,0,0,0,10000000,999,106500061,,,0,Aura +189,10150001,303000,1,0,0,5,390,101500011,101500012,,1,Weapon +190,10151001,303000,1,0,0,5,360,101510011,101510012,,1,Weapon +191,10152001,303000,1,0,0,5,360,101520011,101520012,,1,Weapon +192,10153001,303000,1,0,0,5,375,101530011,101530012,,1,Weapon +193,10154001,303000,1,0,0,5,375,101540011,101540012,,1,Weapon +194,10250000,303000,1,0,0,5,390,102500001,102500002,,1,Armor +195,10251000,303000,1,0,0,5,360,102510001,102510002,,1,Armor +196,10252000,303000,1,0,0,5,360,102520001,102520002,,1,Armor +197,10253000,303000,1,0,0,5,375,102530001,102530002,,1,Armor +198,10254000,303000,1,0,0,5,375,102540001,102540002,,1,Armor +199,10350002,303000,1,0,0,5,390,103500021,103500022,,1,Belt +200,10351002,303000,1,0,0,5,360,103510021,103510022,,1,Belt +201,10352002,303000,1,0,0,5,360,103520021,103520022,,1,Belt +202,10353002,303000,1,0,0,5,375,103530021,103530022,,1,Belt +203,10354002,303000,1,0,0,5,375,103540021,103540022,,1,Belt +204,10450002,303000,1,0,0,5,390,104500021,104500022,,1,Necklace +205,10451002,303000,1,0,0,5,360,104510021,104510022,,1,Necklace +206,10452002,303000,1,0,0,5,360,104520021,104520022,,1,Necklace +207,10453002,303000,1,0,0,5,375,104530021,104530022,,1,Necklace +208,10454002,303000,1,0,0,5,375,104540021,104540022,,1,Necklace +209,10550001,303000,1,0,0,5,390,105500011,105500012,,1,Ring +210,10551001,303000,1,0,0,5,360,105510011,105510012,,1,Ring +211,10552001,303000,1,0,0,5,360,105520011,105520012,,1,Ring +212,10553001,303000,1,0,0,5,375,105530011,105530012,,1,Ring +213,10554001,303000,1,0,0,5,375,105540011,105540012,,1,Ring \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/EquipmentItemSheet.csv b/Lib9c/TableCSV/Item/EquipmentItemSheet.csv index 4185e73945..4404a53f27 100644 --- a/Lib9c/TableCSV/Item/EquipmentItemSheet.csv +++ b/Lib9c/TableCSV/Item/EquipmentItemSheet.csv @@ -35,11 +35,11 @@ id,_name,item_sub_type,grade,elemental_type,set_id,stat_type,stat_value,attack_r 10142001,Asgardian Sword,Weapon,5,Water,13,ATK,798,2,10142001,2 10143001,Asgardian Sword,Weapon,5,Land,14,ATK,1221,2,10143001,2 10144001,Asgardian Sword,Weapon,5,Wind,15,ATK,1525,2,10144001,2 -10150001,Surt's Sword,Weapon,5,Normal,16,ATK,2157,2,10150001,2 -10151001,Surt's Sword,Weapon,5,Fire,17,ATK,2190,2,10151001,2 -10152001,Surt's Sword,Weapon,5,Water,18,ATK,2223,2,10152001,2 -10153001,Surt's Sword,Weapon,5,Land,19,ATK,3390,2,10153001,2 -10154001,Surt's Sword,Weapon,5,Wind,20,ATK,4219,2,10154001,2 +10150001,Surt's Sword,Weapon,6,Normal,0,ATK,2157,2,10150001,2 +10151001,Surt's Sword,Weapon,6,Fire,0,ATK,2190,2,10151001,2 +10152001,Surt's Sword,Weapon,6,Water,0,ATK,2223,2,10152001,2 +10153001,Surt's Sword,Weapon,6,Land,0,ATK,3390,2,10153001,2 +10154001,Surt's Sword,Weapon,6,Wind,0,ATK,4219,2,10154001,2 10155000,Valkyrie’s Sword,Weapon,5,Normal,15,ATK,2902,2,10155000,2 10200000,Ragged Clothes,Armor,0,Normal,0,HP,30,2,Character/Player/10200000,12 10210000,Casual Clothes,Armor,1,Normal,1,HP,180,2,Character/Player/10210000,5 @@ -72,11 +72,11 @@ id,_name,item_sub_type,grade,elemental_type,set_id,stat_type,stat_value,attack_r 10252001,Ancient Armor,Armor,5,Water,13,HP,48224,2,Character/Player/10252001,1800000 10253001,Ancient Armor,Armor,5,Land,14,HP,67513,2,Character/Player/10253001,2400000 10254001,Ancient Armor,Armor,5,Wind,15,HP,67513,2,Character/Player/10254001,2400000 -10250000,Legendary Armor,Armor,5,Normal,11,HP,15550,2,Character/Player/10250000,2 -10251000,Legendary Armor,Armor,5,Fire,12,HP,15848,2,Character/Player/10251000,2 -10252000,Legendary Armor,Armor,5,Water,13,HP,16146,2,Character/Player/10252000,2 -10253000,Legendary Armor,Armor,5,Land,14,HP,24665,2,Character/Player/10253000,2 -10254000,Legendary Armor,Armor,5,Wind,15,HP,30823,2,Character/Player/10254000,2 +10250000,Legendary Armor,Armor,6,Normal,0,HP,15550,2,Character/Player/10250000,2 +10251000,Legendary Armor,Armor,6,Fire,0,HP,15848,2,Character/Player/10251000,2 +10252000,Legendary Armor,Armor,6,Water,0,HP,16146,2,Character/Player/10252000,2 +10253000,Legendary Armor,Armor,6,Land,0,HP,24665,2,Character/Player/10253000,2 +10254000,Legendary Armor,Armor,6,Wind,0,HP,30823,2,Character/Player/10254000,2 10240001,Asgardian Armor,Armor,5,Normal,11,HP,10358,2,Character/Player/10240001,2 10241001,Asgardian Armor,Armor,5,Fire,12,HP,10571,2,Character/Player/10241001,2 10242001,Asgardian Armor,Armor,5,Water,13,HP,10783,2,Character/Player/10242001,2 @@ -192,4 +192,19 @@ id,_name,item_sub_type,grade,elemental_type,set_id,stat_type,stat_value,attack_r 10630003,Barrage Aura,Aura,3,Normal,0,ATK,10,0,10620001,600 10640003,Barrage Aura,Aura,4,Normal,0,ATK,10,0,10620001,6000 10650005,Barrage Aura,Aura,5,Normal,0,ATK,10,0,10620001,60000 -10650006,Lord's Barrage Aura,Aura,5,Normal,0,ATK,10,0,10620001,60000 \ No newline at end of file +10650006,Lord's Barrage Aura,Aura,5,Normal,0,ATK,10,0,10620001,60000 +10350002,,Belt,6,Normal,0,SPD,1,2,10350002,1 +10351002,,Belt,6,Fire,0,SPD,1,2,10351002,1 +10352002,,Belt,6,Water,0,SPD,1,2,10352002,1 +10353002,,Belt,6,Land,0,SPD,1,2,10353002,1 +10354002,,Belt,6,Wind,0,SPD,1,2,10354002,1 +10450002,,Necklace,6,Normal,0,HIT,1,0,10450002,1 +10451002,,Necklace,6,Fire,0,HIT,1,0,10451002,1 +10452002,,Necklace,6,Water,0,HIT,1,0,10452002,1 +10453002,,Necklace,6,Land,0,HIT,1,0,10453002,1 +10454002,,Necklace,6,Wind,0,HIT,1,0,10454002,1 +10550001,,Ring,6,Normal,0,DEF,1,0,10550001,1 +10551001,,Ring,6,Fire,0,DEF,1,0,10551001,1 +10552001,,Ring,6,Water,0,DEF,1,0,10552001,1 +10553001,,Ring,6,Land,0,DEF,1,0,10553001,1 +10554001,,Ring,6,Wind,0,DEF,1,0,10554001,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/EquipmentItemSubRecipeSheetV2.csv b/Lib9c/TableCSV/Item/EquipmentItemSubRecipeSheetV2.csv index cc444268b9..a3eaa286e5 100644 --- a/Lib9c/TableCSV/Item/EquipmentItemSubRecipeSheetV2.csv +++ b/Lib9c/TableCSV/Item/EquipmentItemSubRecipeSheetV2.csv @@ -511,4 +511,54 @@ ID,required_action_point,required_gold,required_block_index,material_id,material 106300031,0,0,1,,,,,,,1063000311,10000,0,1063000312,10000,0,1063000313,10000,0,,,,FALSE,1 106400031,0,0,1,,,,,,,1064000311,10000,0,1064000312,10000,0,1064000313,10000,0,,,,FALSE,1 106500051,0,0,1,,,,,,,1065000511,10000,0,1065000512,10000,0,1065000513,10000,0,,,,FALSE,1 -106500061,0,0,1,,,,,,,1065000611,10000,0,1065000612,10000,0,1065000613,10000,0,,,,FALSE,1 \ No newline at end of file +106500061,0,0,1,,,,,,,1065000611,10000,0,1065000612,10000,0,1065000613,10000,0,,,,FALSE,1 +101500011,0,0,1,,,,,,,1015000111,10000,0,1015000112,6000,6000,1015000113,1100,20000,1015000114,500,100800,FALSE,1 +101500012,0,0,1,,,,,,,1015000121,10000,0,1015000122,6000,6000,1015000123,1100,20000,1015000124,500,100800,FALSE,1 +101510011,0,0,1,,,,,,,1015100111,10000,0,1015100112,6000,6000,1015100113,1100,20000,1015100114,500,100800,FALSE,1 +101510012,0,0,1,,,,,,,1015100121,10000,0,1015100122,6000,6000,1015100123,1100,20000,1015100124,500,100800,FALSE,1 +101520011,0,0,1,,,,,,,1015200111,10000,0,1015200112,6000,6000,1015200113,1100,20000,1015200114,500,100800,FALSE,1 +101520012,0,0,1,,,,,,,1015200121,10000,0,1015200122,6000,6000,1015200123,1100,20000,1015200124,500,100800,FALSE,1 +101530011,0,0,1,,,,,,,1015300111,10000,0,1015300112,6000,6000,1015300113,1100,20000,1015300114,500,100800,FALSE,1 +101530012,0,0,1,,,,,,,1015300121,10000,0,1015300122,6000,6000,1015300123,1100,20000,1015300124,500,100800,FALSE,1 +101540011,0,0,1,,,,,,,1015400111,10000,0,1015400112,6000,6000,1015400113,1100,20000,1015400114,500,100800,FALSE,1 +101540012,0,0,1,,,,,,,1015400121,10000,0,1015400122,6000,6000,1015400123,1100,20000,1015400124,500,100800,FALSE,1 +102500001,0,0,1,,,,,,,1025000011,10000,0,1025000012,6000,6000,1025000013,1100,20000,1025000014,500,100800,FALSE,1 +102500002,0,0,1,,,,,,,1025000021,10000,0,1025000022,6000,6000,1025000023,1100,20000,1025000024,500,100800,FALSE,1 +102510001,0,0,1,,,,,,,1025100011,10000,0,1025100012,6000,6000,1025100013,1100,20000,1025100014,500,100800,FALSE,1 +102510002,0,0,1,,,,,,,1025100021,10000,0,1025100022,6000,6000,1025100023,1100,20000,1025100024,500,100800,FALSE,1 +102520001,0,0,1,,,,,,,1025200011,10000,0,1025200012,6000,6000,1025200013,1100,20000,1025200014,500,100800,FALSE,1 +102520002,0,0,1,,,,,,,1025200021,10000,0,1025200022,6000,6000,1025200023,1100,20000,1025200024,500,100800,FALSE,1 +102530001,0,0,1,,,,,,,1025300011,10000,0,1025300012,6000,6000,1025300013,1100,20000,1025300014,500,100800,FALSE,1 +102530002,0,0,1,,,,,,,1025300021,10000,0,1025300022,6000,6000,1025300023,1100,20000,1025300024,500,100800,FALSE,1 +102540001,0,0,1,,,,,,,1025400011,10000,0,1025400012,6000,6000,1025400013,1100,20000,1025400014,500,100800,FALSE,1 +102540002,0,0,1,,,,,,,1025400021,10000,0,1025400022,6000,6000,1025400023,1100,20000,1025400024,500,100800,FALSE,1 +103500021,0,0,1,,,,,,,1035000211,10000,0,1035000212,6000,6000,1035000213,1100,20000,1035000214,500,100800,FALSE,1 +103500022,0,0,1,,,,,,,1035000221,10000,0,1035000222,6000,6000,1035000223,1100,20000,1035000224,500,100800,FALSE,1 +103510021,0,0,1,,,,,,,1035100211,10000,0,1035100212,6000,6000,1035100213,1100,20000,1035100214,500,100800,FALSE,1 +103510022,0,0,1,,,,,,,1035100221,10000,0,1035100222,6000,6000,1035100223,1100,20000,1035100224,500,100800,FALSE,1 +103520021,0,0,1,,,,,,,1035200211,10000,0,1035200212,6000,6000,1035200213,1100,20000,1035200214,500,100800,FALSE,1 +103520022,0,0,1,,,,,,,1035200221,10000,0,1035200222,6000,6000,1035200223,1100,20000,1035200224,500,100800,FALSE,1 +103530021,0,0,1,,,,,,,1035300211,10000,0,1035300212,6000,6000,1035300213,1100,20000,1035300214,500,100800,FALSE,1 +103530022,0,0,1,,,,,,,1035300221,10000,0,1035300222,6000,6000,1035300223,1100,20000,1035300224,500,100800,FALSE,1 +103540021,0,0,1,,,,,,,1035400211,10000,0,1035400212,6000,6000,1035400213,1100,20000,1035400214,500,100800,FALSE,1 +103540022,0,0,1,,,,,,,1035400221,10000,0,1035400222,6000,6000,1035400223,1100,20000,1035400224,500,100800,FALSE,1 +104500021,0,0,1,,,,,,,1045000211,10000,0,1045000212,6000,6000,1045000213,1100,20000,1045000214,500,100800,FALSE,1 +104500022,0,0,1,,,,,,,1045000221,10000,0,1045000222,6000,6000,1045000223,1100,20000,1045000224,500,100800,FALSE,1 +104510021,0,0,1,,,,,,,1045100211,10000,0,1045100212,6000,6000,1045100213,1100,20000,1045100214,500,100800,FALSE,1 +104510022,0,0,1,,,,,,,1045100221,10000,0,1045100222,6000,6000,1045100223,1100,20000,1045100224,500,100800,FALSE,1 +104520021,0,0,1,,,,,,,1045200211,10000,0,1045200212,6000,6000,1045200213,1100,20000,1045200214,500,100800,FALSE,1 +104520022,0,0,1,,,,,,,1045200221,10000,0,1045200222,6000,6000,1045200223,1100,20000,1045200224,500,100800,FALSE,1 +104530021,0,0,1,,,,,,,1045300211,10000,0,1045300212,6000,6000,1045300213,1100,20000,1045300214,500,100800,FALSE,1 +104530022,0,0,1,,,,,,,1045300221,10000,0,1045300222,6000,6000,1045300223,1100,20000,1045300224,500,100800,FALSE,1 +104540021,0,0,1,,,,,,,1045400211,10000,0,1045400212,6000,6000,1045400213,1100,20000,1045400214,500,100800,FALSE,1 +104540022,0,0,1,,,,,,,1045400221,10000,0,1045400222,6000,6000,1045400223,1100,20000,1045400224,500,100800,FALSE,1 +105500011,0,0,1,,,,,,,1055000111,10000,0,1055000112,6000,6000,1055000113,1100,20000,1055000114,500,100800,FALSE,1 +105500012,0,0,1,,,,,,,1055000121,10000,0,1055000122,6000,6000,1055000123,1100,20000,1055000124,500,100800,FALSE,1 +105510011,0,0,1,,,,,,,1055100111,10000,0,1055100112,6000,6000,1055100113,1100,20000,1055100114,500,100800,FALSE,1 +105510012,0,0,1,,,,,,,1055100121,10000,0,1055100122,6000,6000,1055100123,1100,20000,1055100124,500,100800,FALSE,1 +105520011,0,0,1,,,,,,,1055200111,10000,0,1055200112,6000,6000,1055200113,1100,20000,1055200114,500,100800,FALSE,1 +105520012,0,0,1,,,,,,,1055200121,10000,0,1055200122,6000,6000,1055200123,1100,20000,1055200124,500,100800,FALSE,1 +105530011,0,0,1,,,,,,,1055300111,10000,0,1055300112,6000,6000,1055300113,1100,20000,1055300114,500,100800,FALSE,1 +105530012,0,0,1,,,,,,,1055300121,10000,0,1055300122,6000,6000,1055300123,1100,20000,1055300124,500,100800,FALSE,1 +105540011,0,0,1,,,,,,,1055400111,10000,0,1055400112,6000,6000,1055400113,1100,20000,1055400114,500,100800,FALSE,1 +105540012,0,0,1,,,,,,,1055400121,10000,0,1055400122,6000,6000,1055400123,1100,20000,1055400124,500,100800,FALSE,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/ItemRequirementSheet.csv b/Lib9c/TableCSV/Item/ItemRequirementSheet.csv index 0d17abc6c6..b2f3d3ffab 100644 --- a/Lib9c/TableCSV/Item/ItemRequirementSheet.csv +++ b/Lib9c/TableCSV/Item/ItemRequirementSheet.csv @@ -35,11 +35,11 @@ item_id,level,mimislevel 10142001,999,999 10143001,999,999 10144001,999,999 -10150001,999,999 -10151001,999,999 -10152001,999,999 -10153001,999,999 -10154001,999,999 +10150001,340,340 +10151001,305,305 +10152001,305,305 +10153001,320,320 +10154001,320,320 10155000,999,999 10200000,1,1 10210000,1,1 @@ -77,11 +77,11 @@ item_id,level,mimislevel 10242001,999,999 10243001,999,999 10244001,999,999 -10250000,999,999 -10251000,999,999 -10252000,999,999 -10253000,999,999 -10254000,999,999 +10250000,340,340 +10251000,305,305 +10252000,305,305 +10253000,320,320 +10254000,320,320 10255000,999,999 10310000,1,1 10311000,1,1 @@ -199,7 +199,16 @@ item_id,level,mimislevel 40100015,31,31 40100016,1,1 40100017,1,1 +40100018,1,1 40100019,1,1 +40100020,1,1 +40100021,1,1 +40100023,1,1 +40100024,1,1 +40100025,1,1 +40100026,1,1 +40100028,1,1 +40100029,1,1 40200001,1,1 40200002,1,1 40200003,1,1 @@ -244,6 +253,9 @@ item_id,level,mimislevel 49900009,1,1 49900010,1,1 49900011,1,1 +49900012,1,1 +49900013,1,1 +49900014,1,1 105000,1,1 105001,1,1 105002,1,1 @@ -282,6 +294,13 @@ item_id,level,mimislevel 201031,280,280 201032,300,300 201033,320,320 +201034,1,1 +201035,330,330 +201036,350,350 +201037,370,370 +201038,330,330 +201039,350,350 +201040,370,370 900101,1,1 900102,1,1 900103,1,1 @@ -291,7 +310,6 @@ item_id,level,mimislevel 900105,1,1 900106,1,1 10130002,1,1 -49900012,1,1 10620002,1,1 10630002,1,1 10640002,1,1 @@ -301,4 +319,19 @@ item_id,level,mimislevel 10630003,1,1 10640003,1,1 10650005,1,1 -10650006,1,1 \ No newline at end of file +10650006,1,1 +10350002,340,340 +10351002,305,305 +10352002,305,305 +10353002,320,320 +10354002,320,320 +10450002,340,340 +10451002,305,305 +10452002,305,305 +10453002,320,320 +10454002,320,320 +10550001,340,340 +10551001,305,305 +10552001,305,305 +10553001,320,320 +10554001,320,320 \ No newline at end of file diff --git a/Lib9c/TableCSV/Item/MaterialItemSheet.csv b/Lib9c/TableCSV/Item/MaterialItemSheet.csv index ce007dc802..3c55055fcf 100644 --- a/Lib9c/TableCSV/Item/MaterialItemSheet.csv +++ b/Lib9c/TableCSV/Item/MaterialItemSheet.csv @@ -29,6 +29,11 @@ id,_name,item_sub_type,grade,elemental_type 302025,동글이 잎사귀,FoodMaterial,3,Normal 302026,얼어붙은 열매,FoodMaterial,4,Normal 302027,서리 결정,FoodMaterial,5,Normal +302028,빨간 열매,FoodMaterial,2,Normal +302029,거친 나무 조각,FoodMaterial,3,Normal +302030,어둠 물고기,FoodMaterial,3,Normal +302031,영혼수,FoodMaterial,4,Normal +302032,붉은 파우더,FoodMaterial,5,Normal 800101,조개껍질,FoodMaterial,2,Normal 800102,선글라스,FoodMaterial,3,Normal 800103,아이스크림,FoodMaterial,4,Normal @@ -159,13 +164,29 @@ id,_name,item_sub_type,grade,elemental_type 306082,뽀족한 칼날 가면,MonsterPart,4,Water 306083,랜턴 파편,MonsterPart,4,Water 306084,어둠의 정수 파편,MonsterPart,4,Water +306085,신규 무색 결정,MonsterPart,5,Normal +306086,화염 결정,MonsterPart,5,Fire +306087,물 결정,MonsterPart,5,Water +306088,대지 결정,MonsterPart,5,Land +306089,바람 결정,MonsterPart,5,Wind +306090,얼어붙은 잎사귀,MonsterPart,4,Water +306091,얼어붙은 열매,MonsterPart,4,Water +306092,날카로운 얼음 파편,MonsterPart,4,Water +306093,짙은 보석 결정,MonsterPart,4,Water +306094,튼튼한 뿔 조각,MonsterPart,4,Water +306095,뾰족한 철재 파편,MonsterPart,4,Water +306096,얼어붙은 방패 파편,MonsterPart,4,Water +306097,뽀족한 칼날 가면,MonsterPart,4,Water +306098,랜턴 파편,MonsterPart,4,Water +306099,어둠의 정수 파편,MonsterPart,4,Water 400000,모래시계,Hourglass,4,Normal 500000,AP 스톤,ApStone,4,Normal 600101,Special Crystal Piece,EquipmentMaterial,1,Normal 600102,Special Crystal Lump,EquipmentMaterial,2,Normal 600103,Special Crystal Ore,EquipmentMaterial,3,Normal 600104,Special Crystal Jewel,EquipmentMaterial,4,Normal -600201,golden dust,EquipmentMaterial,4,Normal +600201,Golden Dust,EquipmentMaterial,4,Normal +600202,Ruby Dust,EquipmentMaterial,4,Normal 600301,망치 커먼,EquipmentMaterial,1,Normal 600302,망치 레어,EquipmentMaterial,2,Normal 600303,망치 에픽,EquipmentMaterial,3,Normal @@ -183,9 +204,9 @@ id,_name,item_sub_type,grade,elemental_type 700009,시즌 3 메달 (헤임달용),NormalMaterial,5,Normal 700010,아레나 시즌10 메달,NormalMaterial,5,Normal 700011,챔피언 쉽 0 메달 (헤임달용),NormalMaterial,5,Normal -700102,시즌 3 메달,NormalMaterial,5,Normal -700104,시즌 4 메달,NormalMaterial,5,Normal -700106,시즌 5 메달,NormalMaterial,5,Normal +700102,시즌 4 메달,NormalMaterial,5,Normal +700104,시즌 5 메달,NormalMaterial,5,Normal +700106,시즌 6 메달,NormalMaterial,5,Normal 700108,챔피언 쉽 1 메달,NormalMaterial,5,Normal 700202,시즌 7 메달,NormalMaterial,5,Normal 700204,시즌 8 메달,NormalMaterial,5,Normal diff --git a/Lib9c/TableCSV/Quest/WorldQuestSheet.csv b/Lib9c/TableCSV/Quest/WorldQuestSheet.csv index 9feb912498..87f96859d5 100644 --- a/Lib9c/TableCSV/Quest/WorldQuestSheet.csv +++ b/Lib9c/TableCSV/Quest/WorldQuestSheet.csv @@ -348,4 +348,54 @@ id,goal,quest_reward_id 100347,347,43 100348,348,43 100349,349,43 -100350,350,43 \ No newline at end of file +100350,350,43 +100351,351,43 +100352,352,43 +100353,353,43 +100354,354,43 +100355,355,43 +100356,356,43 +100357,357,43 +100358,358,43 +100359,359,43 +100360,360,43 +100361,361,43 +100362,362,43 +100363,363,43 +100364,364,43 +100365,365,43 +100366,366,43 +100367,367,43 +100368,368,43 +100369,369,43 +100370,370,43 +100371,371,43 +100372,372,43 +100373,373,43 +100374,374,43 +100375,375,43 +100376,376,43 +100377,377,43 +100378,378,43 +100379,379,43 +100380,380,43 +100381,381,43 +100382,382,43 +100383,383,43 +100384,384,43 +100385,385,43 +100386,386,43 +100387,387,43 +100388,388,43 +100389,389,43 +100390,390,43 +100391,391,43 +100392,392,43 +100393,393,43 +100394,394,43 +100395,395,43 +100396,396,43 +100397,397,43 +100398,398,43 +100399,399,43 +100400,400,43 \ No newline at end of file diff --git a/Lib9c/TableCSV/Skill/ActionBuffSheet.csv b/Lib9c/TableCSV/Skill/ActionBuffSheet.csv index 29df06c98c..070d95ec38 100644 --- a/Lib9c/TableCSV/Skill/ActionBuffSheet.csv +++ b/Lib9c/TableCSV/Skill/ActionBuffSheet.csv @@ -5,4 +5,7 @@ id,group,_name,chance,duration,target_type,buff_type,elemental_type,atk_power_ra 600001,600001,출혈,100,0,Enemies,Bleed,Normal,1 704000,704000,기절,100,0,Enemy,Stun,Normal,0 704001,704000,기절,100,0,Enemies,Stun,Normal,0 -705000,705000,흡혈,100,0,Self,Vampiric,Normal,0 \ No newline at end of file +705000,705000,흡혈,100,0,Self,Vampiric,Normal,0 +706000,706000,집중,100,20,Self,Focus,Normal,0 +707000,707000,치유,100,0,Self,Dispel,Normal,0 +708000,708000,면역(1),100,20,Self,Dispel,Normal,0 diff --git a/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv b/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv new file mode 100644 index 0000000000..8d3e682dd6 --- /dev/null +++ b/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv @@ -0,0 +1,6 @@ +group_id,percentage +1,-1 +2,-10 +3,-20 +4,-50 +5,-100 diff --git a/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv.meta b/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv.meta new file mode 100644 index 0000000000..2c364b77be --- /dev/null +++ b/Lib9c/TableCSV/Skill/DeBuffLimitSheet.csv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c9cc235dd2a1a4165a77897cf67590bd +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Lib9c/TableCSV/Skill/EnemySkillSheet.csv b/Lib9c/TableCSV/Skill/EnemySkillSheet.csv index 35eacb7dce..48ef6cee6f 100644 --- a/Lib9c/TableCSV/Skill/EnemySkillSheet.csv +++ b/Lib9c/TableCSV/Skill/EnemySkillSheet.csv @@ -241,4 +241,12 @@ id,character_id,skill_id 240,208007,120005 241,208007,210004 242,208007,220002 -243,208007,250004 \ No newline at end of file +243,208007,250004 +244,209000,110000 +245,209001,110000 +246,209002,110000 +247,209003,110000 +248,209004,110000 +249,209005,110000 +250,209006,110000 +251,209007,110000 \ No newline at end of file diff --git a/Lib9c/TableCSV/Skill/SkillActionBuffSheet.csv b/Lib9c/TableCSV/Skill/SkillActionBuffSheet.csv index 61cbe24420..f38d791a32 100644 --- a/Lib9c/TableCSV/Skill/SkillActionBuffSheet.csv +++ b/Lib9c/TableCSV/Skill/SkillActionBuffSheet.csv @@ -5,4 +5,7 @@ skill_id,buff_id 600001,600001 700004,704000 700005,704001 -700006,705000 \ No newline at end of file +700006,705000 +700007,706000 +700009,707000 +700010,708000 diff --git a/Lib9c/TableCSV/Skill/SkillBuffSheet.csv b/Lib9c/TableCSV/Skill/SkillBuffSheet.csv index ebb87b6aa2..90fa4d8f9d 100644 --- a/Lib9c/TableCSV/Skill/SkillBuffSheet.csv +++ b/Lib9c/TableCSV/Skill/SkillBuffSheet.csv @@ -218,4 +218,4 @@ skill_id,buff_id 230007,104005 230007,204003 240006,105003 -210012,202004 \ No newline at end of file +210012,202004 diff --git a/Lib9c/TableCSV/Skill/SkillSheet.csv b/Lib9c/TableCSV/Skill/SkillSheet.csv index 1a9ef43cc7..3a3d59cfe0 100644 --- a/Lib9c/TableCSV/Skill/SkillSheet.csv +++ b/Lib9c/TableCSV/Skill/SkillSheet.csv @@ -1,4 +1,4 @@ -id,_name,elemental_type,skill_type,skill_category,skill_target_type,hit_count,cooldown +id,_name,elemental_type,skill_type,skill_category,skill_target_type,hit_count,cooldown,combo 100000,기본 공격,Normal,Attack,NormalAttack,Enemy,1,0 100001,일격,Normal,Attack,BlowAttack,Enemy,1,1 _100002,일격(전체),Normal,Attack,BlowAttack,Enemies,1,1 // 노멀 속성의 전체 일격은 추가하면 안 됩니다. @@ -169,4 +169,9 @@ _250001,속도 증가(전체),Normal,Buff,SpeedBuff,Ally,1,1 // 미구현 700003,치명 데미지 증가,Normal,Buff,CriticalDamageBuff,Self,1,15 700004,기절,Normal,Debuff,Buff,Enemy,1,15 700005,기절,Normal,Debuff,Buff,Enemies,1,15 -700006,흡혈,Normal,Buff,Buff,Self,1,15 \ No newline at end of file +700006,흡혈,Normal,Buff,Buff,Self,1,15 +700007,집중,Normal,Buff,Focus,Self,1,15 +700008,더블 어택,Normal,Attack,DoubleAttack,Enemy,2,15,true +700009,디버프 제거,Normal,Buff,Buff,Self,1,15 +700010,면역,Normal,Buff,Dispel,Self,1,15 +700011,HP 비례 데미지,Normal,Attack,ShatterStrike,Enemy,1,15 diff --git a/Lib9c/TableCSV/Skill/StatBuffSheet.csv b/Lib9c/TableCSV/Skill/StatBuffSheet.csv index 0d8c4cef1a..ce99ed9b2c 100644 --- a/Lib9c/TableCSV/Skill/StatBuffSheet.csv +++ b/Lib9c/TableCSV/Skill/StatBuffSheet.csv @@ -121,4 +121,4 @@ id,group,_name,chance,duration,target_type,stat_type,modify_type,modify_value,is 504011,504011,Serimnir Furious Enemy CDMG,100,8,Enemy,CDMG,Add,10000,true 701000,701000,DRV (Rune),100,0,Self,DRV,Add,0,true 702000,701000,DRR (Rune),100,0,Self,DRR,Add,0,true -703000,703000,CDMG (Rune),100,0,Self,CDMG,Add,0,true \ No newline at end of file +703000,703000,CDMG (Rune),100,0,Self,CDMG,Add,0,true diff --git a/Lib9c/TableCSV/StakeRegularFixedRewardSheet_V3.csv.meta b/Lib9c/TableCSV/StakeRegularFixedRewardSheet_V3.csv.meta new file mode 100644 index 0000000000..f39e0426ee --- /dev/null +++ b/Lib9c/TableCSV/StakeRegularFixedRewardSheet_V3.csv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 708f7a00c376d48baa0b3121ff4f4321 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Lib9c/TableCSV/StakeRegularRewardSheet_V6.csv.meta b/Lib9c/TableCSV/StakeRegularRewardSheet_V6.csv.meta new file mode 100644 index 0000000000..72c89ff5f7 --- /dev/null +++ b/Lib9c/TableCSV/StakeRegularRewardSheet_V6.csv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0b966984ecb29471d80098ee72059431 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Lib9c/TableCSV/SweepRequiredCPSheet.csv b/Lib9c/TableCSV/SweepRequiredCPSheet.csv index 629b4bf113..e0d490eabe 100644 --- a/Lib9c/TableCSV/SweepRequiredCPSheet.csv +++ b/Lib9c/TableCSV/SweepRequiredCPSheet.csv @@ -348,4 +348,54 @@ stage_id,required_cp 347,1640445 348,1697748 349,1714993 -350,1732237 \ No newline at end of file +350,1732237 +351,1 +352,1 +353,1 +354,1 +355,1 +356,1 +357,1 +358,1 +359,1 +360,1 +361,1 +362,1 +363,1 +364,1 +365,1 +366,1 +367,1 +368,1 +369,1 +370,1 +371,1 +372,1 +373,1 +374,1 +375,1 +376,1 +377,1 +378,1 +379,1 +380,1 +381,1 +382,1 +383,1 +384,1 +385,1 +386,1 +387,1 +388,1 +389,1 +390,1 +391,1 +392,1 +393,1 +394,1 +395,1 +396,1 +397,1 +398,1 +399,1 +400,1 \ No newline at end of file diff --git a/Lib9c/TableCSV/WorldAndStage/StageSheet.csv b/Lib9c/TableCSV/WorldAndStage/StageSheet.csv index 0cfdfaa834..60f8bff45a 100644 --- a/Lib9c/TableCSV/WorldAndStage/StageSheet.csv +++ b/Lib9c/TableCSV/WorldAndStage/StageSheet.csv @@ -349,6 +349,56 @@ id,cost_ap,turn_limit,hp_additional,atk_additional,def_additional,cri_additional 348,5,150,7500,10800,4297,0,15500,5200,chapter_07_02,bgm_niflheim_02,303204,0.4,2,3,306070,0.2,1,1,306071,0.1,1,1,306074,0.1,1,1,306073,0.1,1,1,306072,0.1,1,1,306072,0.1,1,1,,,,,,,,,,,,,2,3 349,5,150,8000,10900,4313,0,16000,5600,chapter_07_01,bgm_niflheim_01,303304,0.4,2,3,306070,0.2,1,1,306071,0.1,1,1,306074,0.1,1,1,306073,0.1,1,1,306072,0.1,1,1,306072,0.1,1,1,,,,,,,,,,,,,2,3 350,5,150,9000,11000,4330,0,16500,6000,chapter_07_03,bgm_niflheim_03,303404,0.4,2,3,306070,0.2,1,1,306071,0.1,1,1,306074,0.1,1,1,306073,0.1,1,1,306072,0.1,1,1,306072,0.1,1,1,,,,,,,,,,,,,2,3 +351,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303004,1,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +352,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303004,0.4,1,1,306090,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +353,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303004,0.4,1,1,306090,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +354,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303004,0.4,1,1,306090,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +355,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303004,0.4,1,1,306090,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +356,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303204,1,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +357,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303204,0.4,1,1,306091,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +358,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303204,0.4,1,1,306091,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +359,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303204,0.4,1,1,306091,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +360,5,150,9000,11000,4330,0,16500,6000,chapter_08_03,bgm_hel_03,303204,0.4,1,1,306091,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +361,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,1,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +362,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,0.4,1,1,306092,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +363,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303104,0.4,1,1,306092,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +364,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,0.4,1,1,306092,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +365,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,0.4,1,1,306092,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +366,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303304,1,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +367,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,1,1,306093,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +368,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303304,0.4,1,1,306093,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +369,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,1,1,306093,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +370,5,150,9000,11000,4330,0,16500,6000,chapter_08_03,bgm_hel_03,303304,0.4,1,1,306093,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +371,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,1,4,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +372,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,0.4,1,1,306094,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +373,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303404,0.4,1,1,306094,0.45,2,3,306085,0.1,1,1,306086,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +374,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,0.4,1,1,306094,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +375,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,0.4,1,1,306094,0.45,2,3,306085,0.1,1,1,306089,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,2,3 +376,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303004,1,6,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,6 +377,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303204,1,6,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,6 +378,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303004,0.4,1,1,306095,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +379,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303204,0.4,1,1,306096,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +380,5,150,9000,11000,4330,0,16500,6000,chapter_08_03,bgm_hel_03,303004,0.4,1,1,306095,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +381,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303204,0.4,1,1,306096,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +382,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303004,0.4,1,1,306095,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +383,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303204,0.4,1,1,306096,0.45,3,4,306070,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +384,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,1,6,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,6 +385,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,1,6,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,6 +386,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303104,0.4,1,1,306097,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +387,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,1,1,306098,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +388,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303104,0.4,1,1,306097,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +389,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,1,1,306098,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +390,5,150,9000,11000,4330,0,16500,6000,chapter_08_03,bgm_hel_03,303104,0.4,1,1,306097,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +391,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,1,1,306098,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +392,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,1,6,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,6 +393,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303404,0.4,1,1,306099,0.45,3,4,306085,0.1,1,1,306088,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +394,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,0.4,1,1,306099,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +395,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303404,0.4,1,1,306099,0.45,3,4,306085,0.1,1,1,306087,0.05,1,1,,,,,,,,,,,,,,,,,,,,,,,,,3,4 +396,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303004,0.4,2,3,306085,0.2,1,1,306086,0.1,1,1,306089,0.1,1,1,306088,0.1,1,1,306087,0.1,1,1,306087,0.1,1,1,,,,,,,,,,,,,2,3 +397,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303104,0.4,2,3,306085,0.2,1,1,306086,0.1,1,1,306089,0.1,1,1,306088,0.1,1,1,306087,0.1,1,1,306087,0.1,1,1,,,,,,,,,,,,,2,3 +398,5,150,9000,11000,4330,0,16500,6000,chapter_08_02,bgm_hel_02,303204,0.4,2,3,306085,0.2,1,1,306086,0.1,1,1,306089,0.1,1,1,306088,0.1,1,1,306087,0.1,1,1,306087,0.1,1,1,,,,,,,,,,,,,2,3 +399,5,150,9000,11000,4330,0,16500,6000,chapter_08_01,bgm_hel_01,303304,0.4,2,3,306085,0.2,1,1,306086,0.1,1,1,306089,0.1,1,1,306088,0.1,1,1,306087,0.1,1,1,306087,0.1,1,1,,,,,,,,,,,,,2,3 +400,5,150,9000,11000,4330,0,16500,6000,chapter_08_03,bgm_hel_03,303404,0.4,2,3,306085,0.2,1,1,306086,0.1,1,1,306089,0.1,1,1,306088,0.1,1,1,306087,0.1,1,1,306087,0.1,1,1,,,,,,,,,,,,,2,3 10000001,10,150,184,132,132,0,184,132,chapter_99_01,bgm_hard1,303200,0.15,1,1,306064,0.25,1,2,306065,0.1,1,2,306058,0.4,1,1,306059,0.1,1,1,,,,,,,,,,,,,,,,,,,,,4,4 10000002,10,150,228,163,163,0,228,163,chapter_99_01,bgm_hard2,303200,0.15,1,1,306064,0.25,1,2,306065,0.1,1,2,306058,0.4,1,1,306059,0.1,1,1,,,,,,,,,,,,,,,,,,,,,4,4 10000003,10,150,245,175,175,0,245,175,chapter_99_01,bgm_hard1,303200,0.15,1,1,306064,0.25,1,2,306065,0.1,1,2,306058,0.4,1,1,306059,0.1,1,1,,,,,,,,,,,,,,,,,,,,,4,4 diff --git a/Lib9c/TableCSV/WorldAndStage/StageWaveSheet.csv b/Lib9c/TableCSV/WorldAndStage/StageWaveSheet.csv index 272328310f..f0a18a8724 100644 --- a/Lib9c/TableCSV/WorldAndStage/StageWaveSheet.csv +++ b/Lib9c/TableCSV/WorldAndStage/StageWaveSheet.csv @@ -1049,6 +1049,156 @@ stage_id,wave,monster1_id,monster1_level,monster1_count,monster2_id,monster2_lev 350,1,208004,354,2,208003,354,1,208005,354,1,,,,0 350,2,208001,354,4,208002,354,3,208003,354,3,,,,0 350,3,208007,355,1,,,,,,,,,,1 +351,1,209000,300,1,,,,,,,,,,0 +351,2,209000,300,1,,,,,,,,,,0 +351,3,209000,300,1,209001,300,1,,,,,,,0 +352,1,209001,300,1,,,,,,,,,,0 +352,2,209001,300,1,,,,,,,,,,0 +352,3,209001,300,1,209002,300,1,,,,,,,1 +353,1,209002,300,1,,,,,,,,,,0 +353,2,209002,300,1,,,,,,,,,,0 +353,3,209002,300,1,209003,300,1,,,,,,,0 +354,1,209003,300,1,,,,,,,,,,0 +354,2,209003,300,1,,,,,,,,,,0 +354,3,209003,300,1,209004,300,1,,,,,,,1 +355,1,209004,300,1,,,,,,,,,,0 +355,2,209004,300,1,,,,,,,,,,0 +355,3,209004,300,1,209005,300,1,,,,,,,0 +356,1,209005,300,1,,,,,,,,,,0 +356,2,209005,300,1,,,,,,,,,,0 +356,3,209005,300,1,209006,300,1,,,,,,,1 +357,1,209006,300,1,,,,,,,,,,0 +357,2,209006,300,1,,,,,,,,,,0 +357,3,209006,300,1,209007,300,1,,,,,,,0 +358,1,209007,300,1,,,,,,,,,,0 +358,2,209007,300,1,,,,,,,,,,0 +358,3,209007,300,1,,,,,,,,,,1 +359,1,209000,314,3,209002,314,2,,,,,,,0 +359,2,209001,314,3,209002,314,1,,,,,,,0 +359,3,209003,315,1,209001,315,1,,,,,,,0 +360,1,209000,314,3,209002,314,2,,,,,,,0 +360,2,209001,314,3,209002,314,1,,,,,,,0 +360,3,209003,315,1,209001,315,1,,,,,,,1 +361,1,209000,315,4,209002,315,2,,,,,,,0 +361,2,209001,315,3,209001,315,2,,,,,,,0 +361,3,209001,315,3,209002,315,2,,,,,,,0 +362,1,209001,316,3,209002,316,2,,,,,,,0 +362,2,209000,316,4,209002,316,2,,,,,,,0 +362,3,209001,317,1,209002,317,4,,,,,,,1 +363,1,209001,317,4,209002,317,2,,,,,,,0 +363,2,209000,317,5,209002,317,2,,,,,,,0 +363,3,209003,317,1,209001,317,1,,,,,,,0 +364,1,209000,318,4,209001,318,3,209002,318,1,,,,0 +364,2,209001,318,3,209002,318,3,,,,,,,0 +364,3,209001,319,3,209002,319,3,,,,,,,1 +365,1,209000,319,2,209002,319,2,209003,319,1,,,,0 +365,2,209001,319,3,209002,319,3,,,,,,,0 +365,3,209001,319,2,209002,319,2,209003,319,1,,,,0 +366,1,209001,320,4,209002,320,4,,,,,,,0 +366,2,209000,320,5,209002,320,3,,,,,,,0 +366,3,209004,321,1,209000,321,2,209001,321,2,,,,1 +367,1,209000,321,3,209001,321,2,209003,321,1,,,,0 +367,2,209001,321,4,209002,321,3,,,,,,,0 +367,3,209000,321,1,209001,321,2,209002,321,6,,,,0 +368,1,209000,322,3,209001,322,2,209002,322,4,,,,0 +368,2,209001,322,3,209002,322,4,,,,,,,0 +368,3,209000,323,2,209002,323,2,209003,323,1,,,,1 +369,1,209000,323,2,209001,323,2,209002,323,5,,,,0 +369,2,209001,323,4,209002,323,3,,,,,,,0 +369,3,209001,323,3,209002,323,1,209003,323,1,,,,0 +370,1,209003,324,1,209000,324,4,209001,324,2,,,,0 +370,2,209001,324,3,209002,324,4,,,,,,,0 +370,3,209004,325,1,209001,325,3,,,,,,,1 +371,1,209003,325,1,209000,325,3,209001,325,3,,,,0 +371,2,209000,325,1,209001,325,4,209002,325,3,,,,0 +371,3,209003,325,1,209000,325,4,209001,325,2,,,,0 +372,1,209000,326,5,209001,326,3,209002,326,3,,,,0 +372,2,209000,326,3,209001,326,4,209002,326,2,,,,0 +372,3,209003,327,1,209000,327,3,209002,327,3,,,,1 +373,1,209000,327,3,209001,327,4,209002,327,4,,,,0 +373,2,209000,327,2,209001,327,4,209002,327,3,,,,0 +373,3,209004,327,1,209000,327,2,209002,327,3,,,,0 +374,1,209004,328,1,209001,328,2,209002,328,2,,,,0 +374,2,209000,328,3,209001,328,4,209002,328,3,,,,0 +374,3,209003,329,1,209001,329,3,209002,329,3,,,,1 +375,1,209004,329,1,209003,329,1,,,,,,,0 +375,2,209002,329,3,209003,329,1,,,,,,,0 +375,3,209004,329,1,209000,329,3,209002,329,3,,,,0 +376,1,209001,330,3,209002,330,3,209003,330,1,,,,0 +376,2,209000,330,3,209002,330,5,,,,,,,0 +376,3,209005,331,1,209002,331,3,,,,,,,1 +377,1,209004,331,1,209003,331,1,209002,331,3,,,,0 +377,2,209001,331,3,209002,331,4,,,,,,,0 +377,3,209004,331,1,209003,331,1,,,,,,,0 +378,1,209000,332,4,209002,332,4,209003,332,1,,,,0 +378,2,209001,332,3,209002,332,5,,,,,,,0 +378,3,209004,333,1,209000,333,5,209002,333,4,,,,1 +379,1,209001,333,3,209002,333,4,209003,333,1,,,,0 +379,2,209001,333,2,209002,333,6,,,,,,,0 +379,3,209000,333,5,209002,333,3,209003,333,2,,,,0 +380,1,209001,334,3,209002,334,4,209003,334,1,,,,0 +380,2,209001,334,4,209002,334,4,,,,,,,0 +380,3,209005,335,1,209003,335,1,209002,335,1,,,,1 +381,1,209000,335,4,209002,335,4,209003,335,1,,,,0 +381,2,209000,335,3,209001,335,3,209002,335,3,,,,0 +381,3,209004,335,2,209001,335,4,209002,335,1,,,,0 +382,1,209004,336,1,209000,336,4,209001,336,4,,,,0 +382,2,209000,336,3,209002,336,2,209003,336,1,,,,0 +382,3,209004,337,2,209002,337,4,,,,,,,1 +383,1,209004,337,1,209001,337,4,209002,337,3,,,,0 +383,2,209000,337,2,209001,337,3,209002,337,4,,,,0 +383,3,209005,337,1,209004,337,1,,,,,,,0 +384,1,209004,338,2,209001,338,2,,,,,,,0 +384,2,209003,338,1,209000,338,2,209002,338,2,,,,0 +384,3,209005,339,1,209001,339,3,209002,339,3,,,,1 +385,1,209004,339,2,209002,339,2,,,,,,,0 +385,2,209003,339,1,209002,339,4,,,,,,,0 +385,3,209005,339,1,209000,339,3,209001,339,4,,,,0 +386,1,209004,340,1,209003,340,1,209002,340,4,,,,0 +386,2,209003,340,1,209001,340,5,,,,,,,0 +386,3,209006,341,1,209000,341,3,,,,,,,1 +387,1,209005,341,1,209001,341,4,,,,,,,0 +387,2,209004,341,1,209000,341,4,,,,,,,0 +387,3,209004,341,1,209002,341,4,209003,341,1,,,,0 +388,1,209004,342,1,209001,342,3,209002,342,3,,,,0 +388,2,209003,342,1,209000,342,3,209002,342,3,,,,0 +388,3,209005,343,1,209004,343,1,,,,,,,1 +389,1,209002,343,3,209003,343,2,,,,,,,0 +389,2,209003,343,1,209001,343,3,209002,343,3,,,,0 +389,3,209004,343,2,209002,343,3,,,,,,,0 +390,1,209004,344,1,209005,344,1,209003,344,1,,,,0 +390,2,209003,344,2,209001,344,2,,,,,,,0 +390,3,209006,345,1,209003,345,1,,,,,,,1 +391,1,209004,345,1,209003,345,1,209002,345,1,,,,0 +391,2,209000,345,3,209005,345,1,,,,,,,0 +391,3,209003,345,3,209001,345,3,209002,345,3,,,,0 +392,1,209000,346,3,209001,346,3,209003,346,2,,,,0 +392,2,209000,346,3,209004,346,1,209002,346,2,,,,0 +392,3,209005,347,2,209001,347,3,,,,,,,1 +393,1,209001,347,2,209002,347,3,209003,347,2,,,,0 +393,2,209000,347,3,209004,347,1,209002,347,3,,,,0 +393,3,209006,347,1,209004,347,1,,,,,,,0 +394,1,209005,348,2,209002,348,3,209000,348,2,,,,0 +394,2,209000,348,1,209001,348,1,209002,348,1,209003,348,2,0 +394,3,209004,349,1,209005,349,1,209001,349,2,,,,1 +395,1,209004,349,2,209003,349,1,209001,349,3,,,,0 +395,2,209001,349,2,209002,349,1,209003,349,2,,,,0 +395,3,209003,349,2,209004,349,1,209000,349,2,,,,0 +396,1,209005,350,1,209003,350,2,209002,350,4,,,,0 +396,2,209001,350,1,209002,350,2,209003,350,2,,,,0 +396,3,209006,351,1,209005,351,1,,,,,,,1 +397,1,209005,351,2,209003,351,1,,,,,,,0 +397,2,209002,351,3,209001,351,4,209004,351,1,,,,0 +397,3,209004,351,2,209003,351,1,209002,351,3,,,,0 +398,1,209003,352,3,209004,352,1,209000,352,2,,,,0 +398,2,209002,352,2,209001,352,2,209003,352,2,,,,0 +398,3,209003,353,1,209005,353,2,,,,,,,1 +399,1,209006,353,1,209004,353,1,209005,353,1,,,,0 +399,2,209000,353,3,209005,353,2,,,,,,,0 +399,3,209006,353,1,209005,353,1,209002,353,3,,,,0 +400,1,209004,354,2,209003,354,1,209005,354,1,,,,0 +400,2,209001,354,4,209002,354,3,209003,354,3,,,,0 +400,3,209007,355,1,,,,,,,,,,1 10000001,1,203000,95,1,203001,95,1,,,,,,,0 10000001,2,203000,95,1,203001,95,2,203003,95,1,,,,0 10000001,3,203001,95,2,203003,95,2,,,,,,,0 diff --git a/Lib9c/TableCSV/WorldAndStage/WorldSheet.csv b/Lib9c/TableCSV/WorldAndStage/WorldSheet.csv index 182f3e8b10..3fd0148f5f 100644 --- a/Lib9c/TableCSV/WorldAndStage/WorldSheet.csv +++ b/Lib9c/TableCSV/WorldAndStage/WorldSheet.csv @@ -6,4 +6,5 @@ id,name,stage_begin,stage_end 10001,Mimisbrunnr,10000001,10000020 5,Muspelheim,201,250 6,Jotunheim,251,300 -7,Niflheim,301,350 \ No newline at end of file +7,Niflheim,301,350 +8,Hel,351,400 \ No newline at end of file diff --git a/Lib9c/TableCSV/WorldAndStage/WorldUnlockSheet.csv b/Lib9c/TableCSV/WorldAndStage/WorldUnlockSheet.csv index 0d5b673437..34d047dfa9 100644 --- a/Lib9c/TableCSV/WorldAndStage/WorldUnlockSheet.csv +++ b/Lib9c/TableCSV/WorldAndStage/WorldUnlockSheet.csv @@ -5,4 +5,5 @@ id,world_id,stage_id,world_id_to_unlock,required_crystal 4,2,100,10001,0 5,4,200,5,100000 6,5,250,6,1000000 -7,6,300,7,1000000 \ No newline at end of file +7,6,300,7,1000000 +8,7,350,8,1 \ No newline at end of file diff --git a/Lib9c/TableData/Skill/DeBuffLimitSheet.cs b/Lib9c/TableData/Skill/DeBuffLimitSheet.cs new file mode 100644 index 0000000000..817049bb2f --- /dev/null +++ b/Lib9c/TableData/Skill/DeBuffLimitSheet.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using Nekoyume.Model.Stat; +using Org.BouncyCastle.Tls; +using static Nekoyume.TableData.TableExtensions; + +namespace Nekoyume.TableData +{ + public class DeBuffLimitSheet : Sheet + { + public class Row : SheetRow + { + public override int Key => GroupId; + + public int GroupId { get; set; } + + public int Value { get; set; } + + public override void Set(IReadOnlyList fields) + { + GroupId = ParseInt(fields[0]); + Value = ParseInt(fields[1]); + } + + public StatModifier GetModifier(StatType statType) + { + return new StatModifier(statType, StatModifier.OperationType.Percentage, Value); + } + } + + public DeBuffLimitSheet() : base(nameof(DeBuffLimitSheet)) + { + } + } +} diff --git a/Lib9c/TableData/Skill/SkillSheet.cs b/Lib9c/TableData/Skill/SkillSheet.cs index 71ccd7d633..4c636b57ec 100644 --- a/Lib9c/TableData/Skill/SkillSheet.cs +++ b/Lib9c/TableData/Skill/SkillSheet.cs @@ -23,6 +23,7 @@ public class Row : SheetRow, IState public int HitCount { get; private set; } public int Cooldown { get; private set; } + public bool Combo { get; private set; } public Row() {} public Row(Bencodex.Types.Dictionary serialized) @@ -35,6 +36,10 @@ public Row(Bencodex.Types.Dictionary serialized) SkillTargetType = (SkillTargetType) Enum.Parse(typeof(SkillTargetType), (Bencodex.Types.Text) serialized["skill_target_type"]); HitCount = (Bencodex.Types.Integer) serialized["hit_count"]; Cooldown = (Bencodex.Types.Integer) serialized["cooldown"]; + if (serialized.ContainsKey("combo")) + { + Combo = serialized["combo"] is not null && (Bencodex.Types.Boolean)serialized["combo"]; + } } public override void Set(IReadOnlyList fields) @@ -46,6 +51,7 @@ public override void Set(IReadOnlyList fields) SkillTargetType = (SkillTargetType) Enum.Parse(typeof(SkillTargetType), fields[4]); HitCount = ParseInt(fields[5]); Cooldown = ParseInt(fields[6]); + Combo = fields.Count > 7 && ParseBool(fields[7], false); } public IValue Serialize() @@ -57,7 +63,9 @@ public IValue Serialize() .Add("skill_category", SkillCategory.ToString()) .Add("skill_target_type", SkillTargetType.ToString()) .Add("hit_count", HitCount) - .Add("cooldown", Cooldown); + .Add("cooldown", Cooldown) + .Add("combo", Combo) + ; return dict; } diff --git a/_typos.toml b/_typos.toml index d1445d382a..ace23414f7 100644 --- a/_typos.toml +++ b/_typos.toml @@ -12,6 +12,7 @@ ba = "ba" # byte array oce = "oce" # OperationCanceledException ist = "ist" # ItemSubType Equipments = "Equipments" # FIXME: Equipment is noncount word but our team doesn't have the policy about it. +Hel = "Hel" # Stage prefix of world 8 [files] extend-exclude = [