diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll
index 9b22b4c..81bed62 100644
Binary files a/1.6/1.6/Assemblies/ArachnaeSwarm.dll and b/1.6/1.6/Assemblies/ArachnaeSwarm.dll differ
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo
index 1b0ba5b..047ac6f 100644
Binary files a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo and b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/.suo differ
diff --git a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
index 5222678..dbfd2c0 100644
--- a/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
+++ b/Source/ArachnaeSwarm/.vs/ArachnaeSwarm/v17/DocumentLayout.json
@@ -3,8 +3,12 @@
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
{
- "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\ara_hivemind\\compabilityeffect_binddrone.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
- "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:ara_hivemind\\compabilityeffect_binddrone.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\verb\\verb_shootmeltbeam.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:verb\\verb_shootmeltbeam.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+ },
+ {
+ "AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\verb\\compcleave.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+ "RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:verb\\compcleave.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -19,19 +23,32 @@
{
"$type": "Document",
"DocumentIndex": 0,
- "Title": "CompAbilityEffect_BindDrone.cs",
- "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs",
- "RelativeDocumentMoniker": "ARA_HiveMind\\CompAbilityEffect_BindDrone.cs",
- "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_HiveMind\\CompAbilityEffect_BindDrone.cs",
- "RelativeToolTip": "ARA_HiveMind\\CompAbilityEffect_BindDrone.cs",
- "ViewState": "AgIAAAMAAAAAAAAAAAAAABEAAABkAAAAAAAAAA==",
+ "Title": "Verb_ShootMeltBeam.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verb\\Verb_ShootMeltBeam.cs",
+ "RelativeDocumentMoniker": "Verb\\Verb_ShootMeltBeam.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verb\\Verb_ShootMeltBeam.cs",
+ "RelativeToolTip": "Verb\\Verb_ShootMeltBeam.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAA8AAAAyAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
- "WhenOpened": "2025-09-09T02:52:35.777Z",
+ "WhenOpened": "2025-09-09T03:04:00.299Z",
"EditorCaption": ""
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
+ },
+ {
+ "$type": "Document",
+ "DocumentIndex": 1,
+ "Title": "CompCleave.cs",
+ "DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verb\\CompCleave.cs",
+ "RelativeDocumentMoniker": "Verb\\CompCleave.cs",
+ "ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Verb\\CompCleave.cs",
+ "RelativeToolTip": "Verb\\CompCleave.cs",
+ "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAAmAAAAAAAAAA==",
+ "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+ "WhenOpened": "2025-09-09T03:03:30.118Z",
+ "EditorCaption": ""
}
]
}
diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
index 7ea45a9..713ad63 100644
--- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
+++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj
@@ -90,6 +90,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/ArachnaeSwarm/Verb/CompCleave.cs b/Source/ArachnaeSwarm/Verb/CompCleave.cs
new file mode 100644
index 0000000..e969134
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/CompCleave.cs
@@ -0,0 +1,23 @@
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_Cleave : CompProperties
+ {
+ public float cleaveAngle = 90f;
+ public float cleaveRange = 2.9f;
+ public float cleaveDamageFactor = 0.7f;
+ public bool damageDowned = false;
+ public DamageDef explosionDamageDef = null;
+
+ public CompProperties_Cleave()
+ {
+ this.compClass = typeof(CompCleave);
+ }
+ }
+
+ public class CompCleave : ThingComp
+ {
+ public CompProperties_Cleave Props => (CompProperties_Cleave)this.props;
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/CompMultiStrike.cs b/Source/ArachnaeSwarm/Verb/CompMultiStrike.cs
new file mode 100644
index 0000000..c615eb0
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/CompMultiStrike.cs
@@ -0,0 +1,20 @@
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class CompProperties_MultiStrike : CompProperties
+ {
+ public IntRange strikeCount = new IntRange(3, 3);
+ public float damageMultiplierPerStrike = 0.5f;
+
+ public CompProperties_MultiStrike()
+ {
+ compClass = typeof(CompMultiStrike);
+ }
+ }
+
+ public class CompMultiStrike : ThingComp
+ {
+ public CompProperties_MultiStrike Props => (CompProperties_MultiStrike)props;
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/VerbPropertiesExplosiveBeam.cs b/Source/ArachnaeSwarm/Verb/VerbPropertiesExplosiveBeam.cs
new file mode 100644
index 0000000..1288fb3
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/VerbPropertiesExplosiveBeam.cs
@@ -0,0 +1,50 @@
+using RimWorld;
+using Verse;
+using Verse.Sound;
+
+namespace ArachnaeSwarm
+{
+ public class VerbPropertiesExplosiveBeam : VerbProperties
+ {
+ // 爆炸开关
+ public bool enableExplosion = false;
+
+ // 每x个shotcount触发一次爆炸
+ public int explosionShotInterval = 1;
+
+ // 爆炸基础属性
+ public float explosionRadius = 2.9f;
+ public DamageDef explosionDamageDef = null; // null时使用默认的Bomb
+ public int explosionDamage = -1; // -1时使用武器默认伤害
+ public float explosionArmorPenetration = -1f; // -1时使用武器默认穿甲
+
+ // 爆炸音效和特效
+ public SoundDef explosionSound = null;
+ public EffecterDef explosionEffecter = null;
+
+ // 爆炸后生成物品
+ public ThingDef postExplosionSpawnThingDef = null;
+ public float postExplosionSpawnChance = 0f;
+ public int postExplosionSpawnThingCount = 1;
+
+ // 爆炸前生成物品
+ public ThingDef preExplosionSpawnThingDef = null;
+ public float preExplosionSpawnChance = 0f;
+ public int preExplosionSpawnThingCount = 1;
+
+ // 气体效果
+ public GasType? postExplosionGasType = null;
+
+ // 其他爆炸属性
+ public bool applyDamageToExplosionCellsNeighbors = true;
+ public float chanceToStartFire = 0f;
+ public bool damageFalloff = true;
+ public float screenShakeFactor = 0f; // 新增:屏幕震动因子
+
+ public VerbPropertiesExplosiveBeam()
+ {
+ // 设置默认值
+ verbClass = typeof(Verb_ShootBeamExplosive);
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Verb/VerbProperties_Excalibur.cs b/Source/ArachnaeSwarm/Verb/VerbProperties_Excalibur.cs
new file mode 100644
index 0000000..c4595bb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/VerbProperties_Excalibur.cs
@@ -0,0 +1,15 @@
+using Verse;
+using RimWorld;
+
+namespace ArachnaeSwarm
+{
+ 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
+ public float maxRange = 1000f; // Default max range for beams
+ public string beamDefName = "ExcaliburBeam"; // Default beam def name
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/VerbProperties_WeaponStealBeam.cs b/Source/ArachnaeSwarm/Verb/VerbProperties_WeaponStealBeam.cs
new file mode 100644
index 0000000..dfba763
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/VerbProperties_WeaponStealBeam.cs
@@ -0,0 +1,18 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class VerbProperties_WeaponStealBeam : VerbPropertiesExplosiveBeam
+ {
+ public HediffDef hediffToApply;
+ public float hediffSeverityPerHit = 0.1f; // 每次命中增加的严重性百分比
+ public float hediffMaxSeverity = 1.0f; // 达到此严重性时触发抢夺
+ public bool removeHediffOnSteal = true; // 抢夺后是否移除hediff
+
+ public VerbProperties_WeaponStealBeam()
+ {
+ verbClass = typeof(Verb_ShootWeaponStealBeam);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_Cleave.cs b/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_Cleave.cs
new file mode 100644
index 0000000..a4a7bfa
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_Cleave.cs
@@ -0,0 +1,180 @@
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace ArachnaeSwarm
+{
+ public class Verb_MeleeAttack_Cleave : Verb_MeleeAttack
+ {
+ private CompCleave Comp
+ {
+ get
+ {
+ return this.EquipmentSource?.GetComp();
+ }
+ }
+
+ protected override DamageWorker.DamageResult ApplyMeleeDamageToTarget(LocalTargetInfo target)
+ {
+ if (this.Comp == null)
+ {
+ // This verb should only be used with a weapon that has CompCleave
+ return new DamageWorker.DamageResult();
+ }
+
+ DamageWorker.DamageResult result = new DamageWorker.DamageResult();
+
+ // 1. 对主目标造成伤害
+ DamageInfo dinfo = new DamageInfo(
+ this.verbProps.meleeDamageDef,
+ this.verbProps.AdjustedMeleeDamageAmount(this, this.CasterPawn),
+ this.verbProps.AdjustedArmorPenetration(this, this.CasterPawn),
+ -1f,
+ this.CasterPawn,
+ null,
+ this.EquipmentSource?.def
+ );
+ dinfo.SetTool(this.tool);
+
+ if (target.HasThing)
+ {
+ result = target.Thing.TakeDamage(dinfo);
+ }
+
+ // 2. 执行溅射伤害
+ Pawn casterPawn = this.CasterPawn;
+ if (casterPawn == null || !target.HasThing)
+ {
+ return result;
+ }
+
+ Thing mainTarget = target.Thing;
+ Vector3 attackDirection = (mainTarget.Position - casterPawn.Position).ToVector3().normalized;
+ bool mainTargetIsHostile = mainTarget.HostileTo(casterPawn);
+
+ // 查找施法者周围的潜在目标
+ IEnumerable potentialTargets = GenRadial.RadialDistinctThingsAround(casterPawn.Position, casterPawn.Map, this.Comp.Props.cleaveRange, useCenter: true);
+
+ foreach (Thing thing in potentialTargets)
+ {
+ // 跳过主目标、自己和非生物
+ if (thing == mainTarget || thing == casterPawn || !(thing is Pawn secondaryTargetPawn))
+ {
+ continue;
+ }
+
+ // 根据XML配置决定是否跳过倒地的生物
+ if (!this.Comp.Props.damageDowned && secondaryTargetPawn.Downed)
+ {
+ continue;
+ }
+
+ // 智能溅射:次要目标的敌对状态必须与主目标一致
+ if (secondaryTargetPawn.HostileTo(casterPawn) != mainTargetIsHostile)
+ {
+ continue;
+ }
+
+ // 检查目标是否在攻击扇形范围内
+ Vector3 directionToTarget = (thing.Position - casterPawn.Position).ToVector3().normalized;
+ float angle = Vector3.Angle(attackDirection, directionToTarget);
+
+ if (angle <= this.Comp.Props.cleaveAngle / 2f)
+ {
+ // 对次要目标造成伤害
+ DamageInfo cleaveDinfo = new DamageInfo(
+ this.verbProps.meleeDamageDef,
+ this.verbProps.AdjustedMeleeDamageAmount(this, casterPawn) * this.Comp.Props.cleaveDamageFactor,
+ this.verbProps.AdjustedArmorPenetration(this, casterPawn) * this.Comp.Props.cleaveDamageFactor,
+ -1f,
+ casterPawn,
+ null,
+ this.EquipmentSource?.def
+ );
+ cleaveDinfo.SetTool(this.tool);
+ secondaryTargetPawn.TakeDamage(cleaveDinfo);
+ }
+ }
+
+ // 3. 创建扇形爆炸效果
+ CreateCleaveExplosion(casterPawn, mainTarget, this.Comp.Props.cleaveRange, this.Comp.Props.cleaveAngle);
+
+ return result;
+ }
+
+ private void CreateCleaveExplosion(Pawn caster, Thing target, float radius, float angle)
+ {
+ if (caster.Map == null || this.Comp.Props.explosionDamageDef == null) return;
+
+ Vector3 direction = (target.Position - caster.Position).ToVector3().normalized;
+ float baseAngle = direction.AngleFlat();
+
+ float startAngle = baseAngle - (angle / 2f);
+ float endAngle = baseAngle + (angle / 2f);
+
+ GenExplosion.DoExplosion(
+ center: caster.Position,
+ map: caster.Map,
+ radius: radius,
+ damType: this.Comp.Props.explosionDamageDef,
+ instigator: caster,
+ damAmount: 0,
+ armorPenetration: 0,
+ explosionSound: null,
+ weapon: this.EquipmentSource?.def,
+ projectile: null,
+ intendedTarget: target,
+ postExplosionSpawnThingDef: null,
+ postExplosionSpawnChance: 0f,
+ postExplosionSpawnThingCount: 1,
+ postExplosionGasType: null,
+ applyDamageToExplosionCellsNeighbors: false,
+ preExplosionSpawnThingDef: null,
+ preExplosionSpawnChance: 0f,
+ preExplosionSpawnThingCount: 1,
+ chanceToStartFire: 0f,
+ damageFalloff: false,
+ direction: null, // Let affectedAngle handle the direction and arc
+ ignoredThings: null,
+ affectedAngle: new FloatRange(startAngle, endAngle),
+ doVisualEffects: true,
+ propagationSpeed: 1.7f,
+ excludeRadius: 0.9f,
+ doSoundEffects: false,
+ screenShakeFactor: 0.2f
+ );
+ }
+
+ public override void DrawHighlight(LocalTargetInfo target)
+ {
+ base.DrawHighlight(target);
+
+ if (target.IsValid && CasterPawn != null && this.Comp != null)
+ {
+ GenDraw.DrawFieldEdges(GetCleaveCells(target.Cell));
+ }
+ }
+
+ private List GetCleaveCells(IntVec3 center)
+ {
+ if (this.Comp == null)
+ {
+ return new List();
+ }
+
+ IntVec3 casterPos = this.CasterPawn.Position;
+ Map map = this.CasterPawn.Map;
+ Vector3 attackDirection = (center - casterPos).ToVector3().normalized;
+
+ return GenRadial.RadialCellsAround(casterPos, this.Comp.Props.cleaveRange, useCenter: true)
+ .Where(cell => {
+ if (!cell.InBounds(map)) return false;
+ Vector3 directionToCell = (cell - casterPos).ToVector3();
+ if (directionToCell.sqrMagnitude <= 0.001f) return false; // Exclude caster's cell
+ return Vector3.Angle(attackDirection, directionToCell) <= this.Comp.Props.cleaveAngle / 2f;
+ }).ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_MultiStrike.cs b/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_MultiStrike.cs
new file mode 100644
index 0000000..ca731f6
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/Verb_MeleeAttack_MultiStrike.cs
@@ -0,0 +1,53 @@
+using RimWorld;
+using Verse;
+
+namespace ArachnaeSwarm
+{
+ public class Verb_MeleeAttack_MultiStrike : Verb_MeleeAttack
+ {
+ private CompMultiStrike Comp
+ {
+ get
+ {
+ return this.EquipmentSource?.GetComp();
+ }
+ }
+
+ protected override DamageWorker.DamageResult ApplyMeleeDamageToTarget(LocalTargetInfo target)
+ {
+ DamageWorker.DamageResult result = new DamageWorker.DamageResult();
+ if (this.Comp != null && target.HasThing)
+ {
+ int strikes = this.Comp.Props.strikeCount.RandomInRange;
+ for (int i = 0; i < strikes; i++)
+ {
+ if (target.ThingDestroyed)
+ {
+ break;
+ }
+ DamageInfo dinfo = new DamageInfo(this.verbProps.meleeDamageDef, this.verbProps.AdjustedMeleeDamageAmount(this, this.CasterPawn) * this.Comp.Props.damageMultiplierPerStrike, this.verbProps.AdjustedArmorPenetration(this, this.CasterPawn) * this.Comp.Props.damageMultiplierPerStrike, -1f, this.CasterPawn, null, this.EquipmentSource?.def);
+ dinfo.SetTool(this.tool);
+ DamageWorker.DamageResult damageResult = target.Thing.TakeDamage(dinfo);
+ result.totalDamageDealt += damageResult.totalDamageDealt;
+ result.wounded = (result.wounded || damageResult.wounded);
+ result.stunned = (result.stunned || damageResult.stunned);
+ if (damageResult.parts != null)
+ {
+ if (result.parts == null)
+ {
+ result.parts = new System.Collections.Generic.List();
+ }
+ result.parts.AddRange(damageResult.parts);
+ }
+ }
+ }
+ else
+ {
+ DamageInfo dinfo2 = new DamageInfo(this.verbProps.meleeDamageDef, this.verbProps.AdjustedMeleeDamageAmount(this, this.CasterPawn), this.verbProps.AdjustedArmorPenetration(this, this.CasterPawn), -1f, this.CasterPawn, null, this.EquipmentSource?.def);
+ dinfo2.SetTool(this.tool);
+ result = target.Thing.TakeDamage(dinfo2);
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/ArachnaeSwarm/Verb/Verb_ShootArc.cs b/Source/ArachnaeSwarm/Verb/Verb_ShootArc.cs
new file mode 100644
index 0000000..cc3d2bb
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/Verb_ShootArc.cs
@@ -0,0 +1,401 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+
+namespace ArachnaeSwarm
+{
+ public class VerbProperties_Arc : VerbProperties
+ {
+ public DamageDef damageDef;
+
+ public float EMPDamageAmount = -1f;
+
+ public int damageAmount = -1;
+
+ public float armorPenetration = -1f;
+
+ public float affectedAngle;
+
+ public bool isConductible = false;
+
+ public int conductNum;
+
+ public bool conductFriendly = false;
+
+ public bool conductHostile = true;
+ }
+
+ public class Verb_ShootArc : Verb
+ {
+ private VerbProperties_Arc Props
+ {
+ get
+ {
+ return (VerbProperties_Arc)this.verbProps;
+ }
+ }
+
+ private int damageAmount
+ {
+ get
+ {
+ if (this.Props.damageAmount > 0)
+ {
+ return this.Props.damageAmount;
+ }
+ if (this.verbProps.beamDamageDef != null)
+ {
+ return this.verbProps.beamDamageDef.defaultDamage;
+ }
+ Log.ErrorOnce(string.Format("Verb_ShootArc on {0} has no damageAmount and no beamDamageDef.", (this.caster != null) ? this.caster.def.defName : "null"), this.GetHashCode());
+ return 0;
+ }
+ }
+
+ private float armorPenetration
+ {
+ get
+ {
+ if (this.Props.armorPenetration > 0f)
+ {
+ return this.Props.armorPenetration;
+ }
+ if (this.verbProps.beamDamageDef != null)
+ {
+ return this.verbProps.beamDamageDef.defaultArmorPenetration;
+ }
+ return 0f;
+ }
+ }
+
+ public override void WarmupComplete()
+ {
+ this.TryCastShot();
+ }
+
+ protected override bool TryCastShot()
+ {
+ this.MakeExplosion();
+ bool flag = this.verbProps.soundCast != null;
+ bool flag3 = flag;
+ if (flag3)
+ {
+ this.verbProps.soundCast.PlayOneShot(new TargetInfo(this.caster.Position, this.caster.MapHeld, false));
+ }
+ bool flag2 = this.verbProps.soundCastTail != null;
+ bool flag4 = flag2;
+ if (flag4)
+ {
+ this.verbProps.soundCastTail.PlayOneShotOnCamera(this.caster.Map);
+ }
+ return true;
+ }
+
+ private bool IsTargetImmobile(LocalTargetInfo target)
+ {
+ Thing thing = target.Thing;
+ Pawn pawn = thing as Pawn;
+ return pawn != null && !pawn.Downed && pawn.GetPosture() == PawnPosture.Standing;
+ }
+
+ public override bool CanHitTarget(LocalTargetInfo targ)
+ {
+ bool flag = this.caster == null || !this.caster.Spawned;
+ bool flag2 = flag;
+ return !flag2 && (targ == this.caster || this.CanHitTargetFrom(this.caster.Position, targ));
+ }
+
+ protected void MakeExplosion()
+ {
+ Pawn casterPawn = this.CasterPawn;
+ if (!casterPawn.Spawned || this.Props == null)
+ {
+ return;
+ }
+
+ // 技能学习逻辑 (只在目标是站立Pawn时)
+ if (this.currentTarget.Thing is Pawn targetPawn && !targetPawn.Downed && targetPawn.GetPosture() == PawnPosture.Standing && casterPawn.skills != null)
+ {
+ casterPawn.skills.Learn(SkillDefOf.Shooting, 250f * verbProps.AdjustedFullCycleTime(this, casterPawn), false, false);
+ }
+
+ float weaponDamageMultiplier = base.EquipmentSource.GetStatValue(StatDefOf.RangedWeapon_DamageMultiplier, true, -1);
+ int damageMultiplier = this.GetDamageAmount(weaponDamageMultiplier, null);
+ float armorPenetrationMultiplier = this.GetArmorPenetration(weaponDamageMultiplier, null);
+
+ // 总是先收集范围内的Pawn,为后续决策做准备
+ List cells = Verb_ShootArc.circularSectorCellsStartedCaster(casterPawn.Position, casterPawn.Map, this.currentTarget.Cell, this.Props.range, this.Props.affectedAngle, false).ToList();
+ HashSet hashSet = this.HashSetConverter(cells);
+ this.pawnConduct.Add(casterPawn);
+
+ foreach (IntVec3 cell in hashSet)
+ {
+ List list = casterPawn.Map.thingGrid.ThingsListAt(cell);
+ for (int num = list.Count - 1; num >= 0; num--)
+ {
+ if (list[num] is Pawn p)
+ {
+ bool isFriendly = p.Faction != null && casterPawn.Faction != null && !p.Faction.HostileTo(casterPawn.Faction);
+ if ((!this.Props.conductFriendly && isFriendly) || (!this.Props.conductHostile && p.HostileTo(casterPawn)))
+ {
+ continue;
+ }
+ bool isInvalidPosture = p.GetPosture() != PawnPosture.Standing && this.currentTarget.Thing != p;
+ if (!isInvalidPosture)
+ {
+ this.pawnConduct.Add(p);
+ }
+ }
+ }
+ }
+
+ // 决策:如果设为导电模式且有至少一个传导目标,则进行链式攻击
+ if (this.Props.isConductible && this.pawnConduct.Count > 1)
+ {
+ for (int i = 0; i < this.Props.conductNum && i < this.pawnConduct.Count - 1; i++)
+ {
+ if (this.Props.EMPDamageAmount > 0f)
+ {
+ this.TargetTakeDamage(casterPawn, this.pawnConduct[i + 1], DamageDefOf.EMP, this.Props.EMPDamageAmount, -1f);
+ }
+ this.TargetTakeDamage(casterPawn, this.pawnConduct[i + 1], this.Props.damageDef, (float)damageMultiplier, armorPenetrationMultiplier);
+ if (this.verbProps.beamMoteDef != null)
+ {
+ MoteMaker.MakeInteractionOverlay(this.verbProps.beamMoteDef, new TargetInfo(this.pawnConduct[i].Position, this.caster.Map, false), new TargetInfo(this.pawnConduct[i + 1].Position, this.caster.Map, false));
+ }
+ }
+ }
+ // 否则(非导电模式,或没有传导目标),执行一次普通的单体攻击
+ else
+ {
+ Thing primaryTarget = this.currentTarget.Thing;
+ if (primaryTarget != null)
+ {
+ float angle = (primaryTarget.Position - this.caster.Position).AngleFlat;
+ DamageInfo dinfo = new DamageInfo(this.Props.damageDef, (float)damageMultiplier, armorPenetrationMultiplier, angle, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing);
+ primaryTarget.TakeDamage(dinfo);
+ }
+
+ // 无论是否命中,都显示视觉效果
+ if (this.verbProps.beamMoteDef != null)
+ {
+ MoteMaker.MakeInteractionOverlay(this.verbProps.beamMoteDef, new TargetInfo(this.caster.Position, this.caster.Map, false), new TargetInfo(this.currentTarget.Cell, this.caster.Map, false));
+ }
+ }
+ this.pawnConduct.Clear();
+ }
+
+ private void DoExplosion(Pawn casterPawn, int damAmount, float armorPenetration, FloatRange? affectedAngle)
+ {
+ GenExplosion.DoExplosion(
+ center: casterPawn.Position,
+ map: this.caster.MapHeld,
+ radius: this.verbProps.range,
+ damType: this.Props.damageDef,
+ instigator: casterPawn, // Corrected
+ damAmount: damAmount,
+ armorPenetration: armorPenetration,
+ explosionSound: null,
+ weapon: this.CasterPawn.equipment?.Primary?.def, // Safety check
+ projectile: null,
+ intendedTarget: this.currentTarget.Thing, // Corrected
+ postExplosionSpawnThingDef: null, // Simplified
+ postExplosionSpawnChance: 0f,
+ postExplosionSpawnThingCount: 1,
+ postExplosionGasType: null,
+ postExplosionGasRadiusOverride: null,
+ postExplosionGasAmount: 0,
+ applyDamageToExplosionCellsNeighbors: false,
+ preExplosionSpawnThingDef: null,
+ preExplosionSpawnChance: 0f,
+ preExplosionSpawnThingCount: 1,
+ chanceToStartFire: 0f,
+ damageFalloff: false,
+ direction: null,
+ ignoredThings: null,
+ affectedAngle: affectedAngle,
+ doVisualEffects: true,
+ propagationSpeed: 0.6f,
+ excludeRadius: 0f,
+ doSoundEffects: false,
+ postExplosionSpawnThingDefWater: null,
+ screenShakeFactor: 1f,
+ flammabilityChanceCurve: null,
+ overrideCells: null,
+ postExplosionSpawnSingleThingDef: null,
+ preExplosionSpawnSingleThingDef: null
+ );
+ }
+
+
+ public override void DrawHighlight(LocalTargetInfo target)
+ {
+ base.DrawHighlight(target);
+ bool isValid = target.IsValid;
+ bool flag = isValid;
+ if (flag)
+ {
+ IntVec3 position = this.caster.Position;
+ float num = Mathf.Atan2(-(float)(target.Cell.z - position.z), (float)(target.Cell.x - position.x)) * 57.29578f;
+ Verb_ShootArc.RenderPredictedAreaOfEffect(this.caster.Position, this.Props.range, this.verbProps.explosionRadiusRingColor, new FloatRange(num - this.Props.affectedAngle, num + this.Props.affectedAngle));
+ }
+ }
+
+ public static void RenderPredictedAreaOfEffect(IntVec3 loc, float radius, Color color, FloatRange affectedAngle)
+ {
+ bool flag = affectedAngle.min < -180f || affectedAngle.max > 180f;
+ bool flag2 = flag;
+ List cellsSum;
+ if (flag2)
+ {
+ DamageWorker worker = DamageDefOf.Bomb.Worker;
+ Map currentMap = Find.CurrentMap;
+ FloatRange? affectedAngle2 = new FloatRange?(new FloatRange(Verb_ShootArc.AngleWrapped(affectedAngle.min), 180f));
+ List cells = worker.ExplosionCellsToHit(loc, currentMap, radius, null, null, affectedAngle2).ToList();
+ DamageWorker worker2 = DamageDefOf.Bomb.Worker;
+ Map currentMap2 = Find.CurrentMap;
+ affectedAngle2 = new FloatRange?(new FloatRange(-180f, Verb_ShootArc.AngleWrapped(affectedAngle.max)));
+ List cells2 = worker2.ExplosionCellsToHit(loc, currentMap2, radius, null, null, affectedAngle2).ToList();
+ cellsSum = cells.Concat(cells2).ToList();
+ }
+ else
+ {
+ DamageWorker worker3 = DamageDefOf.Bomb.Worker;
+ Map currentMap3 = Find.CurrentMap;
+ FloatRange? affectedAngle3 = new FloatRange?(affectedAngle);
+ cellsSum = worker3.ExplosionCellsToHit(loc, currentMap3, radius, null, null, affectedAngle3).ToList();
+ }
+ GenDraw.DrawFieldEdges(cellsSum, color, null);
+ }
+
+ public static float AngleWrapped(float angle)
+ {
+ while (angle > 180f)
+ {
+ angle -= 360f;
+ }
+ while (angle < -180f)
+ {
+ angle += 360f;
+ }
+ return (angle == 180f) ? -180f : angle;
+ }
+
+ private static IEnumerable circularSectorCellsStartedCaster(IntVec3 center, Map map, IntVec3 target, float radius, float angle, bool useCenter = false)
+ {
+ float num = Mathf.Atan2(-(float)(target.z - center.z), (float)(target.x - center.x)) * 57.29578f;
+ FloatRange affectedAngle = new FloatRange(num - angle, num + angle);
+ bool flag = affectedAngle.min < -180f || affectedAngle.max > 180f;
+ bool flag2 = flag;
+ List cellsSum;
+ if (flag2)
+ {
+ DamageWorker worker = DamageDefOf.Bomb.Worker;
+ FloatRange? affectedAngle2 = new FloatRange?(new FloatRange(Verb_ShootArc.AngleWrapped(affectedAngle.min), 180f));
+ List cells = worker.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle2).ToList();
+ DamageWorker worker2 = DamageDefOf.Bomb.Worker;
+ affectedAngle2 = new FloatRange?(new FloatRange(-180f, Verb_ShootArc.AngleWrapped(affectedAngle.max)));
+ List cells2 = worker2.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle2).ToList();
+ cellsSum = cells.Concat(cells2).ToList();
+ }
+ else
+ {
+ DamageWorker worker3 = DamageDefOf.Bomb.Worker;
+ FloatRange? affectedAngle3 = new FloatRange?(affectedAngle);
+ cellsSum = worker3.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle3).ToList();
+ }
+ return cellsSum;
+ }
+
+ protected virtual HashSet HashSetConverter(IEnumerable points)
+ {
+ HashSet hashSet = new HashSet();
+ bool flag = points.Any();
+ bool flag2 = flag;
+ if (flag2)
+ {
+ foreach (IntVec3 point in points)
+ {
+ hashSet.Add(point);
+ }
+ }
+ return hashSet;
+ }
+
+ private void TargetTakeDamage(Pawn caster, Pawn target, DamageDef damageDef, float damageAmount, float armorPenetration = -1f)
+ {
+ bool flag = caster == null || target == null;
+ bool flag2 = flag;
+ if (flag2)
+ {
+ Log.Error("TargetTakeDamage has null caster or target");
+ }
+ else
+ {
+ float angleFlat = (this.currentTarget.Cell - caster.Position).AngleFlat;
+ BattleLogEntry_RangedImpact log = new BattleLogEntry_RangedImpact(caster, target, this.currentTarget.Thing, base.EquipmentSource.def, null, null);
+ DamageInfo dinfo = new DamageInfo(damageDef, damageAmount, armorPenetration, angleFlat, caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing, true, true, QualityCategory.Normal, true);
+ target.TakeDamage(dinfo).AssociateWithLog(log);
+ }
+ }
+
+ public int GetDamageAmount(float weaponDamageMultiplier, StringBuilder explanation = null)
+ {
+ int num = this.damageAmount;
+ bool flag = explanation != null;
+ bool flag3 = flag;
+ if (flag3)
+ {
+ explanation.AppendLine("StatsReport_BaseValue".Translate() + ": " + num.ToString());
+ explanation.Append("StatsReport_QualityMultiplier".Translate() + ": " + weaponDamageMultiplier.ToStringPercent());
+ }
+ num = Mathf.RoundToInt((float)num * weaponDamageMultiplier);
+ bool flag2 = explanation != null;
+ bool flag4 = flag2;
+ if (flag4)
+ {
+ explanation.AppendLine();
+ explanation.AppendLine();
+ explanation.Append("StatsReport_FinalValue".Translate() + ": " + num.ToString());
+ }
+ return num;
+ }
+
+ public float GetArmorPenetration(float weaponDamageMultiplier, StringBuilder explanation = null)
+ {
+ float num = this.armorPenetration;
+ bool flag = num < 0f;
+ bool flag4 = flag;
+ if (flag4)
+ {
+ num = (float)this.damageAmount * 0.015f;
+ }
+ bool flag2 = explanation != null;
+ bool flag5 = flag2;
+ if (flag5)
+ {
+ explanation.AppendLine("StatsReport_BaseValue".Translate() + ": " + num.ToStringPercent());
+ explanation.AppendLine();
+ explanation.Append("StatsReport_QualityMultiplier".Translate() + ": " + weaponDamageMultiplier.ToStringPercent());
+ }
+ num *= weaponDamageMultiplier;
+ bool flag3 = explanation != null;
+ bool flag6 = flag3;
+ if (flag6)
+ {
+ explanation.AppendLine();
+ explanation.AppendLine();
+ explanation.Append("StatsReport_FinalValue".Translate() + ": " + num.ToStringPercent());
+ }
+ return num;
+ }
+
+ public List pawnConduct = new List();
+ }
+}
+
diff --git a/Source/ArachnaeSwarm/Verb/Verb_ShootBeamExplosive.cs b/Source/ArachnaeSwarm/Verb/Verb_ShootBeamExplosive.cs
new file mode 100644
index 0000000..fe2175e
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/Verb_ShootBeamExplosive.cs
@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+
+namespace ArachnaeSwarm
+{
+ public class Verb_ShootBeamExplosive : Verse.Verb_ShootBeam
+ {
+ private int explosionShotCounter = 0;
+
+ protected override bool TryCastShot()
+ {
+ bool result = base.TryCastShot();
+
+ if (result && verbProps is VerbPropertiesExplosiveBeam explosiveProps && explosiveProps.enableExplosion)
+ {
+ explosionShotCounter++;
+
+ if (explosionShotCounter >= explosiveProps.explosionShotInterval)
+ {
+ explosionShotCounter = 0;
+ TriggerExplosion(explosiveProps);
+ }
+ }
+
+ return result;
+ }
+
+ private void TriggerExplosion(VerbPropertiesExplosiveBeam explosiveProps)
+ {
+ Vector3 explosionPos = InterpolatedPosition;
+ IntVec3 explosionCell = explosionPos.ToIntVec3();
+
+ if (!explosionCell.InBounds(caster.Map))
+ return;
+
+ // 播放爆炸音效
+ if (explosiveProps.explosionSound != null)
+ {
+ explosiveProps.explosionSound.PlayOneShot(new TargetInfo(explosionCell, caster.Map));
+ }
+
+ // 生成爆炸
+ GenExplosion.DoExplosion(
+ center: explosionCell,
+ map: caster.Map,
+ radius: explosiveProps.explosionRadius,
+ damType: explosiveProps.explosionDamageDef ?? DamageDefOf.Bomb,
+ instigator: caster,
+ damAmount: explosiveProps.explosionDamage > 0 ? explosiveProps.explosionDamage : verbProps.defaultProjectile?.projectile?.GetDamageAmount(EquipmentSource) ?? 20,
+ armorPenetration: explosiveProps.explosionArmorPenetration >= 0 ? explosiveProps.explosionArmorPenetration : verbProps.defaultProjectile?.projectile?.GetArmorPenetration(EquipmentSource) ?? 0.3f,
+ explosionSound: null, // 我们已经手动播放了音效
+ weapon: base.EquipmentSource?.def,
+ projectile: null,
+ intendedTarget: currentTarget.Thing,
+ postExplosionSpawnThingDef: explosiveProps.postExplosionSpawnThingDef,
+ postExplosionSpawnChance: explosiveProps.postExplosionSpawnChance,
+ postExplosionSpawnThingCount: explosiveProps.postExplosionSpawnThingCount,
+ postExplosionGasType: explosiveProps.postExplosionGasType,
+ applyDamageToExplosionCellsNeighbors: explosiveProps.applyDamageToExplosionCellsNeighbors,
+ preExplosionSpawnThingDef: explosiveProps.preExplosionSpawnThingDef,
+ preExplosionSpawnChance: explosiveProps.preExplosionSpawnChance,
+ preExplosionSpawnThingCount: explosiveProps.preExplosionSpawnThingCount,
+ chanceToStartFire: explosiveProps.chanceToStartFire,
+ damageFalloff: explosiveProps.damageFalloff,
+ direction: null,
+ ignoredThings: null,
+ affectedAngle: null,
+ doVisualEffects: true,
+ propagationSpeed: 0.6f,
+ excludeRadius: 0f,
+ doSoundEffects: false, // 我们手动处理音效
+ screenShakeFactor: explosiveProps.screenShakeFactor // 新增:屏幕震动因子
+ );
+
+ // 生成额外的视觉效果
+ if (explosiveProps.explosionEffecter != null)
+ {
+ Effecter effecter = explosiveProps.explosionEffecter.Spawn(explosionCell, caster.Map);
+ effecter.Trigger(new TargetInfo(explosionCell, caster.Map), TargetInfo.Invalid);
+ effecter.Cleanup();
+ }
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ Scribe_Values.Look(ref explosionShotCounter, "explosionShotCounter", 0);
+ }
+ }
+}
diff --git a/Source/ArachnaeSwarm/Verb/Verb_ShootMeltBeam.cs b/Source/ArachnaeSwarm/Verb/Verb_ShootMeltBeam.cs
new file mode 100644
index 0000000..0709e93
--- /dev/null
+++ b/Source/ArachnaeSwarm/Verb/Verb_ShootMeltBeam.cs
@@ -0,0 +1,585 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+namespace ArachnaeSwarm
+{
+ // 我们让它继承自我们自己的 Verb_ShootBeamExplosive 以便复用爆炸逻辑
+ public class Verb_ShootMeltBeam : Verb
+ {
+ // --- 从 Verb_ShootBeamExplosive 复制过来的字段 ---
+ private int explosionShotCounter = 0;
+ private int mirroredExplosionShotCounter = 0;
+ // ---------------------------------------------
+
+ protected override int ShotsPerBurst
+ {
+ get
+ {
+ return this.verbProps.burstShotCount;
+ }
+ }
+
+ public float ShotProgress
+ {
+ get
+ {
+ return (float)this.ticksToNextPathStep / (float)this.verbProps.ticksBetweenBurstShots;
+ }
+ }
+
+ public Vector3 InterpolatedPosition
+ {
+ get
+ {
+ Vector3 b = base.CurrentTarget.CenterVector3 - this.initialTargetPosition;
+ return Vector3.Lerp(this.path[this.burstShotsLeft], this.path[Mathf.Min(this.burstShotsLeft + 1, this.path.Count - 1)], this.ShotProgress) + b;
+ }
+ }
+
+ // 为镜像光束添加一个计算位置的属性
+ public Vector3 MirroredInterpolatedPosition
+ {
+ get
+ {
+ Vector3 b = base.CurrentTarget.CenterVector3 - this.initialTargetPosition;
+ return Vector3.Lerp(this.mirroredPath[this.burstShotsLeft], this.mirroredPath[Mathf.Min(this.burstShotsLeft + 1, this.mirroredPath.Count - 1)], this.ShotProgress) + b;
+ }
+ }
+
+ public override float? AimAngleOverride
+ {
+ get
+ {
+ return (this.state != VerbState.Bursting) ? null : new float?((this.InterpolatedPosition - this.caster.DrawPos).AngleFlat());
+ }
+ }
+
+ public override void DrawHighlight(LocalTargetInfo target)
+ {
+ base.DrawHighlight(target);
+ this.CalculatePath(target.CenterVector3, this.tmpPath, this.tmpPathCells, false);
+ foreach (IntVec3 tmpPathCell in this.tmpPathCells)
+ {
+ ShootLine resultingLine;
+ bool flag = this.TryFindShootLineFromTo(this.caster.Position, target, out resultingLine);
+ if ((this.verbProps.stopBurstWithoutLos && !flag) || !this.TryGetHitCell(resultingLine.Source, tmpPathCell, out var hitCell))
+ {
+ continue;
+ }
+ this.tmpHighlightCells.Add(hitCell);
+ if (!this.verbProps.beamHitsNeighborCells)
+ {
+ continue;
+ }
+ foreach (IntVec3 beamHitNeighbourCell in this.GetBeamHitNeighbourCells(resultingLine.Source, hitCell))
+ {
+ if (!this.tmpHighlightCells.Contains(beamHitNeighbourCell))
+ {
+ this.tmpSecondaryHighlightCells.Add(beamHitNeighbourCell);
+ }
+ }
+ }
+ this.tmpSecondaryHighlightCells.RemoveWhere((IntVec3 x) => this.tmpHighlightCells.Contains(x));
+ if (this.tmpHighlightCells.Any())
+ {
+ GenDraw.DrawFieldEdges(this.tmpHighlightCells.ToList(), this.verbProps.highlightColor ?? Color.white);
+ }
+ if (this.tmpSecondaryHighlightCells.Any())
+ {
+ GenDraw.DrawFieldEdges(this.tmpSecondaryHighlightCells.ToList(), this.verbProps.secondaryHighlightColor ?? Color.white);
+ }
+ this.tmpHighlightCells.Clear();
+ this.tmpSecondaryHighlightCells.Clear();
+ }
+
+ protected override bool TryCastShot()
+ {
+ bool flag = this.currentTarget.HasThing && this.currentTarget.Thing.Map != this.caster.Map;
+ bool result;
+ if (flag)
+ {
+ result = false;
+ }
+ else
+ {
+ ShootLine shootLine;
+ bool flag2 = base.TryFindShootLineFromTo(this.caster.Position, this.currentTarget, out shootLine, false);
+ bool flag3 = this.verbProps.stopBurstWithoutLos && !flag2;
+ if (flag3)
+ {
+ result = false;
+ }
+ else
+ {
+ bool flag4 = base.EquipmentSource != null;
+ if (flag4)
+ {
+ CompChangeableProjectile comp = base.EquipmentSource.GetComp();
+ if (comp != null)
+ {
+ comp.Notify_ProjectileLaunched();
+ }
+ CompApparelReloadable comp2 = base.EquipmentSource.GetComp();
+ if (comp2 != null)
+ {
+ comp2.UsedOnce();
+ }
+ }
+ this.lastShotTick = Find.TickManager.TicksGame;
+ this.ticksToNextPathStep = this.verbProps.ticksBetweenBurstShots;
+ IntVec3 targetCell = this.InterpolatedPosition.Yto0().ToIntVec3();
+ IntVec3 intVec;
+ bool flag5 = !this.TryGetHitCell(shootLine.Source, targetCell, out intVec);
+ if (flag5)
+ {
+ result = true;
+ }
+ else
+ {
+ this.HitCell(intVec, shootLine.Source, 1f);
+ bool beamHitsNeighborCells = this.verbProps.beamHitsNeighborCells;
+ if (beamHitsNeighborCells)
+ {
+ this.hitCells.Add(intVec);
+ foreach (IntVec3 intVec2 in this.GetBeamHitNeighbourCells(shootLine.Source, intVec))
+ {
+ bool flag6 = !this.hitCells.Contains(intVec2);
+ if (flag6)
+ {
+ float damageFactor = this.pathCells.Contains(intVec2) ? 1f : 0.5f;
+ this.HitCell(intVec2, shootLine.Source, damageFactor);
+ this.hitCells.Add(intVec2);
+ }
+ }
+ }
+ IntVec3 targetCell2 = this.mirroredPath[Mathf.Min(this.burstShotsLeft, this.mirroredPath.Count - 1)].ToIntVec3();
+ IntVec3 intVec3;
+ bool flag7 = this.TryGetHitCell(shootLine.Source, targetCell2, out intVec3);
+ if (flag7)
+ {
+ this.HitCell(intVec3, shootLine.Source, 1f);
+ this.mirroredHitCells.Add(intVec3);
+ bool beamHitsNeighborCells2 = this.verbProps.beamHitsNeighborCells;
+ if (beamHitsNeighborCells2)
+ {
+ foreach (IntVec3 intVec4 in this.GetBeamHitNeighbourCells(shootLine.Source, intVec3))
+ {
+ bool flag8 = !this.mirroredHitCells.Contains(intVec4);
+ if (flag8)
+ {
+ float damageFactor2 = this.mirroredPathCells.Contains(intVec4) ? 1f : 0.5f;
+ this.HitCell(intVec4, shootLine.Source, damageFactor2);
+ this.mirroredHitCells.Add(intVec4);
+ }
+ }
+ }
+ }
+
+ // --- 添加爆炸逻辑 ---
+ if (verbProps is VerbPropertiesExplosiveBeam explosiveProps && explosiveProps.enableExplosion)
+ {
+ explosionShotCounter++;
+ mirroredExplosionShotCounter++;
+
+ if (explosionShotCounter >= explosiveProps.explosionShotInterval)
+ {
+ explosionShotCounter = 0;
+ TriggerExplosion(explosiveProps, InterpolatedPosition);
+ }
+ if (mirroredExplosionShotCounter >= explosiveProps.explosionShotInterval)
+ {
+ mirroredExplosionShotCounter = 0;
+ TriggerExplosion(explosiveProps, MirroredInterpolatedPosition);
+ }
+ }
+ // ---------------------
+
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ // --- 从 Verb_ShootBeamExplosive 复制过来的方法 ---
+ private void TriggerExplosion(VerbPropertiesExplosiveBeam explosiveProps, Vector3 position)
+ {
+ IntVec3 explosionCell = position.ToIntVec3();
+
+ if (!explosionCell.InBounds(caster.Map))
+ return;
+
+ // 播放爆炸音效
+ if (explosiveProps.explosionSound != null)
+ {
+ explosiveProps.explosionSound.PlayOneShot(new TargetInfo(explosionCell, caster.Map));
+ }
+
+ // 生成爆炸
+ GenExplosion.DoExplosion(
+ center: explosionCell,
+ map: caster.Map,
+ radius: explosiveProps.explosionRadius,
+ damType: explosiveProps.explosionDamageDef ?? DamageDefOf.Bomb,
+ instigator: caster,
+ damAmount: explosiveProps.explosionDamage > 0 ? explosiveProps.explosionDamage : verbProps.defaultProjectile?.projectile?.GetDamageAmount(EquipmentSource) ?? 20,
+ armorPenetration: explosiveProps.explosionArmorPenetration >= 0 ? explosiveProps.explosionArmorPenetration : verbProps.defaultProjectile?.projectile?.GetArmorPenetration(EquipmentSource) ?? 0.3f,
+ explosionSound: null, // 我们已经手动播放了音效
+ weapon: base.EquipmentSource?.def,
+ projectile: null,
+ intendedTarget: currentTarget.Thing,
+ postExplosionSpawnThingDef: explosiveProps.postExplosionSpawnThingDef,
+ postExplosionSpawnChance: explosiveProps.postExplosionSpawnChance,
+ postExplosionSpawnThingCount: explosiveProps.postExplosionSpawnThingCount,
+ postExplosionGasType: explosiveProps.postExplosionGasType,
+ applyDamageToExplosionCellsNeighbors: explosiveProps.applyDamageToExplosionCellsNeighbors,
+ preExplosionSpawnThingDef: explosiveProps.preExplosionSpawnThingDef,
+ preExplosionSpawnChance: explosiveProps.preExplosionSpawnChance,
+ preExplosionSpawnThingCount: explosiveProps.preExplosionSpawnThingCount,
+ chanceToStartFire: explosiveProps.chanceToStartFire,
+ damageFalloff: explosiveProps.damageFalloff,
+ direction: null,
+ ignoredThings: null,
+ affectedAngle: null,
+ doVisualEffects: true,
+ propagationSpeed: 0.6f,
+ excludeRadius: 0f,
+ doSoundEffects: false, // 我们手动处理音效
+ screenShakeFactor: explosiveProps.screenShakeFactor // 新增:屏幕震动因子
+ );
+
+ // 生成额外的视觉效果
+ if (explosiveProps.explosionEffecter != null)
+ {
+ Effecter effecter = explosiveProps.explosionEffecter.Spawn(explosionCell, caster.Map);
+ effecter.Trigger(new TargetInfo(explosionCell, caster.Map), TargetInfo.Invalid);
+ effecter.Cleanup();
+ }
+ }
+ // ---------------------------------------------
+
+
+ protected bool TryGetHitCell(IntVec3 source, IntVec3 targetCell, out IntVec3 hitCell)
+ {
+ IntVec3 intVec = GenSight.LastPointOnLineOfSight(source, targetCell, (IntVec3 c) => c.InBounds(this.caster.Map) && c.CanBeSeenOverFast(this.caster.Map), true);
+ bool flag = this.verbProps.beamCantHitWithinMinRange && (double)intVec.DistanceTo(source) < (double)this.verbProps.minRange;
+ bool result;
+ if (flag)
+ {
+ hitCell = default(IntVec3);
+ result = false;
+ }
+ else
+ {
+ hitCell = (intVec.IsValid ? intVec : targetCell);
+ result = intVec.IsValid;
+ }
+ return result;
+ }
+
+ protected IntVec3 GetHitCell(IntVec3 source, IntVec3 targetCell)
+ {
+ IntVec3 result;
+ this.TryGetHitCell(source, targetCell, out result);
+ return result;
+ }
+
+ protected IEnumerable GetBeamHitNeighbourCells(IntVec3 source, IntVec3 pos)
+ {
+ // 重写反编译的迭代器方法以修复编译错误
+ for (int i = 0; i < GenAdj.AdjacentCells.Length; i++)
+ {
+ IntVec3 cell = pos + GenAdj.AdjacentCells[i];
+ if (cell.InBounds(this.caster.Map))
+ {
+ yield return cell;
+ }
+ }
+ }
+
+ public override bool TryStartCastOn(LocalTargetInfo castTarg, LocalTargetInfo destTarg, bool surpriseAttack = false, bool canHitNonTargetPawns = true, bool preventFriendlyFire = false, bool nonInterruptingSelfCast = false)
+ {
+ return base.TryStartCastOn(this.verbProps.beamTargetsGround ? castTarg.Cell : castTarg, destTarg, surpriseAttack, canHitNonTargetPawns, preventFriendlyFire, nonInterruptingSelfCast);
+ }
+
+ private void UpdateBeamVisuals(List path, MoteDualAttached mote, ref Effecter endEffecter, Vector3 casterPos, IntVec3 casterCell, bool isMirrored = false)
+ {
+ Vector3 vector = path[Mathf.Min(this.burstShotsLeft, path.Count - 1)];
+ Vector3 v = (vector - casterPos).Yto0();
+ float num = v.MagnitudeHorizontal();
+ Vector3 normalized = v.normalized;
+ IntVec3 intVec = vector.ToIntVec3();
+ IntVec3 b = GenSight.LastPointOnLineOfSight(casterCell, intVec, (IntVec3 c) => c.CanBeSeenOverFast(this.caster.Map), true);
+ bool isValid = b.IsValid;
+ if (isValid)
+ {
+ num -= (intVec - b).LengthHorizontal;
+ vector = casterCell.ToVector3Shifted() + normalized * num;
+ intVec = vector.ToIntVec3();
+ }
+ Vector3 offsetA = normalized * this.verbProps.beamStartOffset;
+ Vector3 vector2 = vector - intVec.ToVector3Shifted();
+ if (mote != null)
+ {
+ mote.UpdateTargets(new TargetInfo(casterCell, this.caster.Map, false), new TargetInfo(intVec, this.caster.Map, false), offsetA, vector2);
+ }
+ if (mote != null)
+ {
+ mote.Maintain();
+ }
+ bool flag = this.verbProps.beamGroundFleckDef != null && Rand.Chance(this.verbProps.beamFleckChancePerTick);
+ if (flag)
+ {
+ FleckMaker.Static(vector, this.caster.Map, this.verbProps.beamGroundFleckDef, 1f);
+ }
+ bool flag2 = endEffecter == null && this.verbProps.beamEndEffecterDef != null;
+ if (flag2)
+ {
+ endEffecter = this.verbProps.beamEndEffecterDef.Spawn(intVec, this.caster.Map, vector2, 1f);
+ }
+ bool flag3 = endEffecter != null;
+ if (flag3)
+ {
+ endEffecter.offset = vector2;
+ endEffecter.EffectTick(new TargetInfo(intVec, this.caster.Map, false), TargetInfo.Invalid);
+ endEffecter.ticksLeft--;
+ }
+ bool flag4 = this.verbProps.beamLineFleckDef != null;
+ if (flag4)
+ {
+ float num2 = num;
+ int num3 = 0;
+ while ((float)num3 < num2)
+ {
+ bool flag5 = Rand.Chance(this.verbProps.beamLineFleckChanceCurve.Evaluate((float)num3 / num2));
+ if (flag5)
+ {
+ Vector3 loc = casterPos + (float)num3 * normalized - normalized * Rand.Value + normalized / 2f;
+ FleckMaker.Static(loc, this.caster.Map, this.verbProps.beamLineFleckDef, 1f);
+ }
+ num3++;
+ }
+ }
+ }
+
+ public override void BurstingTick()
+ {
+ this.ticksToNextPathStep--;
+ this.UpdateBeamVisuals(this.path, this.mote, ref this.endEffecter, this.caster.Position.ToVector3Shifted(), this.caster.Position, false);
+ this.UpdateBeamVisuals(this.mirroredPath, this.mirroredMote, ref this.mirroredEndEffecter, this.caster.Position.ToVector3Shifted(), this.caster.Position, true);
+ Sustainer sustainer = this.sustainer;
+ if (sustainer != null)
+ {
+ sustainer.Maintain();
+ }
+ }
+
+ public override void WarmupComplete()
+ {
+ this.burstShotsLeft = this.ShotsPerBurst;
+ this.state = VerbState.Bursting;
+ this.initialTargetPosition = this.currentTarget.CenterVector3;
+ this.CalculatePath(this.currentTarget.CenterVector3, this.path, this.pathCells, true);
+ Vector3 normalized = (this.currentTarget.CenterVector3 - this.caster.Position.ToVector3Shifted()).Yto0().normalized;
+ float angle = 3f;
+ Vector3 a = normalized.RotatedBy(angle);
+ float magnitude = (this.currentTarget.CenterVector3 - this.caster.Position.ToVector3Shifted()).magnitude;
+ Vector3 target = this.caster.Position.ToVector3Shifted() + a * magnitude;
+ this.CalculatePath(target, this.mirroredPath, this.mirroredPathCells, true);
+ this.mirroredPath.Reverse();
+ this.hitCells.Clear();
+ this.mirroredHitCells.Clear();
+ bool flag = this.verbProps.beamMoteDef != null;
+ if (flag)
+ {
+ this.mote = MoteMaker.MakeInteractionOverlay(this.verbProps.beamMoteDef, this.caster, new TargetInfo(this.path[0].ToIntVec3(), this.caster.Map, false));
+ }
+ bool flag2 = this.verbProps.beamMoteDef != null;
+ if (flag2)
+ {
+ this.mirroredMote = MoteMaker.MakeInteractionOverlay(this.verbProps.beamMoteDef, this.caster, new TargetInfo(this.mirroredPath[0].ToIntVec3(), this.caster.Map, false));
+ }
+ base.TryCastNextBurstShot();
+ this.ticksToNextPathStep = this.verbProps.ticksBetweenBurstShots;
+ Effecter effecter = this.endEffecter;
+ if (effecter != null)
+ {
+ effecter.Cleanup();
+ }
+ Effecter effecter2 = this.mirroredEndEffecter;
+ if (effecter2 != null)
+ {
+ effecter2.Cleanup();
+ }
+ bool flag3 = this.verbProps.soundCastBeam == null;
+ if (!flag3)
+ {
+ this.sustainer = this.verbProps.soundCastBeam.TrySpawnSustainer(SoundInfo.InMap(this.caster, MaintenanceType.PerTick));
+ }
+ }
+
+ private void CalculatePath(Vector3 target, List pathList, HashSet pathCellsList, bool addRandomOffset = true)
+ {
+ pathList.Clear();
+ Vector3 vector = (target - this.caster.Position.ToVector3Shifted()).Yto0();
+ float magnitude = vector.magnitude;
+ Vector3 normalized = vector.normalized;
+ Vector3 a = normalized.RotatedBy(-90f);
+ float num = ((double)this.verbProps.beamFullWidthRange > 0.0) ? Mathf.Min(magnitude / this.verbProps.beamFullWidthRange, 1f) : 1f;
+ float d = (this.verbProps.beamWidth + 1f) * num / (float)this.ShotsPerBurst;
+ Vector3 vector2 = target.Yto0() - a * this.verbProps.beamWidth / 2f * num;
+ pathList.Add(vector2);
+ for (int i = 0; i < this.ShotsPerBurst; i++)
+ {
+ Vector3 a2 = normalized * (Rand.Value * this.verbProps.beamMaxDeviation) - normalized / 2f;
+ Vector3 vector3 = Mathf.Sin((float)(((double)i / (double)this.ShotsPerBurst + 0.5) * 3.1415927410125732 * 57.295780181884766)) * this.verbProps.beamCurvature * -normalized - normalized * this.verbProps.beamMaxDeviation / 2f;
+ if (addRandomOffset)
+ {
+ pathList.Add(vector2 + (a2 + vector3) * num);
+ }
+ else
+ {
+ pathList.Add(vector2 + vector3 * num);
+ }
+ vector2 += a * d;
+ }
+ pathCellsList.Clear();
+ foreach (Vector3 vect in pathList)
+ {
+ pathCellsList.Add(vect.ToIntVec3());
+ }
+ }
+
+ private bool CanHit(Thing thing)
+ {
+ return thing.Spawned && !CoverUtility.ThingCovered(thing, this.caster.Map);
+ }
+
+ private void HitCell(IntVec3 cell, IntVec3 sourceCell, float damageFactor = 1f)
+ {
+ bool flag = !cell.InBounds(this.caster.Map);
+ if (!flag)
+ {
+ this.ApplyDamage(VerbUtility.ThingsToHit(cell, this.caster.Map, new Func(this.CanHit)).RandomElementWithFallback(null), sourceCell, damageFactor);
+ bool flag2 = !this.verbProps.beamSetsGroundOnFire || !Rand.Chance(this.verbProps.beamChanceToStartFire);
+ if (!flag2)
+ {
+ FireUtility.TryStartFireIn(cell, this.caster.Map, 1f, this.caster, null);
+ }
+ }
+ }
+
+ private void ApplyDamage(Thing thing, IntVec3 sourceCell, float damageFactor = 1f)
+ {
+ IntVec3 intVec = this.InterpolatedPosition.Yto0().ToIntVec3();
+ IntVec3 intVec2 = GenSight.LastPointOnLineOfSight(sourceCell, intVec, (IntVec3 c) => c.InBounds(this.caster.Map) && c.CanBeSeenOverFast(this.caster.Map), true);
+ bool isValid = intVec2.IsValid;
+ if (isValid)
+ {
+ intVec = intVec2;
+ }
+ Map map = this.caster.Map;
+ bool flag = thing == null || this.verbProps.beamDamageDef == null;
+ if (!flag)
+ {
+ Pawn pawn = thing as Pawn;
+ bool flag2 = pawn != null && pawn.Faction == this.Caster.Faction;
+ if (!flag2)
+ {
+ 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 = ((double)this.verbProps.beamTotalDamage <= 0.0) ? new DamageInfo(this.verbProps.beamDamageDef, (float)this.verbProps.beamDamageDef.defaultDamage * damageFactor, this.verbProps.beamDamageDef.defaultArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing, true, true, QualityCategory.Normal, true, false) : new DamageInfo(this.verbProps.beamDamageDef, this.verbProps.beamTotalDamage / (float)this.pathCells.Count * damageFactor, this.verbProps.beamDamageDef.defaultArmorPenetration, angleFlat, this.caster, null, base.EquipmentSource.def, DamageInfo.SourceCategory.ThingOrUnknown, this.currentTarget.Thing, true, true, QualityCategory.Normal, true, false);
+ thing.TakeDamage(dinfo).AssociateWithLog(log);
+ bool flag3 = thing.CanEverAttachFire();
+ if (flag3)
+ {
+ bool flag4 = !Rand.Chance((this.verbProps.flammabilityAttachFireChanceCurve == null) ? this.verbProps.beamChanceToAttachFire : this.verbProps.flammabilityAttachFireChanceCurve.Evaluate(thing.GetStatValue(StatDefOf.Flammability, true, -1)));
+ if (flag4)
+ {
+ return;
+ }
+ thing.TryAttachFire(this.verbProps.beamFireSizeRange.RandomInRange, this.caster);
+ }
+ else
+ {
+ bool flag5 = !Rand.Chance(this.verbProps.beamChanceToStartFire);
+ if (flag5)
+ {
+ return;
+ }
+ FireUtility.TryStartFireIn(intVec, map, this.verbProps.beamFireSizeRange.RandomInRange, this.caster, this.verbProps.flammabilityAttachFireChanceCurve);
+ }
+ // 移除了热射病和蒸发逻辑
+ }
+ }
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+ Scribe_Collections.Look(ref this.path, "path", LookMode.Value, Array.Empty