2.5 update

This commit is contained in:
2026-09-28 11:07:17 +09:00
parent abd2da2057
commit 7978d82196
976 changed files with 111965 additions and 313 deletions
@@ -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);
}
}
}