2026-02-13 09:22:11 -05:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using Cielonos.MainGame.Characters;
|
|
|
|
|
|
using DamageNumbersPro;
|
|
|
|
|
|
using Sirenix.OdinInspector;
|
|
|
|
|
|
using SLSUtilities.FunctionalAnimation;
|
|
|
|
|
|
using SLSUtilities.General;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using Random = UnityEngine.Random;
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
namespace Cielonos.MainGame
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
[HideReferenceObjectPicker]
|
|
|
|
|
|
public partial class AttackUnit : ICloneable<AttackUnit>
|
|
|
|
|
|
{
|
2026-06-27 12:52:03 -04:00
|
|
|
|
[HideInInspector]
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public AttackData parentData;
|
2026-06-27 12:52:03 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 和AttackData中的key对应,且用于运行时识别这个攻击单元
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[HideInInspector]
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public string unitName;
|
|
|
|
|
|
|
|
|
|
|
|
[Title("Attack Parameters")]
|
|
|
|
|
|
[Tooltip("如果为true,则这个攻击单元不会造成伤害,也不会触发暴击等相关机制,通常用于仅触发特殊效果")]
|
|
|
|
|
|
public bool isInvalidAttack;
|
|
|
|
|
|
|
|
|
|
|
|
[HideIf("isInvalidAttack")]
|
2026-05-23 08:27:50 -04:00
|
|
|
|
public Attack.Type type = Attack.Type.Kinetics;
|
2026-06-27 12:52:03 -04:00
|
|
|
|
|
|
|
|
|
|
[Title("Attack Parameters Override")]
|
2026-02-13 09:22:11 -05:00
|
|
|
|
[HideIf("isInvalidAttack")]
|
2026-06-27 12:52:03 -04:00
|
|
|
|
[LabelText("覆盖默认参数")]
|
|
|
|
|
|
public bool overrideParameters = false;
|
|
|
|
|
|
|
|
|
|
|
|
[HideIf("@isInvalidAttack || overrideParameters")]
|
|
|
|
|
|
[LabelText("伤害倍率")]
|
|
|
|
|
|
public float damageCorrectionMultiplier = 1f;
|
|
|
|
|
|
|
|
|
|
|
|
[HideIf("@isInvalidAttack || overrideParameters")]
|
|
|
|
|
|
[LabelText("削韧倍率")]
|
|
|
|
|
|
public float stanceDepletionCorrectionMultiplier = 1f;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
[Tooltip("是否在多段攻击时独立计算伤害和暴击")]
|
|
|
|
|
|
[HideIf("isInvalidAttack")]
|
|
|
|
|
|
public bool isIndependentPerHit = true;
|
2026-06-27 12:52:03 -04:00
|
|
|
|
|
|
|
|
|
|
[HideIf("@isInvalidAttack || !overrideParameters")]
|
|
|
|
|
|
[LabelText("攻击力")]
|
|
|
|
|
|
public float startDamage;
|
|
|
|
|
|
|
|
|
|
|
|
[HideIf("@isInvalidAttack || !overrideParameters")]
|
|
|
|
|
|
[LabelText("攻击力标准差")]
|
|
|
|
|
|
public float damageDeviation;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
2026-06-27 12:52:03 -04:00
|
|
|
|
[HideIf("@isInvalidAttack || !overrideParameters")]
|
|
|
|
|
|
[LabelText("暴击率")]
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public float criticalChance;
|
|
|
|
|
|
|
2026-06-27 12:52:03 -04:00
|
|
|
|
[HideIf("@isInvalidAttack || !overrideParameters")]
|
|
|
|
|
|
[LabelText("暴击倍率")]
|
|
|
|
|
|
public float criticalMultiplier;
|
|
|
|
|
|
|
|
|
|
|
|
[HideIf("@isInvalidAttack || !overrideParameters")]
|
|
|
|
|
|
[LabelText("削韧值")]
|
|
|
|
|
|
public float stanceDepletion;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
[Title("Hit Effects")]
|
2026-05-23 08:27:50 -04:00
|
|
|
|
public Breakthrough.Type breakthroughType = Breakthrough.Type.Weak;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
public DisruptionType disruptionType = DisruptionType.NormalExternal;
|
|
|
|
|
|
|
2026-04-18 13:57:19 -04:00
|
|
|
|
[Title("Tags")]
|
|
|
|
|
|
public List<string> tags = new List<string>();
|
|
|
|
|
|
|
2026-02-13 09:22:11 -05:00
|
|
|
|
[Title("Hit VFX")]
|
|
|
|
|
|
public bool useVFXDataHit;
|
|
|
|
|
|
[HideIf("useVFXDataHit")]
|
|
|
|
|
|
public GameObject hitVFX;
|
|
|
|
|
|
[ShowIf("useVFXDataHit")]
|
|
|
|
|
|
public string vfxUnitName;
|
2026-06-13 18:43:40 -04:00
|
|
|
|
public bool randomizeVfxRotation;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
|
|
|
|
|
|
[Title("Extra Modules")]
|
|
|
|
|
|
[ListDrawerSettings(ShowFoldout = true)]
|
|
|
|
|
|
[SerializeReference]
|
|
|
|
|
|
public List<Submodule> submodules = new List<Submodule>();
|
|
|
|
|
|
|
|
|
|
|
|
public bool TryGetSubModule<T>(out T submodule) where T : Submodule
|
|
|
|
|
|
{
|
|
|
|
|
|
submodule = submodules.Find(m => m is T) as T;
|
|
|
|
|
|
return submodule != null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 11:47:55 -04:00
|
|
|
|
public T GetSubmodule<T>() where T : Submodule
|
|
|
|
|
|
{
|
|
|
|
|
|
return submodules.Find(m => m is T) as T;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-23 08:27:50 -04:00
|
|
|
|
public Attack.Value GetAttackValue(CharacterBase attacker)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-06-27 12:52:03 -04:00
|
|
|
|
bool isCritical = Random.value < GetFinalCriticalChance(attacker);
|
|
|
|
|
|
float finalDamage = isCritical ? GetFinalCriticalDamage(attacker) : GetFinalRegularDamage();
|
|
|
|
|
|
float finalStanceDepletion = isCritical ? stanceDepletion * GetFinalCriticalMultiplier(attacker) : stanceDepletion;
|
|
|
|
|
|
return new Attack.Value(isCritical, finalDamage, finalStanceDepletion, type, disruptionType, breakthroughType, tags);
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public GameObject GetHitVFX()
|
|
|
|
|
|
{
|
|
|
|
|
|
return hitVFX != null ? hitVFX : parentData.defaultHitVFX;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public float GetFinalRegularDamage()
|
|
|
|
|
|
{
|
|
|
|
|
|
return startDamage + Random.Range(-damageDeviation, damageDeviation);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 12:52:03 -04:00
|
|
|
|
public float GetFinalCriticalDamage(CharacterBase attacker)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-06-27 12:52:03 -04:00
|
|
|
|
return (startDamage + Random.Range(-damageDeviation, damageDeviation)) * GetFinalCriticalMultiplier(attacker);
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 12:52:03 -04:00
|
|
|
|
public float GetFinalCriticalChance(CharacterBase attacker)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-06-27 12:52:03 -04:00
|
|
|
|
float chance = criticalChance;
|
|
|
|
|
|
if (attacker != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
chance += attacker.attributeSm[CharacterAttribute.CriticalAttackChance];
|
|
|
|
|
|
}
|
|
|
|
|
|
return chance;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 12:52:03 -04:00
|
|
|
|
public float GetFinalCriticalMultiplier(CharacterBase attacker)
|
2026-02-13 09:22:11 -05:00
|
|
|
|
{
|
2026-06-27 12:52:03 -04:00
|
|
|
|
float multiplier = 1 + criticalMultiplier;
|
|
|
|
|
|
if (attacker != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
multiplier += attacker.attributeSm[CharacterAttribute.CriticalAttackDamageAmplifier];
|
|
|
|
|
|
}
|
|
|
|
|
|
return multiplier;
|
2026-02-13 09:22:11 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public AttackUnit Clone()
|
|
|
|
|
|
{
|
|
|
|
|
|
return (AttackUnit) this.MemberwiseClone();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class AttackUnit
|
|
|
|
|
|
{
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public abstract class Submodule : SubmoduleBase<AttackUnit>
|
|
|
|
|
|
{
|
|
|
|
|
|
public Submodule(AttackUnit owner) : base(owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class ParameterSubmodule : Submodule
|
|
|
|
|
|
{
|
|
|
|
|
|
public Dictionary<string, float> floatParameters = new Dictionary<string, float>();
|
|
|
|
|
|
public Dictionary<string, int> intParameters = new Dictionary<string, int>();
|
|
|
|
|
|
public Dictionary<string, string> stringParameters = new Dictionary<string, string>();
|
|
|
|
|
|
|
|
|
|
|
|
public ParameterSubmodule() : base(null)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public ParameterSubmodule(AttackUnit owner) : base(owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public T GetParameter<T>(string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (typeof(T) == typeof(float))
|
|
|
|
|
|
{
|
|
|
|
|
|
return floatParameters.TryGetValue(key, out float floatParameter) ? (T)(object)floatParameter : (T)(object)0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof(T) == typeof(int))
|
|
|
|
|
|
{
|
|
|
|
|
|
return intParameters.TryGetValue(key, out int intParameter) ? (T)(object)intParameter : (T)(object)0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (typeof(T) == typeof(string))
|
|
|
|
|
|
{
|
|
|
|
|
|
return stringParameters.TryGetValue(key, out string stringParameter) ? (T)(object)stringParameter : (T)(object)string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new InvalidOperationException($"Unsupported parameter type: {typeof(T)}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Serializable]
|
|
|
|
|
|
public class DamageNumberSubmodule : Submodule
|
|
|
|
|
|
{
|
|
|
|
|
|
public DamageNumber regularPrefab;
|
|
|
|
|
|
public DamageNumber criticalPrefab;
|
|
|
|
|
|
public Dictionary<string, DamageNumber> customPrefabs = new Dictionary<string, DamageNumber>();
|
|
|
|
|
|
|
|
|
|
|
|
public DamageNumberSubmodule() : base(null)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public DamageNumberSubmodule(AttackUnit owner) : base(owner)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|