diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 183e09f..b9a9454 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/1.6/1.6/Defs/Effects/ARA_Flecks.xml b/1.6/1.6/Defs/Effects/ARA_Flecks.xml
new file mode 100644
index 0000000..7d862ce
--- /dev/null
+++ b/1.6/1.6/Defs/Effects/ARA_Flecks.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Fleck_Wula_Dark_Matter_Beam
+ MoteOverhead
+ 0.025
+ 0.025
+ 0.025
+
+ ArachnaeSwarm/Mote/ARA_Lighting_Beam_Horizon
+ (188, 112, 255, 180)
+ MoteGlow
+
+
+
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Laser.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Laser.xml
index b13fbe2..a6ddcc4 100644
--- a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Laser.xml
+++ b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon_Laser.xml
@@ -44,7 +44,7 @@
true
- 1
+ 0
36
6
10
@@ -58,8 +58,7 @@
0.32
Mote_Wula_Dark_Matter_Beam
GraserBeam_End
- 0.35
-
+
0.6
0.6
@@ -67,12 +66,12 @@
- true
+ true
- 4
- 30
+ 6
+ 18
0.7
Mote_Wula_Dark_Matter_Beam
@@ -83,4 +82,89 @@
+
+
+ WULA_RW_DM_AR_SuperArc
+
+ 乌拉帝国一线部队所使用的由暗物质驱动的常规步枪的改造版。现在它发射的能量束会在命中后寻找并跳跃到附近的其他敌人身上,形成致命的能量链。
+ Ultra
+
+ Wula/Weapon/WULA_RW_DM_AR
+ Graphic_Single
+ 1.2
+
+
+ Wula_Ranged_Weapon_T4
+
+ 0.9
+ Interact_ChargeRifle
+
+
+ WULA_Cube_Productor_Energy
+
+ WULA_Synth_Weapon_4_DM_Base_Technology
+ UnfinishedWeapon
+
+
+ 400
+ 200
+ 4
+
+
+ 40000
+ 4.5
+ 1
+ 1
+ 1
+ 1
+ 1.25
+
+
+
+ ArachnaeSwarm.Verb_ShootBeamSplitAndChain
+
+
+ true
+ 0
+ 36
+ 6
+ 10
+
+
+ Wula_Dark_Matter_Beam
+ 90
+ 0.5
+
+
+ 0
+ BeamGraser_Shooting
+ Fleck_BeamBurn
+ 0.32
+ GraserBeam_End
+ Fleck_Wula_Dark_Matter_Beam
+ 0.5
+ 1.5
+
+
+ true
+ 3
+ 7
+ 0.8
+
+ Fleck_Wula_Dark_Matter_Beam
+
+
+ 3
+ 12
+ 0.6
+
+ Fleck_Wula_Dark_Matter_Beam
+
+
+ None
+
+ RewardStandardQualitySuper
+
+
+
\ No newline at end of file
diff --git a/Content/Textures/ArachnaeSwarm/Mote/ARA_Lighting_Beam_Horizon.png b/Content/Textures/ArachnaeSwarm/Mote/ARA_Lighting_Beam_Horizon.png
new file mode 100644
index 0000000..59120cd
Binary files /dev/null and b/Content/Textures/ArachnaeSwarm/Mote/ARA_Lighting_Beam_Horizon.png differ
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index a0be073..87a3515 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -234,6 +234,7 @@
+
@@ -265,6 +266,9 @@
+
+
+
diff --git a/Source/ArachnaeSwarm/Utils/BezierUtil.cs b/Source/ArachnaeSwarm/Utils/BezierUtil.cs
new file mode 100644
index 0000000..b3e4643
--- /dev/null
+++ b/Source/ArachnaeSwarm/Utils/BezierUtil.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace ArachnaeSwarm.Utils
+{
+ public static class BezierUtil
+ {
+ // Generates points for a quadratic Bezier curve.
+ public static List GenerateQuadraticPoints(Vector3 start, Vector3 control, Vector3 end, int segments)
+ {
+ List points = new List();
+ if (segments <= 0) segments = 1;
+
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = (float)i / segments;
+ float u = 1f - t;
+ float tt = t * t;
+ float uu = u * u;
+
+ Vector3 p = uu * start; // (1-t)^2 * P0
+ p += 2 * u * t * control; // 2(1-t)t * P1
+ p += tt * end; // t^2 * P2
+
+ points.Add(p);
+ }
+ return points;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamArc.cs b/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamArc.cs
index a0f5bc1..1cb2f6f 100644
--- a/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamArc.cs
+++ b/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamArc.cs
@@ -14,6 +14,7 @@ namespace ArachnaeSwarm
public float conductRange;
public float secondaryDamageFactor = 0.5f;
public ThingDef chainMoteDef;
+ public float beamArmorPenetration = 0f; // Add missing property
public VerbProperties_BeamArc()
{
@@ -21,70 +22,83 @@ namespace ArachnaeSwarm
}
}
- // This class is a modified copy of Verb_ShootBeam to implement chain-lightning functionality.
public class Verb_ShootBeamArc : Verb
{
- // --- Fields from original Verb_ShootBeam ---
private int ticksToNextPathStep;
- private MoteDualAttached mote; // This will be the main beam
private Effecter endEffecter;
private Sustainer sustainer;
- // --- Custom fields for chain logic ---
protected List chainedTargets = new List();
protected List chainMotes = new List();
private VerbProperties_BeamArc Props => this.verbProps as VerbProperties_BeamArc;
protected override int ShotsPerBurst => base.BurstShotCount;
-
+
public override void WarmupComplete()
{
- // --- Chain Target Finding Logic ---
- chainedTargets.Clear();
- foreach (MoteDualAttached m in chainMotes) { m.Destroy(); }
- chainMotes.Clear();
+ this.Cleanup();
if (this.Props != null && this.Props.conductNum > 0 && this.currentTarget.HasThing)
{
- Thing currentTargetThing = this.currentTarget.Thing;
- chainedTargets.Add(currentTargetThing);
-
- Thing lastTarget = currentTargetThing;
- for (int i = 0; i < this.Props.conductNum; i++)
+ Thing primaryTarget = this.currentTarget.Thing;
+ if (primaryTarget is Pawn p && (p.Dead || p.Downed))
{
- Thing nextTarget = AttackTargetFinder.BestAttackTarget(lastTarget as Pawn, TargetScanFlags.NeedLOSToAll, (Thing t) =>
- t is Pawn p && !p.Downed && !chainedTargets.Contains(t) && t.Position.InHorDistOf(lastTarget.Position, this.Props.conductRange) && this.Caster.HostileTo(t),
- 0f, 9999f, default(IntVec3), this.Props.conductRange) as Thing;
+ // Do not start a chain on an invalid primary target.
+ }
+ else
+ {
+ chainedTargets.Add(primaryTarget);
+ Thing lastTarget = primaryTarget;
- if (nextTarget != null)
+ for (int i = 0; i < this.Props.conductNum; i++)
{
- chainedTargets.Add(nextTarget);
- lastTarget = nextTarget;
+ // MCP Suggested Fix: Manual search for the NEAREST valid target.
+ Thing nextTarget = GenRadial.RadialDistinctThingsAround(lastTarget.Position, this.caster.Map, this.Props.conductRange, false)
+ .OfType()
+ .Where(pawn =>
+ !pawn.Dead &&
+ !pawn.Downed &&
+ !chainedTargets.Contains(pawn) &&
+ this.Caster.HostileTo(pawn) &&
+ GenSight.LineOfSight(lastTarget.Position, pawn.Position, this.caster.Map, true)
+ )
+ .OrderBy(pawn => pawn.Position.DistanceToSquared(lastTarget.Position))
+ .FirstOrDefault();
+
+ if (nextTarget != null)
+ {
+ chainedTargets.Add(nextTarget);
+ lastTarget = nextTarget;
+ }
+ else
+ {
+ break;
+ }
}
- else { break; }
}
}
- // --- Original Verb_ShootBeam Logic (simplified) ---
burstShotsLeft = ShotsPerBurst;
state = VerbState.Bursting;
- // Create main beam mote
- if (verbProps.beamMoteDef != null && this.currentTarget.Thing != null)
+ // Unified visual creation
+ if (chainedTargets.Any())
{
- mote = MoteMaker.MakeInteractionOverlay(verbProps.beamMoteDef, caster, this.currentTarget.Thing);
- }
-
- // Create chain motes
- if (chainedTargets.Count > 1)
- {
- for (int i = 0; i < chainedTargets.Count - 1; i++)
+ // First link: Caster -> Primary Target
+ if (verbProps.beamMoteDef != null)
{
- ThingDef moteDef = this.Props.chainMoteDef ?? this.verbProps.beamMoteDef;
- if (moteDef != null)
+ MoteDualAttached firstLink = MoteMaker.MakeInteractionOverlay(verbProps.beamMoteDef, this.caster, chainedTargets[0]);
+ chainMotes.Add(firstLink);
+ }
+
+ // Subsequent links: Target -> Next Target
+ if (chainedTargets.Count > 1)
+ {
+ ThingDef chainMoteDef = this.Props.chainMoteDef ?? this.verbProps.beamMoteDef;
+ for (int i = 0; i < chainedTargets.Count - 1; i++)
{
- MoteDualAttached chainLinkMote = MoteMaker.MakeInteractionOverlay(moteDef, chainedTargets[i], chainedTargets[i + 1]);
- chainMotes.Add(chainLinkMote);
+ MoteDualAttached chainLink = MoteMaker.MakeInteractionOverlay(chainMoteDef, chainedTargets[i], chainedTargets[i + 1]);
+ chainMotes.Add(chainLink);
}
}
}
@@ -100,28 +114,26 @@ namespace ArachnaeSwarm
public override void BurstingTick()
{
- // --- Update Visuals ---
- mote?.Maintain();
+ if (this.burstShotsLeft <= 0)
+ {
+ this.Cleanup();
+ base.BurstingTick(); // Must be called to properly end the verb state.
+ return;
+ }
+
foreach (MoteDualAttached m in chainMotes) { m.Maintain(); }
- // --- Original ground/end effect logic (simplified to target) ---
- if (this.currentTarget.Thing != null)
+ // Simplified end effecter logic on the last known target.
+ Thing lastTarget = chainedTargets.LastOrDefault();
+ if (lastTarget != null)
{
- Vector3 endPoint = this.currentTarget.Thing.DrawPos;
- IntVec3 endCell = this.currentTarget.Cell;
-
- if (verbProps.beamGroundFleckDef != null && Rand.Chance(verbProps.beamFleckChancePerTick))
+ if (endEffecter == null && verbProps.beamEndEffecterDef != null)
{
- FleckMaker.Static(endPoint, caster.Map, verbProps.beamGroundFleckDef);
- }
- if (endEffecter == null && verbProps.beamEndEffecterDef != null)
- {
- endEffecter = verbProps.beamEndEffecterDef.Spawn(endCell, caster.Map, Vector3.zero);
+ endEffecter = verbProps.beamEndEffecterDef.Spawn(lastTarget.Position, caster.Map, Vector3.zero);
}
if (endEffecter != null)
{
- endEffecter.EffectTick(new TargetInfo(endCell, caster.Map), TargetInfo.Invalid);
- endEffecter.ticksLeft--;
+ endEffecter.EffectTick(new TargetInfo(lastTarget), TargetInfo.Invalid);
}
}
sustainer?.Maintain();
@@ -129,7 +141,7 @@ namespace ArachnaeSwarm
protected override bool TryCastShot()
{
- if (this.currentTarget.HasThing && this.currentTarget.Thing.Map != this.caster.Map) { return false; }
+ if (!this.chainedTargets.Any()) return false;
if (base.EquipmentSource != null)
{
@@ -137,41 +149,44 @@ namespace ArachnaeSwarm
base.EquipmentSource.GetComp()?.UsedOnce();
}
- // --- Apply Damage to Chain ---
- if (this.chainedTargets.Any())
+ this.ApplyChainDamage(this.chainedTargets[0], 1.0f);
+ for (int i = 1; i < this.chainedTargets.Count; i++)
{
- this.ApplyChainDamage(this.chainedTargets[0], 1.0f);
- for (int i = 1; i < this.chainedTargets.Count; i++)
- {
- this.ApplyChainDamage(this.chainedTargets[i], this.Props.secondaryDamageFactor);
- }
- }
- else if(this.currentTarget.Thing != null)
- {
- this.ApplyChainDamage(this.currentTarget.Thing, 1.0f);
+ this.ApplyChainDamage(this.chainedTargets[i], this.Props.secondaryDamageFactor);
}
+ this.ticksToNextPathStep = this.verbProps.ticksBetweenBurstShots;
return true;
}
+ private void Cleanup()
+ {
+ foreach (MoteDualAttached m in chainMotes) { m.Destroy(); }
+ chainMotes.Clear();
+ endEffecter?.Cleanup();
+ endEffecter = null;
+ sustainer?.End();
+ sustainer = null;
+ chainedTargets.Clear();
+ }
+
private void ApplyChainDamage(Thing thing, float damageFactor)
{
- Map map = this.caster.Map;
if (thing == null || this.verbProps.beamDamageDef == null) { return; }
- float angleFlat = (this.currentTarget.Cell - this.caster.Position).AngleFlat;
+ float angleFlat = (thing.Position - this.caster.Position).AngleFlat;
BattleLogEntry_RangedImpact log = new BattleLogEntry_RangedImpact(this.caster, thing, this.currentTarget.Thing, base.EquipmentSource.def, null, null);
DamageInfo dinfo;
if (this.verbProps.beamTotalDamage > 0f)
{
float damagePerShot = this.verbProps.beamTotalDamage / (float)this.ShotsPerBurst;
- dinfo = new DamageInfo(this.verbProps.beamDamageDef, damagePerShot * damageFactor, this.verbProps.beamDamageDef.defaultArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing);
+ dinfo = new DamageInfo(this.verbProps.beamDamageDef, damagePerShot * damageFactor, this.Props.beamArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing);
}
else
{
float amount = (float)this.verbProps.beamDamageDef.defaultDamage * damageFactor;
- dinfo = new DamageInfo(this.verbProps.beamDamageDef, amount, this.verbProps.beamDamageDef.defaultArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing);
+ dinfo = new DamageInfo(this.verbProps.beamDamageDef, amount, this.Props.beamArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing);
}
thing.TakeDamage(dinfo).AssociateWithLog(log);
@@ -186,7 +201,7 @@ namespace ArachnaeSwarm
}
else if (Rand.Chance(this.verbProps.beamChanceToStartFire))
{
- FireUtility.TryStartFireIn(thing.Position, map, this.verbProps.beamFireSizeRange.RandomInRange, this.caster, this.verbProps.flammabilityAttachFireChanceCurve);
+ FireUtility.TryStartFireIn(thing.Position, this.caster.Map, this.verbProps.beamFireSizeRange.RandomInRange, this.caster, this.verbProps.flammabilityAttachFireChanceCurve);
}
}
}
diff --git a/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamSplitAndChain.cs b/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamSplitAndChain.cs
new file mode 100644
index 0000000..8565011
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verbs/Verb_ShootBeamSplitAndChain.cs
@@ -0,0 +1,218 @@
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using Verse.Sound;
+using ArachnaeSwarm.Utils;
+
+namespace ArachnaeSwarm
+{
+ public class VerbProperties_SplitAndChain : VerbProperties
+ {
+ public bool isSplit = false;
+ public int splitNum;
+ public float splitRange;
+ public int conductNum;
+ public float conductRange;
+ public float splitDamageFactor = 0.8f;
+ public float conductDamageFactor = 0.6f;
+ public float beamArmorPenetration = 0f;
+ public int beamPathSteps = 15;
+ public float flecksPerCell = 2f; // Flecks per cell to control beam density
+
+ public FleckDef splitMoteDef;
+ public FleckDef chainMoteDef;
+
+ public VerbProperties_SplitAndChain()
+ {
+ this.verbClass = typeof(Verb_ShootBeamSplitAndChain);
+ }
+ }
+
+ public class Verb_ShootBeamSplitAndChain : Verb
+ {
+ private VerbProperties_SplitAndChain Props => this.verbProps as VerbProperties_SplitAndChain;
+ private Dictionary> attackChains = new Dictionary>();
+ private Dictionary endEffecters = new Dictionary();
+ private Sustainer sustainer;
+ private int ticksToNextPathStep;
+
+ public override void WarmupComplete()
+ {
+ this.Cleanup();
+
+ List mainTargets = new List();
+ if (!this.currentTarget.HasThing) { base.WarmupComplete(); return; }
+
+ Thing primaryTarget = this.currentTarget.Thing;
+ if (primaryTarget is Pawn p_primary && (p_primary.Dead || p_primary.Downed)) return;
+
+ mainTargets.Add(primaryTarget);
+
+ if (this.Props.isSplit && this.Props.splitNum > 0)
+ {
+ var potentialTargets = GenRadial.RadialDistinctThingsAround(primaryTarget.Position, this.caster.Map, this.Props.splitRange, false)
+ .OfType()
+ .Where(p => !p.Dead && !p.Downed && p.HostileTo(this.caster.Faction) && !mainTargets.Contains(p) && GenSight.LineOfSight(primaryTarget.Position, p.Position, this.caster.Map, true))
+ .OrderBy(p => p.Position.DistanceToSquared(primaryTarget.Position))
+ .Take(this.Props.splitNum);
+
+ mainTargets.AddRange(potentialTargets);
+ }
+
+ foreach (Thing mainTarget in mainTargets)
+ {
+ List currentChain = new List();
+ currentChain.Add(mainTarget);
+
+ Thing lastTargetInChain = mainTarget;
+ for (int i = 0; i < this.Props.conductNum; i++)
+ {
+ Thing nextInChain = GenRadial.RadialDistinctThingsAround(lastTargetInChain.Position, this.caster.Map, this.Props.conductRange, false)
+ .OfType()
+ .Where(p => !p.Dead && !p.Downed && !currentChain.Contains(p) && !mainTargets.Except(new[]{mainTarget}).Contains(p) && this.Caster.HostileTo(p) && GenSight.LineOfSight(lastTargetInChain.Position, p.Position, this.caster.Map, true))
+ .OrderBy(p => p.Position.DistanceToSquared(lastTargetInChain.Position))
+ .FirstOrDefault();
+
+ if (nextInChain != null)
+ {
+ currentChain.Add(nextInChain);
+ lastTargetInChain = nextInChain;
+ }
+ else { break; }
+ }
+ attackChains[mainTarget] = currentChain;
+ }
+
+ this.burstShotsLeft = this.verbProps.burstShotCount;
+ this.state = VerbState.Bursting;
+ if (this.Props.soundCastBeam != null)
+ {
+ this.sustainer = this.Props.soundCastBeam.TrySpawnSustainer(SoundInfo.InMap(this.caster, MaintenanceType.PerTick));
+ }
+ base.TryCastNextBurstShot();
+ }
+
+ public override void BurstingTick()
+ {
+ if (this.burstShotsLeft <= 0)
+ {
+ this.Cleanup();
+ base.BurstingTick();
+ return;
+ }
+
+ List deadOrInvalidChains = attackChains.Keys.Where(t => t == null || !t.Spawned).ToList();
+ foreach (var key in deadOrInvalidChains)
+ {
+ if(endEffecters.ContainsKey(key))
+ {
+ endEffecters[key].Cleanup();
+ endEffecters.Remove(key);
+ }
+ attackChains.Remove(key);
+ }
+
+ Vector3 casterPos = this.caster.DrawPos;
+ foreach (var chainEntry in attackChains)
+ {
+ Thing mainTarget = chainEntry.Key;
+ List conductTargets = chainEntry.Value;
+
+ DrawCurvedBeam(casterPos, mainTarget.DrawPos, Props.splitMoteDef ?? verbProps.beamLineFleckDef);
+
+ for (int i = 0; i < conductTargets.Count - 1; i++)
+ {
+ DrawCurvedBeam(conductTargets[i].DrawPos, conductTargets[i+1].DrawPos, Props.chainMoteDef ?? verbProps.beamLineFleckDef);
+ }
+
+ foreach (Thing target in conductTargets)
+ {
+ if (!endEffecters.ContainsKey(target) || endEffecters[target] == null)
+ {
+ endEffecters[target] = verbProps.beamEndEffecterDef?.Spawn(target.Position, target.Map, Vector3.zero);
+ }
+ endEffecters[target]?.EffectTick(new TargetInfo(target), TargetInfo.Invalid);
+ }
+ }
+ sustainer?.Maintain();
+ }
+
+ protected override bool TryCastShot()
+ {
+ if (this.attackChains.NullOrEmpty()) return false;
+
+ bool anyDamaged = false;
+ foreach (var chainEntry in attackChains)
+ {
+ Thing mainTarget = chainEntry.Key;
+ List conductTargets = chainEntry.Value;
+
+ ApplyDamage(mainTarget, Props.splitDamageFactor);
+ anyDamaged = true;
+
+ for (int i = 1; i < conductTargets.Count; i++)
+ {
+ ApplyDamage(conductTargets[i], Props.conductDamageFactor);
+ }
+ }
+
+ this.ticksToNextPathStep = this.verbProps.ticksBetweenBurstShots;
+ return anyDamaged;
+ }
+
+ private void DrawCurvedBeam(Vector3 start, Vector3 end, FleckDef fleckDef)
+ {
+ if (fleckDef == null) return;
+
+ float magnitude = (end - start).MagnitudeHorizontal();
+ if (magnitude <= 0) return;
+
+ // 1. Generate Bezier curve points
+ int segments = Mathf.Max(3, Mathf.CeilToInt(magnitude * Props.flecksPerCell));
+
+ Vector3 controlPoint = Vector3.Lerp(start, end, 0.5f) + new Vector3(0, -magnitude * Props.beamCurvature, 0);
+ var path = BezierUtil.GenerateQuadraticPoints(start, controlPoint, end, segments);
+ // 2. Check if there are enough points to connect
+ if (path.Count < 2)
+ {
+ return;
+ }
+
+ // 3. Iterate through adjacent point pairs and draw connecting lines
+ for (int i = 0; i < path.Count - 1; i++)
+ {
+ Vector3 pointA = path[i];
+ Vector3 pointB = path[i + 1];
+ FleckMaker.ConnectingLine(pointA, pointB, fleckDef, this.caster.Map, 1f);
+ }
+ }
+
+ private void ApplyDamage(Thing thing, float damageFactor)
+ {
+ if (thing == null || verbProps.beamDamageDef == null) return;
+
+ float totalDamage = verbProps.beamTotalDamage > 0 ? verbProps.beamTotalDamage / verbProps.burstShotCount : verbProps.beamDamageDef.defaultDamage;
+ float finalDamage = totalDamage * damageFactor;
+
+ var dinfo = new DamageInfo(verbProps.beamDamageDef, finalDamage, Props.beamArmorPenetration, -1, this.caster, null, base.EquipmentSource.def);
+ thing.TakeDamage(dinfo);
+ }
+
+ private void Cleanup()
+ {
+ attackChains.Clear();
+ foreach (var effecter in endEffecters.Values) effecter.Cleanup();
+ endEffecters.Clear();
+ sustainer?.End();
+ sustainer = null;
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ }
+ }
+}
\ No newline at end of file