Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Optimize HackAndSlash.Execute #1 #2018

Merged
merged 10 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .Lib9c.Tests/Action/HitHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace Lib9c.Tests.Action
{
using System;
using Nekoyume.Battle;
using Xunit;

public class HitHelperTest
{
[Fact]
public void GetHitStep2()
{
// copy from previous logic
int GetHitStep2Legacy(int attackerHit, int defenderHit)
{
attackerHit = Math.Max(1, attackerHit);
defenderHit = Math.Max(1, defenderHit);
var additionalCorrection = (int)((attackerHit - defenderHit / 3m) / defenderHit * 100);
return Math.Min(
Math.Max(additionalCorrection, HitHelper.GetHitStep2AdditionalCorrectionMin),
HitHelper.GetHitStep2AdditionalCorrectionMax);
}

for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
var legacy = GetHitStep2Legacy(i, j);
var current = HitHelper.GetHitStep2(i, j);
Assert.True(legacy == current, $"{i}, {j}, {legacy}, {current}");
}
}
}
}
}
34 changes: 32 additions & 2 deletions .Lib9c.Tests/Model/Skill/CombatTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void CalculateDEFAndDamageReduction(int def, int drv, int drr, int enemyA
var normalAttack = new NormalAttack(skillRow, 0, 100, default, StatType.NONE);

var prevHP = _player.CurrentHP;
normalAttack.Use(_enemy, 1, new List<StatBuff>());
normalAttack.Use(_enemy, 1, new List<StatBuff>(), false);
var currentHP = _player.CurrentHP;
var damage = prevHP - currentHP;

Expand All @@ -100,11 +100,41 @@ public void CalculateCritDamage(int cdmg, int atk, int expectedDamage)
var normalAttack = new NormalAttack(skillRow, 0, 100, default, StatType.NONE);

var prevHP = _player.CurrentHP;
normalAttack.Use(_enemy, 1, new List<StatBuff>());
normalAttack.Use(_enemy, 1, new List<StatBuff>(), false);
var currentHP = _player.CurrentHP;
var damage = prevHP - currentHP;

Assert.Equal(expectedDamage, damage);
}

[Fact]
public void Thorn()
{
var prevHP = _enemy.CurrentHP;
var skill = _enemy.GiveThornDamage(1);
var currentHP = _enemy.CurrentHP;
// get 1dmg from thorn
Assert.Equal(prevHP - 1, currentHP);
Assert.Equal(prevHP, skill.Character.CurrentHP);
var skillInfo = Assert.Single(skill.SkillInfos);
Assert.Equal(currentHP, skillInfo.Target!.CurrentHP);
}

[Fact]
public void Bleed()
{
var actionBuffSheet = _tableSheets.ActionBuffSheet;
var row = actionBuffSheet.Values.First();
var bleed = Assert.IsType<Bleed>(BuffFactory.GetActionBuff(_enemy.Stats, row));
var dmg = bleed.Power;
var prevHP = _player.CurrentHP;
var skill = bleed.GiveEffect(_player, 1);
var currentHP = _player.CurrentHP;
// get dmg from bleed
Assert.Equal(prevHP - dmg, currentHP);
Assert.Equal(prevHP, skill.Character.CurrentHP);
var skillInfo = Assert.Single(skill.SkillInfos);
Assert.Equal(currentHP, skillInfo.Target!.CurrentHP);
}
}
}
21 changes: 12 additions & 9 deletions .Lib9c.Tests/Model/Skill/NormalAttackTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ public NormalAttackTest(ITestOutputHelper outputHelper)
.CreateLogger();
}

[Fact]
public void Use()
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Use(bool copyCharacter)
{
var sheets = TableSheetsImporter.ImportSheets();
var tableSheets = new TableSheets(sheets);
Expand Down Expand Up @@ -65,7 +67,8 @@ public void Use()
StageSimulator.GetWaveRewards(
random,
tableSheets.StageSheet[1],
tableSheets.MaterialItemSheet)
tableSheets.MaterialItemSheet),
copyCharacter
);
var player = new Player(avatarState, simulator);

Expand All @@ -79,13 +82,13 @@ public void Use()
var battleStatusSkill = normalAttack.Use(
player,
0,
new List<StatBuff>());
new List<StatBuff>(),
copyCharacter);
Assert.NotNull(battleStatusSkill);
Assert.Single(battleStatusSkill.SkillInfos);

var skillInfo = battleStatusSkill.SkillInfos.FirstOrDefault();
Assert.NotNull(skillInfo);
Assert.Equal(enemy.Id, skillInfo.Target.Id);
Assert.Equal(!copyCharacter, battleStatusSkill.Character is null);
var skillInfo = Assert.Single(battleStatusSkill.SkillInfos);
Assert.Equal(enemy.Id, skillInfo.CharacterId);
Assert.Equal(!copyCharacter, skillInfo.Target is null);
}
}
}
40 changes: 26 additions & 14 deletions Lib9c/Action/HackAndSlash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,8 +439,14 @@ public IAccountStateDelta Execute(
}
}

var stageWaveRow = sheets.GetSheet<StageWaveSheet>()[StageId];
var enemySkillSheet = sheets.GetSheet<EnemySkillSheet>();
var costumeStatSheet = sheets.GetSheet<CostumeStatSheet>();
var stageCleared = !isNotClearedStage;
var starCount = 0;
for (var i = 0; i < TotalPlayCount; i++)
{
var rewards = StageSimulator.GetWaveRewards(random, stageRow, materialItemSheet);
sw.Restart();
// First simulating will use Foods and Random Skills.
// Remainder simulating will not use Foods.
Expand All @@ -453,13 +459,14 @@ public IAccountStateDelta Execute(
WorldId,
StageId,
stageRow,
sheets.GetSheet<StageWaveSheet>()[StageId],
avatarState.worldInformation.IsStageCleared(StageId),
stageWaveRow,
stageCleared,
StageRewardExpHelper.GetExp(avatarState.level, StageId),
simulatorSheets,
sheets.GetSheet<EnemySkillSheet>(),
sheets.GetSheet<CostumeStatSheet>(),
StageSimulator.GetWaveRewards(random, stageRow, materialItemSheet));
enemySkillSheet,
costumeStatSheet,
rewards,
false);
sw.Stop();
Log.Verbose("{AddressesHex}HAS Initialize Simulator: {Elapsed}", addressesHex, sw.Elapsed);

Expand All @@ -471,13 +478,17 @@ public IAccountStateDelta Execute(
sw.Restart();
if (simulator.Log.IsClear)
{
simulator.Player.worldInformation.ClearStage(
WorldId,
StageId,
blockIndex,
worldSheet,
worldUnlockSheet
);
if (!stageCleared)
{
avatarState.worldInformation.ClearStage(
WorldId,
StageId,
blockIndex,
worldSheet,
worldUnlockSheet
);
stageCleared = true;
}
sw.Stop();
Log.Verbose("{AddressesHex}HAS ClearStage: {Elapsed}", addressesHex, sw.Elapsed);
}
Expand All @@ -504,9 +515,8 @@ public IAccountStateDelta Execute(
player.eventMapForBeforeV100310.Clear();
}

starCount += simulator.Log.clearedWaveNumber;
avatarState.Update(simulator);
// Update CrystalRandomSkillState.Stars by clearedWaveNumber. (add)
skillState?.Update(simulator.Log.clearedWaveNumber, crystalStageBuffSheet);

sw.Stop();
Log.Verbose(
Expand All @@ -526,6 +536,8 @@ public IAccountStateDelta Execute(
Log.Verbose("{AddressesHex}HAS loop Simulate: {Elapsed}, Count: {PlayCount}",
addressesHex, sw.Elapsed, TotalPlayCount);

// Update CrystalRandomSkillState.Stars by clearedWaveNumber. (add)
skillState?.Update(starCount, crystalStageBuffSheet);
sw.Restart();
avatarState.UpdateQuestRewards(materialItemSheet);
avatarState.updatedAt = blockIndex;
Expand Down
127 changes: 51 additions & 76 deletions Lib9c/Battle/AttackCountHelper.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Nekoyume.Battle
{
public static class AttackCountHelper
{
public struct Info
{
public decimal DamageMultiplier;
public decimal AdditionalCriticalChance;
}

public const int CountMaxLowerLimit = 2;
public const int CountMaxUpperLimit = 5;

/// <summary>
/// key: attack count max
/// value: attack count, info
/// </summary>
public static readonly IReadOnlyDictionary<int, IReadOnlyDictionary<int, Info>> CachedInfo =
new Dictionary<int, IReadOnlyDictionary<int, Info>>
{
{
1, new Dictionary<int, Info>
{
{1, new Info {DamageMultiplier = 1m, AdditionalCriticalChance = 0m}}
}
},
{
2, new Dictionary<int, Info>
{
{1, new Info {DamageMultiplier = 1m, AdditionalCriticalChance = 0m}},
{2, new Info {DamageMultiplier = 2m, AdditionalCriticalChance = 25m}}
}
},
{
3, new Dictionary<int, Info>
{
{1, new Info {DamageMultiplier = 1m, AdditionalCriticalChance = 0m}},
{2, new Info {DamageMultiplier = 2m, AdditionalCriticalChance = 10m}},
{3, new Info {DamageMultiplier = 3m, AdditionalCriticalChance = 35m}}
}
},
{
4, new Dictionary<int, Info>
{
{1, new Info {DamageMultiplier = 1m, AdditionalCriticalChance = 0m}},
{2, new Info {DamageMultiplier = 2m, AdditionalCriticalChance = 10m}},
{3, new Info {DamageMultiplier = 3m, AdditionalCriticalChance = 20m}},
{4, new Info {DamageMultiplier = 4m, AdditionalCriticalChance = 45m}}
}
},
{
5, new Dictionary<int, Info>
{
{1, new Info {DamageMultiplier = 1m, AdditionalCriticalChance = 0m}},
{2, new Info {DamageMultiplier = 2m, AdditionalCriticalChance = 10m}},
{3, new Info {DamageMultiplier = 3m, AdditionalCriticalChance = 20m}},
{4, new Info {DamageMultiplier = 4m, AdditionalCriticalChance = 30m}},
{5, new Info {DamageMultiplier = 5m, AdditionalCriticalChance = 55m}}
}
}
};

public static int GetCountMax(int level)
{
if (level < 11)
Expand All @@ -76,40 +22,69 @@ public static int GetCountMax(int level)
: CountMaxUpperLimit;
}

public static decimal GetDamageMultiplier(int attackCount, int attackCountMax)
public static int GetDamageMultiplier(int attackCount, int attackCountMax)
{
if (attackCount > attackCountMax)
throw new ArgumentOutOfRangeException(
$"{nameof(attackCount)}: {attackCount} / {nameof(attackCountMax)}: {attackCountMax}");

var info = GetInfo(attackCount, attackCountMax);
return info.DamageMultiplier;
}

public static decimal GetAdditionalCriticalChance(int attackCount, int attackCountMax)
{
if (attackCount > attackCountMax)
throw new ArgumentOutOfRangeException(
$"{nameof(attackCount)}: {attackCount} / {nameof(attackCountMax)}: {attackCountMax}");
if (attackCountMax <= 5)
{
return Math.Max(1, attackCount);
}

var info = GetInfo(attackCount, attackCountMax);
return info.AdditionalCriticalChance;
throw new ArgumentOutOfRangeException();
}

private static Info GetInfo(int attackCount, int attackCountMax)
public static int GetAdditionalCriticalChance(int attackCount, int attackCountMax)
{
if (attackCount > attackCountMax)
throw new ArgumentOutOfRangeException(
$"{nameof(attackCount)}: {attackCount} / {nameof(attackCountMax)}: {attackCountMax}");
switch (attackCount)
{
case 1:
return 0;
case 2:
switch (attackCountMax)
{
case 2:
return 25;
case 3:
case 4:
case 5:
return 10;
}
break;
case 3:
switch (attackCountMax)
{
case 3:
return 35;
case 4:
case 5:
return 20;
}
break;
case 4:
switch (attackCountMax)
{
case 4:
return 45;
case 5:
return 30;
}
break;
case 5:
switch (attackCountMax)
{
case 5:
return 55;
}
break;
}

if (!CachedInfo.ContainsKey(attackCountMax))
throw new ArgumentOutOfRangeException($"{nameof(attackCountMax)}: {attackCountMax}");

if (!CachedInfo[attackCountMax].ContainsKey(attackCount))
throw new ArgumentOutOfRangeException(
$"{nameof(attackCountMax)}: {attackCountMax} / {nameof(attackCount)}: {attackCount}");

return CachedInfo[attackCountMax][attackCount];
throw new ArgumentOutOfRangeException();
}
}
}
Loading
Loading