This commit is contained in:
2025-10-05 14:46:34 +08:00
parent 6df90475c3
commit 0a44683611
4 changed files with 280 additions and 0 deletions

View File

@@ -233,6 +233,7 @@
<Compile Include="Verbs\Projectiles\Projectile_WulaPenetratingBullet.cs" />
<Compile Include="Verbs\Projectiles\TrackingBulletDef.cs" />
<Compile Include="Verbs\Verb_ShootArc.cs" />
<Compile Include="Verbs\Verb_ShootBeamArc.cs" />
<Compile Include="Verbs\Verb_ShootMeltBeam.cs" />
<Compile Include="Verbs\Verb_ShootShotgun.cs" />
<Compile Include="Verbs\Verb_ShootShotgunWithOffset.cs" />

View File

@@ -0,0 +1,193 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.Sound;
using System.Linq;
namespace ArachnaeSwarm
{
public class VerbProperties_BeamArc : VerbProperties
{
public int conductNum;
public float conductRange;
public float secondaryDamageFactor = 0.5f;
public ThingDef chainMoteDef;
public VerbProperties_BeamArc()
{
this.verbClass = typeof(Verb_ShootBeamArc);
}
}
// 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<Thing> chainedTargets = new List<Thing>();
protected List<MoteDualAttached> chainMotes = new List<MoteDualAttached>();
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();
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 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;
if (nextTarget != null)
{
chainedTargets.Add(nextTarget);
lastTarget = nextTarget;
}
else { break; }
}
}
// --- Original Verb_ShootBeam Logic (simplified) ---
burstShotsLeft = ShotsPerBurst;
state = VerbState.Bursting;
// Create main beam mote
if (verbProps.beamMoteDef != null && this.currentTarget.Thing != null)
{
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++)
{
ThingDef moteDef = this.Props.chainMoteDef ?? this.verbProps.beamMoteDef;
if (moteDef != null)
{
MoteDualAttached chainLinkMote = MoteMaker.MakeInteractionOverlay(moteDef, chainedTargets[i], chainedTargets[i + 1]);
chainMotes.Add(chainLinkMote);
}
}
}
TryCastNextBurstShot();
ticksToNextPathStep = verbProps.ticksBetweenBurstShots;
endEffecter?.Cleanup();
if (verbProps.soundCastBeam != null)
{
sustainer = verbProps.soundCastBeam.TrySpawnSustainer(SoundInfo.InMap(caster, MaintenanceType.PerTick));
}
}
public override void BurstingTick()
{
// --- Update Visuals ---
mote?.Maintain();
foreach (MoteDualAttached m in chainMotes) { m.Maintain(); }
// --- Original ground/end effect logic (simplified to target) ---
if (this.currentTarget.Thing != null)
{
Vector3 endPoint = this.currentTarget.Thing.DrawPos;
IntVec3 endCell = this.currentTarget.Cell;
if (verbProps.beamGroundFleckDef != null && Rand.Chance(verbProps.beamFleckChancePerTick))
{
FleckMaker.Static(endPoint, caster.Map, verbProps.beamGroundFleckDef);
}
if (endEffecter == null && verbProps.beamEndEffecterDef != null)
{
endEffecter = verbProps.beamEndEffecterDef.Spawn(endCell, caster.Map, Vector3.zero);
}
if (endEffecter != null)
{
endEffecter.EffectTick(new TargetInfo(endCell, caster.Map), TargetInfo.Invalid);
endEffecter.ticksLeft--;
}
}
sustainer?.Maintain();
}
protected override bool TryCastShot()
{
if (this.currentTarget.HasThing && this.currentTarget.Thing.Map != this.caster.Map) { return false; }
if (base.EquipmentSource != null)
{
base.EquipmentSource.GetComp<CompChangeableProjectile>()?.Notify_ProjectileLaunched();
base.EquipmentSource.GetComp<CompApparelReloadable>()?.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[i], this.Props.secondaryDamageFactor);
}
}
else if(this.currentTarget.Thing != null)
{
this.ApplyChainDamage(this.currentTarget.Thing, 1.0f);
}
return true;
}
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;
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);
}
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);
}
thing.TakeDamage(dinfo).AssociateWithLog(log);
if (thing.CanEverAttachFire())
{
float chance = this.verbProps.flammabilityAttachFireChanceCurve?.Evaluate(thing.GetStatValue(StatDefOf.Flammability)) ?? this.verbProps.beamChanceToAttachFire;
if (Rand.Chance(chance))
{
thing.TryAttachFire(this.verbProps.beamFireSizeRange.RandomInRange, this.caster);
}
}
else if (Rand.Chance(this.verbProps.beamChanceToStartFire))
{
FireUtility.TryStartFireIn(thing.Position, map, this.verbProps.beamFireSizeRange.RandomInRange, this.caster, this.verbProps.flammabilityAttachFireChanceCurve);
}
}
}
}