This commit is contained in:
2025-08-27 21:21:29 +08:00
parent e1477593a6
commit a0ab68e579
12 changed files with 9691 additions and 556 deletions

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<ThingDef ParentName="MoteBase">
<defName>ExcaliburBeam</defName>
<thingClass>WulaFallenEmpire.Thing_ExcaliburBeam</thingClass>
<label>Excalibur Beam</label>
<altitudeLayer>MoteOverhead</altitudeLayer>
<graphicData>
<texPath>Things/Mote/PowerBeam</texPath>
<shaderType>MoteGlow</shaderType>
</graphicData>
<drawOffscreen>true</drawOffscreen>
<mote>
<fadeInTime>0.5</fadeInTime>
<solidTime>9.3</solidTime>
<fadeOutTime>1.0</fadeOutTime>
</mote>
</ThingDef>
</Defs>

View File

@@ -6,7 +6,7 @@
离子武器最终正确架构配置示例 (V17.0)
=====================================================================
说明:
- 核心架构: 自定义 VerbProperties (VerbProperties_Wula_IonicBeam)
- 核心架构: 自定义 VerbProperties (VerbProperties_Excalibur)
- 伤害逻辑: 完全在C#中自定义,包括路径伤害和能量消耗。
- 特效逻辑: 主要由我们自己在VerbProperties中定义的参数手动控制。
-->
@@ -28,90 +28,56 @@
<AccuracyLong>1</AccuracyLong>
<RangedWeapon_Cooldown>1.5</RangedWeapon_Cooldown>
</statBases>
<weaponTags>
<li>WulaExcalibur</li>
</weaponTags>
</ThingDef>
<!-- ==================== 模式一: 离子突破光束枪 (爆发贯穿 + 路径爆炸) ==================== -->
<ThingDef ParentName="Wula_BaseIonicGun">
<defName>WULA_Weapon_BreachingBeamGun</defName>
<label>离子突破光束枪</label>
<description>发射一道高能离子束,能够烧穿路径上的多个目标,直到能量耗尽。光束路径上会周期性地引发小规模湮灭反应</description>
<verbs>
<li Class="WulaFallenEmpire.VerbProperties_Wula_IonicBeam">
<verbClass>WulaFallenEmpire.Verb_Wula_BreachingBeam</verbClass>
<!-- 基础参数 -->
<hasStandardCommand>true</hasStandardCommand>
<warmupTime>2.5</warmupTime>
<range>40</range>
<burstShotCount>1</burstShotCount>
<soundCast>BeamGraser_Shooting</soundCast>
<!-- 我们自定义的伤害参数 -->
<breachingDamage>300</breachingDamage>
<armorPenetration>0.95</armorPenetration>
<breachingBeamDuration>45</breachingBeamDuration>
<!-- 路径爆炸参数 -->
<explosionEnabled>true</explosionEnabled>
<explosionTickInterval>10</explosionTickInterval>
<explosionDamageDef>Wula_Dark_Matter</explosionDamageDef>
<explosionEnergyCostRatio>0.1</explosionEnergyCostRatio>
<!-- 手动特效参数 -->
<explosionHeatEnergyPerCell>10</explosionHeatEnergyPerCell>
<explosionCellFleck>BlastFlame</explosionCellFleck>
<soundExplosion>Explosion_Flame</soundExplosion>
<!-- 光束视觉特效参数 -->
<soundCastBeam>BeamGraser_Shooting</soundCastBeam>
<muzzleFlashScale>12</muzzleFlashScale>
<beamWidth>3</beamWidth>
<beamMoteDef>Mote_GraserBeamBase</beamMoteDef>
<beamEndEffecterDef>GraserBeam_End</beamEndEffecterDef>
<defName>WULA_Weapon_Excalibur</defName>
<label>王者之剑</label>
<description>一把传奇的剑,能够释放出沿路径爆炸的能量</description>
<graphicData>
<texPath>Wula/Weapon/WULA_RW_DM_AR</texPath>
<graphicClass>Graphic_Single</graphicClass>
</graphicData>
<uiIconScale>1.5</uiIconScale>
<equippedAngleOffset>45</equippedAngleOffset>
<tools>
<li>
<label>剑刃</label>
<capacities>
<li>Cut</li>
</capacities>
<power>15</power>
<cooldownTime>2</cooldownTime>
</li>
</verbs>
</ThingDef>
<!-- ==================== 模式二: 离子灼烧光束枪 (持续伤害 + 路径爆炸) ==================== -->
<ThingDef ParentName="Wula_BaseIonicGun">
<defName>WULA_Weapon_SustainedBeamGun</defName>
<label>离子灼烧光束枪</label>
<description>投射一道持续存在的离子场,对作用范围内的所有敌人进行周期性灼烧,并引发连续的能量爆炸。</description>
</tools>
<verbs>
<li Class="WulaFallenEmpire.VerbProperties_Wula_IonicBeam">
<verbClass>WulaFallenEmpire.Verb_Wula_SustainedBeam</verbClass>
<!-- 基础参数 -->
<li Class="WulaFallenEmpire.VerbProperties_Excalibur">
<verbClass>WulaFallenEmpire.Verb_Excalibur</verbClass>
<hasStandardCommand>true</hasStandardCommand>
<range>25.9</range>
<warmupTime>1.5</warmupTime>
<range>30</range>
<!-- 我们自定义的伤害参数 -->
<sustainedDamagePerTick>15</sustainedDamagePerTick>
<tickInterval>15</tickInterval>
<duration>240</duration>
<armorPenetration>0.5</armorPenetration>
<!-- 路径爆炸参数 -->
<explosionEnabled>true</explosionEnabled>
<explosionTickInterval>25</explosionTickInterval>
<explosionDamageDef>Wula_Dark_Matter_Flame</explosionDamageDef>
<!-- 手动特效参数 -->
<explosionHeatEnergyPerCell>10</explosionHeatEnergyPerCell>
<explosionCellFleck>BlastFlame</explosionCellFleck>
<soundExplosion>Explosion_Flame</soundExplosion>
<!-- 光束视觉特效参数 -->
<soundCastBeam>BeamGraser_Shooting</soundCastBeam>
<muzzleFlashScale>9</muzzleFlashScale>
<beamWidth>3</beamWidth>
<beamMoteDef>Mote_GraserBeamBase</beamMoteDef>
<beamEndEffecterDef>GraserBeam_End</beamEndEffecterDef>
<burstShotCount>10</burstShotCount>
<ticksBetweenBurstShots>50</ticksBetweenBurstShots>
<soundCast>ChargeLance_Fire</soundCast>
<soundCastTail>GunTail_Heavy</soundCastTail>
<targetParams>
<canTargetLocations>true</canTargetLocations>
</targetParams>
<accuracyTouch>0.7</accuracyTouch>
<accuracyShort>0.6</accuracyShort>
<accuracyMedium>0.5</accuracyMedium>
<accuracyLong>0.4</accuracyLong>
<minRange>3</minRange>
<requireLineOfSight>true</requireLineOfSight>
<pathWidth>1</pathWidth>
<damageDef>Vaporize</damageDef>
<damageAmount>50</damageAmount>
<armorPenetration>0.3</armorPenetration>
</li>
</verbs>
</ThingDef>
</ThingDef>
</Defs>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class Thing_ExcaliburBeam : Mote
{
public IntVec3 targetCell;
public Pawn caster;
public ThingDef weaponDef;
public float damageAmount;
public float armorPenetration;
public float pathWidth;
public DamageDef damageDef;
// Burst shot support
public int burstShotsTotal = 1;
public int currentBurstShot = 0;
// Path cells for this burst
private List<IntVec3> currentBurstCells;
private int ticksToDetonate = 0;
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref targetCell, "targetCell");
Scribe_References.Look(ref caster, "caster");
Scribe_Defs.Look(ref weaponDef, "weaponDef");
Scribe_Values.Look(ref damageAmount, "damageAmount");
Scribe_Values.Look(ref armorPenetration, "armorPenetration");
Scribe_Values.Look(ref pathWidth, "pathWidth");
Scribe_Defs.Look(ref damageDef, "damageDef");
Scribe_Values.Look(ref burstShotsTotal, "burstShotsTotal", 1);
Scribe_Values.Look(ref currentBurstShot, "currentBurstShot", 0);
}
public void StartStrike(List<IntVec3> allCells, int burstIndex, int totalBursts)
{
currentBurstCells = allCells;
currentBurstShot = burstIndex;
burstShotsTotal = totalBursts;
ticksToDetonate = 1; // Start detonation immediately
}
protected override void TimeInterval(float deltaTime)
{
base.TimeInterval(deltaTime);
if (ticksToDetonate > 0)
{
ticksToDetonate--;
if (ticksToDetonate == 0)
{
Detonate();
}
}
}
private void Detonate()
{
if (currentBurstCells == null || !currentBurstCells.Any())
{
Destroy();
return;
}
// For this burst, we'll detonate all cells
foreach (IntVec3 cell in currentBurstCells)
{
if (cell.InBounds(Map))
{
// Apply explosion effect, but ignore the caster
List<Thing> ignoredThings = new List<Thing> { caster };
DamageDef explosionDamageType = damageDef ?? DamageDefOf.Bomb;
GenExplosion.DoExplosion(center: cell, map: Map, radius: 0.9f, damType: explosionDamageType, instigator: caster,
damAmount: (int)damageAmount, armorPenetration: armorPenetration,
explosionSound: null, weapon: weaponDef, projectile: null,
intendedTarget: null, postExplosionSpawnThingDef: null,
postExplosionSpawnChance: 0f, postExplosionSpawnThingCount: 1,
postExplosionGasType: null, applyDamageToExplosionCellsNeighbors: false,
preExplosionSpawnThingDef: null, preExplosionSpawnChance: 0f,
preExplosionSpawnThingCount: 1, chanceToStartFire: 0f,
damageFalloff: false, direction: null, ignoredThings: ignoredThings,
affectedAngle: null, doVisualEffects: true, propagationSpeed: 0f,
screenShakeFactor: 0f, doSoundEffects: true, postExplosionSpawnThingDefWater: null,
flammabilityChanceCurve: null, overrideCells: null, postExplosionSpawnSingleThingDef: null, preExplosionSpawnSingleThingDef: null);
}
}
Destroy();
}
}
}

View File

@@ -0,0 +1,13 @@
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
public class VerbProperties_Excalibur : VerbProperties
{
public float pathWidth = 1f; // Default path width
public DamageDef damageDef; // Custom damage type
public float damageAmount = -1f; // Custom damage amount
public float armorPenetration = -1f; // Custom armor penetration
}
}

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class Verb_Excalibur : Verb
{
private new Pawn CasterPawn
{
get
{
return base.CasterPawn;
}
}
private ThingWithComps weapon
{
get
{
return this.CasterPawn.equipment.Primary;
}
}
private QualityCategory quality
{
get
{
return this.weapon.TryGetComp<CompQuality>().Quality;
}
}
private float damageAmountBase
{
get
{
return this.weapon.def.tools.First<Tool>().power;
}
}
private float armorPenetrationBase
{
get
{
return this.weapon.def.tools.First<Tool>().armorPenetration;
}
}
private float damageAmount
{
get
{
// Use the damageAmount from VerbProperties if set, otherwise use the base damage
if (this.ExcaliburProps.damageAmount > 0)
{
return this.ExcaliburProps.damageAmount;
}
// Removed AncotUtility.QualityFactor, using a simple multiplier for now
return 1.0f * this.damageAmountBase;
}
}
private float armorPenetration
{
get
{
// Use the armorPenetration from VerbProperties if set, otherwise use the base value
if (this.ExcaliburProps.armorPenetration >= 0)
{
return this.ExcaliburProps.armorPenetration;
}
// Removed AncotUtility.QualityFactor, using a simple multiplier for now
return 1.0f * this.armorPenetrationBase;
}
}
// Temporarily commented out CompWeaponCharge related code
/*
public CompWeaponCharge compCharge
{
get
{
return this.weapon.TryGetComp<CompWeaponCharge>();
}
}
*/
private VerbProperties_Excalibur ExcaliburProps
{
get
{
return (VerbProperties_Excalibur)this.verbProps;
}
}
protected override bool TryCastShot()
{
// Temporarily commented out CompWeaponCharge related code
/*
bool flag = this.compCharge != null && !this.compCharge.CanBeUsed;
if (!flag)
{
CompWeaponCharge compCharge = this.compCharge;
if (compCharge != null)
{
compCharge.UsedOnce();
}
*/
// Calculate all affected cells once
List<IntVec3> allAffectedCells = this.AffectedCells(this.currentTarget);
// Create a beam for this specific burst
Thing_ExcaliburBeam beam = (Thing_ExcaliburBeam)GenSpawn.Spawn(DefDatabase<ThingDef>.GetNamed("ExcaliburBeam", true), this.CasterPawn.Position, this.CasterPawn.Map);
beam.caster = this.CasterPawn;
beam.targetCell = this.currentTarget.Cell;
beam.damageAmount = this.damageAmount;
beam.armorPenetration = this.armorPenetration;
beam.pathWidth = this.ExcaliburProps.pathWidth;
beam.weaponDef = this.CasterPawn.equipment.Primary.def;
beam.damageDef = this.ExcaliburProps.damageDef;
beam.StartStrike(allAffectedCells, this.BurstShotsLeft, this.BurstShotCount);
return true;
/*
}
return false;
*/
}
public override void DrawHighlight(LocalTargetInfo target)
{
GenDraw.DrawFieldEdges(this.AffectedCells(target), 2900);
}
private List<IntVec3> AffectedCells(LocalTargetInfo target)
{
this.tmpCells.Clear();
Vector3 vector = this.CasterPawn.Position.ToVector3Shifted().Yto0();
IntVec3 endCell = this.TargetPosition(this.CasterPawn, target);
this.tmpCells.Clear();
foreach (IntVec3 cell in GenSight.BresenhamCellsBetween(this.CasterPawn.Position, endCell))
{
if (!cell.InBounds(this.CasterPawn.Map))
{
break;
}
if (cell.GetEdifice(this.CasterPawn.Map) != null && cell.GetEdifice(this.CasterPawn.Map).def.passability == Traversability.Impassable)
{
break;
}
// Add cells around the current cell based on pathWidth
// Convert pathWidth to proper radius for GenRadial
float radius = Math.Max(0.5f, this.ExcaliburProps.pathWidth - 0.5f);
foreach (IntVec3 radialCell in GenRadial.RadialCellsAround(cell, radius, true))
{
if (radialCell.InBounds(this.CasterPawn.Map) && !this.tmpCells.Contains(radialCell))
{
this.tmpCells.Add(radialCell);
}
}
}
return this.tmpCells;
}
public IntVec3 TargetPosition(Pawn pawn, LocalTargetInfo currentTarget)
{
IntVec3 position = pawn.Position;
IntVec3 cell = currentTarget.Cell;
Vector3 direction = (cell - position).ToVector3().normalized;
// Define a maximum range to prevent infinite loops or excessively long beams
float maxRange = 1000f; // Increased range for longer beams
for (float i = 0; i < maxRange; i += 1f)
{
IntVec3 currentCell = (position.ToVector3() + direction * i).ToIntVec3();
if (!currentCell.InBounds(pawn.Map))
{
return currentCell; // Reached map boundary
}
// Check for walls or other impassable terrain
if (currentCell.GetEdifice(pawn.Map) != null && currentCell.GetEdifice(pawn.Map).def.passability == Traversability.Impassable)
{
return currentCell; // Hit an impassable wall
}
return (position.ToVector3() + direction * maxRange).ToIntVec3(); // Reached max range
}
private bool CanUseCell(IntVec3 c)
{
return c.InBounds(this.CasterPawn.Map) && c != this.CasterPawn.Position;
}
private List<IntVec3> tmpCells = new List<IntVec3>();
}
}

View File

@@ -1,31 +0,0 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class VerbProperties_Wula_IonicBeam : VerbProperties
{
// --- Mode 1: Breaching Beam Properties ---
public float breachingDamage = 200f;
public float armorPenetration = 0.8f;
public int breachingBeamDuration = 30; // Brief duration after hit calculation
// --- Mode 2: Sustained Beam Properties ---
public float sustainedDamagePerTick = 15f;
public int tickInterval = 10;
public int duration = 120;
// --- NEW: Explosion Path Properties (for both modes) ---
public bool explosionEnabled = false;
public int explosionTickInterval = 15;
public DamageDef explosionDamageDef;
public float explosionEnergyCostRatio = 0.5f; // Only for Breaching Beam
// Manual explosion effect properties
public float explosionHeatEnergyPerCell = 0;
public FleckDef explosionCellFleck;
public Color explosionColorCenter = Color.white;
public Color explosionColorEdge = Color.white;
public SoundDef soundExplosion;
}
}

View File

@@ -1,198 +0,0 @@
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;
}
}
}

View File

@@ -1,181 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class Verb_Wula_SustainedBeam : Verb
{
// --- Copied from Verb_ShootBeam for visual effects ---
private MoteDualAttached mote;
private Effecter endEffecter;
private Sustainer sustainer;
// --- Our custom state ---
private int ticksLeft;
private int ticksToNextDamage;
private int explosionTicks;
private Vector3 beamEnd;
private VerbProperties_Wula_IonicBeam BeamProps => (VerbProperties_Wula_IonicBeam)verbProps;
public override float? AimAngleOverride => (state == VerbState.Bursting) ? (beamEnd - caster.DrawPos).AngleFlat() : (float?)null;
public override void WarmupComplete()
{
base.WarmupComplete();
var shotAngle = (currentTarget.Cell - caster.Position).AngleFlat;
beamEnd = GetMapEdgePoint(caster.Position, shotAngle);
if (verbProps.beamMoteDef != null)
{
mote = MoteMaker.MakeInteractionOverlay(verbProps.beamMoteDef, caster, new TargetInfo(beamEnd.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(beamEnd.ToIntVec3(), caster.Map), Vector3.zero, Vector3.zero);
mote.Maintain();
}
if (endEffecter == null && verbProps.beamEndEffecterDef != null)
{
endEffecter = verbProps.beamEndEffecterDef.Spawn(beamEnd.ToIntVec3(), caster.Map, Vector3.zero);
}
if (endEffecter != null)
{
endEffecter.EffectTick(new TargetInfo(beamEnd.ToIntVec3(), caster.Map), TargetInfo.Invalid);
}
sustainer?.Maintain();
// --- Beam Damage Logic ---
ticksToNextDamage--;
if (ticksToNextDamage <= 0)
{
ApplyBeamDamage();
ticksToNextDamage = BeamProps.tickInterval;
}
// --- 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;
this.ticksLeft = BeamProps.duration;
this.ticksToNextDamage = 0;
this.explosionTicks = 0;
return true;
}
private void StopBeam()
{
this.state = VerbState.Idle;
mote?.Destroy();
endEffecter?.Cleanup();
sustainer?.End();
}
private void ApplyBeamDamage()
{
var shotAngle = (beamEnd - caster.DrawPos).AngleFlat();
var dinfo = new DamageInfo(verbProps.beamDamageDef ?? DamageDefOf.Burn, BeamProps.sustainedDamagePerTick, BeamProps.armorPenetration, shotAngle, caster, null, EquipmentSource?.def);
var cellsInBeam = WulaBeamUtility.GetCellsInBeamArea(caster.Position, beamEnd.ToIntVec3(), (int)verbProps.beamWidth);
foreach (var cell in cellsInBeam)
{
if (!cell.InBounds(caster.Map)) continue;
var thingsToHit = cell.GetThingList(caster.Map).Where(t => CanHit(t)).ToList();
foreach (var thing in thingsToHit)
{
thing.TakeDamage(dinfo);
}
}
}
private void ApplyPathExplosionDamage()
{
if (BeamProps.explosionDamageDef == null) return;
var pathCells = WulaBeamUtility.GetCellsInBeamArea(caster.Position, beamEnd.ToIntVec3(), (int)verbProps.beamWidth);
var shotAngle = (beamEnd - caster.DrawPos).AngleFlat();
var explosionDamageDef = BeamProps.explosionDamageDef;
foreach (var cell in pathCells)
{
if (!cell.InBounds(caster.Map)) continue;
if (cell.GetHashCode() % 3 != 0) continue;
var thingsToHit = cell.GetThingList(caster.Map).Where(t => CanHit(t)).ToList();
foreach (var thing in thingsToHit)
{
var dinfo = new DamageInfo(explosionDamageDef, explosionDamageDef.defaultDamage, explosionDamageDef.defaultArmorPenetration, shotAngle, caster, null, EquipmentSource?.def);
thing.TakeDamage(dinfo);
}
if(BeamProps.explosionCellFleck != null)
{
FleckMaker.Static(cell, caster.Map, BeamProps.explosionCellFleck);
}
if (BeamProps.soundExplosion != null)
{
BeamProps.soundExplosion.PlayOneShot(new TargetInfo(cell, caster.Map));
}
GenTemperature.PushHeat(cell, caster.Map, BeamProps.explosionHeatEnergyPerCell);
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref ticksLeft, "ticksLeft", 0);
Scribe_Values.Look(ref ticksToNextDamage, "ticksToNextDamage", 0);
Scribe_Values.Look(ref explosionTicks, "explosionTicks");
Scribe_Values.Look(ref beamEnd, "beamEnd");
}
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;
}
}
}

View File

@@ -1,64 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public static class WulaBeamUtility
{
private static readonly Material BeamMaterial = MaterialPool.MatFrom(GenDraw.LineTexPath, ShaderDatabase.Transparent, Color.white);
// A more advanced method to get all cells in a rectangular area
public static IEnumerable<IntVec3> GetCellsInBeamArea(IntVec3 start, IntVec3 end, int width)
{
var beamLine = GenSight.PointsOnLineOfSight(start, end);
if (width <= 1)
{
return beamLine.Distinct();
}
var allCells = new HashSet<IntVec3>(beamLine);
var halfWidth = (width - 1) / 2;
if (halfWidth == 0) return allCells;
var angle = (end - start).AngleFlat;
var perpendicularAngle = angle - 90f;
foreach (var cell in beamLine)
{
for (int i = 1; i <= halfWidth; i++)
{
var offset = Vector3.forward.RotatedBy(perpendicularAngle) * i;
allCells.Add((cell.ToVector3() + offset).ToIntVec3());
allCells.Add((cell.ToVector3() - offset).ToIntVec3());
}
}
return allCells;
}
// A shared drawing method
public static void DrawBeam(Vector3 start, Vector3 end, Color color, float width)
{
var material = BeamMaterial;
if (material.color != color)
{
material = MaterialPool.MatFrom(GenDraw.LineTexPath, ShaderDatabase.Transparent, color);
}
var matrix = default(Matrix4x4);
var distance = Vector3.Distance(start, end);
var angle = (end - start).AngleFlat();
matrix.SetTRS(
pos: start + (end - start) / 2f,
q: Quaternion.AngleAxis(angle, Vector3.up),
s: new Vector3(width, 1f, distance)
);
Graphics.DrawMesh(MeshPool.plane10, matrix, material, 0);
}
}
}

View File

@@ -191,12 +191,12 @@
<Compile Include="CompForceTargetable.cs" />
<Compile Include="Patch_ForceTargetable.cs" />
<Compile Include="Patch_ArmedShuttle_ForceTargetable.cs" />
<Compile Include="VerbProperties_Wula_IonicBeam.cs" />
<Compile Include="Verb_Wula_BreachingBeam.cs" />
<Compile Include="Verb_Wula_SustainedBeam.cs" />
<Compile Include="WulaBeamUtility.cs" />
<Compile Include="Verb\VerbProperties_Excalibur.cs" />
<Compile Include="Verb\Verb_Excalibur.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Thing_ExcaliburBeam.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->
<Target Name="CleanDebugFiles" AfterTargets="Build">