2.5 update
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using BumpCombat.Defense;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Constants
|
||||
{
|
||||
public enum Defense25DRewardSource
|
||||
{
|
||||
LevelUp = 0,
|
||||
GlobalElite = 1,
|
||||
GlobalMidBoss = 2,
|
||||
PersonalMissionRelic = 3,
|
||||
}
|
||||
|
||||
public enum Defense25DRewardTargetKind
|
||||
{
|
||||
PlayerModifier = 0,
|
||||
TowerModifier = 1,
|
||||
PersonalMissionRelic = 2,
|
||||
Currency = 3,
|
||||
}
|
||||
|
||||
public enum Defense25DRewardRarity
|
||||
{
|
||||
None = 0,
|
||||
Common = 1,
|
||||
Rare = 2,
|
||||
Epic = 3,
|
||||
}
|
||||
|
||||
public enum Defense25DTowerModifierEffect
|
||||
{
|
||||
AttackDamageIncreased = 0,
|
||||
AreaRadiusIncreased = 1,
|
||||
ChainCount = 2,
|
||||
AdditionalProjectile = 3,
|
||||
AdditionalTarget = 4,
|
||||
SplitProjectile = 5,
|
||||
ReturnProjectile = 6,
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Defense25DRewardRarityWeights
|
||||
{
|
||||
[Min(0)] public int Common;
|
||||
[Min(0)] public int Rare;
|
||||
[Min(0)] public int Epic;
|
||||
|
||||
public int Total => Common + Rare + Epic;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class Defense25DTowerModifierDefinition
|
||||
{
|
||||
public string DefinitionId;
|
||||
public string DisplayName;
|
||||
[TextArea] public string Description;
|
||||
public DefenseTowerKind Family;
|
||||
public Defense25DTowerModifierEffect Effect;
|
||||
public Defense25DRewardRarity Rarity;
|
||||
[Min(0f)] public float Value;
|
||||
[Min(1)] public int MaximumStacks = 1;
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "BumpCombat/Constants/Defense 2.5D Rewards")]
|
||||
public sealed class Defense25DRewardConstants : ScriptableObject
|
||||
{
|
||||
[Min(0)] public int RunSeed = 1;
|
||||
[Min(1)] public int ChoiceCount = 3;
|
||||
public Defense25DRewardRarityWeights LevelUpRarityWeights =
|
||||
new() { Common = 75, Rare = 20, Epic = 5 };
|
||||
public Defense25DRewardRarityWeights GlobalEliteRarityWeights =
|
||||
new() { Common = 60, Rare = 30, Epic = 10 };
|
||||
[Min(0f)] public float PlayerRareEffectMultiplier = 1.5f;
|
||||
[Min(0f)] public float PlayerEpicEffectMultiplier = 2f;
|
||||
[Min(0)] public int EmptyEpicFallbackCurrency = 10;
|
||||
public Defense25DTowerModifierDefinition[] TowerModifierCatalog =
|
||||
CreateFirstTowerModifierCatalog();
|
||||
|
||||
private const string ChoiceConfirmationText = "선택하면 즉시 적용됩니다.";
|
||||
|
||||
public bool Validate(out string error)
|
||||
{
|
||||
if (ChoiceCount < 1 || !IsFinite(PlayerRareEffectMultiplier)
|
||||
|| PlayerRareEffectMultiplier <= 0f
|
||||
|| !IsFinite(PlayerEpicEffectMultiplier)
|
||||
|| PlayerEpicEffectMultiplier <= 0f
|
||||
|| EmptyEpicFallbackCurrency < 0)
|
||||
{
|
||||
error = "Reward choice count, player rarity multipliers, or currency fallback is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ValidWeights(LevelUpRarityWeights)
|
||||
|| !ValidWeights(GlobalEliteRarityWeights))
|
||||
{
|
||||
error = "Level-up and Global Elite rarity weights must be non-negative and total 100.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TowerModifierCatalog == null)
|
||||
{
|
||||
error = "Tower modifier catalog cannot be null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
HashSet<string> definitionIds = new(StringComparer.Ordinal);
|
||||
Dictionary<string, int> stackCaps = new(StringComparer.Ordinal);
|
||||
foreach (Defense25DTowerModifierDefinition definition in TowerModifierCatalog)
|
||||
{
|
||||
if (definition == null
|
||||
|| string.IsNullOrWhiteSpace(definition.DefinitionId)
|
||||
|| string.IsNullOrWhiteSpace(definition.DisplayName)
|
||||
|| !IsFinite(definition.Value)
|
||||
|| definition.Value <= 0f
|
||||
|| definition.MaximumStacks < 1
|
||||
|| !definitionIds.Add(definition.DefinitionId))
|
||||
{
|
||||
error = "Tower modifier entries need unique IDs, display names, positive finite values, and positive stack caps.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValidFamilyEffect(definition))
|
||||
{
|
||||
error = $"Tower modifier '{definition.DefinitionId}' has an unsupported family/effect/rarity combination.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string stackKey = StackKey(definition.Family, definition.Effect);
|
||||
if (stackCaps.TryGetValue(stackKey, out int existingCap)
|
||||
&& existingCap != definition.MaximumStacks)
|
||||
{
|
||||
error = $"Tower modifier variants in '{stackKey}' must share one stack cap.";
|
||||
return false;
|
||||
}
|
||||
stackCaps[stackKey] = definition.MaximumStacks;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string StackKey(
|
||||
DefenseTowerKind family,
|
||||
Defense25DTowerModifierEffect effect)
|
||||
{
|
||||
return $"{(int)family}:{(int)effect}";
|
||||
}
|
||||
|
||||
private static bool ValidWeights(Defense25DRewardRarityWeights weights)
|
||||
{
|
||||
return weights.Common >= 0 && weights.Rare >= 0
|
||||
&& weights.Epic >= 0 && weights.Total == 100;
|
||||
}
|
||||
|
||||
private static bool IsValidFamilyEffect(
|
||||
Defense25DTowerModifierDefinition definition)
|
||||
{
|
||||
bool tieredRarity = definition.Rarity is Defense25DRewardRarity.Common
|
||||
or Defense25DRewardRarity.Rare
|
||||
or Defense25DRewardRarity.Epic;
|
||||
bool isEpic = definition.Rarity == Defense25DRewardRarity.Epic;
|
||||
return definition.Effect switch
|
||||
{
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased =>
|
||||
definition.Family != DefenseTowerKind.Empowering
|
||||
&& tieredRarity,
|
||||
Defense25DTowerModifierEffect.AreaRadiusIncreased =>
|
||||
definition.Family is DefenseTowerKind.Direct
|
||||
or DefenseTowerKind.Summon
|
||||
or DefenseTowerKind.Cold
|
||||
or DefenseTowerKind.Seismic
|
||||
or DefenseTowerKind.Empowering
|
||||
&& tieredRarity,
|
||||
Defense25DTowerModifierEffect.ChainCount =>
|
||||
definition.Family == DefenseTowerKind.Lightning
|
||||
&& definition.Rarity == Defense25DRewardRarity.Rare,
|
||||
Defense25DTowerModifierEffect.AdditionalProjectile =>
|
||||
definition.Family == DefenseTowerKind.Cold && isEpic,
|
||||
Defense25DTowerModifierEffect.AdditionalTarget =>
|
||||
definition.Family == DefenseTowerKind.Direct && isEpic,
|
||||
Defense25DTowerModifierEffect.SplitProjectile or
|
||||
Defense25DTowerModifierEffect.ReturnProjectile =>
|
||||
definition.Family == DefenseTowerKind.Cold && isEpic,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private static Defense25DTowerModifierDefinition[] CreateFirstTowerModifierCatalog()
|
||||
{
|
||||
List<Defense25DTowerModifierDefinition> definitions = new();
|
||||
AddTiered(definitions, DefenseTowerKind.Direct, "fire", true);
|
||||
AddTiered(definitions, DefenseTowerKind.Summon, "summon", false);
|
||||
AddTiered(definitions, DefenseTowerKind.Lightning, "lightning", false);
|
||||
AddTiered(definitions, DefenseTowerKind.Cold, "cold", true);
|
||||
AddTiered(definitions, DefenseTowerKind.Seismic, "seismic", true);
|
||||
AddTiered(definitions, DefenseTowerKind.Empowering, "empowering", true,
|
||||
includeAttack: false);
|
||||
definitions.Add(Definition("lightning.chain.rare", "번개 연쇄 강화",
|
||||
$"연쇄 대상 +1명. {ChoiceConfirmationText}", DefenseTowerKind.Lightning,
|
||||
Defense25DTowerModifierEffect.ChainCount,
|
||||
Defense25DRewardRarity.Rare, 1f, 3));
|
||||
definitions.Add(Definition("cold.projectile.epic", "냉기 투사체 추가 발사",
|
||||
$"추가 투사체 +1발. {ChoiceConfirmationText}",
|
||||
DefenseTowerKind.Cold,
|
||||
Defense25DTowerModifierEffect.AdditionalProjectile,
|
||||
Defense25DRewardRarity.Epic, 1f, 2));
|
||||
definitions.Add(Definition("fire.targets.epic", "화염 동시 타격 강화",
|
||||
$"동시 타격 대상 +1개체. {ChoiceConfirmationText}", DefenseTowerKind.Direct,
|
||||
Defense25DTowerModifierEffect.AdditionalTarget,
|
||||
Defense25DRewardRarity.Epic, 1f, 1));
|
||||
definitions.Add(Definition("cold.split.epic", "냉기 투사체 분할",
|
||||
$"원본 최초 유효 적중 시 ±25° 방향으로 자식 투사체 2개를 발사합니다. 자식은 재분할하거나 복귀하지 않으며, 각 자식 피해는 원본 피해의 50%입니다. {ChoiceConfirmationText}",
|
||||
DefenseTowerKind.Cold,
|
||||
Defense25DTowerModifierEffect.SplitProjectile,
|
||||
Defense25DRewardRarity.Epic, 1f, 1));
|
||||
definitions.Add(Definition("cold.return.epic", "냉기 투사체 복귀",
|
||||
$"원본 비행 종료 후 발사 위치로 1회 복귀합니다. 복귀 피해는 원본의 50%입니다. {ChoiceConfirmationText}",
|
||||
DefenseTowerKind.Cold,
|
||||
Defense25DTowerModifierEffect.ReturnProjectile,
|
||||
Defense25DRewardRarity.Epic, 1f, 1));
|
||||
return definitions.ToArray();
|
||||
}
|
||||
|
||||
private static void AddTiered(
|
||||
List<Defense25DTowerModifierDefinition> definitions,
|
||||
DefenseTowerKind family,
|
||||
string familyId,
|
||||
bool includeArea,
|
||||
bool includeAttack = true)
|
||||
{
|
||||
string familyName = GetTowerFamilyDisplayName(family);
|
||||
if (includeAttack)
|
||||
{
|
||||
definitions.Add(Definition($"{familyId}.attack.common",
|
||||
$"{familyName} 공격 강화",
|
||||
$"공격 피해 +{FormatPercent(.10f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Common, .10f, 5));
|
||||
definitions.Add(Definition($"{familyId}.attack.rare",
|
||||
$"상급 {familyName} 공격 강화",
|
||||
$"공격 피해 +{FormatPercent(.20f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Rare, .20f, 5));
|
||||
definitions.Add(Definition($"{familyId}.attack.epic",
|
||||
$"최상급 {familyName} 공격 강화",
|
||||
$"공격 피해 +{FormatPercent(.35f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AttackDamageIncreased,
|
||||
Defense25DRewardRarity.Epic, .35f, 5));
|
||||
}
|
||||
|
||||
if (includeArea)
|
||||
{
|
||||
definitions.Add(Definition($"{familyId}.area.common",
|
||||
$"{familyName} 범위 강화",
|
||||
$"효과 반경 +{FormatPercent(.10f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AreaRadiusIncreased,
|
||||
Defense25DRewardRarity.Common, .10f, 3));
|
||||
definitions.Add(Definition($"{familyId}.area.rare",
|
||||
$"상급 {familyName} 범위 강화",
|
||||
$"효과 반경 +{FormatPercent(.15f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AreaRadiusIncreased,
|
||||
Defense25DRewardRarity.Rare, .15f, 3));
|
||||
definitions.Add(Definition($"{familyId}.area.epic",
|
||||
$"최상급 {familyName} 범위 강화",
|
||||
$"효과 반경 +{FormatPercent(.25f)}% 증가. {ChoiceConfirmationText}", family,
|
||||
Defense25DTowerModifierEffect.AreaRadiusIncreased,
|
||||
Defense25DRewardRarity.Epic, .25f, 3));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTowerFamilyDisplayName(DefenseTowerKind family)
|
||||
{
|
||||
return family switch
|
||||
{
|
||||
DefenseTowerKind.Direct => "화염 포탑",
|
||||
DefenseTowerKind.Summon => "소환 포탑",
|
||||
DefenseTowerKind.Lightning => "번개 포탑",
|
||||
DefenseTowerKind.Cold => "냉기 포탑",
|
||||
DefenseTowerKind.Seismic => "지진 포탑",
|
||||
DefenseTowerKind.Empowering => "지원 포탑",
|
||||
_ => "포탑",
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatPercent(float value)
|
||||
{
|
||||
return (value * 100f).ToString("0.##", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static Defense25DTowerModifierDefinition Definition(
|
||||
string id,
|
||||
string displayName,
|
||||
string description,
|
||||
DefenseTowerKind family,
|
||||
Defense25DTowerModifierEffect effect,
|
||||
Defense25DRewardRarity rarity,
|
||||
float value,
|
||||
int maximumStacks)
|
||||
{
|
||||
return new Defense25DTowerModifierDefinition
|
||||
{
|
||||
DefinitionId = id,
|
||||
DisplayName = displayName,
|
||||
Description = description,
|
||||
Family = family,
|
||||
Effect = effect,
|
||||
Rarity = rarity,
|
||||
Value = value,
|
||||
MaximumStacks = maximumStacks,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsFinite(float value)
|
||||
{
|
||||
return !float.IsNaN(value) && !float.IsInfinity(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user