init
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum CombatParticipantTowerValidationFailureReason
|
||||
{
|
||||
None = 0,
|
||||
TowerMissing = 1,
|
||||
MissingMuzzleComponent = 2,
|
||||
MissingBearingComponent = 3,
|
||||
MissingBaseComponent = 4,
|
||||
BrokenMuzzleComponent = 5,
|
||||
BrokenBearingComponent = 6,
|
||||
BrokenBaseComponent = 7
|
||||
}
|
||||
|
||||
public sealed class CombatParticipantTowerValidationResult
|
||||
{
|
||||
public long TowerInstanceId { get; set; }
|
||||
|
||||
public bool IsValid => FailureReason == CombatParticipantTowerValidationFailureReason.None;
|
||||
|
||||
public CombatParticipantTowerValidationFailureReason FailureReason { get; set; }
|
||||
|
||||
public TowerItemData Tower { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CombatParticipantTowerValidationSummary
|
||||
{
|
||||
public IReadOnlyList<TowerItemData> ValidTowers { get; set; } = System.Array.Empty<TowerItemData>();
|
||||
|
||||
public IReadOnlyList<CombatParticipantTowerValidationResult> InvalidResults { get; set; } =
|
||||
System.Array.Empty<CombatParticipantTowerValidationResult>();
|
||||
|
||||
public bool HasAnyValidParticipantTower => ValidTowers != null && ValidTowers.Count > 0;
|
||||
}
|
||||
|
||||
public static class CombatParticipantTowerValidationService
|
||||
{
|
||||
public static CombatParticipantTowerValidationResult ValidateTower(
|
||||
BackpackInventoryData inventory,
|
||||
long towerInstanceId)
|
||||
{
|
||||
if (!TryGetTowerById(inventory, towerInstanceId, out TowerItemData tower))
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = towerInstanceId,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.TowerMissing
|
||||
};
|
||||
}
|
||||
|
||||
return ValidateTower(inventory, tower);
|
||||
}
|
||||
|
||||
public static CombatParticipantTowerValidationResult ValidateTower(
|
||||
BackpackInventoryData inventory,
|
||||
TowerItemData tower)
|
||||
{
|
||||
if (tower == null || tower.InstanceId <= 0)
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower != null ? tower.InstanceId : 0,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.TowerMissing
|
||||
};
|
||||
}
|
||||
|
||||
if (!TryGetComponentById(inventory?.MuzzleComponents, tower.MuzzleComponentInstanceId, out MuzzleCompItemData muzzleComponent))
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.MissingMuzzleComponent
|
||||
};
|
||||
}
|
||||
|
||||
if (muzzleComponent.Endurance <= 0f)
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent
|
||||
};
|
||||
}
|
||||
|
||||
if (!TryGetComponentById(inventory?.BearingComponents, tower.BearingComponentInstanceId, out BearingCompItemData bearingComponent))
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.MissingBearingComponent
|
||||
};
|
||||
}
|
||||
|
||||
if (bearingComponent.Endurance <= 0f)
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenBearingComponent
|
||||
};
|
||||
}
|
||||
|
||||
if (!TryGetComponentById(inventory?.BaseComponents, tower.BaseComponentInstanceId, out BaseCompItemData baseComponent))
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.MissingBaseComponent
|
||||
};
|
||||
}
|
||||
|
||||
if (baseComponent.Endurance <= 0f)
|
||||
{
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.BrokenBaseComponent
|
||||
};
|
||||
}
|
||||
|
||||
return new CombatParticipantTowerValidationResult
|
||||
{
|
||||
TowerInstanceId = tower.InstanceId,
|
||||
Tower = tower,
|
||||
FailureReason = CombatParticipantTowerValidationFailureReason.None
|
||||
};
|
||||
}
|
||||
|
||||
public static CombatParticipantTowerValidationSummary ValidateParticipantTowers(BackpackInventoryData inventory)
|
||||
{
|
||||
List<TowerItemData> validTowers = new List<TowerItemData>();
|
||||
List<CombatParticipantTowerValidationResult> invalidResults =
|
||||
new List<CombatParticipantTowerValidationResult>();
|
||||
HashSet<long> processedTowerIds = new HashSet<long>();
|
||||
|
||||
if (inventory?.ParticipantTowerInstanceIds == null || inventory.ParticipantTowerInstanceIds.Count <= 0)
|
||||
{
|
||||
return new CombatParticipantTowerValidationSummary
|
||||
{
|
||||
ValidTowers = validTowers,
|
||||
InvalidResults = invalidResults
|
||||
};
|
||||
}
|
||||
|
||||
for (int i = 0; i < inventory.ParticipantTowerInstanceIds.Count; i++)
|
||||
{
|
||||
long towerInstanceId = inventory.ParticipantTowerInstanceIds[i];
|
||||
if (towerInstanceId <= 0 || !processedTowerIds.Add(towerInstanceId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CombatParticipantTowerValidationResult result = ValidateTower(inventory, towerInstanceId);
|
||||
if (result.IsValid)
|
||||
{
|
||||
validTowers.Add(result.Tower);
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidResults.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return new CombatParticipantTowerValidationSummary
|
||||
{
|
||||
ValidTowers = validTowers,
|
||||
InvalidResults = invalidResults
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryGetTowerById(BackpackInventoryData inventory, long towerInstanceId, out TowerItemData tower)
|
||||
{
|
||||
tower = null;
|
||||
if (inventory?.Towers == null || towerInstanceId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < inventory.Towers.Count; i++)
|
||||
{
|
||||
TowerItemData candidate = inventory.Towers[i];
|
||||
if (candidate != null && candidate.InstanceId == towerInstanceId)
|
||||
{
|
||||
tower = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetComponentById<TComponent>(
|
||||
IReadOnlyList<TComponent> components,
|
||||
long componentInstanceId,
|
||||
out TComponent resolvedComponent)
|
||||
where TComponent : TowerCompItemData
|
||||
{
|
||||
resolvedComponent = null;
|
||||
if (components == null || componentInstanceId <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < components.Count; i++)
|
||||
{
|
||||
TComponent component = components[i];
|
||||
if (component != null && component.InstanceId == componentInstanceId)
|
||||
{
|
||||
resolvedComponent = component;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class CombatParticipantTowerValidationText
|
||||
{
|
||||
public static string GetFailureReasonMessage(CombatParticipantTowerValidationFailureReason failureReason)
|
||||
{
|
||||
switch (failureReason)
|
||||
{
|
||||
case CombatParticipantTowerValidationFailureReason.TowerMissing:
|
||||
return "已不存在,无法参战。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingMuzzleComponent:
|
||||
return "缺少枪口组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBearingComponent:
|
||||
return "缺少轴承组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.MissingBaseComponent:
|
||||
return "缺少底座组件。";
|
||||
case CombatParticipantTowerValidationFailureReason.BrokenMuzzleComponent:
|
||||
return "枪口组件耐久为 0,无法参战。";
|
||||
case CombatParticipantTowerValidationFailureReason.BrokenBearingComponent:
|
||||
return "轴承组件耐久为 0,无法参战。";
|
||||
case CombatParticipantTowerValidationFailureReason.BrokenBaseComponent:
|
||||
return "底座组件耐久为 0,无法参战。";
|
||||
default:
|
||||
return "不满足当前参战条件。";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
namespace GeometryTD
|
||||
{
|
||||
public static partial class Constant
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源优先级。
|
||||
/// </summary>
|
||||
public static class AssetPriority
|
||||
{
|
||||
public const int ConfigAsset = 100;
|
||||
public const int DataTableAsset = 100;
|
||||
public const int DictionaryAsset = 100;
|
||||
public const int FontAsset = 50;
|
||||
public const int MusicAsset = 20;
|
||||
public const int SceneAsset = 0;
|
||||
public const int SoundAsset = 30;
|
||||
public const int UIFormAsset = 50;
|
||||
public const int UISoundAsset = 30;
|
||||
|
||||
public const int MyAircraftAsset = 90;
|
||||
public const int PlayerAsset = 90;
|
||||
public const int AircraftAsset = 80;
|
||||
public const int ThrusterAsset = 30;
|
||||
public const int WeaponAsset = 30;
|
||||
public const int ArmorAsset = 30;
|
||||
public const int BulletAsset = 80;
|
||||
public const int AsteroiAsset = 80;
|
||||
public const int EnemyAsset = 80;
|
||||
public const int MapAsset = 85;
|
||||
public const int EffectAsset = 80;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD
|
||||
{
|
||||
public static partial class Constant
|
||||
{
|
||||
/// <summary>
|
||||
/// 层。
|
||||
/// </summary>
|
||||
public static class Layer
|
||||
{
|
||||
public const string DefaultLayerName = "Default";
|
||||
public static readonly int DefaultLayerId = LayerMask.NameToLayer(DefaultLayerName);
|
||||
|
||||
public const string UILayerName = "UI";
|
||||
public static readonly int UILayerId = LayerMask.NameToLayer(UILayerName);
|
||||
|
||||
public const string TargetableObjectLayerName = "Targetable Object";
|
||||
public static readonly int TargetableObjectLayerId = LayerMask.NameToLayer(TargetableObjectLayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
namespace GeometryTD
|
||||
{
|
||||
public static partial class Constant
|
||||
{
|
||||
public static class Setting
|
||||
{
|
||||
public const string Language = "Setting.Language";
|
||||
public const string SoundGroupMuted = "Setting.{0}Muted";
|
||||
public const string SoundGroupVolume = "Setting.{0}Volume";
|
||||
public const string MusicMuted = "Setting.MusicMuted";
|
||||
public const string MusicVolume = "Setting.MusicVolume";
|
||||
public const string SoundMuted = "Setting.SoundMuted";
|
||||
public const string SoundVolume = "Setting.SoundVolume";
|
||||
public const string UISoundMuted = "Setting.UISoundMuted";
|
||||
public const string UISoundVolume = "Setting.UISoundVolume";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using GeometryTD.CustomUtility;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class AttackPayload
|
||||
{
|
||||
public int BaseDamage { get; set; }
|
||||
public AttackPropertyType AttackPropertyType { get; set; }
|
||||
public int SourceEntityId { get; set; }
|
||||
public int ProjectileEntityId { get; set; }
|
||||
public Vector3 OriginPosition { get; set; }
|
||||
public TagRuntimeData[] TagRuntimes { get; set; } = Array.Empty<TagRuntimeData>();
|
||||
|
||||
public AttackPayload Clone()
|
||||
{
|
||||
return new AttackPayload
|
||||
{
|
||||
BaseDamage = BaseDamage,
|
||||
AttackPropertyType = AttackPropertyType,
|
||||
SourceEntityId = SourceEntityId,
|
||||
ProjectileEntityId = ProjectileEntityId,
|
||||
OriginPosition = OriginPosition,
|
||||
TagRuntimes = InventoryCloneUtility.CloneTagRuntimes(TagRuntimes)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家背包聚合数据。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class BackpackInventoryData
|
||||
{
|
||||
/// <summary>
|
||||
/// 背包金币。
|
||||
/// </summary>
|
||||
public int Gold { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 背包中的枪口组件实例。
|
||||
/// </summary>
|
||||
public List<MuzzleCompItemData> MuzzleComponents { get; set; } = new List<MuzzleCompItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 背包中的轴承组件实例。
|
||||
/// </summary>
|
||||
public List<BearingCompItemData> BearingComponents { get; set; } = new List<BearingCompItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 背包中的底座组件实例。
|
||||
/// </summary>
|
||||
public List<BaseCompItemData> BaseComponents { get; set; } = new List<BaseCompItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 背包中的防御塔实例。
|
||||
/// </summary>
|
||||
public List<TowerItemData> Towers { get; set; } = new List<TowerItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 参与战斗的防御塔 Id。
|
||||
/// </summary>
|
||||
public List<long> ParticipantTowerInstanceIds { get; set; } = new List<long>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//------------------------------------------------------------
|
||||
// Game Framework
|
||||
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
|
||||
// Homepage: https://gameframework.cn/
|
||||
// Feedback: mailto:ellan@gameframework.cn
|
||||
//------------------------------------------------------------
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class BuildInfo
|
||||
{
|
||||
public string GameVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public int InternalGameVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string CheckVersionUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string WindowsAppUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string MacOSAppUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string IOSAppUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string AndroidAppUrl
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class EventItem
|
||||
{
|
||||
public EventItem(int id, string title, string description, EventOption[] options)
|
||||
{
|
||||
Id = id;
|
||||
Title = title;
|
||||
Description = description;
|
||||
Options = options ?? System.Array.Empty<EventOption>();
|
||||
}
|
||||
|
||||
public int Id { get; private set; }
|
||||
public string Title { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public EventOption[] Options { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class EventOption
|
||||
{
|
||||
public string OptionText { get; private set; }
|
||||
public EventRequirementBase[] Requirements { get; private set; }
|
||||
public EventEffectBase[] CostEffects { get; private set; }
|
||||
public EventEffectBase[] RewardEffects { get; private set; }
|
||||
public float Probability { get; private set; }
|
||||
|
||||
public EventOption(
|
||||
string optionText,
|
||||
EventRequirementBase[] requirements,
|
||||
EventEffectBase[] costEffects,
|
||||
EventEffectBase[] rewardEffects,
|
||||
float probability = 1f)
|
||||
{
|
||||
OptionText = optionText;
|
||||
Requirements = requirements ?? System.Array.Empty<EventRequirementBase>();
|
||||
CostEffects = costEffects ?? System.Array.Empty<EventEffectBase>();
|
||||
RewardEffects = rewardEffects ?? System.Array.Empty<EventEffectBase>();
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class HitStatusModifierContext
|
||||
{
|
||||
public float BonusBurnDurationSeconds { get; set; }
|
||||
public float BonusBurnDamagePerSecond { get; set; }
|
||||
public float BonusSlowDurationSeconds { get; set; }
|
||||
public float BonusSlowRatio { get; set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
BonusBurnDurationSeconds = 0f;
|
||||
BonusBurnDamagePerSecond = 0f;
|
||||
BonusSlowDurationSeconds = 0f;
|
||||
BonusSlowRatio = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class HitContext
|
||||
{
|
||||
public AttackPayload AttackPayload { get; set; }
|
||||
public int FinalDamage { get; set; }
|
||||
public bool IsCriticalHit { get; set; }
|
||||
public bool IsKilled { get; set; }
|
||||
public int TargetEntityId { get; set; }
|
||||
public Vector3 TargetPosition { get; set; }
|
||||
public int TargetCurrentHealthBeforeHit { get; set; }
|
||||
public int TargetCurrentHealthAfterHit { get; set; }
|
||||
public int TargetMaxHealth { get; set; }
|
||||
public float TargetMoveSpeedMultiplierBeforeHit { get; set; } = 1f;
|
||||
public TagType[] TargetStatusTagsBeforeHit { get; set; } = Array.Empty<TagType>();
|
||||
public EnemyTagStatusRuntime TargetStatusRuntime { get; set; }
|
||||
public float? CritRoll { get; set; }
|
||||
public HitStatusModifierContext StatusModifierContext { get; set; } = new HitStatusModifierContext();
|
||||
|
||||
public bool HasTargetStatus(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None || TargetStatusTagsBeforeHit == null || TargetStatusTagsBeforeHit.Length <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < TargetStatusTagsBeforeHit.Length; i++)
|
||||
{
|
||||
if (TargetStatusTagsBeforeHit[i] == tagType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool HasSlowStatusBeforeHit => TargetMoveSpeedMultiplierBeforeHit < 0.999f || HasTargetStatus(TagType.Ice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct ImpactData
|
||||
{
|
||||
public ImpactData(CampType camp, int hp, int attack, int defense)
|
||||
{
|
||||
Camp = camp;
|
||||
HP = hp;
|
||||
Attack = attack;
|
||||
Defense = defense;
|
||||
}
|
||||
|
||||
public CampType Camp { get; }
|
||||
|
||||
public int HP { get; }
|
||||
|
||||
public int Attack { get; }
|
||||
|
||||
public int Defense { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 背包内组件实例基类(非 DataTable,表示玩家持有物)。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class TowerCompItemData
|
||||
{
|
||||
/// <summary>
|
||||
/// 组件实例唯一 Id。
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件配置 Id(对应 DataTable Id)。
|
||||
/// </summary>
|
||||
public int ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件槽位类型。
|
||||
/// </summary>
|
||||
public TowerCompSlotType SlotType { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件名称。
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件品质。
|
||||
/// </summary>
|
||||
public RarityType Rarity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件当前耐久(0~100)。
|
||||
/// </summary>
|
||||
public float Endurance { get; set; } = 100f;
|
||||
|
||||
public bool IsAssembledIntoTower { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件约束(先沿用 DataTable 原定义)。
|
||||
/// </summary>
|
||||
public string Constraint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组件当前 Tag(实例态)。
|
||||
/// </summary>
|
||||
public TagType[] Tags { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class MuzzleCompItemData : TowerCompItemData
|
||||
{
|
||||
public MuzzleCompItemData()
|
||||
{
|
||||
SlotType = TowerCompSlotType.Muzzle;
|
||||
}
|
||||
|
||||
public int[] AttackDamage { get; set; }
|
||||
public float DamageRandomRate { get; set; }
|
||||
public AttackMethodType AttackMethodType { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class BearingCompItemData : TowerCompItemData
|
||||
{
|
||||
public BearingCompItemData()
|
||||
{
|
||||
SlotType = TowerCompSlotType.Bearing;
|
||||
}
|
||||
|
||||
public float[] RotateSpeed { get; set; }
|
||||
public float[] AttackRange { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class BaseCompItemData : TowerCompItemData
|
||||
{
|
||||
public BaseCompItemData()
|
||||
{
|
||||
SlotType = TowerCompSlotType.Base;
|
||||
}
|
||||
|
||||
public float[] AttackSpeed { get; set; }
|
||||
public AttackPropertyType AttackPropertyType { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class AccessoryItemData : TowerCompItemData
|
||||
{
|
||||
public AccessoryItemData()
|
||||
{
|
||||
SlotType = TowerCompSlotType.Accessory;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 背包内防御塔实例数据。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class TowerItemData
|
||||
{
|
||||
/// <summary>
|
||||
/// 防御塔实例唯一 Id。
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 防御塔显示名称。
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 防御塔品质。
|
||||
/// </summary>
|
||||
public RarityType Rarity { get; set; }
|
||||
|
||||
public bool IsParticipatingInCombat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构成该防御塔的枪口组件实例 Id。
|
||||
/// </summary>
|
||||
public long MuzzleComponentInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构成该防御塔的轴承组件实例 Id。
|
||||
/// </summary>
|
||||
public long BearingComponentInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构成该防御塔的底座组件实例 Id。
|
||||
/// </summary>
|
||||
public long BaseComponentInstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 防御塔独立属性,不依赖组件对象引用。
|
||||
/// </summary>
|
||||
public TowerStatsData Stats { get; set; } = new TowerStatsData();
|
||||
|
||||
[JsonIgnore] public Sprite ComposedIconSprite { get; set; }
|
||||
|
||||
[JsonIgnore] public string ComposedIconKey { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 防御塔独立属性快照。
|
||||
/// 注意:这里是塔实例的独立值,不通过组件引用实时计算。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class TowerStatsData
|
||||
{
|
||||
public int[] AttackDamage { get; set; }
|
||||
public float DamageRandomRate { get; set; }
|
||||
public float[] RotateSpeed { get; set; }
|
||||
public float[] AttackRange { get; set; }
|
||||
public float[] AttackSpeed { get; set; }
|
||||
public AttackMethodType AttackMethodType { get; set; }
|
||||
public AttackPropertyType AttackPropertyType { get; set; }
|
||||
public TagRuntimeData[] TagRuntimes { get; set; }
|
||||
public TagType[] Tags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class VersionInfo
|
||||
{
|
||||
// 是否需要强制更新游戏应用
|
||||
public bool ForceUpdateGame
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 最新的游戏版本号
|
||||
public string LatestGameVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 最新的游戏内部版本号
|
||||
public int InternalGameVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 最新的资源内部版本号
|
||||
public int InternalResourceVersion
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 资源更新下载地址
|
||||
public string UpdatePrefixUri
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 资源版本列表长度
|
||||
public int VersionListLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 资源版本列表哈希值
|
||||
public int VersionListHashCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 资源版本列表压缩后长度
|
||||
public int VersionListCompressedLength
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// 资源版本列表压缩后哈希值
|
||||
public int VersionListCompressedHashCode
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum AttackMethodType : byte
|
||||
{
|
||||
None = 0,
|
||||
NormalBullet = 1,
|
||||
Range
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum AttackPropertyType : byte
|
||||
{
|
||||
None = 0,
|
||||
Physics = 1,
|
||||
Fire = 2,
|
||||
Water = 3,
|
||||
Earth = 4,
|
||||
Poison = 5,
|
||||
Ice = 6
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 阵营类型。
|
||||
/// </summary>
|
||||
public enum CampType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 第一玩家阵营。
|
||||
/// </summary>
|
||||
Player,
|
||||
|
||||
/// <summary>
|
||||
/// 第一敌人阵营。
|
||||
/// </summary>
|
||||
Enemy,
|
||||
|
||||
/// <summary>
|
||||
/// 第一中立阵营。
|
||||
/// </summary>
|
||||
Neutral,
|
||||
|
||||
/// <summary>
|
||||
/// 第二玩家阵营。
|
||||
/// </summary>
|
||||
Player2,
|
||||
|
||||
/// <summary>
|
||||
/// 第二敌人阵营。
|
||||
/// </summary>
|
||||
Enemy2,
|
||||
|
||||
/// <summary>
|
||||
/// 第二中立阵营
|
||||
/// </summary>
|
||||
Neutral2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 出怪条目类型
|
||||
/// </summary>
|
||||
public enum EntryType : byte
|
||||
{
|
||||
None = 0,
|
||||
Stream = 1,
|
||||
Burst = 2,
|
||||
Boss = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum EventEffectType
|
||||
{
|
||||
None,
|
||||
|
||||
AddGold,
|
||||
RemoveGold,
|
||||
|
||||
AddRandomComps,
|
||||
RemoveRandomComps,
|
||||
|
||||
AddRandomCompsEndurance,
|
||||
RemoveRandomCompsEndurance,
|
||||
|
||||
AddRandomTowersEndurance,
|
||||
DamageRandomTowersEndurance,
|
||||
|
||||
TransformComponents,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum EventRequirementType
|
||||
{
|
||||
None,
|
||||
GoldAtLeast,
|
||||
CompCountAtLeast,
|
||||
TowerCountAtLeast,
|
||||
HasRelic,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum InventoryTagSourceType : byte
|
||||
{
|
||||
Seed = 1,
|
||||
Shop = 2,
|
||||
Drop = 3,
|
||||
Reward = 4,
|
||||
Event = 5,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum LevelThemeType
|
||||
{
|
||||
None,
|
||||
Plain,
|
||||
Volcano,
|
||||
Mountain
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum LevelVictoryType
|
||||
{
|
||||
None,
|
||||
PhasesCleared,
|
||||
BossDead,
|
||||
TimeElapsed
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 关卡阶段结束类型
|
||||
/// </summary>
|
||||
public enum PhaseEndType : byte
|
||||
{
|
||||
None = 0,
|
||||
TimeElapsed = 1,
|
||||
EnemiesCleared = 2,
|
||||
BossDead = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainCombatEntryBlockReason
|
||||
{
|
||||
None = 0,
|
||||
InventoryUnavailable = 1,
|
||||
NoValidParticipantTower = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainFlowPhase
|
||||
{
|
||||
Hub = 0,
|
||||
NodeActive = 1,
|
||||
RunCompletedPendingFinish = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainRunAdvanceResult
|
||||
{
|
||||
NoChange = 0,
|
||||
NodeException = 1,
|
||||
AdvancedToNextNode = 2,
|
||||
RunCompleted = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ProcedureMainRunCompletionResult
|
||||
{
|
||||
NoChange = 0,
|
||||
ShowCompletionDialog = 1,
|
||||
ReturnToMenu = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum RarityType
|
||||
{
|
||||
None = 0,
|
||||
White = 1,
|
||||
Green = 2,
|
||||
Blue = 3,
|
||||
Purple = 4,
|
||||
Red = 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 关系类型。
|
||||
/// </summary>
|
||||
public enum RelationType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// 未知的。
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// 友好的。
|
||||
/// </summary>
|
||||
Friendly,
|
||||
|
||||
/// <summary>
|
||||
/// 中立的。
|
||||
/// </summary>
|
||||
Neutral,
|
||||
|
||||
/// <summary>
|
||||
/// 敌对的。
|
||||
/// </summary>
|
||||
Hostile
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum RepoItemClickActionType
|
||||
{
|
||||
OpenDetail,
|
||||
RemoveParticipant,
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum SceneType : byte
|
||||
{
|
||||
Launch = 0,
|
||||
Menu = 1,
|
||||
Main = 2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TagCategory : byte
|
||||
{
|
||||
None = 0,
|
||||
Status = 1,
|
||||
NumericModifier = 2,
|
||||
AttackShape = 3,
|
||||
// Enhances another applied status on the same hit, but does not create its own enemy-held state.
|
||||
StatusModifier = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TagTriggerPhase : byte
|
||||
{
|
||||
None = 0,
|
||||
OnBeforeHit = 1,
|
||||
OnHit = 2,
|
||||
OnAfterHit = 3,
|
||||
OnKill = 4
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TagType : byte
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 元素
|
||||
/// </summary>
|
||||
Fire,
|
||||
BurnSpread,
|
||||
IgniteBurst,
|
||||
Inferno,
|
||||
|
||||
/// <summary>
|
||||
/// 控制
|
||||
/// </summary>
|
||||
Ice,
|
||||
FreezeMask,
|
||||
Shatter,
|
||||
AbsoluteZero,
|
||||
|
||||
/// <summary>
|
||||
/// 穿透
|
||||
/// </summary>
|
||||
Pierce,
|
||||
Crit,
|
||||
Overpenetrate,
|
||||
Execution,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TowerCompSlotType : byte
|
||||
{
|
||||
None = 0,
|
||||
Muzzle = 1,
|
||||
Bearing = 2,
|
||||
Base = 3,
|
||||
Accessory = 4,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
/// <summary>
|
||||
/// 界面编号。
|
||||
/// </summary>
|
||||
public enum UIFormType : byte
|
||||
{
|
||||
Undefined = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 弹出框。
|
||||
/// </summary>
|
||||
DialogForm = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 主菜单。
|
||||
/// </summary>
|
||||
MenuForm = 100,
|
||||
|
||||
/// <summary>
|
||||
/// 设置。
|
||||
/// </summary>
|
||||
SettingForm = 101,
|
||||
|
||||
/// <summary>
|
||||
/// 关于。
|
||||
/// </summary>
|
||||
AboutForm = 102,
|
||||
|
||||
/// <summary>
|
||||
/// 主界面。
|
||||
/// </summary>
|
||||
MainForm = 110,
|
||||
|
||||
/// <summary>
|
||||
/// 仓库界面。
|
||||
/// </summary>
|
||||
RepoForm = 111,
|
||||
|
||||
/// <summary>
|
||||
/// 节点地图界面。
|
||||
/// </summary>
|
||||
NodeMapForm = 112,
|
||||
|
||||
/// <summary>
|
||||
/// 道具详细信息界面。
|
||||
/// </summary>
|
||||
ItemDescForm = 113,
|
||||
|
||||
/// <summary>
|
||||
/// 奖励三选一界面。
|
||||
/// </summary>
|
||||
RewardSelectForm = 114,
|
||||
|
||||
/// <summary>
|
||||
/// 事件节点界面。
|
||||
/// </summary>
|
||||
EventForm = 130,
|
||||
|
||||
/// <summary>
|
||||
/// 战斗信息界面。
|
||||
/// </summary>
|
||||
CombatInfoForm = 140,
|
||||
|
||||
/// <summary>
|
||||
/// 战斗结算界面。
|
||||
/// </summary>
|
||||
CombatFinishForm = 141,
|
||||
|
||||
/// <summary>
|
||||
/// 战斗选择界面
|
||||
/// </summary>
|
||||
CombatSelectForm = 142,
|
||||
|
||||
/// <summary>
|
||||
/// 商店界面。
|
||||
/// </summary>
|
||||
ShopForm = 150,
|
||||
|
||||
/// <summary>
|
||||
/// 测试菜单。
|
||||
/// </summary>
|
||||
TestMenuForm = 200,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class AddGoldEffect : EventEffectBase
|
||||
{
|
||||
public override EventEffectType EffectType => EventEffectType.AddGold;
|
||||
public override EventEffectParam Param => _param;
|
||||
private AddGoldParam _param;
|
||||
|
||||
public AddGoldEffect(AddGoldParam param, float? probability = null)
|
||||
{
|
||||
_param = param;
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
|
||||
public class AddGoldParam : EventEffectParam
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public AddGoldParam(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class AddRandomCompsEffect : EventEffectBase
|
||||
{
|
||||
public override EventEffectType EffectType => EventEffectType.AddRandomComps;
|
||||
public override EventEffectParam Param => _param;
|
||||
private AddRandomCompsParam _param;
|
||||
|
||||
public AddRandomCompsEffect(AddRandomCompsParam param, float? probability = null)
|
||||
{
|
||||
_param = param;
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class AddRandomCompsParam : EventEffectParam
|
||||
{
|
||||
public int Count;
|
||||
public RarityType Rarity;
|
||||
public RarityType MinRarity;
|
||||
public RarityType MaxRarity;
|
||||
|
||||
public AddRandomCompsParam(int count, RarityType rarity)
|
||||
{
|
||||
Count = count;
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
Rarity = normalizedRarity;
|
||||
MinRarity = normalizedRarity;
|
||||
MaxRarity = normalizedRarity;
|
||||
}
|
||||
|
||||
public AddRandomCompsParam(int count, RarityType minRarity, RarityType maxRarity)
|
||||
{
|
||||
Count = count;
|
||||
MinRarity = InventoryRarityRuleService.NormalizeComponentRarity(minRarity);
|
||||
MaxRarity = InventoryRarityRuleService.NormalizeComponentRarity(maxRarity);
|
||||
if (MinRarity > MaxRarity)
|
||||
{
|
||||
throw new System.ArgumentOutOfRangeException(
|
||||
nameof(minRarity),
|
||||
$"AddRandomComps rarity range is invalid: {MinRarity} > {MaxRarity}.");
|
||||
}
|
||||
|
||||
Rarity = MinRarity == MaxRarity ? MinRarity : RarityType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class DamageRandomTowerEnduranceEffect : EventEffectBase
|
||||
{
|
||||
public override EventEffectType EffectType => EventEffectType.DamageRandomTowersEndurance;
|
||||
public override EventEffectParam Param => _param;
|
||||
private DamageRandomTowerEnduranceParam _param;
|
||||
|
||||
public DamageRandomTowerEnduranceEffect(DamageRandomTowerEnduranceParam param, float? probability = null)
|
||||
{
|
||||
_param = param;
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
|
||||
public class DamageRandomTowerEnduranceParam : EventEffectParam
|
||||
{
|
||||
/// <summary>
|
||||
/// 减少耐久的防御塔数量
|
||||
/// </summary>
|
||||
public int Count;
|
||||
|
||||
/// <summary>
|
||||
/// 防御塔耐久减少的量
|
||||
/// </summary>
|
||||
public int Amount;
|
||||
|
||||
public DamageRandomTowerEnduranceParam(int count, int amount)
|
||||
{
|
||||
Count = count;
|
||||
Amount = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EventEffectBase
|
||||
{
|
||||
public abstract EventEffectType EffectType { get; }
|
||||
public abstract EventEffectParam Param { get; }
|
||||
public float? Probability;
|
||||
}
|
||||
|
||||
public class EventEffectParam
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class RemoveRandomCompsEffect : EventEffectBase
|
||||
{
|
||||
public override EventEffectType EffectType => EventEffectType.RemoveRandomComps;
|
||||
public override EventEffectParam Param => _param;
|
||||
private RemoveRandomCompsParam _param;
|
||||
|
||||
public RemoveRandomCompsEffect(RemoveRandomCompsParam param, float? probability = null)
|
||||
{
|
||||
_param = param;
|
||||
Probability = probability;
|
||||
}
|
||||
}
|
||||
|
||||
public class RemoveRandomCompsParam : EventEffectParam
|
||||
{
|
||||
public int Count;
|
||||
public RarityType Rarity;
|
||||
|
||||
public RemoveRandomCompsParam(int Count, RarityType Rarity)
|
||||
{
|
||||
this.Count = Count;
|
||||
this.Rarity = Rarity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.CustomComponent;
|
||||
using GeometryTD.CustomUtility;
|
||||
using GeometryTD.Procedure;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class EventOptionExecutor
|
||||
{
|
||||
public EventOptionAvailability EvaluateOption(EventOption option, BackpackInventoryData inventory)
|
||||
{
|
||||
if (option == null)
|
||||
{
|
||||
return EventOptionAvailability.Blocked("选项配置无效");
|
||||
}
|
||||
|
||||
EventRequirementBase[] requirements = option.Requirements ?? Array.Empty<EventRequirementBase>();
|
||||
for (int i = 0; i < requirements.Length; i++)
|
||||
{
|
||||
EventRequirementBase requirement = requirements[i];
|
||||
if (requirement == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsRequirementSatisfied(requirement, inventory))
|
||||
{
|
||||
return EventOptionAvailability.Blocked(BuildBlockedReason(requirement));
|
||||
}
|
||||
}
|
||||
|
||||
return EventOptionAvailability.Selectable();
|
||||
}
|
||||
|
||||
public EventOptionExecutionResult Execute(
|
||||
EventItem eventItem,
|
||||
int optionIndex,
|
||||
RunNodeExecutionContext context,
|
||||
BackpackInventoryData workingInventory)
|
||||
{
|
||||
if (eventItem == null || workingInventory == null)
|
||||
{
|
||||
return EventOptionExecutionResult.Rejected("事件数据无效");
|
||||
}
|
||||
|
||||
if (optionIndex < 0 || optionIndex >= eventItem.Options.Length)
|
||||
{
|
||||
return EventOptionExecutionResult.Rejected("事件选项索引无效");
|
||||
}
|
||||
|
||||
EventOption option = eventItem.Options[optionIndex];
|
||||
EventOptionAvailability availability = EvaluateOption(option, workingInventory);
|
||||
if (!availability.IsSelectable)
|
||||
{
|
||||
return EventOptionExecutionResult.Rejected(availability.BlockedReason);
|
||||
}
|
||||
|
||||
ApplyEffects(option.CostEffects, eventItem, optionIndex, context, workingInventory, isRewardPhase: false);
|
||||
|
||||
bool isProbabilitySuccess = RollProbability(eventItem.Id, optionIndex, context, option.Probability);
|
||||
if (isProbabilitySuccess)
|
||||
{
|
||||
ApplyEffects(option.RewardEffects, eventItem, optionIndex, context, workingInventory, isRewardPhase: true);
|
||||
}
|
||||
|
||||
return EventOptionExecutionResult.Accepted(isProbabilitySuccess);
|
||||
}
|
||||
|
||||
private void ApplyEffects(
|
||||
EventEffectBase[] effects,
|
||||
EventItem eventItem,
|
||||
int optionIndex,
|
||||
RunNodeExecutionContext context,
|
||||
BackpackInventoryData workingInventory,
|
||||
bool isRewardPhase)
|
||||
{
|
||||
if (effects == null || effects.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < effects.Length; i++)
|
||||
{
|
||||
EventEffectBase effect = effects[i];
|
||||
if (effect == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (effect.EffectType)
|
||||
{
|
||||
case EventEffectType.AddGold:
|
||||
ApplyAddGoldEffect((AddGoldParam)effect.Param, workingInventory);
|
||||
break;
|
||||
case EventEffectType.RemoveRandomComps:
|
||||
ApplyRemoveRandomComponentsEffect(
|
||||
(RemoveRandomCompsParam)effect.Param,
|
||||
eventItem.Id,
|
||||
optionIndex,
|
||||
context,
|
||||
workingInventory,
|
||||
i);
|
||||
break;
|
||||
case EventEffectType.AddRandomComps:
|
||||
ApplyAddRandomComponentsEffect(
|
||||
(AddRandomCompsParam)effect.Param,
|
||||
eventItem.Id,
|
||||
optionIndex,
|
||||
context,
|
||||
workingInventory,
|
||||
i);
|
||||
break;
|
||||
case EventEffectType.DamageRandomTowersEndurance:
|
||||
ApplyDamageRandomTowerEnduranceEffect(
|
||||
(DamageRandomTowerEnduranceParam)effect.Param,
|
||||
eventItem.Id,
|
||||
optionIndex,
|
||||
context,
|
||||
workingInventory,
|
||||
i);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported event effect at runtime: {effect.EffectType} (rewardPhase={isRewardPhase}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyAddGoldEffect(AddGoldParam param, BackpackInventoryData workingInventory)
|
||||
{
|
||||
int nextGold = workingInventory.Gold + (param?.Count ?? 0);
|
||||
if (nextGold < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Event gold effect would reduce gold below zero.");
|
||||
}
|
||||
|
||||
workingInventory.Gold = nextGold;
|
||||
}
|
||||
|
||||
private static void ApplyRemoveRandomComponentsEffect(
|
||||
RemoveRandomCompsParam param,
|
||||
int eventId,
|
||||
int optionIndex,
|
||||
RunNodeExecutionContext context,
|
||||
BackpackInventoryData workingInventory,
|
||||
int effectIndex)
|
||||
{
|
||||
int removeCount = Mathf.Max(0, param?.Count ?? 0);
|
||||
if (removeCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<TowerCompItemData> candidates = CollectLooseComponents(workingInventory, param.Rarity);
|
||||
if (candidates.Count < removeCount)
|
||||
{
|
||||
throw new InvalidOperationException("Event component removal effect does not have enough candidates.");
|
||||
}
|
||||
|
||||
System.Random random = CreateRandom(context, eventId, optionIndex, effectIndex, 101);
|
||||
Shuffle(candidates, random);
|
||||
for (int i = 0; i < removeCount; i++)
|
||||
{
|
||||
RemoveComponentByInstanceId(workingInventory, candidates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyAddRandomComponentsEffect(
|
||||
AddRandomCompsParam param,
|
||||
int eventId,
|
||||
int optionIndex,
|
||||
RunNodeExecutionContext context,
|
||||
BackpackInventoryData workingInventory,
|
||||
int effectIndex)
|
||||
{
|
||||
int addCount = Mathf.Max(0, param?.Count ?? 0);
|
||||
if (addCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameEntry.InventoryGeneration == null)
|
||||
{
|
||||
throw new InvalidOperationException("Event component generation requires InventoryGenerationComponent.");
|
||||
}
|
||||
|
||||
IReadOnlyList<TowerCompItemData> generatedComponents = GameEntry.InventoryGeneration.BuildEventRewardComponents(
|
||||
addCount,
|
||||
param.MinRarity,
|
||||
param.MaxRarity,
|
||||
context?.RunSeed ?? 0,
|
||||
context?.SequenceIndex ?? -1,
|
||||
eventId,
|
||||
optionIndex,
|
||||
effectIndex);
|
||||
for (int i = 0; i < generatedComponents.Count; i++)
|
||||
{
|
||||
AddComponentToInventory(workingInventory, generatedComponents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDamageRandomTowerEnduranceEffect(
|
||||
DamageRandomTowerEnduranceParam param,
|
||||
int eventId,
|
||||
int optionIndex,
|
||||
RunNodeExecutionContext context,
|
||||
BackpackInventoryData workingInventory,
|
||||
int effectIndex)
|
||||
{
|
||||
int towerCount = Mathf.Max(0, param?.Count ?? 0);
|
||||
float enduranceLoss = Mathf.Max(0, param?.Amount ?? 0);
|
||||
if (towerCount <= 0 || enduranceLoss <= 0f || workingInventory.Towers == null || workingInventory.Towers.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<long> candidateTowerIds = new List<long>(workingInventory.Towers.Count);
|
||||
for (int i = 0; i < workingInventory.Towers.Count; i++)
|
||||
{
|
||||
TowerItemData tower = workingInventory.Towers[i];
|
||||
if (tower != null && tower.InstanceId > 0)
|
||||
{
|
||||
candidateTowerIds.Add(tower.InstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
if (candidateTowerIds.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
System.Random random = CreateRandom(context, eventId, optionIndex, effectIndex, 211);
|
||||
Shuffle(candidateTowerIds, random);
|
||||
int resolvedCount = Mathf.Min(towerCount, candidateTowerIds.Count);
|
||||
List<long> selectedTowerIds = candidateTowerIds.GetRange(0, resolvedCount);
|
||||
InventoryTowerEnduranceUtility.ReduceTowerEndurance(workingInventory, selectedTowerIds, enduranceLoss);
|
||||
}
|
||||
|
||||
private static bool IsRequirementSatisfied(EventRequirementBase requirement, BackpackInventoryData inventory)
|
||||
{
|
||||
switch (requirement.RequirementType)
|
||||
{
|
||||
case EventRequirementType.GoldAtLeast:
|
||||
return (inventory?.Gold ?? 0) >= ((GoldAtLeastParam)requirement.Param).Gold;
|
||||
case EventRequirementType.CompCountAtLeast:
|
||||
CompCountAtLeastParam compParam = (CompCountAtLeastParam)requirement.Param;
|
||||
return CollectLooseComponents(inventory, compParam.Rarity).Count >= compParam.Count;
|
||||
case EventRequirementType.TowerCountAtLeast:
|
||||
return (inventory?.Towers?.Count ?? 0) >= ((TowerCountAtLeastParam)requirement.Param).Count;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported event requirement at runtime: {requirement.RequirementType}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildBlockedReason(EventRequirementBase requirement)
|
||||
{
|
||||
switch (requirement.RequirementType)
|
||||
{
|
||||
case EventRequirementType.GoldAtLeast:
|
||||
return $"需要至少 {((GoldAtLeastParam)requirement.Param).Gold} 金币";
|
||||
case EventRequirementType.CompCountAtLeast:
|
||||
CompCountAtLeastParam compParam = (CompCountAtLeastParam)requirement.Param;
|
||||
return $"需要至少 {compParam.Count} 个未装配的{GetRarityText(compParam.Rarity)}组件";
|
||||
case EventRequirementType.TowerCountAtLeast:
|
||||
return $"需要至少 {((TowerCountAtLeastParam)requirement.Param).Count} 座防御塔";
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported event requirement at runtime: {requirement.RequirementType}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RollProbability(int eventId, int optionIndex, RunNodeExecutionContext context, float probability)
|
||||
{
|
||||
float clampedProbability = Mathf.Clamp01(probability);
|
||||
if (clampedProbability >= 1f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (clampedProbability <= 0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
System.Random random = CreateRandom(context, eventId, optionIndex, 0, 17);
|
||||
return random.NextDouble() <= clampedProbability;
|
||||
}
|
||||
|
||||
private static System.Random CreateRandom(
|
||||
RunNodeExecutionContext context,
|
||||
int eventId,
|
||||
int optionIndex,
|
||||
int effectIndex,
|
||||
int salt)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int seed = 17;
|
||||
seed = seed * 31 + (context?.RunSeed ?? 0);
|
||||
seed = seed * 31 + (context?.SequenceIndex ?? -1);
|
||||
seed = seed * 31 + eventId;
|
||||
seed = seed * 31 + optionIndex;
|
||||
seed = seed * 31 + effectIndex;
|
||||
seed = seed * 31 + salt;
|
||||
return new System.Random(seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<TowerCompItemData> CollectLooseComponents(
|
||||
BackpackInventoryData inventory,
|
||||
RarityType rarity)
|
||||
{
|
||||
List<TowerCompItemData> result = new List<TowerCompItemData>();
|
||||
if (inventory == null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
CollectFromList(result, inventory.MuzzleComponents, normalizedRarity);
|
||||
CollectFromList(result, inventory.BearingComponents, normalizedRarity);
|
||||
CollectFromList(result, inventory.BaseComponents, normalizedRarity);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void CollectFromList<TComp>(
|
||||
List<TowerCompItemData> destination,
|
||||
List<TComp> source,
|
||||
RarityType rarity)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
TComp component = source[i];
|
||||
if (component == null || component.IsAssembledIntoTower)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (InventoryRarityRuleService.NormalizeComponentRarity(component.Rarity) != rarity)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
destination.Add(component);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveComponentByInstanceId(BackpackInventoryData inventory, TowerCompItemData component)
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case MuzzleCompItemData muzzleComp:
|
||||
RemoveByInstanceId(inventory.MuzzleComponents, muzzleComp.InstanceId);
|
||||
break;
|
||||
case BearingCompItemData bearingComp:
|
||||
RemoveByInstanceId(inventory.BearingComponents, bearingComp.InstanceId);
|
||||
break;
|
||||
case BaseCompItemData baseComp:
|
||||
RemoveByInstanceId(inventory.BaseComponents, baseComp.InstanceId);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported component type for event removal: {component?.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddComponentToInventory(BackpackInventoryData inventory, TowerCompItemData component)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (component)
|
||||
{
|
||||
case MuzzleCompItemData muzzleComp:
|
||||
inventory.MuzzleComponents.Add(InventoryCloneUtility.CloneMuzzleComp(muzzleComp));
|
||||
break;
|
||||
case BearingCompItemData bearingComp:
|
||||
inventory.BearingComponents.Add(InventoryCloneUtility.CloneBearingComp(bearingComp));
|
||||
break;
|
||||
case BaseCompItemData baseComp:
|
||||
inventory.BaseComponents.Add(InventoryCloneUtility.CloneBaseComp(baseComp));
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported component type for event addition: {component.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveByInstanceId<TComp>(List<TComp> components, long instanceId)
|
||||
where TComp : TowerCompItemData
|
||||
{
|
||||
if (components == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < components.Count; i++)
|
||||
{
|
||||
TComp component = components[i];
|
||||
if (component != null && component.InstanceId == instanceId)
|
||||
{
|
||||
components.RemoveAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to remove component instance #{instanceId} from inventory.");
|
||||
}
|
||||
|
||||
private static void Shuffle<T>(IList<T> values, System.Random random)
|
||||
{
|
||||
for (int i = values.Count - 1; i > 0; i--)
|
||||
{
|
||||
int swapIndex = random.Next(0, i + 1);
|
||||
(values[i], values[swapIndex]) = (values[swapIndex], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRarityText(RarityType rarity)
|
||||
{
|
||||
return InventoryRarityRuleService.NormalizeComponentRarity(rarity) switch
|
||||
{
|
||||
RarityType.White => "白色",
|
||||
RarityType.Green => "绿色",
|
||||
RarityType.Blue => "蓝色",
|
||||
RarityType.Purple => "紫色",
|
||||
RarityType.Red => "红色",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EventOptionAvailability
|
||||
{
|
||||
private EventOptionAvailability(bool isSelectable, string blockedReason)
|
||||
{
|
||||
IsSelectable = isSelectable;
|
||||
BlockedReason = blockedReason ?? string.Empty;
|
||||
}
|
||||
|
||||
public bool IsSelectable { get; }
|
||||
|
||||
public string BlockedReason { get; }
|
||||
|
||||
public static EventOptionAvailability Selectable()
|
||||
{
|
||||
return new EventOptionAvailability(true, string.Empty);
|
||||
}
|
||||
|
||||
public static EventOptionAvailability Blocked(string blockedReason)
|
||||
{
|
||||
return new EventOptionAvailability(false, blockedReason);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EventOptionExecutionResult
|
||||
{
|
||||
private EventOptionExecutionResult(bool isAccepted, bool isProbabilitySuccess, string failureReason)
|
||||
{
|
||||
IsAccepted = isAccepted;
|
||||
IsProbabilitySuccess = isProbabilitySuccess;
|
||||
FailureReason = failureReason ?? string.Empty;
|
||||
}
|
||||
|
||||
public bool IsAccepted { get; }
|
||||
|
||||
public bool IsProbabilitySuccess { get; }
|
||||
|
||||
public string FailureReason { get; }
|
||||
|
||||
public static EventOptionExecutionResult Accepted(bool isProbabilitySuccess)
|
||||
{
|
||||
return new EventOptionExecutionResult(true, isProbabilitySuccess, string.Empty);
|
||||
}
|
||||
|
||||
public static EventOptionExecutionResult Rejected(string failureReason)
|
||||
{
|
||||
return new EventOptionExecutionResult(false, false, failureReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class CompCountAtLeastRequirement : EventRequirementBase
|
||||
{
|
||||
public override EventRequirementType RequirementType => EventRequirementType.CompCountAtLeast;
|
||||
public override EventRequirementParam Param => _param;
|
||||
private CompCountAtLeastParam _param;
|
||||
|
||||
public CompCountAtLeastRequirement(CompCountAtLeastParam param)
|
||||
{
|
||||
_param = param;
|
||||
}
|
||||
}
|
||||
|
||||
public class CompCountAtLeastParam : EventRequirementParam
|
||||
{
|
||||
public int Count;
|
||||
public RarityType Rarity;
|
||||
|
||||
public CompCountAtLeastParam(int count, RarityType rarity)
|
||||
{
|
||||
Count = count;
|
||||
Rarity = rarity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EventRequirementBase
|
||||
{
|
||||
public abstract EventRequirementType RequirementType { get; }
|
||||
public abstract EventRequirementParam Param { get; }
|
||||
}
|
||||
|
||||
public class EventRequirementParam
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class GoldAtLeastRequirement : EventRequirementBase
|
||||
{
|
||||
public override EventRequirementType RequirementType => EventRequirementType.GoldAtLeast;
|
||||
public override EventRequirementParam Param => _param;
|
||||
private GoldAtLeastParam _param;
|
||||
|
||||
public GoldAtLeastRequirement(GoldAtLeastParam param)
|
||||
{
|
||||
_param = param;
|
||||
}
|
||||
}
|
||||
|
||||
public class GoldAtLeastParam : EventRequirementParam
|
||||
{
|
||||
public int Gold;
|
||||
|
||||
public GoldAtLeastParam(int gold)
|
||||
{
|
||||
Gold = gold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class HasRelicRequirement : EventRequirementBase
|
||||
{
|
||||
public override EventRequirementType RequirementType => EventRequirementType.HasRelic;
|
||||
public override EventRequirementParam Param => _param;
|
||||
private HasRelicParam _param;
|
||||
|
||||
public HasRelicRequirement(HasRelicParam param)
|
||||
{
|
||||
_param = param;
|
||||
}
|
||||
}
|
||||
|
||||
public class HasRelicParam : EventRequirementParam
|
||||
{
|
||||
public int RelicId;
|
||||
|
||||
public HasRelicParam(int relicId)
|
||||
{
|
||||
RelicId = relicId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public class TowerCountAtLeastRequirement : EventRequirementBase
|
||||
{
|
||||
public override EventRequirementType RequirementType => EventRequirementType.TowerCountAtLeast;
|
||||
public override EventRequirementParam Param => _param;
|
||||
private TowerCountAtLeastParam _param;
|
||||
|
||||
public TowerCountAtLeastRequirement(TowerCountAtLeastParam param)
|
||||
{
|
||||
_param = param;
|
||||
}
|
||||
}
|
||||
|
||||
public class TowerCountAtLeastParam : EventRequirementParam
|
||||
{
|
||||
public int Count;
|
||||
|
||||
public TowerCountAtLeastParam(int count)
|
||||
{
|
||||
Count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class InventoryRarityRuleService
|
||||
{
|
||||
public static RarityType NormalizeComponentRarity(RarityType rarity)
|
||||
{
|
||||
if (rarity < RarityType.White || rarity > RarityType.Red)
|
||||
{
|
||||
return RarityType.White;
|
||||
}
|
||||
|
||||
return rarity;
|
||||
}
|
||||
|
||||
public static RarityType ResolveTowerRarity(
|
||||
RarityType muzzleRarity,
|
||||
RarityType bearingRarity,
|
||||
RarityType baseRarity)
|
||||
{
|
||||
int normalizedMuzzle = (int)NormalizeComponentRarity(muzzleRarity);
|
||||
int normalizedBearing = (int)NormalizeComponentRarity(bearingRarity);
|
||||
int normalizedBase = (int)NormalizeComponentRarity(baseRarity);
|
||||
|
||||
float average = (normalizedMuzzle + normalizedBearing + normalizedBase) / 3f;
|
||||
int floored = Mathf.FloorToInt(average);
|
||||
int clamped = Mathf.Clamp(floored, (int)RarityType.White, (int)RarityType.Red);
|
||||
return (RarityType)clamped;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum ParticipantTowerAssignFailureReason
|
||||
{
|
||||
None = 0,
|
||||
TowerMissing = 1,
|
||||
InvalidTower = 2,
|
||||
AlreadyAssigned = 3,
|
||||
ParticipantAreaFull = 4
|
||||
}
|
||||
|
||||
public sealed class ParticipantTowerAssignResult
|
||||
{
|
||||
public long TowerInstanceId { get; set; }
|
||||
|
||||
public bool IsSuccess => FailureReason == ParticipantTowerAssignFailureReason.None;
|
||||
|
||||
public ParticipantTowerAssignFailureReason FailureReason { get; set; }
|
||||
|
||||
public CombatParticipantTowerValidationFailureReason ValidationFailureReason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class TagRuntimeData
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public int TotalStack { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TowerTagAggregationService
|
||||
{
|
||||
public static TagRuntimeData[] AggregateTowerTags(params TagType[][] componentTags)
|
||||
{
|
||||
Dictionary<TagType, int> stackByTag = new Dictionary<TagType, int>();
|
||||
if (componentTags != null)
|
||||
{
|
||||
foreach (var tags in componentTags)
|
||||
{
|
||||
if (tags == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var tagType in tags)
|
||||
{
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stackByTag.TryGetValue(tagType, out int stack))
|
||||
{
|
||||
stackByTag[tagType] = stack + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
stackByTag[tagType] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stackByTag.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
List<TagRuntimeData> runtimes = new List<TagRuntimeData>(stackByTag.Count);
|
||||
foreach (var pair in stackByTag)
|
||||
{
|
||||
runtimes.Add(new TagRuntimeData
|
||||
{
|
||||
TagType = pair.Key,
|
||||
TotalStack = Math.Max(1, pair.Value)
|
||||
});
|
||||
}
|
||||
|
||||
runtimes.Sort((left, right) => left.TagType.CompareTo(right.TagType));
|
||||
return runtimes.ToArray();
|
||||
}
|
||||
|
||||
public static TagRuntimeData[] BuildRuntimeTagsFromUniqueTags(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||
foreach (var tagType in tags)
|
||||
{
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueTags.Add(tagType);
|
||||
}
|
||||
|
||||
if (uniqueTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagType[] orderedTags = new TagType[uniqueTags.Count];
|
||||
uniqueTags.CopyTo(orderedTags);
|
||||
Array.Sort(orderedTags);
|
||||
|
||||
TagRuntimeData[] runtimes = new TagRuntimeData[orderedTags.Length];
|
||||
for (int i = 0; i < orderedTags.Length; i++)
|
||||
{
|
||||
runtimes[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = orderedTags[i],
|
||||
TotalStack = 1
|
||||
};
|
||||
}
|
||||
|
||||
return runtimes;
|
||||
}
|
||||
|
||||
public static TagType[] FlattenUniqueTags(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
List<TagType> tags = new List<TagType>(tagRuntimes.Count);
|
||||
foreach (var runtime in tagRuntimes)
|
||||
{
|
||||
if (runtime == null || runtime.TagType == TagType.None || !Enum.IsDefined(typeof(TagType), runtime.TagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tags.Add(runtime.TagType);
|
||||
}
|
||||
|
||||
if (tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
tags.Sort();
|
||||
return tags.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class EnemyStatusTagRegistry
|
||||
{
|
||||
// Only tags that create or tick enemy-held status state belong here.
|
||||
private static readonly Dictionary<TagType, IEnemyStatusTagEffect> EffectsByTag =
|
||||
new Dictionary<TagType, IEnemyStatusTagEffect>
|
||||
{
|
||||
[TagType.Fire] = new FireTagEffect(),
|
||||
[TagType.Ice] = new IceTagEffect()
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<TagType, IEnemyStatusTagEffect> Effects => EffectsByTag;
|
||||
|
||||
public static bool TryGetEffect(TagType tagType, out IEnemyStatusTagEffect effect)
|
||||
{
|
||||
return EffectsByTag.TryGetValue(tagType, out effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class AttackShapeTagEffectHandler
|
||||
{
|
||||
public static void Apply(HitContext hitContext, TagTriggerPhase triggerPhase)
|
||||
{
|
||||
if (hitContext?.AttackPayload?.TagRuntimes == null || hitContext.AttackPayload.TagRuntimes.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < hitContext.AttackPayload.TagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = hitContext.AttackPayload.TagRuntimes[i];
|
||||
if (runtime == null || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.AttackShape ||
|
||||
definition.TriggerPhase != triggerPhase ||
|
||||
definition.Config == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (runtime.TagType)
|
||||
{
|
||||
case TagType.BurnSpread:
|
||||
case TagType.IgniteBurst:
|
||||
case TagType.FreezeMask:
|
||||
case TagType.Pierce:
|
||||
case TagType.Overpenetrate:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class NumericTagEffectHandler
|
||||
{
|
||||
public static void ApplyBeforeHit(HitContext hitContext)
|
||||
{
|
||||
if (hitContext == null || hitContext.AttackPayload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TagRuntimeData[] tagRuntimes = hitContext.AttackPayload.TagRuntimes;
|
||||
if (tagRuntimes == null || tagRuntimes.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = tagRuntimes[i];
|
||||
if (runtime == null || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.NumericModifier ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnBeforeHit ||
|
||||
definition.Config == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (runtime.TagType)
|
||||
{
|
||||
case TagType.Crit:
|
||||
ApplyCrit(hitContext, runtime.TotalStack, definition.Config as CritTagConfig);
|
||||
break;
|
||||
case TagType.Execution:
|
||||
ApplyExecution(hitContext, runtime.TotalStack, definition.Config as ExecutionTagConfig);
|
||||
break;
|
||||
case TagType.Shatter:
|
||||
ApplyShatter(hitContext, runtime.TotalStack, definition.Config as ShatterTagConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hitContext.IsKilled = hitContext.TargetCurrentHealthBeforeHit > 0 &&
|
||||
hitContext.FinalDamage >= hitContext.TargetCurrentHealthBeforeHit;
|
||||
}
|
||||
|
||||
private static void ApplyCrit(HitContext hitContext, int stack, CritTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float chance = Mathf.Clamp01(stack * config.CritChancePerStack);
|
||||
float resolvedCritRoll = hitContext.CritRoll ?? Random.value;
|
||||
if (resolvedCritRoll > chance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * config.CritDamageMultiplier));
|
||||
hitContext.IsCriticalHit = true;
|
||||
}
|
||||
|
||||
private static void ApplyExecution(
|
||||
HitContext hitContext,
|
||||
int stack,
|
||||
ExecutionTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0 || hitContext.TargetMaxHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float healthRatio = Mathf.Clamp01((float)Mathf.Max(0, hitContext.TargetCurrentHealthBeforeHit) / hitContext.TargetMaxHealth);
|
||||
if (healthRatio > config.TargetHealthThreshold)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float multiplier = 1f + stack * config.DamageBonusPerStack;
|
||||
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * multiplier));
|
||||
}
|
||||
|
||||
private static void ApplyShatter(HitContext hitContext, int stack, ShatterTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.RequiresSlowedTarget && !hitContext.HasSlowStatusBeforeHit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float multiplier = 1f + stack * config.DamageBonusPerStack;
|
||||
hitContext.FinalDamage = Mathf.Max(0, Mathf.RoundToInt(hitContext.FinalDamage * multiplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EnemyStatusTagStateBase
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class FireTagState : EnemyStatusTagStateBase
|
||||
{
|
||||
public float RemainingDuration { get; set; }
|
||||
public float DamagePerSecond { get; set; }
|
||||
public float PendingDamage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class IceTagState : EnemyStatusTagStateBase
|
||||
{
|
||||
public float RemainingDuration { get; set; }
|
||||
public float SlowMultiplier { get; set; } = 1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EnemyStatusTagEffectBase : IEnemyStatusTagEffect
|
||||
{
|
||||
public abstract TagType TagType { get; }
|
||||
|
||||
public virtual void Apply(HitContext hitContext, TagRuntimeData runtimeData)
|
||||
{
|
||||
_ = hitContext;
|
||||
_ = runtimeData;
|
||||
}
|
||||
|
||||
public virtual bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
|
||||
{
|
||||
_ = runtime;
|
||||
_ = deltaTime;
|
||||
_ = applyDamage;
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime)
|
||||
{
|
||||
_ = runtime;
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class FireTagEffect : EnemyStatusTagEffectBase
|
||||
{
|
||||
public override TagType TagType => TagType.Fire;
|
||||
|
||||
public override void Apply(HitContext hitContext, TagRuntimeData runtimeData)
|
||||
{
|
||||
Debug.Assert(TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition));
|
||||
Debug.Assert(hitContext != null);
|
||||
Debug.Assert(hitContext.TargetStatusRuntime != null);
|
||||
|
||||
FireTagConfig config = definition.Config as FireTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime;
|
||||
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
|
||||
int effectiveStack = Mathf.Max(0, Mathf.Min(runtimeData.TotalStack, config.MaxEffectiveStack));
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext();
|
||||
float burnDamagePerSecond = Mathf.Max(
|
||||
0f,
|
||||
config.BurnDamagePerSecondPerStack * effectiveStack + modifierContext.BonusBurnDamagePerSecond);
|
||||
|
||||
if (burnDamagePerSecond <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.RemainingDuration = Mathf.Max(
|
||||
state.RemainingDuration,
|
||||
config.BurnDurationSeconds + modifierContext.BonusBurnDurationSeconds);
|
||||
state.DamagePerSecond = Mathf.Max(state.DamagePerSecond, burnDamagePerSecond);
|
||||
runtime.Activate(TagType);
|
||||
}
|
||||
|
||||
public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
|
||||
{
|
||||
FireTagState state = runtime.GetState<FireTagState>(TagType);
|
||||
Debug.Assert(state != null);
|
||||
|
||||
float resolvedDeltaTime = Mathf.Max(0f, deltaTime);
|
||||
if (resolvedDeltaTime <= 0f || state.RemainingDuration <= 0f)
|
||||
{
|
||||
return state.RemainingDuration > 0f;
|
||||
}
|
||||
|
||||
float appliedDuration = Mathf.Min(resolvedDeltaTime, state.RemainingDuration);
|
||||
state.RemainingDuration = Mathf.Max(0f, state.RemainingDuration - appliedDuration);
|
||||
state.PendingDamage += state.DamagePerSecond * appliedDuration;
|
||||
|
||||
int resolvedDamage = Mathf.FloorToInt(state.PendingDamage);
|
||||
if (resolvedDamage > 0)
|
||||
{
|
||||
applyDamage?.Invoke(resolvedDamage);
|
||||
state.PendingDamage -= resolvedDamage;
|
||||
}
|
||||
|
||||
if (state.RemainingDuration > 0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
state.PendingDamage = 0f;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public interface IEnemyStatusTagEffect
|
||||
{
|
||||
TagType TagType { get; }
|
||||
void Apply(HitContext hitContext, TagRuntimeData runtimeData);
|
||||
bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage);
|
||||
float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class IceTagEffect : EnemyStatusTagEffectBase
|
||||
{
|
||||
public override TagType TagType => TagType.Ice;
|
||||
|
||||
public override void Apply(HitContext hitContext, TagRuntimeData runtimeData)
|
||||
{
|
||||
Debug.Assert(hitContext != null);
|
||||
Debug.Assert(hitContext.TargetStatusRuntime != null);
|
||||
Debug.Assert(runtimeData != null);
|
||||
Debug.Assert(runtimeData.TotalStack > 0);
|
||||
Debug.Assert(TagDefinitionRegistry.TryGetDefinition(TagType, out TagDefinition definition));
|
||||
|
||||
IceTagConfig config = definition.Config as IceTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnemyTagStatusRuntime runtime = hitContext.TargetStatusRuntime;
|
||||
IceTagState state = runtime.GetOrCreateState<IceTagState>(TagType);
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? new HitStatusModifierContext();
|
||||
float slowRatio = runtimeData.TotalStack * config.SlowRatioPerStack + modifierContext.BonusSlowRatio;
|
||||
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier, 1f - slowRatio);
|
||||
state.RemainingDuration = Mathf.Max(
|
||||
state.RemainingDuration,
|
||||
config.SlowDurationSeconds + modifierContext.BonusSlowDurationSeconds);
|
||||
state.SlowMultiplier = Mathf.Min(state.SlowMultiplier, slowMultiplier);
|
||||
runtime.Activate(TagType);
|
||||
}
|
||||
|
||||
public override bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage)
|
||||
{
|
||||
_ = applyDamage;
|
||||
IceTagState state = runtime.GetState<IceTagState>(TagType);
|
||||
Debug.Assert(state != null);
|
||||
|
||||
state.RemainingDuration = Mathf.Max(0f, state.RemainingDuration - Mathf.Max(0f, deltaTime));
|
||||
return state.RemainingDuration > 0f;
|
||||
}
|
||||
|
||||
public override float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime)
|
||||
{
|
||||
IceTagState state = runtime.GetState<IceTagState>(TagType);
|
||||
return state == null ? 1f : state.SlowMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagEffectResolver
|
||||
{
|
||||
private static readonly HitStatusModifierContext EmptyStatusModifierContext = new HitStatusModifierContext();
|
||||
|
||||
public static HitContext ResolveBeforeHit(HitContext hitContext)
|
||||
{
|
||||
if (hitContext == null)
|
||||
{
|
||||
return new HitContext
|
||||
{
|
||||
AttackPayload = new AttackPayload()
|
||||
};
|
||||
}
|
||||
|
||||
AttackPayload resolvedPayload = hitContext.AttackPayload?.Clone() ?? new AttackPayload();
|
||||
HitContext resolvedContext = new HitContext
|
||||
{
|
||||
AttackPayload = resolvedPayload,
|
||||
FinalDamage = Mathf.Max(0, resolvedPayload.BaseDamage),
|
||||
IsCriticalHit = false,
|
||||
IsKilled = hitContext.TargetCurrentHealthBeforeHit > 0 &&
|
||||
resolvedPayload.BaseDamage >= hitContext.TargetCurrentHealthBeforeHit,
|
||||
TargetEntityId = hitContext.TargetEntityId,
|
||||
TargetPosition = hitContext.TargetPosition,
|
||||
TargetCurrentHealthBeforeHit = hitContext.TargetCurrentHealthBeforeHit,
|
||||
TargetCurrentHealthAfterHit = hitContext.TargetCurrentHealthAfterHit,
|
||||
TargetMaxHealth = hitContext.TargetMaxHealth,
|
||||
TargetMoveSpeedMultiplierBeforeHit = hitContext.TargetMoveSpeedMultiplierBeforeHit,
|
||||
TargetStatusTagsBeforeHit = hitContext.TargetStatusTagsBeforeHit ?? System.Array.Empty<TagType>(),
|
||||
TargetStatusRuntime = hitContext.TargetStatusRuntime,
|
||||
CritRoll = hitContext.CritRoll,
|
||||
StatusModifierContext = new HitStatusModifierContext()
|
||||
};
|
||||
|
||||
NumericTagEffectHandler.ApplyBeforeHit(resolvedContext);
|
||||
return resolvedContext;
|
||||
}
|
||||
|
||||
public static void ApplyAfterHit(HitContext hitContext)
|
||||
{
|
||||
if (hitContext?.StatusModifierContext == null)
|
||||
{
|
||||
if (hitContext != null)
|
||||
{
|
||||
hitContext.StatusModifierContext = new HitStatusModifierContext();
|
||||
}
|
||||
}
|
||||
|
||||
hitContext?.StatusModifierContext?.Reset();
|
||||
ApplyStatusModifiers(hitContext);
|
||||
|
||||
if (hitContext?.AttackPayload != null && hitContext.TargetStatusRuntime != null)
|
||||
{
|
||||
TagRuntimeData[] tagRuntimes = hitContext.AttackPayload.TagRuntimes;
|
||||
if (tagRuntimes != null && tagRuntimes.Length > 0)
|
||||
{
|
||||
foreach (var tag in tagRuntimes)
|
||||
{
|
||||
if (tag == null || tag.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(tag.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.Status ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
|
||||
definition.Config == null ||
|
||||
!EnemyStatusTagRegistry.TryGetEffect(tag.TagType, out IEnemyStatusTagEffect effect))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
effect.Apply(hitContext, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AttackShapeTagEffectHandler.Apply(hitContext, TagTriggerPhase.OnAfterHit);
|
||||
}
|
||||
|
||||
public static void ApplyOnHit(HitContext hitContext)
|
||||
{
|
||||
AttackShapeTagEffectHandler.Apply(hitContext, TagTriggerPhase.OnHit);
|
||||
}
|
||||
|
||||
public static void ApplyOnKill(HitContext hitContext)
|
||||
{
|
||||
AttackShapeTagEffectHandler.Apply(hitContext, TagTriggerPhase.OnKill);
|
||||
}
|
||||
|
||||
public static int GetTagStack(TagRuntimeData[] tagRuntimes, TagType tagType)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Length <= 0 || tagType == TagType.None)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach (var tag in tagRuntimes)
|
||||
{
|
||||
if (tag == null || tag.TagType != tagType || tag.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return tag.TotalStack;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void ApplyStatusModifiers(HitContext hitContext)
|
||||
{
|
||||
if (hitContext?.AttackPayload?.TagRuntimes == null || hitContext.AttackPayload.TagRuntimes.Length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HitStatusModifierContext modifierContext = hitContext.StatusModifierContext ?? EmptyStatusModifierContext;
|
||||
for (int i = 0; i < hitContext.AttackPayload.TagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = hitContext.AttackPayload.TagRuntimes[i];
|
||||
if (runtime == null || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagDefinitionRegistry.TryGetDefinition(runtime.TagType, out TagDefinition definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.StatusModifier ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
|
||||
definition.Config == null ||
|
||||
!definition.Config.IsImplemented)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (runtime.TagType)
|
||||
{
|
||||
case TagType.Inferno:
|
||||
ApplyInfernoModifier(runtime.TotalStack, definition.Config as InfernoTagConfig, modifierContext);
|
||||
break;
|
||||
case TagType.AbsoluteZero:
|
||||
ApplyAbsoluteZeroModifier(runtime.TotalStack, definition.Config as AbsoluteZeroTagConfig, modifierContext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyInfernoModifier(int stack, InfernoTagConfig config, HitStatusModifierContext modifierContext)
|
||||
{
|
||||
if (config == null || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modifierContext.BonusBurnDurationSeconds += stack * config.BonusBurnDurationSeconds;
|
||||
modifierContext.BonusBurnDamagePerSecond += stack * config.BonusBurnDamagePerSecondPerStack;
|
||||
}
|
||||
|
||||
private static void ApplyAbsoluteZeroModifier(int stack, AbsoluteZeroTagConfig config, HitStatusModifierContext modifierContext)
|
||||
{
|
||||
if (config == null || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modifierContext.BonusSlowDurationSeconds += stack * config.BonusSlowDurationSeconds;
|
||||
modifierContext.BonusSlowRatio += stack * config.BonusSlowRatioPerStack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = System.Random;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class ComponentTagGenerationService
|
||||
{
|
||||
public static TagType[] ResolveComponentTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
InventoryTagRandomContext randomContext,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
TagType[] eligibleTags = GetEligibleTags(possibleTags, rarity, ruleLookup);
|
||||
if (eligibleTags.Length <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
Random random = new Random(BuildStableSeed(rarity, randomContext));
|
||||
int tagBudget = ResolveRarityTagBudget(rarity, random, rarityTagBudgetRulesByRarity);
|
||||
if (tagBudget <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
int finalCount = Math.Min(tagBudget, eligibleTags.Length);
|
||||
List<TagType> pool = new List<TagType>(eligibleTags);
|
||||
TagType[] result = new TagType[finalCount];
|
||||
for (int i = 0; i < finalCount; i++)
|
||||
{
|
||||
int index = RollWeightedIndex(pool, ruleLookup, random);
|
||||
result[i] = pool[index];
|
||||
pool.RemoveAt(index);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TagType[] ResolveComponentTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
InventoryTagSourceType sourceType,
|
||||
long itemInstanceId,
|
||||
int configId,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
return ResolveComponentTags(
|
||||
possibleTags,
|
||||
rarity,
|
||||
new InventoryTagRandomContext(0, sourceType, itemInstanceId, configId),
|
||||
rulesByTag,
|
||||
rarityTagBudgetRulesByRarity);
|
||||
}
|
||||
|
||||
public static TagType[] GetEligibleTags(
|
||||
IReadOnlyList<TagType> possibleTags,
|
||||
RarityType rarity,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> rulesByTag = null)
|
||||
{
|
||||
if (possibleTags == null || possibleTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup = rulesByTag ?? TagGenerationRuleRegistry.Rules;
|
||||
HashSet<TagType> uniqueTags = new HashSet<TagType>();
|
||||
|
||||
for (int i = 0; i < possibleTags.Count; i++)
|
||||
{
|
||||
TagType tagType = possibleTags[i];
|
||||
if (tagType == TagType.None || !Enum.IsDefined(typeof(TagType), tagType) || !IsSupportedLaunchTag(tagType))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryGetRule(tagType, ruleLookup, out TagGenerationRule rule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizedRarity < InventoryRarityRuleService.NormalizeComponentRarity(rule.MinRarity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uniqueTags.Add(tagType);
|
||||
}
|
||||
|
||||
if (uniqueTags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<TagType>();
|
||||
}
|
||||
|
||||
TagType[] result = new TagType[uniqueTags.Count];
|
||||
uniqueTags.CopyTo(result);
|
||||
Array.Sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int ResolveRarityTagBudget(
|
||||
RarityType rarity,
|
||||
Random random,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> rarityTagBudgetRulesByRarity = null)
|
||||
{
|
||||
RarityType normalizedRarity = InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> ruleLookup = rarityTagBudgetRulesByRarity ?? RarityTagBudgetRuleRegistry.Rules;
|
||||
RarityTagBudgetRule rule = GetRarityTagBudgetRule(normalizedRarity, ruleLookup);
|
||||
|
||||
Debug.Assert(rule.MinCount >= 0);
|
||||
Debug.Assert(rule.MaxCount >= rule.MinCount);
|
||||
|
||||
if (rule.MinCount == rule.MaxCount)
|
||||
{
|
||||
return rule.MinCount;
|
||||
}
|
||||
|
||||
random ??= new Random(0);
|
||||
return random.Next(rule.MinCount, rule.MaxCount + 1);
|
||||
}
|
||||
|
||||
private static bool IsSupportedLaunchTag(TagType tagType)
|
||||
{
|
||||
return TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition) &&
|
||||
definition.Config != null &&
|
||||
definition.Config.IsImplemented;
|
||||
}
|
||||
|
||||
private static bool TryGetRule(
|
||||
TagType tagType,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup,
|
||||
out TagGenerationRule rule)
|
||||
{
|
||||
if (ruleLookup != null && ruleLookup.TryGetValue(tagType, out rule))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
rule = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static RarityTagBudgetRule GetRarityTagBudgetRule(
|
||||
RarityType rarity,
|
||||
IReadOnlyDictionary<RarityType, RarityTagBudgetRule> ruleLookup)
|
||||
{
|
||||
Debug.Assert(ruleLookup != null);
|
||||
Debug.Assert(ruleLookup.TryGetValue(rarity, out _));
|
||||
return ruleLookup[rarity];
|
||||
}
|
||||
|
||||
private static int RollWeightedIndex(
|
||||
IReadOnlyList<TagType> pool,
|
||||
IReadOnlyDictionary<TagType, TagGenerationRule> ruleLookup,
|
||||
Random random)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRule rule = ruleLookup[pool[i]];
|
||||
Debug.Assert(rule.Weight > 0);
|
||||
totalWeight += rule.Weight;
|
||||
}
|
||||
|
||||
Debug.Assert(totalWeight > 0);
|
||||
|
||||
int roll = random.Next(1, totalWeight + 1);
|
||||
int cumulative = 0;
|
||||
for (int i = 0; i < pool.Count; i++)
|
||||
{
|
||||
TagGenerationRule rule = ruleLookup[pool[i]];
|
||||
cumulative += rule.Weight;
|
||||
if (roll <= cumulative)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return pool.Count - 1;
|
||||
}
|
||||
|
||||
private static int BuildStableSeed(RarityType rarity, InventoryTagRandomContext randomContext)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int seed = 17;
|
||||
seed = seed * 31 + randomContext.RunSeed;
|
||||
seed = seed * 31 + (int)InventoryRarityRuleService.NormalizeComponentRarity(rarity);
|
||||
seed = seed * 31 + (int)randomContext.SourceType;
|
||||
seed = seed * 31 + randomContext.ConfigId;
|
||||
seed = seed * 31 + (int)randomContext.ItemInstanceId;
|
||||
seed = seed * 31 + (int)(randomContext.ItemInstanceId >> 32);
|
||||
return seed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public readonly struct InventoryTagRandomContext
|
||||
{
|
||||
public InventoryTagRandomContext(int runSeed, InventoryTagSourceType sourceType, long itemInstanceId, int configId)
|
||||
{
|
||||
RunSeed = runSeed;
|
||||
SourceType = sourceType;
|
||||
ItemInstanceId = itemInstanceId;
|
||||
ConfigId = configId;
|
||||
}
|
||||
|
||||
public int RunSeed { get; }
|
||||
|
||||
public InventoryTagSourceType SourceType { get; }
|
||||
|
||||
public long ItemInstanceId { get; }
|
||||
|
||||
public int ConfigId { get; }
|
||||
|
||||
public static InventoryTagRandomContext CreateSeed(int runSeed, long itemInstanceId, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(runSeed, InventoryTagSourceType.Seed, itemInstanceId, configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateShop(int runSeed, int nodeSequenceIndex, int goodsIndex, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Shop,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, goodsIndex),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateDrop(int runSeed, int nodeSequenceIndex, int dropOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Drop,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, dropOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateReward(int runSeed, int nodeSequenceIndex, int rewardOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Reward,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, rewardOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static InventoryTagRandomContext CreateEvent(int runSeed, int nodeSequenceIndex, int eventOrdinal, int configId)
|
||||
{
|
||||
return new InventoryTagRandomContext(
|
||||
runSeed,
|
||||
InventoryTagSourceType.Event,
|
||||
ComposeLocalItemInstanceId(nodeSequenceIndex, eventOrdinal),
|
||||
configId);
|
||||
}
|
||||
|
||||
public static long ComposeLocalItemInstanceId(int nodeSequenceIndex, int localOrdinal)
|
||||
{
|
||||
long normalizedSequence = Math.Max(0, nodeSequenceIndex) + 1L;
|
||||
long normalizedOrdinal = Math.Max(0, localOrdinal) + 1L;
|
||||
return (normalizedSequence << 32) | (uint)normalizedOrdinal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class RarityTagBudgetRule
|
||||
{
|
||||
public RarityType Rarity { get; set; }
|
||||
public int MinCount { get; set; }
|
||||
public int MaxCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class RarityTagBudgetRuleRegistry
|
||||
{
|
||||
private static readonly Dictionary<RarityType, RarityTagBudgetRule> RulesByRarity = CreateDefaultRules();
|
||||
|
||||
public static IReadOnlyDictionary<RarityType, RarityTagBudgetRule> Rules => RulesByRarity;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
RulesByRarity.Clear();
|
||||
foreach (KeyValuePair<RarityType, RarityTagBudgetRule> pair in CreateDefaultRules())
|
||||
{
|
||||
RulesByRarity.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRRarityTagBudget> rows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
foreach (DRRarityTagBudget row in rows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRule(RarityType rarity, out RarityTagBudgetRule rule)
|
||||
{
|
||||
return RulesByRarity.TryGetValue(rarity, out rule);
|
||||
}
|
||||
|
||||
private static Dictionary<RarityType, RarityTagBudgetRule> CreateDefaultRules()
|
||||
{
|
||||
return new Dictionary<RarityType, RarityTagBudgetRule>
|
||||
{
|
||||
[RarityType.White] = CreateRule(RarityType.White, 0, 1),
|
||||
[RarityType.Green] = CreateRule(RarityType.Green, 0, 2),
|
||||
[RarityType.Blue] = CreateRule(RarityType.Blue, 1, 3),
|
||||
[RarityType.Purple] = CreateRule(RarityType.Purple, 1, 3),
|
||||
[RarityType.Red] = CreateRule(RarityType.Red, 2, 4)
|
||||
};
|
||||
}
|
||||
|
||||
private static RarityTagBudgetRule CreateRule(RarityType rarity, int minCount, int maxCount)
|
||||
{
|
||||
Debug.Assert(rarity >= RarityType.White && rarity <= RarityType.Red);
|
||||
Debug.Assert(minCount >= 0);
|
||||
Debug.Assert(maxCount >= minCount);
|
||||
|
||||
return new RarityTagBudgetRule
|
||||
{
|
||||
Rarity = rarity,
|
||||
MinCount = minCount,
|
||||
MaxCount = maxCount
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRRarityTagBudget row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(row.Rarity >= RarityType.White && row.Rarity <= RarityType.Red);
|
||||
Debug.Assert(row.MinCount >= 0);
|
||||
Debug.Assert(row.MaxCount >= row.MinCount);
|
||||
|
||||
RulesByRarity[row.Rarity] = CreateRule(row.Rarity, row.MinCount, row.MaxCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class TagGenerationRule
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public RarityType MinRarity { get; set; }
|
||||
public int Weight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagGenerationRuleRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagGenerationRule> RulesByTag = CreateDefaultRules();
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagGenerationRule> Rules => RulesByTag;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
RulesByTag.Clear();
|
||||
foreach (KeyValuePair<TagType, TagGenerationRule> pair in CreateDefaultRules())
|
||||
{
|
||||
RulesByTag.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRTag> rows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
foreach (DRTag row in rows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetRule(TagType tagType, out TagGenerationRule rule)
|
||||
{
|
||||
return RulesByTag.TryGetValue(tagType, out rule);
|
||||
}
|
||||
|
||||
private static Dictionary<TagType, TagGenerationRule> CreateDefaultRules()
|
||||
{
|
||||
return new Dictionary<TagType, TagGenerationRule>
|
||||
{
|
||||
[TagType.Fire] = CreateRule(TagType.Fire, RarityType.White, 20),
|
||||
[TagType.BurnSpread] = CreateRule(TagType.BurnSpread, RarityType.White, 20),
|
||||
[TagType.IgniteBurst] = CreateRule(TagType.IgniteBurst, RarityType.Green, 15),
|
||||
[TagType.Inferno] = CreateRule(TagType.Inferno, RarityType.Purple, 5),
|
||||
[TagType.Ice] = CreateRule(TagType.Ice, RarityType.White, 1),
|
||||
[TagType.FreezeMask] = CreateRule(TagType.FreezeMask, RarityType.White, 20),
|
||||
[TagType.Shatter] = CreateRule(TagType.Shatter, RarityType.Green, 15),
|
||||
[TagType.AbsoluteZero] = CreateRule(TagType.AbsoluteZero, RarityType.Purple, 1),
|
||||
[TagType.Pierce] = CreateRule(TagType.Pierce, RarityType.White, 20),
|
||||
[TagType.Crit] = CreateRule(TagType.Crit, RarityType.White, 20),
|
||||
[TagType.Overpenetrate] = CreateRule(TagType.Overpenetrate, RarityType.Green, 15),
|
||||
[TagType.Execution] = CreateRule(TagType.Execution, RarityType.Purple, 5)
|
||||
};
|
||||
}
|
||||
|
||||
private static TagGenerationRule CreateRule(TagType tagType, RarityType minRarity, int weight)
|
||||
{
|
||||
return new TagGenerationRule
|
||||
{
|
||||
TagType = tagType,
|
||||
MinRarity = minRarity,
|
||||
Weight = weight
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRTag row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(row.Weight > 0);
|
||||
|
||||
RulesByTag[row.TagType] = CreateRule(row.TagType, row.MinRarity, row.Weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class AbsoluteZeroTagConfig : TagConfigBase
|
||||
{
|
||||
public AbsoluteZeroTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float BonusSlowDurationSeconds { get; set; } = 1f;
|
||||
public float BonusSlowRatioPerStack { get; set; } = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class BurnSpreadTagConfig : TagConfigBase
|
||||
{
|
||||
public BurnSpreadTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class CritTagConfig : TagConfigBase
|
||||
{
|
||||
public CritTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float CritChancePerStack { get; set; } = 0.1f;
|
||||
public float CritDamageMultiplier { get; set; } = 1.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class ExecutionTagConfig : TagConfigBase
|
||||
{
|
||||
public ExecutionTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float TargetHealthThreshold { get; set; } = 0.3f;
|
||||
public float DamageBonusPerStack { get; set; } = 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class FireTagConfig : TagConfigBase
|
||||
{
|
||||
public FireTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float BurnDurationSeconds { get; set; } = 3f;
|
||||
public float BurnDamagePerSecondPerStack { get; set; } = 20f;
|
||||
public int MaxEffectiveStack { get; set; } = 5;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class FreezeMaskTagConfig : TagConfigBase
|
||||
{
|
||||
public FreezeMaskTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class IceTagConfig : TagConfigBase
|
||||
{
|
||||
public IceTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float SlowDurationSeconds { get; set; } = 2f;
|
||||
public float SlowRatioPerStack { get; set; } = 0.2f;
|
||||
public float MinMoveSpeedMultiplier { get; set; } = 0.4f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class IgniteBurstTagConfig : TagConfigBase
|
||||
{
|
||||
public IgniteBurstTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class InfernoTagConfig : TagConfigBase
|
||||
{
|
||||
public InfernoTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float BonusBurnDurationSeconds { get; set; } = 2f;
|
||||
public float BonusBurnDamagePerSecondPerStack { get; set; } = 20f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class OverpenetrateTagConfig : TagConfigBase
|
||||
{
|
||||
public OverpenetrateTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PierceTagConfig : TagConfigBase
|
||||
{
|
||||
public PierceTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class ShatterTagConfig : TagConfigBase
|
||||
{
|
||||
public ShatterTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public bool RequiresSlowedTarget { get; set; } = true;
|
||||
public float DamageBonusPerStack { get; set; } = 0.25f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class TagConfigBase
|
||||
{
|
||||
public bool IsImplemented { get; set; }
|
||||
|
||||
protected TagConfigBase(bool isImplemented)
|
||||
{
|
||||
IsImplemented = isImplemented;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class TagDefinition
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public TagCategory Category { get; set; }
|
||||
public TagTriggerPhase TriggerPhase { get; set; }
|
||||
public string Description { get; set; }
|
||||
public TagConfigBase Config { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagDefinitionRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagDefinition> DefinitionsByTag = CreateDefaultDefinitions();
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagDefinition> Definitions => DefinitionsByTag;
|
||||
|
||||
public static void ResetToDefaults()
|
||||
{
|
||||
DefinitionsByTag.Clear();
|
||||
foreach (KeyValuePair<TagType, TagDefinition> pair in CreateDefaultDefinitions())
|
||||
{
|
||||
DefinitionsByTag.Add(pair.Key, pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LoadFromRows(IEnumerable<DRTagConfig> rows)
|
||||
{
|
||||
ReloadFromRows(rows, null);
|
||||
}
|
||||
|
||||
public static bool TryGetDefinition(TagType tagType, out TagDefinition definition)
|
||||
{
|
||||
return DefinitionsByTag.TryGetValue(tagType, out definition);
|
||||
}
|
||||
|
||||
public static void ApplyTagRows(IEnumerable<DRTag> rows)
|
||||
{
|
||||
if (rows == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DRTag row in rows)
|
||||
{
|
||||
ApplyTagRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadFromRows(IEnumerable<DRTagConfig> tagConfigRows, IEnumerable<DRTag> tagRows)
|
||||
{
|
||||
ResetToDefaults();
|
||||
|
||||
if (tagConfigRows != null)
|
||||
{
|
||||
foreach (DRTagConfig row in tagConfigRows)
|
||||
{
|
||||
ApplyRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
if (tagRows != null)
|
||||
{
|
||||
foreach (DRTag row in tagRows)
|
||||
{
|
||||
ApplyTagRow(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<TagType, TagDefinition> CreateDefaultDefinitions()
|
||||
{
|
||||
return new Dictionary<TagType, TagDefinition>
|
||||
{
|
||||
[TagType.Fire] = CreateDefinition(TagType.Fire, TagCategory.Status, TagTriggerPhase.OnAfterHit, new FireTagConfig(true)),
|
||||
[TagType.BurnSpread] = CreateDefinition(TagType.BurnSpread, TagCategory.AttackShape, TagTriggerPhase.None, new BurnSpreadTagConfig(false)),
|
||||
[TagType.IgniteBurst] = CreateDefinition(TagType.IgniteBurst, TagCategory.AttackShape, TagTriggerPhase.None, new IgniteBurstTagConfig(false)),
|
||||
[TagType.Inferno] = CreateDefinition(TagType.Inferno, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit, new InfernoTagConfig(true)),
|
||||
[TagType.Ice] = CreateDefinition(TagType.Ice, TagCategory.Status, TagTriggerPhase.OnAfterHit, new IceTagConfig(true)),
|
||||
[TagType.FreezeMask] = CreateDefinition(TagType.FreezeMask, TagCategory.AttackShape, TagTriggerPhase.None, new FreezeMaskTagConfig(false)),
|
||||
[TagType.Shatter] = CreateDefinition(TagType.Shatter, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ShatterTagConfig(true)),
|
||||
[TagType.AbsoluteZero] = CreateDefinition(TagType.AbsoluteZero, TagCategory.StatusModifier, TagTriggerPhase.OnAfterHit, new AbsoluteZeroTagConfig(true)),
|
||||
[TagType.Pierce] = CreateDefinition(TagType.Pierce, TagCategory.AttackShape, TagTriggerPhase.None, new PierceTagConfig(false)),
|
||||
[TagType.Crit] = CreateDefinition(TagType.Crit, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new CritTagConfig(true)),
|
||||
[TagType.Overpenetrate] = CreateDefinition(TagType.Overpenetrate, TagCategory.AttackShape, TagTriggerPhase.None, new OverpenetrateTagConfig(false)),
|
||||
[TagType.Execution] = CreateDefinition(TagType.Execution, TagCategory.NumericModifier, TagTriggerPhase.OnBeforeHit, new ExecutionTagConfig(true))
|
||||
};
|
||||
}
|
||||
|
||||
private static TagDefinition CreateDefinition(
|
||||
TagType tagType,
|
||||
TagCategory category,
|
||||
TagTriggerPhase triggerPhase,
|
||||
TagConfigBase config)
|
||||
{
|
||||
return new TagDefinition
|
||||
{
|
||||
TagType = tagType,
|
||||
Category = category,
|
||||
TriggerPhase = triggerPhase,
|
||||
Description = string.Empty,
|
||||
Config = config
|
||||
};
|
||||
}
|
||||
|
||||
private static void ApplyRow(DRTagConfig row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(DefinitionsByTag.TryGetValue(row.TagType, out TagDefinition definition));
|
||||
|
||||
definition.TriggerPhase = row.TriggerPhase;
|
||||
definition.Description = row.Description ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(row.ParamJson))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JObject param = JObject.Parse(row.ParamJson);
|
||||
switch (row.TagType)
|
||||
{
|
||||
case TagType.Fire:
|
||||
ApplyFireConfig((FireTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Inferno:
|
||||
ApplyInfernoConfig((InfernoTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Ice:
|
||||
ApplyIceConfig((IceTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Shatter:
|
||||
ApplyShatterConfig((ShatterTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.AbsoluteZero:
|
||||
ApplyAbsoluteZeroConfig((AbsoluteZeroTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Crit:
|
||||
ApplyCritConfig((CritTagConfig)definition.Config, param);
|
||||
break;
|
||||
case TagType.Execution:
|
||||
ApplyExecutionConfig((ExecutionTagConfig)definition.Config, param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyTagRow(DRTag row)
|
||||
{
|
||||
Debug.Assert(row != null);
|
||||
Debug.Assert(row.Id > 0);
|
||||
Debug.Assert(DefinitionsByTag.TryGetValue(row.TagType, out TagDefinition definition));
|
||||
Debug.Assert(definition.Config != null);
|
||||
|
||||
definition.Config.IsImplemented = row.IsImplemented;
|
||||
}
|
||||
|
||||
private static void ApplyFireConfig(FireTagConfig config, JObject param)
|
||||
{
|
||||
config.BurnDurationSeconds = ReadFloat(param, nameof(FireTagConfig.BurnDurationSeconds), config.BurnDurationSeconds);
|
||||
config.BurnDamagePerSecondPerStack = ReadFloat(param, nameof(FireTagConfig.BurnDamagePerSecondPerStack), config.BurnDamagePerSecondPerStack);
|
||||
config.MaxEffectiveStack = ReadInt(param, nameof(FireTagConfig.MaxEffectiveStack), config.MaxEffectiveStack);
|
||||
}
|
||||
|
||||
private static void ApplyInfernoConfig(InfernoTagConfig config, JObject param)
|
||||
{
|
||||
config.BonusBurnDurationSeconds = ReadFloat(param, nameof(InfernoTagConfig.BonusBurnDurationSeconds), config.BonusBurnDurationSeconds);
|
||||
config.BonusBurnDamagePerSecondPerStack = ReadFloat(param, nameof(InfernoTagConfig.BonusBurnDamagePerSecondPerStack), config.BonusBurnDamagePerSecondPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyIceConfig(IceTagConfig config, JObject param)
|
||||
{
|
||||
config.SlowDurationSeconds = ReadFloat(param, nameof(IceTagConfig.SlowDurationSeconds), config.SlowDurationSeconds);
|
||||
config.SlowRatioPerStack = ReadFloat(param, nameof(IceTagConfig.SlowRatioPerStack), config.SlowRatioPerStack);
|
||||
config.MinMoveSpeedMultiplier = ReadFloat(param, nameof(IceTagConfig.MinMoveSpeedMultiplier), config.MinMoveSpeedMultiplier);
|
||||
}
|
||||
|
||||
private static void ApplyShatterConfig(ShatterTagConfig config, JObject param)
|
||||
{
|
||||
config.RequiresSlowedTarget = ReadBool(param, nameof(ShatterTagConfig.RequiresSlowedTarget), config.RequiresSlowedTarget);
|
||||
config.DamageBonusPerStack = ReadFloat(param, nameof(ShatterTagConfig.DamageBonusPerStack), config.DamageBonusPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyAbsoluteZeroConfig(AbsoluteZeroTagConfig config, JObject param)
|
||||
{
|
||||
config.BonusSlowDurationSeconds = ReadFloat(param, nameof(AbsoluteZeroTagConfig.BonusSlowDurationSeconds), config.BonusSlowDurationSeconds);
|
||||
config.BonusSlowRatioPerStack = ReadFloat(param, nameof(AbsoluteZeroTagConfig.BonusSlowRatioPerStack), config.BonusSlowRatioPerStack);
|
||||
}
|
||||
|
||||
private static void ApplyCritConfig(CritTagConfig config, JObject param)
|
||||
{
|
||||
config.CritChancePerStack = ReadFloat(param, nameof(CritTagConfig.CritChancePerStack), config.CritChancePerStack);
|
||||
config.CritDamageMultiplier = ReadFloat(param, nameof(CritTagConfig.CritDamageMultiplier), config.CritDamageMultiplier);
|
||||
}
|
||||
|
||||
private static void ApplyExecutionConfig(ExecutionTagConfig config, JObject param)
|
||||
{
|
||||
config.TargetHealthThreshold = ReadFloat(param, nameof(ExecutionTagConfig.TargetHealthThreshold), config.TargetHealthThreshold);
|
||||
config.DamageBonusPerStack = ReadFloat(param, nameof(ExecutionTagConfig.DamageBonusPerStack), config.DamageBonusPerStack);
|
||||
}
|
||||
|
||||
private static float ReadFloat(JObject param, string key, float defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<float>();
|
||||
}
|
||||
|
||||
private static int ReadInt(JObject param, string key, int defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<int>();
|
||||
}
|
||||
|
||||
private static bool ReadBool(JObject param, string key, bool defaultValue)
|
||||
{
|
||||
JToken token = param[key];
|
||||
return token == null ? defaultValue : token.Value<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.DataTable;
|
||||
using GeometryTD.Definition;
|
||||
|
||||
namespace GeometryTD.CustomUtility
|
||||
{
|
||||
public static class TagDisplayUtility
|
||||
{
|
||||
public static string ResolveTagName(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (GameEntry.DataTable != null)
|
||||
{
|
||||
var tagTable = GameEntry.DataTable.GetDataTable<DRTag>();
|
||||
if (tagTable != null)
|
||||
{
|
||||
DRTag tagRow = tagTable.GetDataRow((int)tagType);
|
||||
if (tagRow != null && !string.IsNullOrWhiteSpace(tagRow.Name))
|
||||
{
|
||||
return tagRow.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagType.ToString();
|
||||
}
|
||||
|
||||
public static string[] BuildTagTexts(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tags.Count);
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
string tagName = ResolveTagName(tags[i]);
|
||||
if (!string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
results.Add(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public static string[] BuildTagTexts(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tagRuntimes.Count);
|
||||
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||
{
|
||||
TagRuntimeData tagRuntime = tagRuntimes[i];
|
||||
if (tagRuntime == null || tagRuntime.TagType == TagType.None || tagRuntime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(tagRuntime.TagType);
|
||||
if (string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(tagRuntime.TotalStack > 1 ? $"{tagName} x{tagRuntime.TotalStack}" : tagName);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
public static string[] BuildTowerTagTexts(TowerStatsData towerStats)
|
||||
{
|
||||
if (towerStats == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
if (towerStats.TagRuntimes != null && towerStats.TagRuntimes.Length > 0)
|
||||
{
|
||||
return BuildTagTexts(towerStats.TagRuntimes);
|
||||
}
|
||||
|
||||
return BuildTagTexts(towerStats.Tags);
|
||||
}
|
||||
|
||||
public static string BuildTagDescriptionText(IReadOnlyList<TagType> tags)
|
||||
{
|
||||
if (tags == null || tags.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tags.Count);
|
||||
for (int i = 0; i < tags.Count; i++)
|
||||
{
|
||||
TagType tagType = tags[i];
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(tagType);
|
||||
string tagDescription = ResolveTagDescription(tagType);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(string.IsNullOrWhiteSpace(tagName) ? tagDescription : $"{tagName}: {tagDescription}");
|
||||
}
|
||||
|
||||
return string.Join("\n", results);
|
||||
}
|
||||
|
||||
public static string BuildTagDescriptionText(IReadOnlyList<TagRuntimeData> tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Count <= 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> results = new List<string>(tagRuntimes.Count);
|
||||
for (int i = 0; i < tagRuntimes.Count; i++)
|
||||
{
|
||||
TagRuntimeData runtime = tagRuntimes[i];
|
||||
if (runtime == null || runtime.TagType == TagType.None || runtime.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string tagName = ResolveTagName(runtime.TagType);
|
||||
if (runtime.TotalStack > 1 && !string.IsNullOrWhiteSpace(tagName))
|
||||
{
|
||||
tagName = $"{tagName} x{runtime.TotalStack}";
|
||||
}
|
||||
|
||||
string tagDescription = ResolveTagDescription(runtime.TagType);
|
||||
if (string.IsNullOrWhiteSpace(tagDescription))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(string.IsNullOrWhiteSpace(tagName) ? tagDescription : $"{tagName}: {tagDescription}");
|
||||
}
|
||||
|
||||
return string.Join("\n", results);
|
||||
}
|
||||
|
||||
public static string ResolveTagDescription(TagType tagType)
|
||||
{
|
||||
if (tagType == TagType.None)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (TagDefinitionRegistry.TryGetDefinition(tagType, out TagDefinition definition) &&
|
||||
!string.IsNullOrWhiteSpace(definition.Description))
|
||||
{
|
||||
return definition.Description;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user