引雷标记extender

This commit is contained in:
SoulliesOfficial
2026-07-26 09:42:03 -04:00
parent 39b43680a9
commit b047eb5e6d
22 changed files with 530 additions and 76 deletions

View File

@@ -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
};
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0572525d1cf145cd84962c632a304e45

View File

@@ -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();

View File

@@ -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>();

View File

@@ -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;

View File

@@ -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 已把基础倍率结算进 startDamageclone 后只修改本次 Lightning。
AttackUnit modified = source.Clone();
modified.startDamage *= 2f;
modified.damageDeviation *= 2f;
modified.breakthroughType = Breakthrough.Type.Disruption;
return modified;
}
private bool PlayStellarRingQuickcasterSpinArea()

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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)
{