引雷标记extender
This commit is contained in:
@@ -36,6 +36,7 @@ namespace Cielonos.MainGame
|
||||
public bool canTriggerHitEvent = true;
|
||||
public List<string> tags = new List<string>();
|
||||
public Action updateAction;
|
||||
private Predicate<CharacterBase> _targetFilter;
|
||||
[NonSerialized, HideInEditorMode] public CreatorTimePolicy timePolicy;
|
||||
|
||||
/// <summary>
|
||||
@@ -93,6 +94,7 @@ namespace Cielonos.MainGame
|
||||
this.targetFractions = targetFractions.ToList();
|
||||
this.canTriggerHitEvent = true;
|
||||
this.tags = new List<string>();
|
||||
this._targetFilter = null;
|
||||
this.timePolicy = DefaultTimePolicy;
|
||||
this.reactedTargets.Clear();
|
||||
|
||||
@@ -411,14 +413,25 @@ namespace Cielonos.MainGame
|
||||
public partial class AttackAreaBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查目标角色是否为有效攻击目标:非创建者自身,且属于目标阵营。
|
||||
/// 设置只对当前池化实例有效的额外目标条件。
|
||||
/// </summary>
|
||||
public T SetTargetFilter<T>(Predicate<CharacterBase> targetFilter) where T : AttackAreaBase
|
||||
{
|
||||
// 对池化 AttackArea 施加本次运行时目标约束;Initialize 会负责清空。
|
||||
_targetFilter = targetFilter;
|
||||
return this as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查目标角色是否为有效攻击目标:非创建者自身,且属于目标阵营并满足额外目标条件。
|
||||
/// </summary>
|
||||
public bool IsValidTarget(CharacterBase target)
|
||||
{
|
||||
if (target == null || target == creator) return false;
|
||||
if (!target.gameObject.activeInHierarchy) return false;
|
||||
if (target.statusSm == null || target.statusSm.isDead) return false;
|
||||
return targetFractions.Contains(target.fraction);
|
||||
if (!targetFractions.Contains(target.fraction)) return false;
|
||||
return _targetFilter?.Invoke(target) ?? true;
|
||||
}
|
||||
|
||||
public virtual void HitCharacter(Collider characterCollider, Vector3 hitPosition)
|
||||
|
||||
@@ -44,7 +44,9 @@ namespace Cielonos.MainGame.Characters
|
||||
backpackSm.ObtainItem<Frostfield>();
|
||||
backpackSm.ObtainItem<HummingbirdSwarmMissile>();
|
||||
backpackSm.ObtainItem<ThermalDetonator>();
|
||||
backpackSm.ObtainItem<SpacetimeTurbulence>();
|
||||
//backpackSm.ObtainItem<SpacetimeTurbulence>();
|
||||
|
||||
backpackSm.ObtainItem<LightningConcentrator>();
|
||||
/*backpackSm.ObtainItem<PhotonWarper>();
|
||||
backpackSm.ObtainItem<PhotonDissociator>();
|
||||
backpackSm.ObtainItem<PhotonAccelerator>();
|
||||
|
||||
@@ -48,6 +48,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
|
||||
public class FunctionUnit
|
||||
{
|
||||
public const string DisruptionTag = "Disruption";
|
||||
|
||||
[ReadOnly]
|
||||
public FunctionData parentData;
|
||||
|
||||
@@ -81,8 +83,8 @@ namespace Cielonos.MainGame.Inventory
|
||||
|
||||
public static List<string> Tags = new()
|
||||
{
|
||||
"Disruption"
|
||||
DisruptionTag
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Cielonos.MainGame.Inventory.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// 引雷标记 / Lightning Concentrator
|
||||
/// 将 FutureWand 的 Lightning 集中到单个目标,并将其转化为 Disruption 攻击。
|
||||
/// </summary>
|
||||
public class LightningConcentrator : ExtenderBase
|
||||
{
|
||||
public override void OnObtained()
|
||||
{
|
||||
hostType = typeof(FutureWand);
|
||||
base.OnObtained();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0572525d1cf145cd84962c632a304e45
|
||||
@@ -15,6 +15,20 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
private void FAPF_GenerateLightning(RuntimeFuncAnim rtFuncAnim)
|
||||
{
|
||||
CustomFunction.PC_StringString p = rtFuncAnim.GetParams<CustomFunction.PC_StringString>();
|
||||
|
||||
if (_hasLightningConcentrator)
|
||||
{
|
||||
Enemy target = PrimaryTarget;
|
||||
if (target == null || !target.gameObject.activeInHierarchy || target.statusSm.isDead)
|
||||
{
|
||||
target = RefreshLightningConcentratorTarget(out _);
|
||||
}
|
||||
|
||||
AttackUnit modifiedAttack = ApplyLightningConcentrator(attackData[p.str1]);
|
||||
Vector3 fallbackPosition = player.transform.position + player.transform.forward * 10f;
|
||||
GenerateLightning(p.str0, modifiedAttack, target, fallbackPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Enemy> targets = CombatManager.EnemySm.Query(25f).From(player.transform).Candidates();
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
|
||||
NormalArea lightning = vfxData.SpawnVFX(vfxName, player).GetComponentInChildren<NormalArea>();
|
||||
lightning.Initialize<NormalArea>(player, this, Fraction.Enemy)
|
||||
.SetTargetFilter<NormalArea>(candidate => candidate == target)
|
||||
.SetAttackSubmodule<NormalArea>(attackUnit)
|
||||
.SetTimeSubmodule<NormalArea>(2f, 0.1f, 0.04f)
|
||||
.SetHitSubmodule<NormalArea>();
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Cielonos.MainGame.Characters;
|
||||
using Cielonos.MainGame.Effects.Feedback;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.FunctionalAnimation;
|
||||
|
||||
namespace Cielonos.MainGame.Inventory.Collections
|
||||
{
|
||||
@@ -62,11 +65,25 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
return played;
|
||||
}
|
||||
|
||||
private bool PlayLightning(CharacterBase target)
|
||||
private bool PlayLightning(CharacterBase target, bool playStartupFeedback = false)
|
||||
{
|
||||
bool played = PlayTargetedAnimation("GenerateLightning", target);
|
||||
if (played)
|
||||
{
|
||||
if (_hasLightningConcentrator && playStartupFeedback)
|
||||
{
|
||||
float duration = fullBodyFuncAnimSm.collection["GenerateLightning"]
|
||||
.Interval(IntervalType.Startup).Duration * 2f;
|
||||
player.feedbackSc.PlayFeedback("DisruptionStartup", runtimeFeedback =>
|
||||
{
|
||||
FeedbackClip timeScaleModifierClip = runtimeFeedback.Clip<TimeScaleModifierAction>("Time");
|
||||
if (timeScaleModifierClip != null)
|
||||
{
|
||||
timeScaleModifierClip.duration = duration;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
functionSm["Lightning"].Execute();
|
||||
}
|
||||
return played;
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
private bool _hasCubicScatterIndexer;
|
||||
private bool _hasFusionChargeCore;
|
||||
private bool _hasSigilBreakWedge;
|
||||
private bool _hasLightningConcentrator;
|
||||
|
||||
private const int StellarRingQuickcasterSpinAreaUpgradeLevel = 1;
|
||||
private const int FusionChargeCoreExtraFusionStack = 1;
|
||||
@@ -32,6 +33,23 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
_hasCubicScatterIndexer = HasExtender<CubicScatterIndexer>();
|
||||
_hasFusionChargeCore = HasExtender<FusionChargeCore>();
|
||||
_hasSigilBreakWedge = HasExtender<SigilBreakWedge>();
|
||||
_hasLightningConcentrator = HasExtender<LightningConcentrator>();
|
||||
functionSm?["Lightning"]?.SetRuntimeTag(
|
||||
nameof(LightningConcentrator),
|
||||
FunctionData.FunctionUnit.DisruptionTag,
|
||||
_hasLightningConcentrator);
|
||||
}
|
||||
|
||||
private AttackUnit ApplyLightningConcentrator(AttackUnit source)
|
||||
{
|
||||
if (!_hasLightningConcentrator) return source;
|
||||
|
||||
// AttackData 已把基础倍率结算进 startDamage;clone 后只修改本次 Lightning。
|
||||
AttackUnit modified = source.Clone();
|
||||
modified.startDamage *= 2f;
|
||||
modified.damageDeviation *= 2f;
|
||||
modified.breakthroughType = Breakthrough.Type.Disruption;
|
||||
return modified;
|
||||
}
|
||||
|
||||
private bool PlayStellarRingQuickcasterSpinArea()
|
||||
|
||||
@@ -95,7 +95,11 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
{
|
||||
if (functionSm["Lightning"].IsAvailable() && fullBodyFuncAnimSm.CheckPlayability())
|
||||
{
|
||||
PlayLightning(RefreshPrimaryTarget());
|
||||
bool playStartupFeedback = false;
|
||||
CharacterBase target = _hasLightningConcentrator
|
||||
? RefreshLightningConcentratorTarget(out playStartupFeedback)
|
||||
: RefreshPrimaryTarget();
|
||||
PlayLightning(target, playStartupFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,25 @@ namespace Cielonos.MainGame.Inventory.Collections
|
||||
return RefreshPrimaryTarget(TargetingScorePreset.MeleeAttack, radius);
|
||||
}
|
||||
|
||||
private Enemy RefreshLightningConcentratorTarget(out bool hasDisruptableTarget,
|
||||
float radius = DefaultTargetingRadius)
|
||||
{
|
||||
_currentTargets.Clear();
|
||||
|
||||
Enemy target = CombatManager.EnemySm.Query(radius, TargetingScorePreset.RangedAttack)
|
||||
.OnlyDisruptable(Breakthrough.Type.Disruption)
|
||||
.Best();
|
||||
hasDisruptableTarget = target != null;
|
||||
target ??= CombatManager.EnemySm.Query(radius, TargetingScorePreset.RangedAttack).LockonFirst();
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
_currentTargets.Add(target);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private Enemy RefreshPrimaryTarget(TargetingScorePreset preset, float radius = DefaultTargetingRadius)
|
||||
{
|
||||
RefreshCurrentTargets(1, radius, preset);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cielonos.MainGame.Characters;
|
||||
@@ -44,6 +45,9 @@ namespace Cielonos.MainGame.Inventory
|
||||
public FunctionData.FunctionUnit data;
|
||||
public float currentCooldown;
|
||||
public float maxCooldown;
|
||||
|
||||
private Dictionary<string, HashSet<string>> _runtimeTagsBySource;
|
||||
public event Action OnPresentationChanged;
|
||||
|
||||
public RuntimeFunctionUnit(FunctionSubmodule owner, FunctionData.FunctionUnit data) : base(owner)
|
||||
{
|
||||
@@ -51,6 +55,59 @@ namespace Cielonos.MainGame.Inventory
|
||||
maxCooldown = data.cooldown;
|
||||
currentCooldown = 0f;
|
||||
}
|
||||
|
||||
public bool HasTag(string tag)
|
||||
{
|
||||
// Runtime tag 只影响当前 FunctionUnit 实例,不修改共享的 FunctionData。
|
||||
if (data.tags != null && data.tags.Contains(tag)) return true;
|
||||
if (_runtimeTagsBySource == null) return false;
|
||||
|
||||
foreach (HashSet<string> tags in _runtimeTagsBySource.Values)
|
||||
{
|
||||
if (tags.Contains(tag)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetRuntimeTag(string sourceKey, string tag, bool enabled)
|
||||
{
|
||||
// sourceKey 让多个装备可以独立添加或撤销同名表现标签。
|
||||
bool changed = false;
|
||||
if (enabled)
|
||||
{
|
||||
_runtimeTagsBySource ??= new Dictionary<string, HashSet<string>>();
|
||||
if (!_runtimeTagsBySource.TryGetValue(sourceKey, out HashSet<string> tags))
|
||||
{
|
||||
tags = new HashSet<string>();
|
||||
_runtimeTagsBySource.Add(sourceKey, tags);
|
||||
}
|
||||
|
||||
changed = tags.Add(tag);
|
||||
}
|
||||
else if (_runtimeTagsBySource != null &&
|
||||
_runtimeTagsBySource.TryGetValue(sourceKey, out HashSet<string> tags))
|
||||
{
|
||||
changed = tags.Remove(tag);
|
||||
if (tags.Count == 0)
|
||||
{
|
||||
_runtimeTagsBySource.Remove(sourceKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
OnPresentationChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveRuntimeTags(string sourceKey)
|
||||
{
|
||||
if (_runtimeTagsBySource != null && _runtimeTagsBySource.Remove(sourceKey))
|
||||
{
|
||||
OnPresentationChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
|
||||
@@ -19,9 +19,24 @@ namespace Cielonos.MainGame.UI
|
||||
|
||||
private Sequence _frameOutlineSequence;
|
||||
private ItemBase.RuntimeFunctionUnit _functionUnit;
|
||||
private Color _defaultIconColor;
|
||||
private Color _defaultFrameColor;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_defaultIconColor = iconImage.color;
|
||||
_defaultFrameColor = frame.color;
|
||||
}
|
||||
|
||||
public void Initialize(ItemBase.RuntimeFunctionUnit functionUnit)
|
||||
{
|
||||
if (_functionUnit != null)
|
||||
{
|
||||
_functionUnit.OnPresentationChanged -= RefreshPresentation;
|
||||
}
|
||||
|
||||
this._functionUnit = functionUnit;
|
||||
_functionUnit.OnPresentationChanged += RefreshPresentation;
|
||||
iconImage.sprite = functionUnit.data.icon != null ? functionUnit.data.icon : null;
|
||||
|
||||
if (_functionUnit.maxCooldown <= 0)
|
||||
@@ -39,7 +54,15 @@ namespace Cielonos.MainGame.UI
|
||||
costText.text = Mathf.CeilToInt(_functionUnit.data.energyCost).ToString("D");
|
||||
}
|
||||
|
||||
if (_functionUnit.data.tags.Contains("Disruption"))
|
||||
RefreshPresentation();
|
||||
}
|
||||
|
||||
private void RefreshPresentation()
|
||||
{
|
||||
iconImage.color = _defaultIconColor;
|
||||
frame.color = _defaultFrameColor;
|
||||
|
||||
if (_functionUnit.HasTag(FunctionData.FunctionUnit.DisruptionTag))
|
||||
{
|
||||
Color newColor = Color.yellow;
|
||||
iconImage.color = newColor;
|
||||
@@ -48,6 +71,14 @@ namespace Cielonos.MainGame.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_functionUnit != null)
|
||||
{
|
||||
_functionUnit.OnPresentationChanged -= RefreshPresentation;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateUI()
|
||||
{
|
||||
if (_functionUnit.maxCooldown <= 0f)
|
||||
|
||||
@@ -20,9 +20,23 @@ namespace Cielonos.MainGame.UI
|
||||
|
||||
private ItemBase.RuntimeFunctionUnit _functionUnit;
|
||||
private Sequence _frameOutlineSequence;
|
||||
private Color _defaultIconColor;
|
||||
private Color _defaultFrameColor;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_defaultIconColor = iconImage.color;
|
||||
_defaultFrameColor = frame.color;
|
||||
}
|
||||
|
||||
public void Initialize(SupportEquipmentBase supportEquipment)
|
||||
{
|
||||
if (_functionUnit != null)
|
||||
{
|
||||
_functionUnit.OnPresentationChanged -= RefreshPresentation;
|
||||
_functionUnit = null;
|
||||
}
|
||||
|
||||
if (supportEquipment == null)
|
||||
{
|
||||
DisableAllParts();
|
||||
@@ -49,11 +63,13 @@ namespace Cielonos.MainGame.UI
|
||||
//如果有多个功能单元,优先显示主功能单元
|
||||
_functionUnit = supportEquipment.functionSm.mainFunction;
|
||||
}
|
||||
|
||||
_functionUnit.OnPresentationChanged += RefreshPresentation;
|
||||
|
||||
timerImage.gameObject.SetActive(true);
|
||||
timerText.gameObject.SetActive(true);
|
||||
costText.gameObject.SetActive(true);
|
||||
iconImage.color = Color.white;
|
||||
iconImage.color = _defaultIconColor;
|
||||
|
||||
if (_functionUnit.maxCooldown <= 0)
|
||||
{
|
||||
@@ -70,13 +86,7 @@ namespace Cielonos.MainGame.UI
|
||||
costText.text = Mathf.CeilToInt(_functionUnit.data.energyCost).ToString("D");
|
||||
}
|
||||
|
||||
if (_functionUnit.data.tags.Contains("Disruption"))
|
||||
{
|
||||
Color newColor = Color.yellow;
|
||||
iconImage.color = newColor;
|
||||
newColor.a = 0.5f;
|
||||
frame.color = newColor;
|
||||
}
|
||||
RefreshPresentation();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -85,10 +95,34 @@ namespace Cielonos.MainGame.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPresentation()
|
||||
{
|
||||
if (_functionUnit == null) return;
|
||||
|
||||
iconImage.color = _defaultIconColor;
|
||||
frame.color = _defaultFrameColor;
|
||||
if (_functionUnit.HasTag(FunctionData.FunctionUnit.DisruptionTag))
|
||||
{
|
||||
Color newColor = Color.yellow;
|
||||
iconImage.color = newColor;
|
||||
newColor.a = 0.5f;
|
||||
frame.color = newColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_functionUnit != null)
|
||||
{
|
||||
_functionUnit.OnPresentationChanged -= RefreshPresentation;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableAllParts()
|
||||
{
|
||||
iconImage.sprite = null;
|
||||
iconImage.color = Color.clear;
|
||||
frame.color = _defaultFrameColor;
|
||||
timerImage.gameObject.SetActive(false);
|
||||
timerText.gameObject.SetActive(false);
|
||||
costText.gameObject.SetActive(false);
|
||||
|
||||
Reference in New Issue
Block a user