将所有的verb从乌拉迁移(不包括咖喱棒)

This commit is contained in:
2025-09-09 11:05:06 +08:00
parent 025881a310
commit d18e70f2e4
16 changed files with 1713 additions and 9 deletions

Binary file not shown.

View File

@@ -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": ""
}
]
}

View File

@@ -90,6 +90,18 @@
<Compile Include="ARA_HiveMind\CompAbilityEffect_BindDrone.cs" />
<Compile Include="ARA_HiveMind\CompProperties_AbilityBindDrone.cs" />
<Compile Include="ARA_SpawnPawnFromList\JobGiver_MaintainBuildings.cs" />
<Compile Include="Verb\CompCleave.cs" />
<Compile Include="Verb\CompMultiStrike.cs" />
<Compile Include="Verb\VerbPropertiesExplosiveBeam.cs" />
<Compile Include="Verb\VerbProperties_Excalibur.cs" />
<Compile Include="Verb\VerbProperties_WeaponStealBeam.cs" />
<Compile Include="Verb\Verb_MeleeAttack_Cleave.cs" />
<Compile Include="Verb\Verb_MeleeAttack_MultiStrike.cs" />
<Compile Include="Verb\Verb_ShootArc.cs" />
<Compile Include="Verb\Verb_ShootBeamExplosive.cs" />
<Compile Include="Verb\Verb_ShootMeltBeam.cs" />
<Compile Include="Verb\Verb_ShootShotgun.cs" />
<Compile Include="Verb\Verb_ShootWeaponStealBeam.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="ARA_CompHediffGiver\CompHediffGiver.cs" />

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<CompCleave>();
}
}
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<Thing> 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<IntVec3> GetCleaveCells(IntVec3 center)
{
if (this.Comp == null)
{
return new List<IntVec3>();
}
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();
}
}
}

View File

@@ -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<CompMultiStrike>();
}
}
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<BodyPartRecord>();
}
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;
}
}
}

View File

@@ -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<IntVec3> cells = Verb_ShootArc.circularSectorCellsStartedCaster(casterPawn.Position, casterPawn.Map, this.currentTarget.Cell, this.Props.range, this.Props.affectedAngle, false).ToList<IntVec3>();
HashSet<IntVec3> hashSet = this.HashSetConverter(cells);
this.pawnConduct.Add(casterPawn);
foreach (IntVec3 cell in hashSet)
{
List<Thing> 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<IntVec3> 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<IntVec3> cells = worker.ExplosionCellsToHit(loc, currentMap, radius, null, null, affectedAngle2).ToList<IntVec3>();
DamageWorker worker2 = DamageDefOf.Bomb.Worker;
Map currentMap2 = Find.CurrentMap;
affectedAngle2 = new FloatRange?(new FloatRange(-180f, Verb_ShootArc.AngleWrapped(affectedAngle.max)));
List<IntVec3> cells2 = worker2.ExplosionCellsToHit(loc, currentMap2, radius, null, null, affectedAngle2).ToList<IntVec3>();
cellsSum = cells.Concat(cells2).ToList<IntVec3>();
}
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<IntVec3>();
}
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<IntVec3> 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<IntVec3> cellsSum;
if (flag2)
{
DamageWorker worker = DamageDefOf.Bomb.Worker;
FloatRange? affectedAngle2 = new FloatRange?(new FloatRange(Verb_ShootArc.AngleWrapped(affectedAngle.min), 180f));
List<IntVec3> cells = worker.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle2).ToList<IntVec3>();
DamageWorker worker2 = DamageDefOf.Bomb.Worker;
affectedAngle2 = new FloatRange?(new FloatRange(-180f, Verb_ShootArc.AngleWrapped(affectedAngle.max)));
List<IntVec3> cells2 = worker2.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle2).ToList<IntVec3>();
cellsSum = cells.Concat(cells2).ToList<IntVec3>();
}
else
{
DamageWorker worker3 = DamageDefOf.Bomb.Worker;
FloatRange? affectedAngle3 = new FloatRange?(affectedAngle);
cellsSum = worker3.ExplosionCellsToHit(center, map, radius, null, null, affectedAngle3).ToList<IntVec3>();
}
return cellsSum;
}
protected virtual HashSet<IntVec3> HashSetConverter(IEnumerable<IntVec3> points)
{
HashSet<IntVec3> hashSet = new HashSet<IntVec3>();
bool flag = points.Any<IntVec3>();
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<Pawn> pawnConduct = new List<Pawn>();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<CompChangeableProjectile>();
if (comp != null)
{
comp.Notify_ProjectileLaunched();
}
CompApparelReloadable comp2 = base.EquipmentSource.GetComp<CompApparelReloadable>();
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<IntVec3> 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<Vector3> 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<Vector3> pathList, HashSet<IntVec3> 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<Thing, bool>(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<Vector3>(ref this.path, "path", LookMode.Value, Array.Empty<object>());
Scribe_Values.Look<int>(ref this.ticksToNextPathStep, "ticksToNextPathStep", 0, false);
Scribe_Values.Look<Vector3>(ref this.initialTargetPosition, "initialTargetPosition", default(Vector3), false);
Scribe_Collections.Look<Vector3>(ref this.mirroredPath, "mirroredPath", LookMode.Value, Array.Empty<object>());
// --- 添加爆炸计数器的保存 ---
Scribe_Values.Look(ref explosionShotCounter, "explosionShotCounter", 0);
Scribe_Values.Look(ref mirroredExplosionShotCounter, "mirroredExplosionShotCounter", 0);
// -------------------------
bool flag = Scribe.mode == LoadSaveMode.PostLoadInit;
if (flag)
{
bool flag2 = this.path == null;
if (flag2)
{
this.path = new List<Vector3>();
}
bool flag3 = this.mirroredPath == null;
if (flag3)
{
this.mirroredPath = new List<Vector3>();
}
}
}
private List<Vector3> path = new List<Vector3>();
private List<Vector3> tmpPath = new List<Vector3>();
private int ticksToNextPathStep;
private Vector3 initialTargetPosition;
private MoteDualAttached mote;
private Effecter endEffecter;
private Sustainer sustainer;
private HashSet<IntVec3> pathCells = new HashSet<IntVec3>();
private HashSet<IntVec3> tmpPathCells = new HashSet<IntVec3>();
private HashSet<IntVec3> tmpHighlightCells = new HashSet<IntVec3>();
private HashSet<IntVec3> tmpSecondaryHighlightCells = new HashSet<IntVec3>();
private HashSet<IntVec3> hitCells = new HashSet<IntVec3>();
private const int NumSubdivisionsPerUnitLength = 1;
private List<Vector3> mirroredPath = new List<Vector3>();
private HashSet<IntVec3> mirroredPathCells = new HashSet<IntVec3>();
private HashSet<IntVec3> mirroredHitCells = new HashSet<IntVec3>();
private MoteDualAttached mirroredMote;
private Effecter mirroredEndEffecter;
}
}

View File

@@ -0,0 +1,63 @@
using RimWorld;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Verse;
namespace ArachnaeSwarm
{
public class Verb_ShootShotgun : Verb_LaunchProjectile
{
protected override int ShotsPerBurst
{
get
{
return this.verbProps.burstShotCount;
}
}
public override void WarmupComplete()
{
base.WarmupComplete();
Pawn pawn = this.currentTarget.Thing as Pawn;
if (pawn != null && !pawn.Downed && this.CasterIsPawn && this.CasterPawn.skills != null)
{
float num = pawn.HostileTo(this.caster) ? 170f : 20f;
float num2 = this.verbProps.AdjustedFullCycleTime(this, this.CasterPawn);
this.CasterPawn.skills.Learn(SkillDefOf.Shooting, num * num2, false, false);
}
}
protected override bool TryCastShot()
{
bool flag = base.TryCastShot();
if (flag && this.CasterIsPawn)
{
this.CasterPawn.records.Increment(RecordDefOf.ShotsFired);
}
ShotgunExtension shotgunExtension = ShotgunExtension.Get(this.verbProps.defaultProjectile);
if (flag && shotgunExtension.pelletCount - 1 > 0)
{
for (int i = 0; i < shotgunExtension.pelletCount - 1; i++)
{
base.TryCastShot();
}
}
return flag;
}
}
public class ShotgunExtension : DefModExtension
{
public static ShotgunExtension Get(Def def)
{
return def.GetModExtension<ShotgunExtension>() ?? ShotgunExtension.defaultValues;
}
private static readonly ShotgunExtension defaultValues = new ShotgunExtension();
public int pelletCount = 1;
}
}

View File

@@ -0,0 +1,174 @@
using RimWorld;
using Verse;
using System.Linq;
using System.Collections.Generic;
using UnityEngine; // For Vector3
using Verse.Sound; // For SoundDef.PlayOneShot
using Verse.AI; // For JobQueue
namespace ArachnaeSwarm
{
public class Verb_ShootWeaponStealBeam : Verse.Verb_ShootBeam
{
private int explosionShotCounter = 0;
protected VerbProperties_WeaponStealBeam StealBeamVerbProps => (VerbProperties_WeaponStealBeam)verbProps;
protected override bool TryCastShot()
{
bool result = base.TryCastShot();
// 如果光束命中对目标Pawn施加Hediff并检查是否抢夺武器
if (result && currentTarget.Thing is Pawn targetPawn && targetPawn.RaceProps.Humanlike) // 只对人形Pawn生效
{
ApplyHediffAndCheckForSteal(targetPawn);
}
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 // 新增:屏幕震动因子
);
// 在这里添加武器抢夺和Hediff施加的逻辑爆炸命中目标时
if (currentTarget.Thing is Pawn targetPawn && targetPawn.RaceProps.Humanlike) // 只对人形Pawn生效
{
ApplyHediffAndCheckForSteal(targetPawn);
}
// 生成额外的视觉效果
if (explosiveProps.explosionEffecter != null)
{
Effecter effecter = explosiveProps.explosionEffecter.Spawn(explosionCell, caster.Map);
effecter.Trigger(new TargetInfo(explosionCell, caster.Map), TargetInfo.Invalid);
effecter.Cleanup();
}
}
private void ApplyHediffAndCheckForSteal(Pawn targetPawn)
{
if (StealBeamVerbProps.hediffToApply == null)
{
return;
}
Hediff hediff = targetPawn.health.hediffSet.GetFirstHediffOfDef(StealBeamVerbProps.hediffToApply);
if (hediff == null)
{
hediff = HediffMaker.MakeHediff(StealBeamVerbProps.hediffToApply, targetPawn);
targetPawn.health.AddHediff(hediff);
}
hediff.Severity += StealBeamVerbProps.hediffSeverityPerHit;
if (hediff.Severity >= StealBeamVerbProps.hediffMaxSeverity)
{
TryStealWeapon(targetPawn);
if (StealBeamVerbProps.removeHediffOnSteal)
{
targetPawn.health.RemoveHediff(hediff);
}
}
}
private void TryStealWeapon(Pawn targetPawn)
{
if (!CasterIsPawn || CasterPawn == null)
{
return;
}
// 获取目标Pawn的装备武器
ThingWithComps targetWeapon = targetPawn.equipment?.Primary;
if (targetWeapon != null)
{
// 将武器从目标Pawn身上移除
targetPawn.equipment.Remove(targetWeapon);
// 将武器添加到发射者(宿主)的库存中
if (!CasterPawn.inventory.innerContainer.TryAdd(targetWeapon))
{
// 如果无法添加到库存,则尝试丢弃在地上
GenPlace.TryPlaceThing(targetWeapon, CasterPawn.Position, CasterPawn.Map, ThingPlaceMode.Near);
return; // 如果丢弃了,就不尝试装备了
}
// 强制发射者装备该武器,并替换当前武器 (JobDriver_Equip 核心逻辑)
// 强制发射者装备该武器,并替换当前武器 (JobDriver_Equip 核心逻辑)
CasterPawn.equipment.MakeRoomFor(targetWeapon); // 为新装备腾出空间,并处理旧装备
// 在 AddEquipment 之前,确保武器不在库存中
if (CasterPawn.inventory.innerContainer.Contains(targetWeapon))
{
CasterPawn.inventory.innerContainer.Remove(targetWeapon);
}
CasterPawn.equipment.AddEquipment(targetWeapon); // 添加装备
targetWeapon.def.soundInteract?.PlayOneShot(new TargetInfo(CasterPawn.Position, CasterPawn.Map)); // 播放音效
}
}
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look(ref explosionShotCounter, "explosionShotCounter", 0);
}
}
}