Files
WulaFallenEmpireRW/Source/WulaFallenEmpire/Verb_Wula_BreachingBeam.cs
2025-08-27 18:28:14 +08:00

198 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class Verb_Wula_BreachingBeam : Verb
{
// --- Copied from Verb_ShootBeam for visual effects ---
private MoteDualAttached mote;
private Effecter endEffecter;
private Sustainer sustainer;
// --- Our custom state ---
private Vector3 beamEndPoint;
private int ticksLeft;
private bool beamHitMapEdge;
private int explosionTicks;
private float beamEnergy;
private VerbProperties_Wula_IonicBeam BeamProps => (VerbProperties_Wula_IonicBeam)verbProps;
public override float? AimAngleOverride => (state == VerbState.Bursting) ? (beamEndPoint - caster.DrawPos).AngleFlat() : (float?)null;
public override void WarmupComplete()
{
base.WarmupComplete();
// --- Initial Damage and Path Calculation ---
beamHitMapEdge = true;
float shotAngle = (currentTarget.Cell - caster.Position).AngleFlat;
beamEndPoint = GetMapEdgePoint(caster.Position, shotAngle);
var cellsOnPath = WulaBeamUtility.GetCellsInBeamArea(caster.Position, beamEndPoint.ToIntVec3(), (int)verbProps.beamWidth);
this.beamEnergy = BeamProps.breachingDamage;
// This loop calculates the final beam end point based on the initial piercing damage
foreach (var cell in cellsOnPath)
{
if (!cell.InBounds(caster.Map)) continue;
var thingsToHit = cell.GetThingList(caster.Map).Where(t => CanHit(t)).ToList();
foreach (var thing in thingsToHit)
{
if (beamEnergy <= 0) break;
float damageToDeal = Mathf.Min(beamEnergy, thing.HitPoints);
var dinfo = new DamageInfo(verbProps.beamDamageDef ?? DamageDefOf.Burn, damageToDeal, BeamProps.armorPenetration, shotAngle, caster, null, EquipmentSource?.def);
thing.TakeDamage(dinfo);
beamEnergy -= thing.HitPoints;
}
if (beamEnergy <= 0)
{
beamEndPoint = cell.ToVector3Shifted();
beamHitMapEdge = false;
break;
}
}
// --- Start Visual Effects ---
if (verbProps.beamMoteDef != null)
{
mote = MoteMaker.MakeInteractionOverlay(verbProps.beamMoteDef, caster, new TargetInfo(beamEndPoint.ToIntVec3(), caster.Map));
}
if (verbProps.soundCastBeam != null)
{
sustainer = verbProps.soundCastBeam.TrySpawnSustainer(SoundInfo.InMap(caster, MaintenanceType.PerTick));
}
}
public override void BurstingTick()
{
if (ticksLeft > 0)
{
// --- Maintain Visual Effects ---
if (mote != null)
{
mote.UpdateTargets(new TargetInfo(caster.Position, caster.Map), new TargetInfo(beamEndPoint.ToIntVec3(), caster.Map), Vector3.zero, Vector3.zero);
mote.Maintain();
}
if (endEffecter == null && verbProps.beamEndEffecterDef != null)
{
endEffecter = verbProps.beamEndEffecterDef.Spawn(beamEndPoint.ToIntVec3(), caster.Map, Vector3.zero);
}
if (endEffecter != null)
{
endEffecter.EffectTick(new TargetInfo(beamEndPoint.ToIntVec3(), caster.Map), TargetInfo.Invalid);
}
sustainer?.Maintain();
// --- Path Explosion Logic ---
if (BeamProps.explosionEnabled)
{
explosionTicks--;
if (explosionTicks <= 0)
{
ApplyPathExplosionDamage();
explosionTicks = BeamProps.explosionTickInterval;
}
}
ticksLeft--;
if (ticksLeft <= 0)
{
StopBeam();
}
}
}
protected override bool TryCastShot()
{
this.state = VerbState.Bursting;
if (beamHitMapEdge)
{
this.ticksLeft = BeamProps.breachingBeamDuration;
}
else
{
this.ticksLeft = 1;
}
this.explosionTicks = 0;
return true;
}
private void StopBeam()
{
this.state = VerbState.Idle;
mote?.Destroy();
endEffecter?.Cleanup();
sustainer?.End();
}
private void ApplyPathExplosionDamage()
{
if (this.beamEnergy <= 0 || BeamProps.explosionDamageDef == null) return;
var pathCells = WulaBeamUtility.GetCellsInBeamArea(caster.Position, beamEndPoint.ToIntVec3(), (int)verbProps.beamWidth);
var shotAngle = (beamEndPoint - caster.DrawPos).AngleFlat();
var explosionDamageDef = BeamProps.explosionDamageDef;
foreach (var cell in pathCells)
{
if (this.beamEnergy <= 0) break;
if (!cell.InBounds(caster.Map)) continue;
// Performance optimization: don't create explosions on every single cell of the path
if (cell.GetHashCode() % 2 != 0) continue;
var thingsToHit = cell.GetThingList(caster.Map).Where(t => CanHit(t)).ToList();
foreach (var thing in thingsToHit)
{
if (this.beamEnergy <= 0) break;
var dinfo = new DamageInfo(explosionDamageDef, explosionDamageDef.defaultDamage, explosionDamageDef.defaultArmorPenetration, shotAngle, caster, null, EquipmentSource?.def);
float damageDealt = Mathf.Min(thing.HitPoints, dinfo.Amount);
thing.TakeDamage(dinfo);
this.beamEnergy -= damageDealt * BeamProps.explosionEnergyCostRatio;
}
if(explosionDamageDef?.explosionCellMote != null)
{
FleckMaker.Static(cell, caster.Map, explosionDamageDef.explosionCellMote);
}
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref beamEndPoint, "beamEndPoint");
Scribe_Values.Look(ref ticksLeft, "ticksLeft");
Scribe_Values.Look(ref beamHitMapEdge, "beamHitMapEdge");
Scribe_Values.Look(ref explosionTicks, "explosionTicks");
Scribe_Values.Look(ref beamEnergy, "beamEnergy");
}
private bool CanHit(Thing t)
{
return t != null && t.Spawned && t != caster && !t.def.IsFilth;
}
private Vector3 GetMapEdgePoint(IntVec3 start, float angle)
{
float mapSize = Mathf.Max(caster.Map.Size.x, caster.Map.Size.z) * 1.5f;
Vector3 direction = Quaternion.AngleAxis(angle, Vector3.up) * Vector3.forward;
return start.ToVector3() + direction * mapSize;
}
}
}