Start Tag System
This commit is contained in:
parent
34ef001ef3
commit
52f9e212b9
|
|
@ -20,7 +20,7 @@ If uncertain, prefer **simple direct code** over generalized frameworks.
|
|||
- `Assets/GameMain/Configs/` and `Assets/GameMain/DataTables/` store runtime configuration and data tables.
|
||||
- `StreamingAssets/` is for runtime-loaded files that must be preserved on build.
|
||||
- `docs/` contains design notes (see `docs/GameDesign.md`).
|
||||
- `<EFBFBD><EFBFBD><EFBFBD>ݱ<EFBFBD>/` is a top-level data table workspace; keep it in sync with `Assets/GameMain/DataTables/` when exporting.
|
||||
- `数据表/` is a top-level data table workspace; keep it in sync with `Assets/GameMain/DataTables/` when exporting.
|
||||
|
||||
### Build, Test, and Development Commands
|
||||
- Open the project with Unity Hub and load `GeometryTD` as a Unity project.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ namespace Components
|
|||
{
|
||||
public interface IDamageReceiver
|
||||
{
|
||||
void TakeDamage(int damage, AttackPropertyType attackPropertyType);
|
||||
void TakeDamage(AttackPayload attackPayload);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ namespace Components
|
|||
private bool _isMoving = false;
|
||||
private Vector3 _direction = Vector3.forward;
|
||||
private float _speed = 0;
|
||||
private float _speedMultiplier = 1f;
|
||||
private Transform _cachedTransform;
|
||||
|
||||
public void OnInit(float speed, Transform transform)
|
||||
{
|
||||
_speed = speed;
|
||||
_speedMultiplier = 1f;
|
||||
_cachedTransform = transform;
|
||||
}
|
||||
|
||||
|
|
@ -26,16 +28,18 @@ namespace Components
|
|||
public void OnReset()
|
||||
{
|
||||
_speed = 0;
|
||||
_speedMultiplier = 1f;
|
||||
_cachedTransform = null;
|
||||
_isMoving = false;
|
||||
}
|
||||
|
||||
private void Move(float deltaTime = 0)
|
||||
{
|
||||
_cachedTransform.Translate(_direction * _speed * deltaTime);
|
||||
_cachedTransform.Translate(_direction * (_speed * _speedMultiplier) * deltaTime);
|
||||
}
|
||||
|
||||
public void SetMove(bool isMoving) => _isMoving = isMoving;
|
||||
public void SetDirection(Vector3 direction) => _direction = direction;
|
||||
public void SetSpeedMultiplier(float speedMultiplier) => _speedMultiplier = Mathf.Max(0f, speedMultiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ namespace Components
|
|||
|
||||
private Transform _target;
|
||||
private float _speed;
|
||||
private int _damage;
|
||||
private AttackPropertyType _attackPropertyType;
|
||||
private AttackPayload _attackPayload;
|
||||
private float _lifetime;
|
||||
private float _runtimeMaxLifetime;
|
||||
private bool _isRunning;
|
||||
|
|
@ -23,8 +22,7 @@ namespace Components
|
|||
public void OnShow(BulletData bulletData)
|
||||
{
|
||||
_target = bulletData != null ? bulletData.Target : null;
|
||||
_damage = bulletData != null ? Mathf.Max(0, bulletData.Damage) : 0;
|
||||
_attackPropertyType = bulletData != null ? bulletData.AttackPropertyType : AttackPropertyType.None;
|
||||
_attackPayload = bulletData?.AttackPayload?.Clone() ?? new AttackPayload();
|
||||
_speed = bulletData != null && bulletData.Speed > 0f ? bulletData.Speed : _defaultSpeed;
|
||||
_runtimeMaxLifetime = bulletData != null && bulletData.MaxLifetime > 0f ? bulletData.MaxLifetime : _maxLifetime;
|
||||
_lifetime = 0f;
|
||||
|
|
@ -46,8 +44,7 @@ namespace Components
|
|||
{
|
||||
_target = null;
|
||||
_speed = 0f;
|
||||
_damage = 0;
|
||||
_attackPropertyType = AttackPropertyType.None;
|
||||
_attackPayload = new AttackPayload();
|
||||
_lifetime = 0f;
|
||||
_runtimeMaxLifetime = _maxLifetime;
|
||||
_isRunning = false;
|
||||
|
|
@ -117,7 +114,7 @@ namespace Components
|
|||
{
|
||||
if (mono is IDamageReceiver damageReceiver)
|
||||
{
|
||||
damageReceiver.TakeDamage(_damage, _attackPropertyType);
|
||||
damageReceiver.TakeDamage(_attackPayload);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,21 @@ namespace Components
|
|||
|
||||
[SerializeField] private SpriteRenderer _renderer;
|
||||
|
||||
private TagRuntimeData[] _tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
|
||||
public int AttackDamage => _attackDamage;
|
||||
public AttackMethodType AttackMethodType => _attackMethodType;
|
||||
|
||||
public void OnInit(int attackDamage, AttackMethodType attackMethodType = AttackMethodType.NormalBullet, int bulletTypeId = 501)
|
||||
public void OnInit(
|
||||
int attackDamage,
|
||||
AttackMethodType attackMethodType = AttackMethodType.NormalBullet,
|
||||
int bulletTypeId = 501,
|
||||
TagRuntimeData[] tagRuntimes = null)
|
||||
{
|
||||
_attackDamage = Mathf.Max(1, attackDamage);
|
||||
_attackMethodType = attackMethodType;
|
||||
_bulletTypeId = Mathf.Max(1, bulletTypeId);
|
||||
_tagRuntimes = CloneTagRuntimes(tagRuntimes);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
|
|
@ -35,6 +42,7 @@ namespace Components
|
|||
_attackDamage = 1;
|
||||
_attackMethodType = AttackMethodType.None;
|
||||
_bulletTypeId = 501;
|
||||
_tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
|
|
@ -64,16 +72,47 @@ namespace Components
|
|||
|
||||
Transform spawnPoint = _muzzlePoint != null ? _muzzlePoint : transform;
|
||||
int bulletEntityId = GameEntry.Entity.GenerateSerialId();
|
||||
AttackPayload attackPayload = new AttackPayload
|
||||
{
|
||||
BaseDamage = _attackDamage,
|
||||
AttackPropertyType = attackPropertyType,
|
||||
TagRuntimes = CloneTagRuntimes(_tagRuntimes)
|
||||
};
|
||||
BulletData bulletData = new BulletData(
|
||||
bulletEntityId,
|
||||
_bulletTypeId,
|
||||
spawnPoint.position,
|
||||
target,
|
||||
_attackDamage,
|
||||
_bulletSpeed,
|
||||
attackPropertyType);
|
||||
attackPayload,
|
||||
_bulletSpeed);
|
||||
GameEntry.Entity.ShowBullet(bulletData);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] tagRuntimes)
|
||||
{
|
||||
if (tagRuntimes == null || tagRuntimes.Length <= 0)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagRuntimeData[] cloned = new TagRuntimeData[tagRuntimes.Length];
|
||||
for (int i = 0; i < tagRuntimes.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = tagRuntimes[i];
|
||||
if (runtime == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cloned[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = runtime.TagType,
|
||||
TotalStack = runtime.TotalStack
|
||||
};
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace Components
|
|||
private Color _muzzleColor = Color.white;
|
||||
private Color _bearingColor = Color.white;
|
||||
private Color _baseColor = Color.white;
|
||||
private TagRuntimeData[] _tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
|
||||
public Transform CurrentTarget => _currentTarget;
|
||||
|
||||
|
|
@ -63,8 +64,9 @@ namespace Components
|
|||
float rotateSpeed = ResolveFloatValue(stats.RotateSpeed, _towerLevel, 180f, 1f);
|
||||
float attackRange = ResolveFloatValue(stats.AttackRange, _towerLevel, 5f, 0.1f);
|
||||
float attackSpeed = ResolveFloatValue(stats.AttackSpeed, _towerLevel, 1f, 0.01f);
|
||||
_tagRuntimes = ResolveRuntimeTags(stats);
|
||||
|
||||
_muzzleComp?.OnInit(attackDamage, stats.AttackMethodType);
|
||||
_muzzleComp?.OnInit(attackDamage, stats.AttackMethodType, tagRuntimes: _tagRuntimes);
|
||||
_bearingComp?.OnInit(rotateSpeed, attackRange);
|
||||
_baseComp?.OnInit(attackSpeed, stats.AttackPropertyType);
|
||||
ApplyComponentColors();
|
||||
|
|
@ -80,6 +82,7 @@ namespace Components
|
|||
_currentTarget = null;
|
||||
_retargetTimer = 0f;
|
||||
_towerLevel = MinTowerLevel;
|
||||
_tagRuntimes = System.Array.Empty<TagRuntimeData>();
|
||||
_muzzleComp?.OnReset();
|
||||
_bearingComp?.OnReset();
|
||||
_baseComp?.OnReset();
|
||||
|
|
@ -221,6 +224,47 @@ namespace Components
|
|||
return target != null && target.gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] CloneTagRuntimes(TagRuntimeData[] source)
|
||||
{
|
||||
if (source == null || source.Length <= 0)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
TagRuntimeData[] cloned = new TagRuntimeData[source.Length];
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
TagRuntimeData runtime = source[i];
|
||||
if (runtime == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cloned[i] = new TagRuntimeData
|
||||
{
|
||||
TagType = runtime.TagType,
|
||||
TotalStack = runtime.TotalStack
|
||||
};
|
||||
}
|
||||
|
||||
return cloned;
|
||||
}
|
||||
|
||||
private static TagRuntimeData[] ResolveRuntimeTags(TowerStatsData stats)
|
||||
{
|
||||
if (stats == null)
|
||||
{
|
||||
return System.Array.Empty<TagRuntimeData>();
|
||||
}
|
||||
|
||||
if (stats.TagRuntimes != null && stats.TagRuntimes.Length > 0)
|
||||
{
|
||||
return CloneTagRuntimes(stats.TagRuntimes);
|
||||
}
|
||||
|
||||
return TowerTagAggregationService.BuildRuntimeTagsFromUniqueTags(stats.Tags);
|
||||
}
|
||||
|
||||
private static int ResolveIntValue(int[] values, int level, int fallback, int minValue)
|
||||
{
|
||||
int resolved = Mathf.Max(minValue, fallback);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using GeometryTD.CustomUtility;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class AttackPayload
|
||||
{
|
||||
public int BaseDamage { get; set; }
|
||||
public AttackPropertyType AttackPropertyType { get; set; }
|
||||
public TagRuntimeData[] TagRuntimes { get; set; } = Array.Empty<TagRuntimeData>();
|
||||
|
||||
public AttackPayload Clone()
|
||||
{
|
||||
return new AttackPayload
|
||||
{
|
||||
BaseDamage = BaseDamage,
|
||||
AttackPropertyType = AttackPropertyType,
|
||||
TagRuntimes = InventoryCloneUtility.CloneTagRuntimes(TagRuntimes)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4ed2fd20f9feb0f4d9448029b5bf63df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c26677bee93904e4da19e339c6e3a803
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TagCategory : byte
|
||||
{
|
||||
None = 0,
|
||||
Status = 1,
|
||||
NumericModifier = 2,
|
||||
AttackShape = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c0d6ea8de936264db4aa2f5df963d2b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public enum TagTriggerPhase : byte
|
||||
{
|
||||
None = 0,
|
||||
OnBeforeHit = 1,
|
||||
OnHit = 2,
|
||||
OnAfterHit = 3,
|
||||
OnKill = 4
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bdfe1f7eba0a78742ae112cd61e9da49
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a2a3c49c622040edbd52d0c66df2377e
|
||||
timeCreated: 1773105664
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1ff28b0dd76d49ecb0f6e615df4f7a62
|
||||
timeCreated: 1773105681
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1909b51822cc4368bc604674fd119123
|
||||
timeCreated: 1773105715
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class AbsoluteZeroTagEffect : EnemyStatusTagEffectBase
|
||||
{
|
||||
public override TagType TagType => TagType.AbsoluteZero;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: deecaa9a38c04725b5abeb5488cbe53f
|
||||
timeCreated: 1773106137
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EnemyStatusTagEffectBase : IEnemyStatusTagEffect
|
||||
{
|
||||
public abstract TagType TagType { get; }
|
||||
|
||||
public virtual void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
|
||||
TagRuntimeData runtimeData)
|
||||
{
|
||||
_ = attackPayload;
|
||||
_ = runtime;
|
||||
_ = 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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: aefaa5745cc94c0aae92109e64d83dc1
|
||||
timeCreated: 1773105755
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
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(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
|
||||
TagRuntimeData runtimeData)
|
||||
{
|
||||
Debug.Assert(TagConfigRegistry.TryGetDefinition(TagType, out TagDefinitionData definition));
|
||||
|
||||
FireTagConfig config = definition.Config as FireTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FireTagState state = runtime.GetOrCreateState<FireTagState>(TagType);
|
||||
float burnDamagePerSecond = Mathf.Max(0f,
|
||||
config.BurnDamagePerSecondPerStack * runtimeData.TotalStack);
|
||||
|
||||
if (burnDamagePerSecond <= 0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.BurnDurationSeconds);
|
||||
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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6609b360347c4fc9bccbe288efa60ae4
|
||||
timeCreated: 1773105786
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
using GeometryTD.Entity;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public interface IEnemyStatusTagEffect
|
||||
{
|
||||
TagType TagType { get; }
|
||||
void Apply(AttackPayload attackPayload, EnemyTagStatusRuntime runtime, TagRuntimeData runtimeData);
|
||||
bool Tick(EnemyTagStatusRuntime runtime, float deltaTime, Action<int> applyDamage);
|
||||
float GetMoveSpeedMultiplier(EnemyTagStatusRuntime runtime);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07eac037b9274ddaa6352412c9043f62
|
||||
timeCreated: 1773105725
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
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(AttackPayload attackPayload, EnemyTagStatusRuntime runtime,
|
||||
TagRuntimeData runtimeData)
|
||||
{
|
||||
_ = attackPayload;
|
||||
Debug.Assert(runtimeData != null);
|
||||
Debug.Assert(runtimeData.TotalStack > 0);
|
||||
Debug.Assert(TagConfigRegistry.TryGetDefinition(TagType, out TagDefinitionData definition));
|
||||
|
||||
IceTagConfig config = definition.Config as IceTagConfig;
|
||||
Debug.Assert(config != null);
|
||||
if (!config.IsImplemented)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IceTagState state = runtime.GetOrCreateState<IceTagState>(TagType);
|
||||
float slowMultiplier = Mathf.Max(config.MinMoveSpeedMultiplier,
|
||||
1f - runtimeData.TotalStack * config.SlowRatioPerStack);
|
||||
state.RemainingDuration = Mathf.Max(state.RemainingDuration, config.SlowDurationSeconds);
|
||||
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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0787cd2263b441ceba5b3ad381827677
|
||||
timeCreated: 1773106119
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public sealed class InfernoTagEffect : EnemyStatusTagEffectBase
|
||||
{
|
||||
public override TagType TagType => TagType.Inferno;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2aa813ec42ea4dddb717ff28da764f15
|
||||
timeCreated: 1773106082
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class EnemyStatusTagRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, IEnemyStatusTagEffect> EffectsByTag =
|
||||
new Dictionary<TagType, IEnemyStatusTagEffect>
|
||||
{
|
||||
[TagType.Fire] = new FireTagEffect(),
|
||||
[TagType.Inferno] = new InfernoTagEffect(),
|
||||
[TagType.Ice] = new IceTagEffect(),
|
||||
[TagType.AbsoluteZero] = new AbsoluteZeroTagEffect()
|
||||
};
|
||||
|
||||
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,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 49ed835d5a444f199fa74b2cb4d06a51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c90264b23a9c45c0b21bc627a6109282
|
||||
timeCreated: 1773105821
|
||||
|
|
@ -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; } = 0f;
|
||||
public float BonusSlowRatioPerStack { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a92545f498ff4b8ab974a73d220f2cbc
|
||||
timeCreated: 1773105984
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class BurnSpreadTagConfig : TagConfigBase
|
||||
{
|
||||
public BurnSpreadTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float SpreadRadius { get; set; } = 0f;
|
||||
public float SpreadDamageRate { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 16fa8f668caf4a6a9d384f53c86ef6d8
|
||||
timeCreated: 1773105868
|
||||
|
|
@ -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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 21c22a1b79d74aa58ef0054fed71b90a
|
||||
timeCreated: 1773106012
|
||||
|
|
@ -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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 865ffdb6e1df446fabfb2355d06ef60a
|
||||
timeCreated: 1773106044
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3225e9a8d28b4133a6484f576c4662ae
|
||||
timeCreated: 1773105844
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class FreezeMaskTagConfig : TagConfigBase
|
||||
{
|
||||
public FreezeMaskTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float FreezeBuildUpPerStack { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa016e0d4c76466b8fb665656ee4bc9e
|
||||
timeCreated: 1773105940
|
||||
|
|
@ -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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 546b506913154d74a9a7cd4b3aa36d12
|
||||
timeCreated: 1773105919
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class IgniteBurstTagConfig : TagConfigBase
|
||||
{
|
||||
public IgniteBurstTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public float BurstRadius { get; set; } = 0f;
|
||||
public float BurstDamageRate { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b412ab82145242e3a7ce99991199cc35
|
||||
timeCreated: 1773105884
|
||||
|
|
@ -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; } = 0f;
|
||||
public float BonusBurnDamagePerSecondPerStack { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be707e334ca74a2a8a2a9d7773e70138
|
||||
timeCreated: 1773105897
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class OverpenetrateTagConfig : TagConfigBase
|
||||
{
|
||||
public OverpenetrateTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public int ExtraPenetrationCount { get; set; } = 0;
|
||||
public float RemainingDamageRate { get; set; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1d017f96198041cc97f2db1cb4f84fcb
|
||||
timeCreated: 1773106028
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PierceTagConfig : TagConfigBase
|
||||
{
|
||||
public PierceTagConfig(bool isImplemented) : base(isImplemented)
|
||||
{
|
||||
}
|
||||
|
||||
public int ExtraPierceCount { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3081fa157ac74961a2a4ffb3a6e10851
|
||||
timeCreated: 1773105999
|
||||
|
|
@ -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; } = 0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0f84f8e59bd4d0ea620742802223d87
|
||||
timeCreated: 1773105968
|
||||
|
|
@ -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,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c2d0d4db7370b9f4b9d4d2cc4e84e46c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagConfigRegistry
|
||||
{
|
||||
private static readonly Dictionary<TagType, TagDefinitionData> DefinitionsByTag =
|
||||
new Dictionary<TagType, TagDefinitionData>
|
||||
{
|
||||
[TagType.Fire] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Fire,
|
||||
Category = TagCategory.Status,
|
||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
||||
Config = new FireTagConfig(true)
|
||||
},
|
||||
[TagType.BurnSpread] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.BurnSpread,
|
||||
Category = TagCategory.AttackShape,
|
||||
TriggerPhase = TagTriggerPhase.OnKill,
|
||||
Config = new BurnSpreadTagConfig(false)
|
||||
},
|
||||
[TagType.IgniteBurst] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.IgniteBurst,
|
||||
Category = TagCategory.AttackShape,
|
||||
TriggerPhase = TagTriggerPhase.OnKill,
|
||||
Config = new IgniteBurstTagConfig(false)
|
||||
},
|
||||
[TagType.Inferno] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Inferno,
|
||||
Category = TagCategory.Status,
|
||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
||||
Config = new InfernoTagConfig(false)
|
||||
},
|
||||
[TagType.Ice] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Ice,
|
||||
Category = TagCategory.Status,
|
||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
||||
Config = new IceTagConfig(true)
|
||||
},
|
||||
[TagType.FreezeMask] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.FreezeMask,
|
||||
Category = TagCategory.AttackShape,
|
||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
||||
Config = new FreezeMaskTagConfig(false)
|
||||
},
|
||||
[TagType.Shatter] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Shatter,
|
||||
Category = TagCategory.NumericModifier,
|
||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
||||
Config = new ShatterTagConfig(false)
|
||||
},
|
||||
[TagType.AbsoluteZero] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.AbsoluteZero,
|
||||
Category = TagCategory.Status,
|
||||
TriggerPhase = TagTriggerPhase.OnAfterHit,
|
||||
Config = new AbsoluteZeroTagConfig(false)
|
||||
},
|
||||
[TagType.Pierce] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Pierce,
|
||||
Category = TagCategory.AttackShape,
|
||||
TriggerPhase = TagTriggerPhase.OnHit,
|
||||
Config = new PierceTagConfig(false)
|
||||
},
|
||||
[TagType.Crit] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Crit,
|
||||
Category = TagCategory.NumericModifier,
|
||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
||||
Config = new CritTagConfig(true)
|
||||
},
|
||||
[TagType.Overpenetrate] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Overpenetrate,
|
||||
Category = TagCategory.AttackShape,
|
||||
TriggerPhase = TagTriggerPhase.OnHit,
|
||||
Config = new OverpenetrateTagConfig(false)
|
||||
},
|
||||
[TagType.Execution] = new TagDefinitionData
|
||||
{
|
||||
TagType = TagType.Execution,
|
||||
Category = TagCategory.NumericModifier,
|
||||
TriggerPhase = TagTriggerPhase.OnBeforeHit,
|
||||
Config = new ExecutionTagConfig(true)
|
||||
}
|
||||
};
|
||||
|
||||
public static IReadOnlyDictionary<TagType, TagDefinitionData> Definitions => DefinitionsByTag;
|
||||
|
||||
public static bool TryGetDefinition(TagType tagType, out TagDefinitionData definition)
|
||||
{
|
||||
return DefinitionsByTag.TryGetValue(tagType, out definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 717bc2031fdf31e45a3de05cf4e2f24f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class TagDefinitionData
|
||||
{
|
||||
public TagType TagType { get; set; }
|
||||
public TagCategory Category { get; set; }
|
||||
public TagTriggerPhase TriggerPhase { get; set; }
|
||||
public TagConfigBase Config { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c0011d6ce1b3dd14a89c6af6524cc732
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6fb08c76711246c2afa02d60769d76ef
|
||||
timeCreated: 1773106291
|
||||
|
|
@ -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 (!TagConfigRegistry.TryGetDefinition(runtime.TagType, out TagDefinitionData 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,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 434d659f0e0963d40a7da7e03b144966
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class NumericTagEffectHandler
|
||||
{
|
||||
public static void ApplyBeforeHit(
|
||||
HitContext hitContext,
|
||||
int targetCurrentHealth,
|
||||
int targetMaxHealth,
|
||||
bool targetHasSlowStatus,
|
||||
float? critRoll = null)
|
||||
{
|
||||
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 (!TagConfigRegistry.TryGetDefinition(runtime.TagType, out TagDefinitionData 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, critRoll);
|
||||
break;
|
||||
case TagType.Execution:
|
||||
ApplyExecution(hitContext, runtime.TotalStack, targetCurrentHealth, targetMaxHealth, definition.Config as ExecutionTagConfig);
|
||||
break;
|
||||
case TagType.Shatter:
|
||||
ApplyShatter(hitContext, runtime.TotalStack, targetHasSlowStatus, definition.Config as ShatterTagConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hitContext.IsKilled = targetCurrentHealth > 0 && hitContext.FinalDamage >= targetCurrentHealth;
|
||||
}
|
||||
|
||||
private static void ApplyCrit(HitContext hitContext, int stack, CritTagConfig config, float? critRoll)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float chance = Mathf.Clamp01(stack * config.CritChancePerStack);
|
||||
float resolvedCritRoll = 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,
|
||||
int targetCurrentHealth,
|
||||
int targetMaxHealth,
|
||||
ExecutionTagConfig config)
|
||||
{
|
||||
if (config == null || !config.IsImplemented || stack <= 0 || targetMaxHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float healthRatio = Mathf.Clamp01((float)Mathf.Max(0, targetCurrentHealth) / 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, bool targetHasSlowStatus, ShatterTagConfig config)
|
||||
{
|
||||
_ = hitContext;
|
||||
_ = stack;
|
||||
_ = targetHasSlowStatus;
|
||||
_ = config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eef83fab509bf1b4aaea8504c8a2fc7f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
using GeometryTD.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GeometryTD.Definition
|
||||
{
|
||||
public static class TagEffectResolver
|
||||
{
|
||||
public static HitContext ResolveBeforeHit(
|
||||
AttackPayload attackPayload,
|
||||
int targetCurrentHealth,
|
||||
int targetMaxHealth,
|
||||
bool targetHasSlowStatus,
|
||||
float? critRoll = null)
|
||||
{
|
||||
_ = targetHasSlowStatus;
|
||||
AttackPayload resolvedPayload = attackPayload?.Clone() ?? new AttackPayload();
|
||||
HitContext hitContext = new HitContext
|
||||
{
|
||||
AttackPayload = resolvedPayload,
|
||||
FinalDamage = Mathf.Max(0, resolvedPayload.BaseDamage),
|
||||
IsCriticalHit = false,
|
||||
IsKilled = targetCurrentHealth > 0 && resolvedPayload.BaseDamage >= targetCurrentHealth
|
||||
};
|
||||
|
||||
NumericTagEffectHandler.ApplyBeforeHit(
|
||||
hitContext,
|
||||
targetCurrentHealth,
|
||||
targetMaxHealth,
|
||||
targetHasSlowStatus,
|
||||
critRoll);
|
||||
return hitContext;
|
||||
}
|
||||
|
||||
public static void ApplyAfterHit(AttackPayload attackPayload, EnemyTagStatusRuntime targetStatusRuntime)
|
||||
{
|
||||
if (attackPayload != null && targetStatusRuntime != null)
|
||||
{
|
||||
TagRuntimeData[] tagRuntimes = attackPayload.TagRuntimes;
|
||||
if (tagRuntimes != null && tagRuntimes.Length > 0)
|
||||
{
|
||||
foreach (var tag in tagRuntimes)
|
||||
{
|
||||
if (tag == null || tag.TotalStack <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TagConfigRegistry.TryGetDefinition(tag.TagType, out TagDefinitionData definition) ||
|
||||
definition == null ||
|
||||
definition.Category != TagCategory.Status ||
|
||||
definition.TriggerPhase != TagTriggerPhase.OnAfterHit ||
|
||||
definition.Config == null ||
|
||||
!EnemyStatusTagRegistry.TryGetEffect(tag.TagType, out IEnemyStatusTagEffect effect))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
effect.Apply(attackPayload, targetStatusRuntime, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AttackShapeTagEffectHandler.Apply(new HitContext
|
||||
{
|
||||
AttackPayload = attackPayload
|
||||
}, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e0b2b45c147579459081b36d26d471c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0f2ab8a7e488575498ac933a125360d1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace GeometryTD.Definition
|
||||
{
|
||||
public abstract class EnemyStatusTagStateBase
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36215e56239c41c5bb196de6927643a8
|
||||
timeCreated: 1773105536
|
||||
|
|
@ -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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 20ad1b81a1634d06b9c8bbadec37bcd4
|
||||
timeCreated: 1773105566
|
||||
|
|
@ -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,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 43c7eb87fa774e60a894f4cc8e0089a5
|
||||
timeCreated: 1773105599
|
||||
|
|
@ -8,27 +8,24 @@ namespace GeometryTD.Entity.EntityData
|
|||
public class BulletData : EntityDataBase
|
||||
{
|
||||
[SerializeField] private Transform _target = null;
|
||||
[SerializeField] private int _damage = 0;
|
||||
[SerializeField] private float _speed = 0f;
|
||||
[SerializeField] private float _maxLifetime = 3f;
|
||||
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.None;
|
||||
[SerializeField] private AttackPayload _attackPayload = new AttackPayload();
|
||||
|
||||
public BulletData(
|
||||
int entityId,
|
||||
int typeId,
|
||||
Vector3 position,
|
||||
Transform target,
|
||||
int damage,
|
||||
AttackPayload attackPayload,
|
||||
float speed,
|
||||
AttackPropertyType attackPropertyType = AttackPropertyType.None,
|
||||
float maxLifetime = 3f) : base(entityId, typeId)
|
||||
{
|
||||
Position = position;
|
||||
|
||||
_target = target;
|
||||
_damage = damage;
|
||||
_attackPayload = attackPayload?.Clone() ?? new AttackPayload();
|
||||
_speed = speed;
|
||||
_attackPropertyType = attackPropertyType;
|
||||
_maxLifetime = maxLifetime;
|
||||
}
|
||||
|
||||
|
|
@ -38,12 +35,6 @@ namespace GeometryTD.Entity.EntityData
|
|||
set => _target = value;
|
||||
}
|
||||
|
||||
public int Damage
|
||||
{
|
||||
get => _damage;
|
||||
set => _damage = value;
|
||||
}
|
||||
|
||||
public float Speed
|
||||
{
|
||||
get => _speed;
|
||||
|
|
@ -56,10 +47,10 @@ namespace GeometryTD.Entity.EntityData
|
|||
set => _maxLifetime = value;
|
||||
}
|
||||
|
||||
public AttackPropertyType AttackPropertyType
|
||||
public AttackPayload AttackPayload
|
||||
{
|
||||
get => _attackPropertyType;
|
||||
set => _attackPropertyType = value;
|
||||
get => _attackPayload;
|
||||
set => _attackPayload = value?.Clone() ?? new AttackPayload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,13 @@ namespace GeometryTD.Entity
|
|||
private readonly List<Vector3> _pathPoints = new();
|
||||
private int _pathPointIndex;
|
||||
private bool _isDespawnRequested;
|
||||
private readonly EnemyTagStatusRuntime _tagStatusRuntime = new();
|
||||
|
||||
public static IReadOnlyList<EnemyEntity> ActiveEnemies => _activeEnemies;
|
||||
public int CurrentHealth => _currentHealth;
|
||||
public int MaxHealth => _maxHealth;
|
||||
public bool HasSlowStatus => _tagStatusRuntime.GetMoveSpeedMultiplier() < 0.999f;
|
||||
public float MoveSpeedMultiplier => _tagStatusRuntime.GetMoveSpeedMultiplier();
|
||||
|
||||
public static bool TryConsumeKilledFlag(int entityId)
|
||||
{
|
||||
|
|
@ -46,6 +51,7 @@ namespace GeometryTD.Entity
|
|||
_isDespawnRequested = false;
|
||||
_maxHealth = 1;
|
||||
_currentHealth = 1;
|
||||
_tagStatusRuntime.Reset();
|
||||
if (userData is EnemyData enemyData)
|
||||
{
|
||||
_speed = enemyData.Speed;
|
||||
|
|
@ -74,6 +80,7 @@ namespace GeometryTD.Entity
|
|||
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
base.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
_tagStatusRuntime.Tick(elapseSeconds, ApplyDamageFromStatus);
|
||||
|
||||
if (_pathPoints.Count > 0)
|
||||
{
|
||||
|
|
@ -90,6 +97,7 @@ namespace GeometryTD.Entity
|
|||
_isDespawnRequested = false;
|
||||
_maxHealth = 0;
|
||||
_currentHealth = 0;
|
||||
_tagStatusRuntime.Reset();
|
||||
_activeEnemies.Remove(this);
|
||||
|
||||
base.OnHide(isShutdown, userData);
|
||||
|
|
@ -100,26 +108,24 @@ namespace GeometryTD.Entity
|
|||
_activeEnemies.Remove(this);
|
||||
}
|
||||
|
||||
public void TakeDamage(int damage, AttackPropertyType attackPropertyType)
|
||||
public void TakeDamage(AttackPayload attackPayload)
|
||||
{
|
||||
_ = attackPropertyType;
|
||||
if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0)
|
||||
if (_isDespawnRequested || _currentHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int previousHealth = _currentHealth;
|
||||
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
||||
if (_maxHealth > 0)
|
||||
HitContext hitContext = TagEffectResolver.ResolveBeforeHit(
|
||||
attackPayload,
|
||||
_currentHealth,
|
||||
_maxHealth,
|
||||
HasSlowStatus);
|
||||
ApplyDirectDamage(hitContext.FinalDamage);
|
||||
TagEffectResolver.ApplyOnHit(hitContext);
|
||||
TagEffectResolver.ApplyAfterHit(hitContext.AttackPayload, _tagStatusRuntime);
|
||||
if (hitContext.IsKilled)
|
||||
{
|
||||
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth,
|
||||
(float)_currentHealth / _maxHealth);
|
||||
}
|
||||
|
||||
if (_currentHealth <= 0)
|
||||
{
|
||||
_killedEnemyEntityIds.Add(Id);
|
||||
RequestDespawn();
|
||||
TagEffectResolver.ApplyOnKill(hitContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +161,7 @@ namespace GeometryTD.Entity
|
|||
return;
|
||||
}
|
||||
|
||||
_movementComponent.SetSpeedMultiplier(_tagStatusRuntime.GetMoveSpeedMultiplier());
|
||||
_movementComponent.SetMove(true);
|
||||
_movementComponent.SetDirection(direction);
|
||||
_movementComponent.OnUpdate(elapseSeconds, realElapseSeconds);
|
||||
|
|
@ -188,5 +195,34 @@ namespace GeometryTD.Entity
|
|||
_movementComponent.SetMove(false);
|
||||
GameEntry.Entity.HideEntity(Entity);
|
||||
}
|
||||
|
||||
private void ApplyDamageFromStatus(int damage)
|
||||
{
|
||||
ApplyDirectDamage(damage);
|
||||
}
|
||||
|
||||
private void ApplyDirectDamage(int damage)
|
||||
{
|
||||
if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int previousHealth = _currentHealth;
|
||||
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
||||
if (_maxHealth > 0)
|
||||
{
|
||||
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth,
|
||||
(float)_currentHealth / _maxHealth);
|
||||
}
|
||||
|
||||
if (_currentHealth > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_killedEnemyEntityIds.Add(Id);
|
||||
RequestDespawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue