2.5 update
This commit is contained in:
@@ -0,0 +1,842 @@
|
||||
using System.Linq;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.Defense.Comparison;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.PlayModeTests
|
||||
{
|
||||
public sealed class Defense25DRewardChoicePlayModeTests
|
||||
{
|
||||
[Test]
|
||||
public void LevelUp_OffersMixedDistinctChoicesAndIgnoresDuplicateToken()
|
||||
{
|
||||
GameObject playerObject = new("Reward Choice Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("move-speed");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DTowerModifiers towerModifiers =
|
||||
new(constants);
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
towerModifiers,
|
||||
_ => true);
|
||||
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:2"), Is.True);
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:2"), Is.False);
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(3));
|
||||
Assert.That(choices.CurrentChoices.Any(choice =>
|
||||
choice.TargetKind == Defense25DRewardTargetKind.PlayerModifier),
|
||||
Is.True);
|
||||
Assert.That(choices.CurrentChoices.Any(choice =>
|
||||
choice.TargetKind == Defense25DRewardTargetKind.TowerModifier),
|
||||
Is.True);
|
||||
Assert.That(choices.CurrentChoices.Select(choice =>
|
||||
(choice.TargetKind, choice.DefinitionId, choice.Rarity))
|
||||
.Distinct().ToList(),
|
||||
Has.Count.EqualTo(choices.CurrentChoices.Count));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayerChoice_ShowsKoreanEffectAndRareValueThatMatchesAppliedModifier()
|
||||
{
|
||||
GameObject playerObject = new("Reward Display Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("move-speed");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.ChoiceCount = 1;
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 100,
|
||||
Epic = 0,
|
||||
};
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false);
|
||||
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:display"), Is.True);
|
||||
Defense25DRewardChoice choice = choices.CurrentChoices.Single();
|
||||
|
||||
Assert.That(choice.Rarity, Is.EqualTo(Defense25DRewardRarity.Rare));
|
||||
Assert.That(choice.DisplayData.Title, Is.EqualTo("이동 속도 강화"));
|
||||
Assert.That(choice.DisplayData.Description,
|
||||
Is.EqualTo("이동 속도 +30%\n선택하면 즉시 적용됩니다."));
|
||||
Assert.That(choices.TryChooseReward(0, "level:display"), Is.True);
|
||||
Assert.That(player.Evaluate(CharacterStat.MoveSpeed, 100f),
|
||||
Is.EqualTo(130f).Within(0.001f));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RefreshChoices_PreservesSnapshotAndReplacesOnlyAnIneligibleEntry()
|
||||
{
|
||||
GameObject playerObject = new("Refresh Snapshot Player");
|
||||
ModifierDefinition[] definitions = Enumerable.Range(0, 4)
|
||||
.Select(index => CreatePlayerModifier($"snapshot-{index}"))
|
||||
.ToArray();
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 100,
|
||||
Rare = 0,
|
||||
Epic = 0,
|
||||
};
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
definitions,
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false);
|
||||
choices.QueueReward(Defense25DRewardSource.LevelUp, "level:snapshot");
|
||||
Defense25DRewardChoice[] snapshot = choices.CurrentChoices.ToArray();
|
||||
Assert.That(snapshot, Has.Length.EqualTo(3));
|
||||
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 0,
|
||||
Epic = 100,
|
||||
};
|
||||
choices.RefreshChoices();
|
||||
for (int index = 0; index < snapshot.Length; index++)
|
||||
{
|
||||
Assert.That(choices.CurrentChoices[index], Is.SameAs(snapshot[index]),
|
||||
"A UI rebind retains each eligible token candidate and its order.");
|
||||
}
|
||||
|
||||
ModifierDefinition invalidated = definitions.Single(definition =>
|
||||
definition.ModifierId == snapshot[0].DefinitionId);
|
||||
Assert.That(invalidated.TryApplyToModel(player, null), Is.True);
|
||||
Assert.That(invalidated.TryApplyToModel(player, null), Is.True);
|
||||
choices.RefreshChoices();
|
||||
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(3));
|
||||
Assert.That(choices.CurrentChoices[0], Is.Not.SameAs(snapshot[0]));
|
||||
Assert.That(choices.CurrentChoices[0].DefinitionId,
|
||||
Is.Not.EqualTo(snapshot[0].DefinitionId));
|
||||
Assert.That(choices.CurrentChoices[1], Is.SameAs(snapshot[1]));
|
||||
Assert.That(choices.CurrentChoices[2], Is.SameAs(snapshot[2]));
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (ModifierDefinition definition in definitions)
|
||||
Object.DestroyImmediate(definition);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptyRarityFallbacksOnlyMoveDownward()
|
||||
{
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = new[]
|
||||
{
|
||||
TowerModifier("only-rare", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Rare, 0.2f, 5),
|
||||
};
|
||||
try
|
||||
{
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 100,
|
||||
Rare = 0,
|
||||
Epic = 0,
|
||||
};
|
||||
Defense25DRewardChoices commonWithoutCommon = NewTowerOnlyChoices(constants);
|
||||
commonWithoutCommon.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:common-no-upgrade");
|
||||
Assert.That(commonWithoutCommon.CurrentChoices, Is.Empty,
|
||||
"A Common request does not upgrade to a populated Rare tier.");
|
||||
|
||||
constants.TowerModifierCatalog = new[]
|
||||
{
|
||||
TowerModifier("only-common", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Common, 0.1f, 5),
|
||||
};
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 100,
|
||||
Epic = 0,
|
||||
};
|
||||
Defense25DRewardChoices rareToCommon = NewTowerOnlyChoices(constants);
|
||||
rareToCommon.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:rare-to-common");
|
||||
Assert.That(rareToCommon.CurrentChoices.Single().Rarity,
|
||||
Is.EqualTo(Defense25DRewardRarity.Common));
|
||||
|
||||
constants.TowerModifierCatalog = new[]
|
||||
{
|
||||
TowerModifier("only-rare", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Rare, 0.2f, 5),
|
||||
};
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 0,
|
||||
Epic = 100,
|
||||
};
|
||||
Defense25DRewardChoices epicToRare = NewTowerOnlyChoices(constants);
|
||||
epicToRare.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:epic-to-rare");
|
||||
Assert.That(epicToRare.CurrentChoices.Single().Rarity,
|
||||
Is.EqualTo(Defense25DRewardRarity.Rare));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(constants);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PendingLevelUpPreemptsUnopenedRewardsButNotAnOpenedReward()
|
||||
{
|
||||
GameObject playerObject = new("Reward Priority Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("priority");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 100,
|
||||
Rare = 0,
|
||||
Epic = 0,
|
||||
};
|
||||
constants.GlobalEliteRarityWeights = constants.LevelUpRarityWeights;
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false);
|
||||
|
||||
choices.QueueReward(Defense25DRewardSource.GlobalElite, "elite:pending");
|
||||
Defense25DRewardChoice unopenedEliteChoice = choices.CurrentChoices[0];
|
||||
choices.QueueReward(Defense25DRewardSource.LevelUp, "level:priority");
|
||||
Assert.That(choices.CurrentRewardToken, Is.EqualTo("level:priority"));
|
||||
Assert.That(choices.CurrentRewardIsOpened, Is.False);
|
||||
Assert.That(choices.GetPendingRewardCount(
|
||||
Defense25DRewardSource.GlobalElite), Is.EqualTo(1));
|
||||
|
||||
Assert.That(choices.TryChooseReward(0, "level:priority"), Is.True);
|
||||
Assert.That(choices.CurrentRewardToken, Is.EqualTo("elite:pending"));
|
||||
Assert.That(choices.CurrentChoices[0], Is.SameAs(unopenedEliteChoice),
|
||||
"The preempted unopened reward retains its token snapshot.");
|
||||
|
||||
choices.Reset();
|
||||
choices.QueueReward(Defense25DRewardSource.GlobalElite, "elite:opened");
|
||||
Assert.That(choices.MarkCurrentRewardOpened(), Is.True);
|
||||
choices.QueueReward(Defense25DRewardSource.LevelUp, "level:while-open");
|
||||
Assert.That(choices.CurrentRewardToken, Is.EqualTo("elite:opened"));
|
||||
Assert.That(choices.CurrentRewardIsOpened, Is.True);
|
||||
|
||||
Assert.That(choices.TryChooseReward(0, "elite:opened"), Is.True);
|
||||
Assert.That(choices.CurrentRewardToken, Is.EqualTo("level:while-open"));
|
||||
Assert.That(choices.CurrentRewardIsOpened, Is.False);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MixedTargetGuaranteeIsResolvedBeforeEachTargetsRarityRoll()
|
||||
{
|
||||
GameObject playerObject = new("Target First Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("target-first");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = new[]
|
||||
{
|
||||
TowerModifier("tower.epic.only", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Epic, 0.35f, 1),
|
||||
};
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 80,
|
||||
Rare = 0,
|
||||
Epic = 20,
|
||||
};
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true);
|
||||
|
||||
for (int index = 0; index < 20; index++)
|
||||
{
|
||||
choices.Reset();
|
||||
choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp,
|
||||
$"level:target-first-{index}");
|
||||
Assert.That(choices.CurrentChoices.Select(choice => choice.TargetKind)
|
||||
.Distinct().ToList(), Is.EquivalentTo(new[]
|
||||
{
|
||||
Defense25DRewardTargetKind.PlayerModifier,
|
||||
Defense25DRewardTargetKind.TowerModifier,
|
||||
}), "Both eligible target groups are selected before rarity is rolled.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MissionRelicRewardUsesTokenCandidatesAndCanResolveOnlyOnce()
|
||||
{
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
Defense25DRewardChoice[] candidates =
|
||||
{
|
||||
Defense25DRewardChoice.CreatePersonalMissionRelic(
|
||||
"relic.first", "Relic", new Defense25DRewardDisplayData("First", "First relic")),
|
||||
Defense25DRewardChoice.CreatePersonalMissionRelic(
|
||||
"relic.second", "Relic", new Defense25DRewardDisplayData("Second", "Second relic")),
|
||||
};
|
||||
int resolved = 0;
|
||||
try
|
||||
{
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
System.Array.Empty<ModifierDefinition>(),
|
||||
null,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false,
|
||||
getMissionRelicCandidates: _ => candidates,
|
||||
chooseMissionRelic: choice =>
|
||||
{
|
||||
resolved++;
|
||||
return choice.DefinitionId == "relic.second";
|
||||
});
|
||||
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.PersonalMissionRelic,
|
||||
"mission:15:relic"), Is.True);
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.PersonalMissionRelic,
|
||||
"mission:15:relic"), Is.False);
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(2));
|
||||
Assert.That(choices.TryChooseReward(1, "mission:15:relic"), Is.True);
|
||||
Assert.That(resolved, Is.EqualTo(1));
|
||||
Assert.That(choices.TryChooseReward(1, "mission:15:relic"), Is.False);
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.PersonalMissionRelic,
|
||||
"mission:15:relic"), Is.False);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(constants);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SameSeedAndTokenProduceSameCandidateSnapshot()
|
||||
{
|
||||
GameObject firstPlayerObject = new("Seeded Reward Player A");
|
||||
GameObject secondPlayerObject = new("Seeded Reward Player B");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("seeded");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
try
|
||||
{
|
||||
PlayerStats firstPlayer = firstPlayerObject.AddComponent<PlayerStats>();
|
||||
PlayerStats secondPlayer = secondPlayerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices first = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
firstPlayer,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true);
|
||||
Defense25DRewardChoices replay = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
secondPlayer,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true);
|
||||
|
||||
first.QueueReward(Defense25DRewardSource.LevelUp, "level:seeded-replay");
|
||||
replay.QueueReward(Defense25DRewardSource.LevelUp, "level:seeded-replay");
|
||||
Assert.That(first.CurrentChoices.Select(choice =>
|
||||
(choice.TargetKind, choice.DefinitionId, choice.Rarity)).ToArray(),
|
||||
Is.EqualTo(replay.CurrentChoices.Select(choice =>
|
||||
(choice.TargetKind, choice.DefinitionId, choice.Rarity)).ToArray()));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(firstPlayerObject);
|
||||
Object.DestroyImmediate(secondPlayerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GlobalMidBoss_OffersEpicAndFallsBackToCurrencyWhenNoEpicIsEligible()
|
||||
{
|
||||
GameObject playerObject = new("MidBoss Reward Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("collision-damage");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
int grantedCurrency = 0;
|
||||
int grantedAmount = 0;
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true);
|
||||
|
||||
choices.QueueReward(
|
||||
Defense25DRewardSource.GlobalMidBoss,
|
||||
"midboss:240");
|
||||
Assert.That(choices.CurrentChoices.Any(choice =>
|
||||
choice.Rarity == Defense25DRewardRarity.Epic), Is.True);
|
||||
|
||||
constants.TowerModifierCatalog = constants.TowerModifierCatalog
|
||||
.Where(definition => definition.Rarity != Defense25DRewardRarity.Epic)
|
||||
.ToArray();
|
||||
player.AddModifier(new StatModifier(
|
||||
playerModifier.ModifierId,
|
||||
playerModifier.Stat,
|
||||
playerModifier.Operation,
|
||||
playerModifier.Value,
|
||||
playerModifier.MaximumStacks));
|
||||
player.AddModifier(new StatModifier(
|
||||
playerModifier.ModifierId,
|
||||
playerModifier.Stat,
|
||||
playerModifier.Operation,
|
||||
playerModifier.Value,
|
||||
playerModifier.MaximumStacks));
|
||||
Defense25DRewardChoices noEpicChoices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true,
|
||||
grantCurrency: amount =>
|
||||
{
|
||||
grantedCurrency++;
|
||||
grantedAmount += amount;
|
||||
});
|
||||
noEpicChoices.QueueReward(
|
||||
Defense25DRewardSource.GlobalMidBoss,
|
||||
"midboss:no-epic");
|
||||
|
||||
Assert.That(noEpicChoices.CurrentChoices.Any(choice =>
|
||||
choice.TargetKind == Defense25DRewardTargetKind.Currency),
|
||||
Is.True);
|
||||
int currencyChoiceIndex = noEpicChoices.CurrentChoices
|
||||
.Select((choice, index) => (choice, index))
|
||||
.Single(pair => pair.choice.TargetKind
|
||||
== Defense25DRewardTargetKind.Currency).index;
|
||||
Assert.That(noEpicChoices.TryChooseReward(
|
||||
currencyChoiceIndex, "midboss:no-epic"), Is.True);
|
||||
Assert.That(grantedCurrency, Is.EqualTo(1));
|
||||
Assert.That(grantedAmount,
|
||||
Is.EqualTo(constants.EmptyEpicFallbackCurrency));
|
||||
Assert.That(noEpicChoices.TryChooseReward(
|
||||
currencyChoiceIndex, "midboss:no-epic"), Is.False);
|
||||
Assert.That(grantedCurrency, Is.EqualTo(1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Defense25DRewardSource.LevelUp)]
|
||||
[TestCase(Defense25DRewardSource.GlobalElite)]
|
||||
public void EmptyModifierPoolOffersResolvableCurrencyFallback(
|
||||
Defense25DRewardSource source)
|
||||
{
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
int grantCount = 0;
|
||||
int grantedAmount = 0;
|
||||
try
|
||||
{
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
System.Array.Empty<ModifierDefinition>(),
|
||||
null,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false,
|
||||
grantCurrency: amount =>
|
||||
{
|
||||
grantCount++;
|
||||
grantedAmount = amount;
|
||||
});
|
||||
|
||||
Assert.That(choices.QueueReward(source, "empty:" + source), Is.True);
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(1));
|
||||
Assert.That(choices.CurrentChoices[0].TargetKind,
|
||||
Is.EqualTo(Defense25DRewardTargetKind.Currency));
|
||||
Assert.That(choices.CurrentChoices[0].DisplayData.Title,
|
||||
Is.EqualTo("보상 대체"));
|
||||
Assert.That(choices.CurrentChoices[0].DisplayData.Description,
|
||||
Is.EqualTo("적격한 보상이 없어 재화 10개를 받습니다.\n선택하면 즉시 적용됩니다."));
|
||||
Assert.That(choices.TryChooseReward(0, "empty:" + source), Is.True);
|
||||
Assert.That(grantCount, Is.EqualTo(1));
|
||||
Assert.That(grantedAmount,
|
||||
Is.EqualTo(constants.EmptyEpicFallbackCurrency));
|
||||
Assert.That(choices.HasPendingReward, Is.False);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(constants);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EmptyRewardWithoutCurrencyCallbackAutomaticallyResolvesToken()
|
||||
{
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
try
|
||||
{
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
System.Array.Empty<ModifierDefinition>(),
|
||||
null,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false);
|
||||
int autoResolvedCount = 0;
|
||||
string autoResolvedToken = null;
|
||||
choices.AutomaticallyResolved += (source, token) =>
|
||||
{
|
||||
Assert.That(source, Is.EqualTo(Defense25DRewardSource.LevelUp));
|
||||
autoResolvedCount++;
|
||||
autoResolvedToken = token;
|
||||
};
|
||||
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "empty:no-currency"), Is.True);
|
||||
Assert.That(choices.HasPendingReward, Is.False);
|
||||
Assert.That(choices.PendingRewardCount, Is.Zero);
|
||||
Assert.That(autoResolvedCount, Is.EqualTo(1));
|
||||
Assert.That(autoResolvedToken, Is.EqualTo("empty:no-currency"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(constants);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FullyInvalidatedOpenSnapshotFallsBackToCurrency()
|
||||
{
|
||||
GameObject playerObject = new("Invalidated Reward Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("invalidated");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
constants.TowerModifierCatalog = System.Array.Empty<Defense25DTowerModifierDefinition>();
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 100,
|
||||
Rare = 0,
|
||||
Epic = 0,
|
||||
};
|
||||
int grantedCurrency = 0;
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false,
|
||||
grantCurrency: amount => grantedCurrency += amount);
|
||||
|
||||
choices.QueueReward(Defense25DRewardSource.LevelUp, "stale:all-invalid");
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(1));
|
||||
Assert.That(choices.MarkCurrentRewardOpened(), Is.True);
|
||||
for (int index = 0; index < playerModifier.MaximumStacks; index++)
|
||||
{
|
||||
Assert.That(playerModifier.TryApplyToModel(player, null), Is.True);
|
||||
}
|
||||
|
||||
choices.RefreshChoices();
|
||||
|
||||
Assert.That(choices.CurrentRewardToken, Is.EqualTo("stale:all-invalid"));
|
||||
Assert.That(choices.CurrentRewardIsOpened, Is.True);
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(1));
|
||||
Assert.That(choices.CurrentChoices[0].TargetKind,
|
||||
Is.EqualTo(Defense25DRewardTargetKind.Currency));
|
||||
Assert.That(choices.TryChooseReward(0, "stale:all-invalid"), Is.True);
|
||||
Assert.That(grantedCurrency,
|
||||
Is.EqualTo(constants.EmptyEpicFallbackCurrency));
|
||||
Assert.That(choices.HasPendingReward, Is.False);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChoosingPlayerModifier_ChangesOnlyPlayerModelAndConsumesRewardOnce()
|
||||
{
|
||||
GameObject playerObject = new("Apply Reward Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("collision-damage");
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
try
|
||||
{
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DTowerModifiers towerModifiers = new(constants);
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
towerModifiers,
|
||||
_ => true);
|
||||
choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp,
|
||||
"level:apply-once");
|
||||
int playerChoiceIndex = choices.CurrentChoices.ToList()
|
||||
.FindIndex(choice => choice.TargetKind
|
||||
== Defense25DRewardTargetKind.PlayerModifier);
|
||||
Assert.That(playerChoiceIndex, Is.GreaterThanOrEqualTo(0));
|
||||
|
||||
Assert.That(choices.TryChooseReward(playerChoiceIndex), Is.True);
|
||||
Assert.That(player.Evaluate(CharacterStat.CollisionDamage, 10f),
|
||||
Is.GreaterThan(10f));
|
||||
Assert.That(towerModifiers.AppliedChoiceCount, Is.Zero);
|
||||
Assert.That(choices.HasPendingReward, Is.False);
|
||||
Assert.That(choices.TryChooseReward(playerChoiceIndex), Is.False);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayerRarityVariantsShareStacksAndHealOnlyAfterSuccessfulApply()
|
||||
{
|
||||
GameObject playerObject = new("Rarity Variant Player");
|
||||
ModifierDefinition playerModifier = CreatePlayerModifier("move-speed");
|
||||
playerModifier.Configure(
|
||||
"test.move-speed", "Move speed", CharacterStat.MoveSpeed,
|
||||
ModifierOperation.Increased, 0.2f, 2, 6f);
|
||||
Defense25DRewardConstants constants = CreateConstants(includeEpic: true);
|
||||
int healCalls = 0;
|
||||
float totalHeal = 0f;
|
||||
try
|
||||
{
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 100,
|
||||
Epic = 0,
|
||||
};
|
||||
PlayerStats player = playerObject.AddComponent<PlayerStats>();
|
||||
Defense25DRewardChoices choices = new(
|
||||
constants,
|
||||
new[] { playerModifier },
|
||||
player,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => false,
|
||||
amount =>
|
||||
{
|
||||
healCalls++;
|
||||
totalHeal += amount;
|
||||
});
|
||||
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:rare"), Is.True);
|
||||
Assert.That(choices.CurrentChoices, Has.Count.EqualTo(1));
|
||||
Assert.That(choices.CurrentChoices[0].Rarity,
|
||||
Is.EqualTo(Defense25DRewardRarity.Rare));
|
||||
Assert.That(choices.TryChooseReward(0), Is.True);
|
||||
Assert.That(player.Evaluate(CharacterStat.MoveSpeed, 10f),
|
||||
Is.EqualTo(13f).Within(0.001f));
|
||||
Assert.That(healCalls, Is.EqualTo(1));
|
||||
Assert.That(totalHeal, Is.EqualTo(9f).Within(0.001f));
|
||||
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 0,
|
||||
Rare = 0,
|
||||
Epic = 100,
|
||||
};
|
||||
Assert.That(playerModifier.CanApplyToModel(player), Is.True,
|
||||
"A rare stack leaves one shared stack available to an Epic variant.");
|
||||
Assert.That(choices.QueueReward(
|
||||
Defense25DRewardSource.LevelUp, "level:epic"), Is.True);
|
||||
Assert.That(choices.CurrentChoices[0].Rarity,
|
||||
Is.EqualTo(Defense25DRewardRarity.Epic));
|
||||
Assert.That(choices.TryChooseReward(0), Is.True);
|
||||
Assert.That(player.Evaluate(CharacterStat.MoveSpeed, 10f),
|
||||
Is.EqualTo(17f).Within(0.001f));
|
||||
Assert.That(healCalls, Is.EqualTo(2));
|
||||
Assert.That(totalHeal, Is.EqualTo(21f).Within(0.001f));
|
||||
|
||||
Assert.That(playerModifier.TryApplyToModel(
|
||||
player,
|
||||
amount =>
|
||||
{
|
||||
healCalls++;
|
||||
totalHeal += amount;
|
||||
},
|
||||
1.5f), Is.False,
|
||||
"The shared cap rejects a later rarity variant.");
|
||||
Assert.That(healCalls, Is.EqualTo(2));
|
||||
Assert.That(totalHeal, Is.EqualTo(21f).Within(0.001f));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(playerModifier);
|
||||
Object.DestroyImmediate(constants);
|
||||
Object.DestroyImmediate(playerObject);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModifierDefinition CreatePlayerModifier(string id)
|
||||
{
|
||||
ModifierDefinition definition =
|
||||
ScriptableObject.CreateInstance<ModifierDefinition>();
|
||||
definition.Configure(
|
||||
$"test.{id}",
|
||||
id,
|
||||
id == "move-speed"
|
||||
? CharacterStat.MoveSpeed
|
||||
: CharacterStat.CollisionDamage,
|
||||
ModifierOperation.Increased,
|
||||
0.2f,
|
||||
2);
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static Defense25DRewardChoices NewTowerOnlyChoices(
|
||||
Defense25DRewardConstants constants)
|
||||
{
|
||||
return new Defense25DRewardChoices(
|
||||
constants,
|
||||
System.Array.Empty<ModifierDefinition>(),
|
||||
null,
|
||||
new Defense25DTowerModifiers(constants),
|
||||
_ => true);
|
||||
}
|
||||
|
||||
private static Defense25DRewardConstants CreateConstants(bool includeEpic)
|
||||
{
|
||||
Defense25DRewardConstants constants =
|
||||
ScriptableObject.CreateInstance<Defense25DRewardConstants>();
|
||||
constants.RunSeed = 137;
|
||||
constants.ChoiceCount = 3;
|
||||
constants.LevelUpRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 75,
|
||||
Rare = 20,
|
||||
Epic = 5,
|
||||
};
|
||||
constants.GlobalEliteRarityWeights = new Defense25DRewardRarityWeights
|
||||
{
|
||||
Common = 60,
|
||||
Rare = 30,
|
||||
Epic = 10,
|
||||
};
|
||||
constants.EmptyEpicFallbackCurrency = 10;
|
||||
constants.TowerModifierCatalog = new[]
|
||||
{
|
||||
TowerModifier("fire.attack.common", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Common, 0.1f, 5),
|
||||
TowerModifier("fire.attack.rare", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Rare, 0.2f, 5),
|
||||
TowerModifier("fire.attack.epic", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Epic, 0.35f, 5),
|
||||
TowerModifier("lightning.chain.rare", DefenseTowerKind.Lightning,
|
||||
Defense25DTowerModifierEffect.ChainCount,
|
||||
Defense25DRewardRarity.Rare, 1f, 3),
|
||||
TowerModifier("cold.projectile.epic", DefenseTowerKind.Cold,
|
||||
Defense25DTowerModifierEffect.AdditionalProjectile,
|
||||
Defense25DRewardRarity.Epic, 1f, 2),
|
||||
}.Where(definition => includeEpic
|
||||
|| definition.Rarity != Defense25DRewardRarity.Epic).ToArray();
|
||||
return constants;
|
||||
}
|
||||
|
||||
private static Defense25DTowerModifierDefinition TowerModifier(
|
||||
string id,
|
||||
DefenseTowerKind family,
|
||||
Defense25DTowerModifierEffect effect,
|
||||
Defense25DRewardRarity rarity,
|
||||
float value,
|
||||
int maximumStacks)
|
||||
{
|
||||
return new Defense25DTowerModifierDefinition
|
||||
{
|
||||
DefinitionId = id,
|
||||
DisplayName = id,
|
||||
Description = id,
|
||||
Family = family,
|
||||
Effect = effect,
|
||||
Rarity = rarity,
|
||||
Value = value,
|
||||
MaximumStacks = maximumStacks,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user