diff --git a/.Lib9c.Tests/Action/ActionEvaluationTest.cs b/.Lib9c.Tests/Action/ActionEvaluationTest.cs index 2f20cfef93..a897fbbff2 100644 --- a/.Lib9c.Tests/Action/ActionEvaluationTest.cs +++ b/.Lib9c.Tests/Action/ActionEvaluationTest.cs @@ -93,6 +93,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); @@ -482,6 +483,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 2d36313d67..fd48733be3 100644 --- a/.Lib9c.Tests/Action/ActivateCollectionTest.cs +++ b/.Lib9c.Tests/Action/ActivateCollectionTest.cs @@ -137,6 +137,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/RaidTest.cs b/.Lib9c.Tests/Action/RaidTest.cs index c46af29cdd..b977c7a6af 100644 --- a/.Lib9c.Tests/Action/RaidTest.cs +++ b/.Lib9c.Tests/Action/RaidTest.cs @@ -261,7 +261,9 @@ int runeId2 null, raidSimulatorSheets, _tableSheets.CostumeStatSheet, - new List()); + new List(), + _tableSheets.DeBuffLimitSheet + ); simulator.Simulate(); var score = simulator.DamageDealt; @@ -498,7 +500,9 @@ public void Execute_With_Reward() null, _tableSheets.GetRaidSimulatorSheets(), _tableSheets.CostumeStatSheet, - new List()); + new List(), + _tableSheets.DeBuffLimitSheet + ); simulator.Simulate(); Dictionary rewardMap diff --git a/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs b/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs new file mode 100644 index 0000000000..45a07e5d92 --- /dev/null +++ b/.Lib9c.Tests/Action/RetrieveAvatarAssetsTest.cs @@ -0,0 +1,164 @@ +namespace Lib9c.Tests.Action +{ + using System; + using System.Collections.Generic; + using Bencodex.Types; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Mocks; + 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(MockWorldState.CreateModern()); + 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(MockWorldState.CreateModern()), + 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 2aa1414c0e..2fa35fe85d 100644 --- a/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs +++ b/.Lib9c.Tests/Action/Scenario/AuraScenarioTest.cs @@ -258,7 +258,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()) { 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 76% rename from .Lib9c.Tests/Model/Skill/CombatTest.cs rename to .Lib9c.Tests/Model/Skill/Adventure/CombatTest.cs index b01d7fce99..b628676303 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; @@ -138,45 +139,43 @@ public void Bleed() } [Theory] - [InlineData(700009, new[] { 600001 })] - [InlineData(700009, new[] { 600001, 704000 })] - public void DispelOnUse(int dispelId, int[] debuffIdList) + [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 + 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 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 BuffSkill(skillRow, 0, 100, 0, StatType.NONE); + 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, - BuffFactory.GetBuffs( - _player.Stats, - dispel, - _tableSheets.SkillBuffSheet, - _tableSheets.StatBuffSheet, - _tableSheets.SkillActionBuffSheet, - _tableSheets.ActionBuffSheet - ), + new List() { new Dispel(actionBuffRow) }, false); Assert.NotNull(battleStatus); // Remove Bleed, add Dispel - Assert.Single(_player.Buffs); - Assert.Equal(dispelRow.GroupId, _player.Buffs.First().Value.BuffInfo.GroupId); + Assert.Equal(expectedResult.Length, _player.Buffs.Count); + Assert.Equal(expectedResult, _player.Buffs.Values.Select(bf => bf.BuffInfo.GroupId).ToArray()); } [Fact] @@ -247,6 +246,62 @@ public void DispelOnDuration_Affect() Assert.True(battleStatus.SkillInfos.First().Affected); } + [Fact] + public void DispelOnDuration_Nothing() + { + var actionBuffSheet = _tableSheets.ActionBuffSheet; + + // Use Dispel first + var dispel = actionBuffSheet.Values.First(bf => bf.ActionBuffType == ActionBuffType.Dispel); + _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/DoubleAttackTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs similarity index 97% rename from .Lib9c.Tests/Model/Skill/DoubleAttackTest.cs rename to .Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs index 89320fb05b..4ccbbfadab 100644 --- a/.Lib9c.Tests/Model/Skill/DoubleAttackTest.cs +++ b/.Lib9c.Tests/Model/Skill/Adventure/DoubleAttackTest.cs @@ -1,4 +1,4 @@ -namespace Lib9c.Tests.Model.Skill +namespace Lib9c.Tests.Model.Skill.Adventure { using System; using System.Collections.Generic; @@ -54,7 +54,7 @@ public class DoubleAttackTest [InlineData(700008, 250, 4, 1, false)] [InlineData(700008, 250, 5, 2, true)] [InlineData(700008, 250, 5, 2, false)] - public void DoubleAttack( + public void DoubleAttackStage( int skillId, int level, int initialAttackCount, @@ -98,6 +98,7 @@ bool copyCharacter _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), new List(), + _tableSheets.DeBuffLimitSheet, copyCharacter ); var player = new Player(avatarState, simulator) diff --git a/.Lib9c.Tests/Model/Skill/NormalAttackTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs similarity index 96% rename from .Lib9c.Tests/Model/Skill/NormalAttackTest.cs rename to .Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs index a10dddb665..2c3667a196 100644 --- a/.Lib9c.Tests/Model/Skill/NormalAttackTest.cs +++ b/.Lib9c.Tests/Model/Skill/Adventure/NormalAttackTest.cs @@ -1,4 +1,4 @@ -namespace Lib9c.Tests.Model.Skill +namespace Lib9c.Tests.Model.Skill.Adventure { using System; using System.Collections.Generic; @@ -67,6 +67,7 @@ public void Use(bool copyCharacter) _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), new List(), + _tableSheets.DeBuffLimitSheet, copyCharacter ); var player = new Player(avatarState, simulator); @@ -130,7 +131,8 @@ public void FocusSkill() new TestRandom(seed), _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); var player = new Player(avatarState, simulator); @@ -178,7 +180,8 @@ public void FocusSkill() new TestRandom(seed), _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), - new List() + new List(), + _tableSheets.DeBuffLimitSheet ); player = new Player(avatarState, simulator); player.AddBuff(new Focus(_tableSheets.ActionBuffSheet.OrderedList.First(s => s.ActionBuffType == ActionBuffType.Focus))); diff --git a/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs similarity index 64% rename from .Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs rename to .Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs index 142a08efbb..876eebde7a 100644 --- a/.Lib9c.Tests/Model/Skill/ShatterStrikeTest.cs +++ b/.Lib9c.Tests/Model/Skill/Adventure/ShatterStrikeTest.cs @@ -1,8 +1,9 @@ -namespace Lib9c.Tests.Model.Skill +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; @@ -19,16 +20,20 @@ public class ShatterStrikeTest [Theory] // 1bp == 0.01% - [InlineData(10000, true)] - [InlineData(10000, false)] - [InlineData(1000, true)] - [InlineData(1000, false)] - [InlineData(3700, true)] - [InlineData(3700, false)] - [InlineData(100000, true)] - [InlineData(100000, false)] - public void Use(int ratioBp, bool copyCharacter) + [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(700010, out var skillRow) ); // 700011 is ShatterStrike @@ -66,11 +71,18 @@ public void Use(int ratioBp, bool copyCharacter) _tableSheets.StageSheet[1], _tableSheets.MaterialItemSheet), new List(), - copyCharacter + _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); @@ -80,13 +92,14 @@ public void Use(int ratioBp, bool copyCharacter) Assert.NotNull(used); var skillInfo = Assert.Single(used.SkillInfos); Assert.Equal( - (long)(enemy.HP * ratioBp / 10000m) - enemy.DEF + player.ArmorPenetration, + Math.Clamp( + enemy.HP * ratioBp / 10000m - enemy.DEF + player.ArmorPenetration, + 1, + gameConfigState.ShatterStrikeMaxDamage + ), skillInfo.Effect ); - if (ratioBp > 10000) - { - Assert.True(skillInfo.IsDead); - } + Assert.Equal(expectedEnemyDead, skillInfo.IsDead); } } } diff --git a/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs index 1cee8a57dd..d20e1a8ad8 100644 --- a/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaCombatTest.cs @@ -193,5 +193,79 @@ public void DispelOnDuration_Affect() 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.ActionBuffType == ActionBuffType.Dispel); + 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/ArenaShatterStrikeTest.cs b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs index c616f3e393..d3e8109ad5 100644 --- a/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs +++ b/.Lib9c.Tests/Model/Skill/Arena/ArenaShatterStrikeTest.cs @@ -1,7 +1,9 @@ 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; @@ -46,13 +48,19 @@ public ArenaShatterStrikeTest() [Theory] // 1bp == 0.01% - [InlineData(10000)] - [InlineData(1000)] - [InlineData(3700)] - [InlineData(100000)] - public void Use(int ratioBp) + [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 simulator = new ArenaSimulator(new TestRandom()); + 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(); @@ -78,13 +86,14 @@ public void Use(int ratioBp) var used = shatterStrike.Use(challenger, enemy, simulator.Turn, new List()); Assert.Single(used.SkillInfos); Assert.Equal( - (long)(enemy.HP * ratioBp / 10000m) - enemy.DEF + challenger.ArmorPenetration, + Math.Clamp( + enemy.HP * ratioBp / 10000m - enemy.DEF + challenger.ArmorPenetration, + 1, + gameConfigState.ShatterStrikeMaxDamage + ), used.SkillInfos.First().Effect ); - if (ratioBp > 10000) - { - Assert.True(enemy.IsDead); - } + 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/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/Lib9c.MessagePack/Action/NCActionEvaluation.cs b/Lib9c.MessagePack/Action/NCActionEvaluation.cs index f977a3489d..f7df7c63bd 100644 --- a/Lib9c.MessagePack/Action/NCActionEvaluation.cs +++ b/Lib9c.MessagePack/Action/NCActionEvaluation.cs @@ -49,9 +49,9 @@ public struct NCActionEvaluation [Key(8)] [MessagePackFormatter(typeof(TxIdFormatter))] - #pragma warning disable MsgPack003 +#pragma warning disable MsgPack003 public TxId? TxId { get; set; } - #pragma warning restore MsgPack003 +#pragma warning restore MsgPack003 [SerializationConstructor] public NCActionEvaluation( diff --git a/Lib9c.Policy/Policy/BlockPolicySource.cs b/Lib9c.Policy/Policy/BlockPolicySource.cs index c18d245bf0..946749aa62 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.sln b/Lib9c.sln index 79f6d8b418..91fe49b5ec 100644 --- a/Lib9c.sln +++ b/Lib9c.sln @@ -70,6 +70,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin", ".Lib9c.Plug EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin.Shared", ".Lib9c.Plugin.Shared\Lib9c.Plugin.Shared.csproj", "{76F6C25E-94D2-4EA9-B88D-0249F44D1D16}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Mocks", ".Libplanet\Libplanet.Mocks\Libplanet.Mocks.csproj", "{8BC561CB-91EF-4290-893F-025E9E6A0CF7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -196,6 +198,10 @@ Global {76F6C25E-94D2-4EA9-B88D-0249F44D1D16}.Debug|Any CPU.Build.0 = Debug|Any CPU {76F6C25E-94D2-4EA9-B88D-0249F44D1D16}.Release|Any CPU.ActiveCfg = Release|Any CPU {76F6C25E-94D2-4EA9-B88D-0249F44D1D16}.Release|Any CPU.Build.0 = Release|Any CPU + {8BC561CB-91EF-4290-893F-025E9E6A0CF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BC561CB-91EF-4290-893F-025E9E6A0CF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BC561CB-91EF-4290-893F-025E9E6A0CF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BC561CB-91EF-4290-893F-025E9E6A0CF7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -211,6 +217,7 @@ Global {E29043C5-43C9-4EBB-9632-8A80F9F35EE6} = {AFA609F0-8CAE-4494-A4E2-EABD9E95D678} {BB4464CB-B9DD-45E5-9450-38A2922FB178} = {AFA609F0-8CAE-4494-A4E2-EABD9E95D678} {82BCD815-0AB6-4EEF-A12B-CDB9CD98EEA1} = {AFA609F0-8CAE-4494-A4E2-EABD9E95D678} + {8BC561CB-91EF-4290-893F-025E9E6A0CF7} = {AFA609F0-8CAE-4494-A4E2-EABD9E95D678} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {547C7D6E-6301-4BB4-AEF7-979CD504D913} 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/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/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/HackAndSlash.cs b/Lib9c/Action/HackAndSlash.cs index e1ef713d78..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); 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/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/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/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 76b6532a27..cca3300271 100644 --- a/Lib9c/Battle/Simulator.cs +++ b/Lib9c/Battle/Simulator.cs @@ -5,7 +5,6 @@ using Nekoyume.Model; using Nekoyume.Model.BattleStatus; using Nekoyume.Model.Item; -using Nekoyume.Model.Skill; using Nekoyume.Model.State; using Nekoyume.TableData; using Priority_Queue; @@ -29,9 +28,9 @@ public abstract class Simulator : ISimulator public readonly CharacterSheet CharacterSheet; public readonly CharacterLevelSheet CharacterLevelSheet; public readonly EquipmentItemSetEffectSheet EquipmentItemSetEffectSheet; + public DeBuffLimitSheet DeBuffLimitSheet { get; protected set; } - public readonly List StatDebuffList; - public readonly List ActionDebuffList; + public long ShatterStrikeMaxDamage { get; private set; } protected const int MaxTurn = 200; public int TurnNumber; @@ -48,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, @@ -65,18 +77,30 @@ SimulatorSheetsV1 simulatorSheets CharacterSheet = simulatorSheets.CharacterSheet; CharacterLevelSheet = simulatorSheets.CharacterLevelSheet; EquipmentItemSetEffectSheet = simulatorSheets.EquipmentItemSetEffectSheet; - var debuffSkillIdList = SkillSheet.Values - .Where(s => s.SkillType == SkillType.Debuff).Select(s => s.Id); - StatDebuffList = SkillBuffSheet.Values.Where( - bf => debuffSkillIdList.Contains(bf.SkillId)).Aggregate( - new List(), - (current, bf) => current.Concat(bf.BuffIds).ToList() - ); - ActionDebuffList = SkillActionBuffSheet.Values.Where( - bf => debuffSkillIdList.Contains(bf.SkillId)).Aggregate( - new List(), - (current, bf) => current.Concat(bf.BuffIds).ToList() - ); + Log = new BattleLog(); + player.Simulator = this; + Player = player; + Player.Use(foods); + 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; 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/Buff/StatBuff.cs b/Lib9c/Model/Buff/StatBuff.cs index 348adc9967..71b3ebe573 100644 --- a/Lib9c/Model/Buff/StatBuff.cs +++ b/Lib9c/Model/Buff/StatBuff.cs @@ -44,12 +44,12 @@ public StatModifier GetModifier() public override bool IsBuff() { - return RowData.Value >= 0; + return !IsDebuff(); } public override bool IsDebuff() { - return RowData.Value < 0; + return RowData.Value < 0 || CustomField?.BuffValue < 0; } public override object Clone() diff --git a/Lib9c/Model/Character/ArenaCharacter.cs b/Lib9c/Model/Character/ArenaCharacter.cs index 81d2ddd848..6c8e8d36fc 100644 --- a/Lib9c/Model/Character/ArenaCharacter.cs +++ b/Lib9c/Model/Character/ArenaCharacter.cs @@ -30,9 +30,6 @@ public class ArenaCharacter : ICloneable private readonly ActionBuffSheet _actionBuffSheet; private readonly ArenaSkills _skills; - private readonly List _statDebuffList; - private readonly List _actionDebuffList; - public readonly IArenaSimulator Simulator; public readonly ArenaSkills _runeSkills = new ArenaSkills(); public readonly Dictionary RuneSkillCooldownMap = new Dictionary(); @@ -90,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, @@ -144,20 +108,6 @@ public ArenaCharacter( _skillActionBuffSheet = sheets.SkillActionBuffSheet; _actionBuffSheet = sheets.ActionBuffSheet; - var debuffSkillIdList = _skillSheet.Values - .Where(s => s.SkillType == SkillType.Debuff).Select(s => s.Id); - _statDebuffList = _skillBuffSheet.Values.Where( - bf => debuffSkillIdList.Contains(bf.SkillId)).Aggregate( - new List(), - (current, bf) => current.Concat(bf.BuffIds).ToList() - ); - _actionDebuffList = _skillActionBuffSheet.Values.Where( - bf => debuffSkillIdList.Contains(bf.SkillId)).Aggregate( - new List(), - (current, bf) => current.Concat(bf.BuffIds).ToList() - ); - - Simulator = simulator; Stats = GetStatV1( digest, @@ -238,9 +188,6 @@ private ArenaCharacter(ArenaCharacter value) _skillActionBuffSheet = value._skillActionBuffSheet; _actionBuffSheet = value._actionBuffSheet; - _statDebuffList = value._statDebuffList; - _actionDebuffList = value._actionDebuffList; - Simulator = value.Simulator; Stats = new CharacterStats(value.Stats); _skills = value._skills; @@ -593,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) @@ -628,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() { @@ -786,32 +710,6 @@ selectedSkill is ArenaBuffSkill && 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; @@ -830,7 +728,7 @@ private void RemoveBuffs() if (!isBuffRemoved) return; - Stats.SetBuffs(StatBuffs); + Stats.SetBuffs(StatBuffs, Simulator.DeBuffLimitSheet); } [Obsolete("Use RemoveBuffs")] @@ -852,7 +750,7 @@ private void RemoveBuffsV1() if (isApply) { - Stats.SetBuffs(StatBuffs); + Stats.SetBuffs(StatBuffs, Simulator.DeBuffLimitSheet); } } @@ -868,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) { @@ -896,7 +787,7 @@ public void SpawnV2(ArenaCharacter target) { var clone = (StatBuff)stat.Clone(); Buffs[stat.RowData.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); + Stats.AddBuff(clone, Simulator.DeBuffLimitSheet, updateImmediate); break; } case ActionBuff action: @@ -948,18 +839,6 @@ public void SpawnV2(ArenaCharacter target) return dispelList; } - [Obsolete("Use AddBuff")] - public void AddBuffV1(Buff.Buff buff, bool updateImmediate = true) - { - if (Buffs.TryGetValue(buff.BuffInfo.GroupId, out var outBuff) && - outBuff.BuffInfo.Id > buff.BuffInfo.Id) - return; - - var clone = (Buff.StatBuff) buff.Clone(); - Buffs[buff.BuffInfo.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); - } - public void RemoveActionBuff(ActionBuff removedBuff) { Buffs.Remove(removedBuff.RowData.GroupId); diff --git a/Lib9c/Model/Character/CharacterBase.cs b/Lib9c/Model/Character/CharacterBase.cs index 1e87d7308c..a3eca37b33 100644 --- a/Lib9c/Model/Character/CharacterBase.cs +++ b/Lib9c/Model/Character/CharacterBase.cs @@ -338,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())); @@ -378,7 +378,7 @@ protected virtual void EndTurn() { var clone = (StatBuff)stat.Clone(); Buffs[stat.RowData.GroupId] = clone; - Stats.AddBuff(clone, updateImmediate); + Stats.AddBuff(clone, Simulator.DeBuffLimitSheet, updateImmediate); break; } case ActionBuff action: @@ -401,7 +401,7 @@ protected virtual void EndTurn() dispelList = Buffs.Values.Where( bff => bff.IsDebuff() && Simulator.Random.Next(0, 100) < - action.RowData.Chance).ToList(); + dispel.BuffInfo.Chance).ToList(); foreach (var bff in dispelList) { 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 6025242684..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; @@ -60,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 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 b023208fd9..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,7 +24,7 @@ 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); @@ -36,26 +37,5 @@ public override BattleStatus.Arena.ArenaSkill Use( 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); - - 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 index f1521f1eb7..cab04595c0 100644 --- a/Lib9c/Model/Skill/Arena/ArenaShatterStrike.cs +++ b/Lib9c/Model/Skill/Arena/ArenaShatterStrike.cs @@ -20,11 +20,5 @@ public override BattleStatus.Arena.ArenaSkill Use(ArenaCharacter caster, ArenaCh return new BattleStatus.Arena.ArenaShatterStrike(clone, damage, buff); } - - [Obsolete("Use Use")] - public override BattleStatus.Arena.ArenaSkill UseV1(ArenaCharacter caster, ArenaCharacter target, int turn, IEnumerable buffs) - { - return Use(caster, target, turn, buffs); - } } } diff --git a/Lib9c/Model/Skill/Arena/ArenaSkill.cs b/Lib9c/Model/Skill/Arena/ArenaSkill.cs index eb6625c782..b6eb4b58de 100644 --- a/Lib9c/Model/Skill/Arena/ArenaSkill.cs +++ b/Lib9c/Model/Skill/Arena/ArenaSkill.cs @@ -42,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); @@ -123,38 +115,6 @@ public override int GetHashCode() 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)); - break; - - case SkillTargetType.Self: - case SkillTargetType.Ally: - caster.AddBuffV1(buff); - infos.Add(GetSkillInfo(caster, turn, buff)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - return infos; - } - private BattleStatus.Arena.ArenaSkill.ArenaSkillInfo GetSkillInfo(ICloneable target, int turn, Buff.Buff buff, bool affected = true, IEnumerable dispelList = null) diff --git a/Lib9c/Model/Skill/AttackSkill.cs b/Lib9c/Model/Skill/AttackSkill.cs index bc348e69f3..f2c66fbf66 100644 --- a/Lib9c/Model/Skill/AttackSkill.cs +++ b/Lib9c/Model/Skill/AttackSkill.cs @@ -92,6 +92,13 @@ SkillRow.SkillCategory is not SkillCategory.ShatterStrike && // double attack must be shown as critical attack 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/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/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/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/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/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/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)) + { + } + } +}