整理
This commit is contained in:
23
Source/ArachnaeSwarm/Verbs/Cleave/CompCleave.cs
Normal file
23
Source/ArachnaeSwarm/Verbs/Cleave/CompCleave.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
180
Source/ArachnaeSwarm/Verbs/Cleave/Verb_MeleeAttack_Cleave.cs
Normal file
180
Source/ArachnaeSwarm/Verbs/Cleave/Verb_MeleeAttack_Cleave.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Source/ArachnaeSwarm/Verbs/MultiStrike/CompMultiStrike.cs
Normal file
20
Source/ArachnaeSwarm/Verbs/MultiStrike/CompMultiStrike.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Source/ArachnaeSwarm/Verbs/Projectiles/BulletWithTrail.cs
Normal file
98
Source/ArachnaeSwarm/Verbs/Projectiles/BulletWithTrail.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class BulletWithTrail : Bullet
|
||||
{
|
||||
private TrackingBulletDef trackingDefInt;
|
||||
private int Fleck_MakeFleckTick;
|
||||
private Vector3 lastTickPosition;
|
||||
|
||||
public TrackingBulletDef TrackingDef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
trackingDefInt = def.GetModExtension<TrackingBulletDef>();
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
return trackingDefInt;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||||
{
|
||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||
lastTickPosition = origin;
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
// 处理拖尾特效
|
||||
if (TrackingDef != null && TrackingDef.tailFleckDef != null)
|
||||
{
|
||||
Fleck_MakeFleckTick++;
|
||||
if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks)
|
||||
{
|
||||
if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax))
|
||||
{
|
||||
Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks;
|
||||
}
|
||||
|
||||
Map map = base.Map;
|
||||
int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange;
|
||||
Vector3 currentPosition = base.ExactPosition;
|
||||
Vector3 previousPosition = lastTickPosition;
|
||||
|
||||
for (int i = 0; i < randomInRange; i++)
|
||||
{
|
||||
float num = (currentPosition - previousPosition).AngleFlat();
|
||||
float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num;
|
||||
float randomInRange2 = TrackingDef.fleckScale.RandomInRange;
|
||||
float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange;
|
||||
|
||||
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2);
|
||||
dataStatic.rotation = (currentPosition - previousPosition).AngleFlat();
|
||||
dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange;
|
||||
dataStatic.velocityAngle = velocityAngle;
|
||||
dataStatic.velocitySpeed = randomInRange3;
|
||||
map.flecks.CreateFleck(dataStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTickPosition = base.ExactPosition;
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref Fleck_MakeFleckTick, "Fleck_MakeFleckTick", 0);
|
||||
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition", Vector3.zero);
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
this.trackingDefInt = this.def.GetModExtension<TrackingBulletDef>();
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef is null for projectile {this.def.defName} after PostLoadInit. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class ExplosiveTrackingBulletDef : DefModExtension
|
||||
{
|
||||
public float explosionRadius = 1.9f;
|
||||
public DamageDef damageDef;
|
||||
public int explosionDelay = 0;
|
||||
public SoundDef soundExplode;
|
||||
public FleckDef preExplosionFlash;
|
||||
public ThingDef postExplosionSpawnThingDef;
|
||||
public float postExplosionSpawnChance = 0f;
|
||||
public int postExplosionSpawnThingCount = 1;
|
||||
public GasType? gasType; // 修改为可空类型
|
||||
public ThingDef postExplosionSpawnThingDefWater; // 新增
|
||||
public ThingDef preExplosionSpawnThingDef; // 新增
|
||||
public float preExplosionSpawnChance = 0f; // 新增
|
||||
public int preExplosionSpawnThingCount = 0; // 新增
|
||||
public float screenShakeFactor = 1f; // 新增
|
||||
public bool applyDamageToExplosionCellsNeighbors = false;
|
||||
public bool doExplosionDamageAfterThingDestroyed = false;
|
||||
public float preExplosionSpawnMinMeleeThreat = -1f;
|
||||
public float explosionChanceToStartFire = 0f; // 从bool改为float,并设置默认值
|
||||
public bool explosionDamageFalloff = false;
|
||||
public bool doExplosionVFX = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڶ<EFBFBD><DAB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>XML<4D><4C> <projectile> <20>ڵ<EFBFBD><DAB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><C3B5>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ԡ<EFBFBD>
|
||||
/// </summary>
|
||||
public class ProjectileProperties_ConfigurableHellsphereCannon : ProjectileProperties
|
||||
{
|
||||
// --- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD><D6A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> explosionChanceToStartFire ---
|
||||
// ʹ<><CAB9> 'new' <20>ؼ<EFBFBD><D8BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ز<EFBFBD><D8B2>滻<EFBFBD><E6BBBB><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD>ֶ<EFBFBD>
|
||||
public new float explosionRadius = 4.9f;
|
||||
public new float explosionChanceToStartFire = 0f; // <--- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>У<EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD>XML<4D><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
public new bool applyDamageToExplosionCellsNeighbors = false;
|
||||
public new ThingDef preExplosionSpawnThingDef = null;
|
||||
public new float preExplosionSpawnChance = 0f;
|
||||
public new int preExplosionSpawnThingCount = 1;
|
||||
public new ThingDef postExplosionSpawnThingDef = null;
|
||||
public new float postExplosionSpawnChance = 0f;
|
||||
public new int postExplosionSpawnThingCount = 1;
|
||||
public new GasType? postExplosionGasType = null;
|
||||
public new float screenShakeFactor = 1f;
|
||||
public new ThingDef postExplosionSpawnThingDefWater = null;
|
||||
public new ThingDef postExplosionSpawnSingleThingDef = null;
|
||||
public new ThingDef preExplosionSpawnSingleThingDef = null;
|
||||
|
||||
// <20><>Щ<EFBFBD><D0A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӵ<EFBFBD>ȫ<EFBFBD><C8AB><EFBFBD>ֶΣ<D6B6><CEA3><EFBFBD><EFBFBD><EFBFBD>Ҫ 'new' <20>ؼ<EFBFBD><D8BC><EFBFBD>
|
||||
public SoundDef explosionSound = null;
|
||||
public bool damageFalloff = false;
|
||||
public bool doVisualEffects = true;
|
||||
public bool doSoundEffects = true;
|
||||
public float? postExplosionGasRadiusOverride = null;
|
||||
public int postExplosionGasAmount = 255;
|
||||
public float? direction = null;
|
||||
public FloatRange? affectedAngle = null;
|
||||
public float excludeRadius = 0f;
|
||||
public SimpleCurve flammabilityChanceCurve = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD>Ͷ<EFBFBD><CDB6><EFBFBD><EFBFBD>ĺ<EFBFBD><C4BA><EFBFBD><EFBFBD><EFBFBD><DFBC>࣬<EFBFBD><E0A3AC>XML<4D>е<EFBFBD> <thingClass> ָ<><D6B8><EFBFBD><EFBFBD>
|
||||
/// </summary>
|
||||
public class Projectile_ConfigurableHellsphereCannon : Projectile
|
||||
{
|
||||
public ProjectileProperties_ConfigurableHellsphereCannon Props => (ProjectileProperties_ConfigurableHellsphereCannon)def.projectile;
|
||||
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||
{
|
||||
Map map = base.Map;
|
||||
base.Impact(hitThing, blockedByShield);
|
||||
|
||||
GenExplosion.DoExplosion(
|
||||
center: base.Position,
|
||||
map: map,
|
||||
radius: Props.explosionRadius,
|
||||
damType: base.DamageDef,
|
||||
instigator: launcher,
|
||||
damAmount: DamageAmount,
|
||||
armorPenetration: ArmorPenetration,
|
||||
explosionSound: Props.explosionSound,
|
||||
weapon: equipmentDef,
|
||||
projectile: def,
|
||||
intendedTarget: intendedTarget.Thing,
|
||||
postExplosionSpawnThingDef: Props.postExplosionSpawnThingDef,
|
||||
postExplosionSpawnChance: Props.postExplosionSpawnChance,
|
||||
postExplosionSpawnThingCount: Props.postExplosionSpawnThingCount,
|
||||
postExplosionGasType: Props.postExplosionGasType,
|
||||
postExplosionGasRadiusOverride: Props.postExplosionGasRadiusOverride,
|
||||
postExplosionGasAmount: Props.postExplosionGasAmount,
|
||||
applyDamageToExplosionCellsNeighbors: Props.applyDamageToExplosionCellsNeighbors,
|
||||
preExplosionSpawnThingDef: Props.preExplosionSpawnThingDef,
|
||||
preExplosionSpawnChance: Props.preExplosionSpawnChance,
|
||||
preExplosionSpawnThingCount: Props.preExplosionSpawnThingCount,
|
||||
// --- <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD><C8B7><EFBFBD>ֶ<EFBFBD> ---
|
||||
chanceToStartFire: Props.explosionChanceToStartFire, // <--- <20><><EFBFBD><EFBFBD><EFBFBD>˴<EFBFBD><CBB4><EFBFBD>
|
||||
damageFalloff: Props.damageFalloff,
|
||||
direction: Props.direction,
|
||||
affectedAngle: Props.affectedAngle,
|
||||
doVisualEffects: Props.doVisualEffects,
|
||||
propagationSpeed: base.DamageDef.expolosionPropagationSpeed,
|
||||
excludeRadius: Props.excludeRadius,
|
||||
doSoundEffects: Props.doSoundEffects,
|
||||
postExplosionSpawnThingDefWater: Props.postExplosionSpawnThingDefWater,
|
||||
screenShakeFactor: Props.screenShakeFactor,
|
||||
flammabilityChanceCurve: Props.flammabilityChanceCurve,
|
||||
postExplosionSpawnSingleThingDef: Props.postExplosionSpawnSingleThingDef,
|
||||
preExplosionSpawnSingleThingDef: Props.preExplosionSpawnSingleThingDef
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class CruiseMissileProperties : DefModExtension
|
||||
{
|
||||
public DamageDef customDamageDef;
|
||||
public int customDamageAmount = 5;
|
||||
public float customExplosionRadius = 1.1f;
|
||||
public SoundDef customSoundExplode;
|
||||
|
||||
public bool useSubExplosions = true;
|
||||
public int subExplosionCount = 3;
|
||||
public float subExplosionRadius = 1.9f;
|
||||
public int subExplosionDamage = 30;
|
||||
public float subExplosionSpread = 6f;
|
||||
public DamageDef subDamageDef;
|
||||
public SoundDef subSoundExplode;
|
||||
|
||||
// 新增的弹道配置参数
|
||||
public float bezierArcHeightFactor = 0.05f; // 贝塞尔曲线高度因子
|
||||
public float bezierMinArcHeight = 2f; // 贝塞尔曲线最小高度
|
||||
public float bezierMaxArcHeight = 6f; // 贝塞尔曲线最大高度
|
||||
public float bezierHorizontalOffsetFactor = 0.1f; // 贝塞尔曲线水平偏移因子
|
||||
public float bezierSideOffsetFactor = 0.2f; // 贝塞尔曲线侧向偏移因子
|
||||
public float bezierRandomOffsetScale = 0.5f; // 贝塞尔曲线随机偏移缩放
|
||||
|
||||
}
|
||||
|
||||
public class Projectile_CruiseMissile : Projectile_Explosive
|
||||
{
|
||||
private CruiseMissileProperties settings;
|
||||
private bool flag2;
|
||||
private Vector3 Randdd;
|
||||
private Vector3 position2;
|
||||
public Vector3 ExPos;
|
||||
|
||||
public override void SpawnSetup(Map map, bool respawningAfterLoad)
|
||||
{
|
||||
base.SpawnSetup(map, respawningAfterLoad);
|
||||
settings = def.GetModExtension<CruiseMissileProperties>() ?? new CruiseMissileProperties();
|
||||
}
|
||||
|
||||
private void RandFactor()
|
||||
{
|
||||
// 调整随机范围,用于控制C形弹道的随机偏移
|
||||
Randdd = new Vector3(
|
||||
Rand.Range(-settings.bezierRandomOffsetScale, settings.bezierRandomOffsetScale), // X轴的随机偏移
|
||||
Rand.Range(0f, 0f), // Y轴(高度)不进行随机,保持平稳
|
||||
Rand.Range(-settings.bezierRandomOffsetScale, settings.bezierRandomOffsetScale) // Z轴的随机偏移
|
||||
);
|
||||
flag2 = true;
|
||||
}
|
||||
|
||||
public Vector3 BPos(float t)
|
||||
{
|
||||
if (!flag2) RandFactor();
|
||||
|
||||
// 计算水平距离
|
||||
float horizontalDistance = Vector3.Distance(new Vector3(origin.x, 0, origin.z),
|
||||
new Vector3(destination.x, 0, destination.z));
|
||||
|
||||
// 动态调整控制点高度,使其更扁平,使用XML配置的高度因子
|
||||
float arcHeight = Mathf.Clamp(horizontalDistance * settings.bezierArcHeightFactor, settings.bezierMinArcHeight, settings.bezierMaxArcHeight);
|
||||
|
||||
// 计算从起点到终点的方向向量
|
||||
Vector3 direction = (destination - origin).normalized;
|
||||
// 计算垂直于方向向量的水平向量(用于侧向偏移),确保C形弯曲方向一致
|
||||
Vector3 perpendicularDirection = Vector3.Cross(direction, Vector3.up).normalized;
|
||||
|
||||
// 调整控制点以形成扁平 C 形,使用XML配置的偏移因子
|
||||
// P1: 在起点附近,向前偏移,向上偏移,并向一侧偏移
|
||||
Vector3 p1 = origin + direction * horizontalDistance * settings.bezierHorizontalOffsetFactor + Vector3.up * arcHeight + perpendicularDirection * horizontalDistance * settings.bezierSideOffsetFactor + Randdd;
|
||||
// P2: 在终点附近,向后偏移,向上偏移,并向同一侧偏移
|
||||
Vector3 p2 = destination - direction * horizontalDistance * settings.bezierHorizontalOffsetFactor + Vector3.up * arcHeight + perpendicularDirection * horizontalDistance * settings.bezierSideOffsetFactor + Randdd;
|
||||
|
||||
return BezierCurve(origin, p1, p2, destination, t);
|
||||
}
|
||||
|
||||
private Vector3 BezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
|
||||
{
|
||||
float u = 1 - t;
|
||||
return u * u * u * p0
|
||||
+ 3 * u * u * t * p1
|
||||
+ 3 * u * t * t * p2
|
||||
+ t * t * t * p3;
|
||||
}
|
||||
|
||||
private IEnumerable<IntVec3> GetValidCells(Map map)
|
||||
{
|
||||
if (map == null || settings == null) yield break;
|
||||
|
||||
var cells = GenRadial.RadialCellsAround(
|
||||
base.Position,
|
||||
settings.subExplosionSpread,
|
||||
false
|
||||
).Where(c => c.InBounds(map));
|
||||
|
||||
var randomizedCells = cells.InRandomOrder().Take(settings.subExplosionCount);
|
||||
|
||||
foreach (var cell in randomizedCells)
|
||||
{
|
||||
yield return cell;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||
{
|
||||
var map = base.Map;
|
||||
base.Impact(hitThing, blockedByShield);
|
||||
|
||||
DoExplosion(
|
||||
base.Position,
|
||||
map,
|
||||
settings.customExplosionRadius,
|
||||
settings.customDamageDef,
|
||||
settings.customDamageAmount,
|
||||
settings.customSoundExplode
|
||||
);
|
||||
|
||||
if (settings.useSubExplosions)
|
||||
{
|
||||
foreach (var cell in GetValidCells(map))
|
||||
{
|
||||
DoExplosion(
|
||||
cell,
|
||||
map,
|
||||
settings.subExplosionRadius,
|
||||
settings.subDamageDef,
|
||||
settings.subExplosionDamage,
|
||||
settings.subSoundExplode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DoExplosion(IntVec3 pos, Map map, float radius, DamageDef dmgDef, int dmgAmount, SoundDef sound)
|
||||
{
|
||||
GenExplosion.DoExplosion(
|
||||
pos,
|
||||
map,
|
||||
radius,
|
||||
dmgDef,
|
||||
launcher,
|
||||
dmgAmount,
|
||||
ArmorPenetration,
|
||||
sound
|
||||
);
|
||||
}
|
||||
|
||||
protected override void DrawAt(Vector3 position, bool flip = false)
|
||||
{
|
||||
position2 = BPos(DistanceCoveredFraction - 0.01f);
|
||||
ExPos = position = BPos(DistanceCoveredFraction);
|
||||
base.DrawAt(position, flip);
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
if (intendedTarget.Thing is Pawn pawn && pawn.Spawned && !pawn.Destroyed)
|
||||
{
|
||||
if ((pawn.Dead || pawn.Downed) && DistanceCoveredFraction < 0.6f)
|
||||
{
|
||||
FindNextTarget(pawn.DrawPos);
|
||||
}
|
||||
destination = pawn.DrawPos;
|
||||
}
|
||||
base.Tick();
|
||||
}
|
||||
|
||||
private void FindNextTarget(Vector3 center)
|
||||
{
|
||||
var map = base.Map;
|
||||
if (map == null) return;
|
||||
|
||||
foreach (IntVec3 cell in GenRadial.RadialCellsAround(IntVec3.FromVector3(center), 7f, true))
|
||||
{
|
||||
if (!cell.InBounds(map)) continue;
|
||||
|
||||
Pawn target = cell.GetFirstPawn(map);
|
||||
if (target != null && target.Faction.HostileTo(launcher?.Faction))
|
||||
{
|
||||
intendedTarget = target;
|
||||
return;
|
||||
}
|
||||
}
|
||||
intendedTarget = CellRect.CenteredOn(IntVec3.FromVector3(center), 7).RandomCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Projectile_ExplosiveTrackingBullet : Projectile_TrackingBullet
|
||||
{
|
||||
private ExplosiveTrackingBulletDef explosiveDefInt;
|
||||
private int ticksToDetonation;
|
||||
|
||||
public ExplosiveTrackingBulletDef ExplosiveDef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (explosiveDefInt == null)
|
||||
{
|
||||
explosiveDefInt = def.GetModExtension<ExplosiveTrackingBulletDef>();
|
||||
if (explosiveDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"ExplosiveTrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345679);
|
||||
this.explosiveDefInt = new ExplosiveTrackingBulletDef();
|
||||
}
|
||||
}
|
||||
return explosiveDefInt;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref this.ticksToDetonation, "ticksToDetonation", 0, false);
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick(); // Call base Projectile_TrackingBullet Tick logic
|
||||
bool flag = this.ticksToDetonation > 0;
|
||||
if (flag)
|
||||
{
|
||||
this.ticksToDetonation--;
|
||||
bool flag2 = this.ticksToDetonation <= 0;
|
||||
if (flag2)
|
||||
{
|
||||
this.Explode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||
{
|
||||
bool flag = hitThing == null || blockedByShield || ExplosiveDef.explosionDelay == 0; // Use ExplosiveDef for explosionDelay
|
||||
if (flag)
|
||||
{
|
||||
this.Explode();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.landed = true;
|
||||
this.ticksToDetonation = ExplosiveDef.explosionDelay; // Use ExplosiveDef for explosionDelay
|
||||
GenExplosion.NotifyNearbyPawnsOfDangerousExplosive(this, ExplosiveDef.damageDef ?? DamageDefOf.Bomb, this.launcher.Faction, this.launcher); // Use ExplosiveDef for damageDef
|
||||
// 停止追踪并清空速度,确保子弹停止移动
|
||||
this.homing = false;
|
||||
this.curSpeed = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Explode()
|
||||
{
|
||||
Map map = base.Map;
|
||||
// ModExtension_Cone modExtension = this.def.GetModExtension<ModExtension_Cone>(); // Not used in this explosive logic
|
||||
this.DoExplosion(map); // Call the helper DoExplosion with map
|
||||
|
||||
// Cone explosion logic (if needed, based on ModExtension_Cone) - Currently not implemented for this class
|
||||
// bool flag = modExtension != null;
|
||||
// if (flag)
|
||||
// {
|
||||
// ProjectileProperties projectile = this.def.projectile;
|
||||
// ModExtension_Cone modExtension_Cone = modExtension;
|
||||
// IntVec3 position = base.Position;
|
||||
// Map map2 = map;
|
||||
// Quaternion exactRotation = this.ExactRotation;
|
||||
// DamageDef damageDef = projectile.damageDef;
|
||||
// Thing launcher = base.Launcher;
|
||||
// int damageAmount = this.DamageAmount;
|
||||
// float armorPenetration = this.ArmorPenetration;
|
||||
// SoundDef soundExplode = this.def.projectile.soundExplode;
|
||||
// ThingDef equipmentDef = this.equipmentDef;
|
||||
// ThingDef def = this.def;
|
||||
// Thing thing = this.intendedTarget.Thing;
|
||||
// ThingDef postExplosionSpawnThingDef = null;
|
||||
// float postExplosionSpawnChance = 0f;
|
||||
// int postExplosionSpawnThingCount = 1;
|
||||
// float screenShakeFactor = this.def.projectile.screenShakeFactor;
|
||||
// modExtension_Cone.DoConeExplosion(position, map2, exactRotation, damageDef, launcher, damageAmount, armorPenetration, soundExplode, equipmentDef, def, thing, postExplosionSpawnThingDef, postExplosionSpawnChance, postExplosionSpawnThingCount, null, null, 255, false, null, 0f, 1, 0f, false, null, null, 1f, 0f, null, screenShakeFactor, null, null);
|
||||
// }
|
||||
|
||||
// Explosion effect (if needed, based on def.projectile.explosionEffect) - Currently not implemented for this class
|
||||
// bool flag2 = this.def.projectile.explosionEffect != null;
|
||||
// if (flag2)
|
||||
// {
|
||||
// Effecter effecter = this.def.projectile.explosionEffect.Spawn();
|
||||
// bool flag3 = this.def.projectile.explosionEffectLifetimeTicks != 0;
|
||||
// if (flag3)
|
||||
// {
|
||||
// map.effecterMaintainer.AddEffecterToMaintain(effecter, base.Position.ToVector3().ToIntVec3(), this.def.projectile.explosionEffectLifetimeTicks);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// effecter.Trigger(new TargetInfo(base.Position, map, false), new TargetInfo(base.Position, map, false), -1);
|
||||
// effecter.Cleanup();
|
||||
// }
|
||||
// }
|
||||
this.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
|
||||
protected void DoExplosion(Map map)
|
||||
{
|
||||
IntVec3 position = base.Position;
|
||||
float explosionRadius = ExplosiveDef.explosionRadius; // Use ExplosiveDef for explosionRadius
|
||||
DamageDef damageDef = ExplosiveDef.damageDef ?? DamageDefOf.Bomb; // Use ExplosiveDef for damageDef
|
||||
Thing launcher = this.launcher;
|
||||
int damageAmount = this.DamageAmount;
|
||||
float armorPenetration = this.ArmorPenetration;
|
||||
SoundDef soundExplode = ExplosiveDef.soundExplode; // Use ExplosiveDef for soundExplode
|
||||
ThingDef equipmentDef = this.equipmentDef;
|
||||
ThingDef def = this.def; // This is the projectile's ThingDef
|
||||
Thing thing = this.intendedTarget.Thing;
|
||||
ThingDef postExplosionSpawnThingDef = ExplosiveDef.postExplosionSpawnThingDef; // Use ExplosiveDef for postExplosionSpawnThingDef
|
||||
ThingDef postExplosionSpawnThingDefWater = ExplosiveDef.postExplosionSpawnThingDefWater; // Use ExplosiveDef for postExplosionSpawnThingDefWater
|
||||
float postExplosionSpawnChance = ExplosiveDef.postExplosionSpawnChance; // Use ExplosiveDef for postExplosionSpawnChance
|
||||
int postExplosionSpawnThingCount = ExplosiveDef.postExplosionSpawnThingCount; // Use ExplosiveDef for postExplosionSpawnThingCount
|
||||
GasType? postExplosionGasType = ExplosiveDef.gasType; // Use ExplosiveDef for gasType
|
||||
ThingDef preExplosionSpawnThingDef = ExplosiveDef.preExplosionSpawnThingDef; // Use ExplosiveDef for preExplosionSpawnThingDef
|
||||
float preExplosionSpawnChance = ExplosiveDef.preExplosionSpawnChance; // Use ExplosiveDef for preExplosionSpawnChance
|
||||
int preExplosionSpawnThingCount = ExplosiveDef.preExplosionSpawnThingCount; // Use ExplosiveDef for preExplosionSpawnThingCount
|
||||
bool applyDamageToExplosionCellsNeighbors = ExplosiveDef.applyDamageToExplosionCellsNeighbors; // Use ExplosiveDef for applyDamageToExplosionCellsNeighbors
|
||||
float explosionChanceToStartFire = ExplosiveDef.explosionChanceToStartFire; // Use ExplosiveDef for explosionChanceToStartFire
|
||||
bool explosionDamageFalloff = ExplosiveDef.explosionDamageFalloff; // Use ExplosiveDef for explosionDamageFalloff
|
||||
float? direction = new float?(this.origin.AngleToFlat(this.destination)); // This remains from original logic
|
||||
float screenShakeFactor = ExplosiveDef.screenShakeFactor; // Use ExplosiveDef for screenShakeFactor
|
||||
bool doExplosionVFX = ExplosiveDef.doExplosionVFX; // Use ExplosiveDef for doExplosionVFX
|
||||
|
||||
GenExplosion.DoExplosion(
|
||||
center: ExactPosition.ToIntVec3(), // 爆炸中心
|
||||
map: map, // 地图
|
||||
radius: explosionRadius, // 爆炸半径
|
||||
damType: damageDef, // 伤害类型
|
||||
instigator: launcher, // 制造者
|
||||
damAmount: damageAmount, // 伤害量
|
||||
armorPenetration: armorPenetration, // 护甲穿透
|
||||
explosionSound: soundExplode, // 爆炸音效
|
||||
weapon: equipmentDef, // 武器
|
||||
projectile: def, // 弹药定义
|
||||
intendedTarget: thing, // 预期目标
|
||||
postExplosionSpawnThingDef: postExplosionSpawnThingDef, // 爆炸后生成物
|
||||
postExplosionSpawnChance: postExplosionSpawnChance, // 爆炸后生成几率
|
||||
postExplosionSpawnThingCount: postExplosionSpawnThingCount, // 爆炸后生成数量
|
||||
postExplosionGasType: postExplosionGasType, // 气体类型
|
||||
postExplosionGasRadiusOverride: null, // 爆炸气体半径覆盖
|
||||
postExplosionGasAmount: 255, // 爆炸气体数量
|
||||
applyDamageToExplosionCellsNeighbors: applyDamageToExplosionCellsNeighbors, // 是否对爆炸单元格邻居造成伤害
|
||||
preExplosionSpawnThingDef: preExplosionSpawnThingDef, // 爆炸前生成物
|
||||
preExplosionSpawnChance: preExplosionSpawnChance, // 爆炸前生成几率
|
||||
preExplosionSpawnThingCount: preExplosionSpawnThingCount, // 爆炸前生成数量
|
||||
chanceToStartFire: explosionChanceToStartFire, // 是否有几率点燃
|
||||
damageFalloff: explosionDamageFalloff, // 爆炸伤害衰减
|
||||
direction: direction, // 方向
|
||||
ignoredThings: null, // 忽略的物体
|
||||
affectedAngle: null, // 受影响角度
|
||||
doVisualEffects: doExplosionVFX, // 是否显示视觉效果
|
||||
propagationSpeed: 1f, // 传播速度
|
||||
excludeRadius: 0f, // 排除半径
|
||||
doSoundEffects: true, // 是否播放音效
|
||||
postExplosionSpawnThingDefWater: postExplosionSpawnThingDefWater, // 爆炸后在水中生成物
|
||||
screenShakeFactor: screenShakeFactor, // 屏幕震动因子
|
||||
flammabilityChanceCurve: null, // 易燃性几率曲线
|
||||
overrideCells: null, // 覆盖单元格
|
||||
postExplosionSpawnSingleThingDef: null, // 爆炸后生成单个物体
|
||||
preExplosionSpawnSingleThingDef: null // 爆炸前生成单个物体
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Projectile_ExplosiveWithTrail : Projectile_Explosive
|
||||
{
|
||||
private TrackingBulletDef trackingDefInt;
|
||||
private int Fleck_MakeFleckTick;
|
||||
private Vector3 lastTickPosition;
|
||||
|
||||
public TrackingBulletDef TrackingDef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
trackingDefInt = def.GetModExtension<TrackingBulletDef>();
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
return trackingDefInt;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||||
{
|
||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||
lastTickPosition = origin;
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick();
|
||||
|
||||
// 处理拖尾特效
|
||||
if (TrackingDef != null && TrackingDef.tailFleckDef != null)
|
||||
{
|
||||
Fleck_MakeFleckTick++;
|
||||
if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks)
|
||||
{
|
||||
if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax))
|
||||
{
|
||||
Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks;
|
||||
}
|
||||
|
||||
Map map = base.Map;
|
||||
int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange;
|
||||
Vector3 currentPosition = base.ExactPosition;
|
||||
Vector3 previousPosition = lastTickPosition;
|
||||
|
||||
for (int i = 0; i < randomInRange; i++)
|
||||
{
|
||||
float num = (currentPosition - previousPosition).AngleFlat();
|
||||
float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num;
|
||||
float randomInRange2 = TrackingDef.fleckScale.RandomInRange;
|
||||
float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange;
|
||||
|
||||
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2);
|
||||
dataStatic.rotation = (currentPosition - previousPosition).AngleFlat();
|
||||
dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange;
|
||||
dataStatic.velocityAngle = velocityAngle;
|
||||
dataStatic.velocitySpeed = randomInRange3;
|
||||
map.flecks.CreateFleck(dataStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTickPosition = base.ExactPosition;
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref Fleck_MakeFleckTick, "Fleck_MakeFleckTick", 0);
|
||||
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition", Vector3.zero);
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
this.trackingDefInt = this.def.GetModExtension<TrackingBulletDef>();
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef is null for projectile {this.def.defName} after PostLoadInit. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.AI;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class Projectile_TrackingBullet : Bullet
|
||||
{
|
||||
private TrackingBulletDef trackingDefInt;
|
||||
|
||||
protected Vector3 exactPositionInt;
|
||||
public Vector3 curSpeed;
|
||||
public bool homing = true;
|
||||
private int destroyTicksAfterLosingTrack = -1; // 失去追踪后多少tick自毁,-1表示不自毁
|
||||
private int Fleck_MakeFleckTick; // 拖尾特效的计时器
|
||||
private Vector3 lastTickPosition; // 记录上一帧的位置,用于计算移动方向
|
||||
|
||||
private static class NonPublicFields
|
||||
{
|
||||
public static FieldInfo Projectile_AmbientSustainer = typeof(Projectile).GetField("ambientSustainer", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
public static FieldInfo ThingWithComps_comps = typeof(ThingWithComps).GetField("comps", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
public static MethodInfo ProjectileCheckForFreeInterceptBetween = typeof(Projectile).GetMethod("CheckForFreeInterceptBetween", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
}
|
||||
|
||||
public TrackingBulletDef TrackingDef
|
||||
{
|
||||
get
|
||||
{
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
trackingDefInt = def.GetModExtension<TrackingBulletDef>();
|
||||
if (trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
return trackingDefInt;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector3 ExactPosition => exactPositionInt;
|
||||
|
||||
public override Quaternion ExactRotation => Quaternion.LookRotation(curSpeed);
|
||||
|
||||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||||
{
|
||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||
exactPositionInt = origin.Yto0() + Vector3.up * def.Altitude;
|
||||
|
||||
// 初始化子弹速度,指向目标,并考虑初始旋转角度
|
||||
Vector3 initialDirection = (destination - origin).Yto0().normalized;
|
||||
float degrees = Rand.Range(0f - TrackingDef.initRotateAngle, TrackingDef.initRotateAngle);
|
||||
Vector2 v = new Vector2(initialDirection.x, initialDirection.z);
|
||||
v = v.RotatedBy(degrees);
|
||||
Vector3 rotatedDirection = new Vector3(v.x, 0f, v.y);
|
||||
curSpeed = rotatedDirection * def.projectile.SpeedTilesPerTick;
|
||||
|
||||
ReflectInit();
|
||||
lastTickPosition = origin; // 初始化 lastTickPosition
|
||||
}
|
||||
|
||||
protected void ReflectInit()
|
||||
{
|
||||
// 确保私有字段的访问
|
||||
if (!def.projectile.soundAmbient.NullOrUndefined())
|
||||
{
|
||||
// This line might cause issues if ambientSustainer is not directly settable or if Projectile type changes.
|
||||
// For simplicity, we might omit it for now or find a safer way.
|
||||
// ambientSustainer = (Sustainer)NonPublicFields.Projectile_AmbientSustainer.GetValue(this);
|
||||
}
|
||||
// comps = (List<ThingComp>)NonPublicFields.ThingWithComps_comps.GetValue(this); // 如果需要CompTick,需要这个
|
||||
}
|
||||
|
||||
public virtual void MovementTick()
|
||||
{
|
||||
Vector3 vect = ExactPosition + curSpeed;
|
||||
ShootLine shootLine = new ShootLine(ExactPosition.ToIntVec3(), vect.ToIntVec3());
|
||||
Vector3 vectorToTarget = (intendedTarget.Cell.ToVector3() - ExactPosition).Yto0();
|
||||
|
||||
if (homing)
|
||||
{
|
||||
// 首先检查目标是否是一个有效的 Thing
|
||||
if (!intendedTarget.HasThing)
|
||||
{
|
||||
homing = false; // 如果目标是地面,则禁用追踪
|
||||
}
|
||||
// 如果目标消失或距离太远,停止追踪
|
||||
else if (!intendedTarget.IsValid || !intendedTarget.Thing.Spawned || (intendedTarget.Cell.ToVector3() - ExactPosition).magnitude > def.projectile.speed * 2f) // 假设2倍速度为最大追踪距离
|
||||
{
|
||||
homing = false;
|
||||
destroyTicksAfterLosingTrack = TrackingDef.destroyTicksAfterLosingTrack.RandomInRange; // 失去追踪后根据XML配置的范围自毁
|
||||
}
|
||||
else
|
||||
{
|
||||
// 计算需要转向的方向
|
||||
Vector3 desiredDirection = vectorToTarget.normalized;
|
||||
Vector3 currentDirection = curSpeed.normalized;
|
||||
|
||||
// 计算方向差异
|
||||
Vector3 directionDifference = desiredDirection - currentDirection;
|
||||
|
||||
// 如果方向差异过大,可能失去追踪,或者直接转向
|
||||
if (directionDifference.sqrMagnitude > 0.001f) // 避免浮点数精度问题
|
||||
{
|
||||
// 调整当前速度,使其更接近目标方向
|
||||
curSpeed += directionDifference * TrackingDef.homingSpeed * curSpeed.magnitude;
|
||||
curSpeed = curSpeed.normalized * def.projectile.SpeedTilesPerTick; // 保持速度恒定
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exactPositionInt = ExactPosition + curSpeed; // 更新位置
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref exactPositionInt, "exactPosition");
|
||||
Scribe_Values.Look(ref curSpeed, "curSpeed");
|
||||
Scribe_Values.Look(ref homing, "homing", defaultValue: true);
|
||||
Scribe_Values.Look(ref destroyTicksAfterLosingTrack, "destroyTicksAfterLosingTrack", -1);
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
ReflectInit();
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
this.trackingDefInt = this.def.GetModExtension<TrackingBulletDef>();
|
||||
if (this.trackingDefInt == null)
|
||||
{
|
||||
Log.ErrorOnce($"TrackingBulletDef is null for projectile {this.def.defName} after PostLoadInit. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
|
||||
this.trackingDefInt = new TrackingBulletDef();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
base.Tick(); // 调用父类Bullet的Tick,处理 ticksToImpact 减少和最终命中
|
||||
|
||||
if (destroyTicksAfterLosingTrack > 0)
|
||||
{
|
||||
destroyTicksAfterLosingTrack--;
|
||||
if (destroyTicksAfterLosingTrack <= 0)
|
||||
{
|
||||
Destroy(); // 如果自毁计时器归零,直接销毁
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理拖尾特效
|
||||
if (TrackingDef != null && TrackingDef.tailFleckDef != null)
|
||||
{
|
||||
Fleck_MakeFleckTick++;
|
||||
// 只有当达到延迟时间后才开始生成Fleck
|
||||
if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks)
|
||||
{
|
||||
if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax))
|
||||
{
|
||||
Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks; // 重置计时器,从延迟时间开始循环
|
||||
}
|
||||
|
||||
Map map = base.Map;
|
||||
int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange;
|
||||
Vector3 currentPosition = ExactPosition;
|
||||
Vector3 previousPosition = lastTickPosition;
|
||||
|
||||
for (int i = 0; i < randomInRange; i++)
|
||||
{
|
||||
float num = (currentPosition - previousPosition).AngleFlat();
|
||||
float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num;
|
||||
float randomInRange2 = TrackingDef.fleckScale.RandomInRange;
|
||||
float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange;
|
||||
|
||||
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2);
|
||||
dataStatic.rotation = (currentPosition - previousPosition).AngleFlat();
|
||||
dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange;
|
||||
dataStatic.velocityAngle = velocityAngle;
|
||||
dataStatic.velocitySpeed = randomInRange3;
|
||||
map.flecks.CreateFleck(dataStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTickPosition = ExactPosition; // 更新上一帧位置
|
||||
|
||||
// 保存移动前的精确位置
|
||||
Vector3 exactPositionBeforeMove = exactPositionInt;
|
||||
|
||||
MovementTick(); // 调用追踪移动逻辑,更新 exactPositionInt (即新的 ExactPosition)
|
||||
|
||||
// 检查是否超出地图边界
|
||||
if (!ExactPosition.InBounds(base.Map))
|
||||
{
|
||||
// 如果超出地图,直接销毁,不触发 ImpactSomething()
|
||||
Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// 在调用 ProjectileCheckForFreeInterceptBetween 之前,添加近距离命中检测
|
||||
if (intendedTarget != null && intendedTarget.Thing != null && intendedTarget.Thing.Spawned)
|
||||
{
|
||||
float distanceToTarget = (ExactPosition - intendedTarget.Thing.DrawPos).magnitude;
|
||||
if (distanceToTarget <= TrackingDef.impactThreshold)
|
||||
{
|
||||
Impact(intendedTarget.Thing); // 强制命中目标
|
||||
return; // 命中后立即返回,不再执行后续逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有东西在路径上拦截
|
||||
// ProjectileCheckForFreeInterceptBetween 会在内部处理命中,并调用 ImpactSomething()
|
||||
// 所以这里不需要额外的 ImpactSomething() 调用
|
||||
object[] parameters = new object[2] { exactPositionBeforeMove, exactPositionInt }; // 传入移动前和移动后的位置
|
||||
|
||||
// 调用 ProjectileCheckForFreeInterceptBetween
|
||||
// 如果它返回 true,说明有拦截,并且拦截逻辑已在内部处理。
|
||||
// 如果返回 false,说明没有拦截,子弹继续飞行。
|
||||
NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters);
|
||||
}
|
||||
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||
{
|
||||
// 默认Impact逻辑,可以根据需要扩展
|
||||
base.Impact(hitThing, blockedByShield);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
// A new, dedicated extension class for the penetrating beam.
|
||||
public class Wula_BeamPierce_Extension : DefModExtension
|
||||
{
|
||||
public int maxHits = 3;
|
||||
public float damageFalloff = 0.25f;
|
||||
public bool preventFriendlyFire = false;
|
||||
public ThingDef beamMoteDef;
|
||||
public float beamWidth = 1f;
|
||||
public float beamStartOffset = 0f;
|
||||
}
|
||||
|
||||
public class Projectile_WulaPenetratingBeam : Bullet
|
||||
{
|
||||
private int hitCounter = 0;
|
||||
private List<Thing> alreadyDamaged = new List<Thing>();
|
||||
|
||||
// It now gets its properties from the new, dedicated extension.
|
||||
private Wula_BeamPierce_Extension Props => def.GetModExtension<Wula_BeamPierce_Extension>();
|
||||
|
||||
public override Vector3 ExactPosition => destination + Vector3.up * def.Altitude;
|
||||
|
||||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||||
{
|
||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||
|
||||
Wula_BeamPierce_Extension props = Props;
|
||||
if (props == null)
|
||||
{
|
||||
Log.Error("Projectile_WulaBeam requires a Wula_BeamPierce_Extension in its def.");
|
||||
Destroy(DestroyMode.Vanish);
|
||||
return;
|
||||
}
|
||||
|
||||
this.hitCounter = 0;
|
||||
this.alreadyDamaged.Clear();
|
||||
|
||||
bool shouldPreventFriendlyFire = preventFriendlyFire || props.preventFriendlyFire;
|
||||
|
||||
Map map = this.Map;
|
||||
// --- Corrected Start Position Calculation ---
|
||||
// The beam should start from the gun's muzzle, not the pawn's center.
|
||||
Vector3 endPosition = usedTarget.Cell.ToVector3Shifted();
|
||||
Vector3 castPosition = origin + (endPosition - origin).Yto0().normalized * props.beamStartOffset;
|
||||
|
||||
// --- Vanilla Beam Drawing Logic ---
|
||||
if (props.beamMoteDef != null)
|
||||
{
|
||||
// Calculate the offset exactly like the vanilla Beam class does.
|
||||
// The offset for the mote is calculated from the launcher's true position, not the cast position.
|
||||
Vector3 moteOffset = (endPosition - launcher.Position.ToVector3Shifted()).Yto0().normalized * props.beamStartOffset;
|
||||
MoteMaker.MakeInteractionOverlay(props.beamMoteDef, launcher, usedTarget.ToTargetInfo(map), moteOffset, Vector3.zero);
|
||||
}
|
||||
|
||||
float distance = Vector3.Distance(castPosition, endPosition);
|
||||
Vector3 direction = (endPosition - castPosition).normalized;
|
||||
|
||||
var thingsOnPath = new HashSet<Thing>();
|
||||
for (float i = 0; i < distance; i += 1.0f)
|
||||
{
|
||||
IntVec3 cell = (castPosition + direction * i).ToIntVec3();
|
||||
if (cell.InBounds(map))
|
||||
{
|
||||
thingsOnPath.AddRange(map.thingGrid.ThingsListAt(cell));
|
||||
}
|
||||
}
|
||||
// CRITICAL FIX: Manually add the intended target to the list.
|
||||
// This guarantees the primary target is always processed, even if the loop sampling misses its exact cell.
|
||||
if (intendedTarget.HasThing)
|
||||
{
|
||||
thingsOnPath.Add(intendedTarget.Thing);
|
||||
}
|
||||
|
||||
int maxHits = props.maxHits;
|
||||
bool infinitePenetration = maxHits < 0;
|
||||
|
||||
foreach (Thing thing in thingsOnPath)
|
||||
{
|
||||
if (!infinitePenetration && hitCounter >= maxHits) break;
|
||||
|
||||
// 统一处理 Pawn 和 Building 的伤害逻辑
|
||||
// 确保 Thing 未被伤害过且不是发射者
|
||||
if (thing != launcher && !alreadyDamaged.Contains(thing))
|
||||
{
|
||||
bool shouldDamage = false;
|
||||
Pawn pawn = thing as Pawn;
|
||||
Building building = thing as Building;
|
||||
|
||||
if (pawn != null) // 如果是 Pawn
|
||||
{
|
||||
if (intendedTarget.Thing == pawn) shouldDamage = true;
|
||||
else if (pawn.HostileTo(launcher)) shouldDamage = true;
|
||||
else if (!shouldPreventFriendlyFire) shouldDamage = true;
|
||||
}
|
||||
else if (building != null) // 如果是 Building
|
||||
{
|
||||
shouldDamage = true; // 默认对 Building 造成伤害
|
||||
}
|
||||
|
||||
if (shouldDamage)
|
||||
{
|
||||
ApplyPathDamage(thing, props); // 传递 Thing
|
||||
}
|
||||
}
|
||||
// 只有当遇到完全阻挡的 Thing 且不是 Pawn 或 Building 时才停止穿透
|
||||
else if (thing.def.Fillage == FillCategory.Full && thing.def.blockLight && !(thing is Pawn) && !(thing is Building))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
|
||||
private void ApplyPathDamage(Thing targetThing, Wula_BeamPierce_Extension props) // 接受 Thing 参数
|
||||
{
|
||||
float damageMultiplier = 1f;
|
||||
if (targetThing is Pawn) // 只有 Pawn 才计算穿透衰减
|
||||
{
|
||||
damageMultiplier = Mathf.Pow(1f - props.damageFalloff, hitCounter);
|
||||
}
|
||||
// Building 不受穿透衰减影响,或者 Building 的穿透衰减始终为 1 (不衰减)
|
||||
|
||||
int damageAmount = (int)(this.DamageAmount * damageMultiplier);
|
||||
|
||||
if (damageAmount <= 0) return;
|
||||
|
||||
var dinfo = new DamageInfo(
|
||||
this.def.projectile.damageDef,
|
||||
damageAmount,
|
||||
this.ArmorPenetration * damageMultiplier,
|
||||
this.ExactRotation.eulerAngles.y,
|
||||
this.launcher,
|
||||
null,
|
||||
this.equipmentDef,
|
||||
DamageInfo.SourceCategory.ThingOrUnknown,
|
||||
this.intendedTarget.Thing);
|
||||
|
||||
targetThing.TakeDamage(dinfo); // 对 targetThing 造成伤害
|
||||
alreadyDamaged.Add(targetThing);
|
||||
|
||||
if (targetThing is Pawn) // 只有 Pawn 才增加 hitCounter
|
||||
{
|
||||
hitCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Tick() { }
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
// Final, robust extension class for configuring path-based penetration.
|
||||
public class Wula_PathPierce_Extension : DefModExtension
|
||||
{
|
||||
// Set to a positive number for limited hits, or -1 for infinite penetration.
|
||||
public int maxHits = 3;
|
||||
// The percentage of damage lost per hit. 0.25 means 25% damage loss per hit.
|
||||
public float damageFalloff = 0.25f;
|
||||
// If true, this projectile will never cause friendly fire, regardless of game settings.
|
||||
public bool preventFriendlyFire = false;
|
||||
public FleckDef tailFleckDef; // 用于配置拖尾特效的 FleckDef
|
||||
public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间(tick)
|
||||
}
|
||||
|
||||
public class Projectile_WulaLineAttack : Bullet
|
||||
{
|
||||
private int hitCounter = 0;
|
||||
private List<Thing> alreadyDamaged = new List<Thing>();
|
||||
private Vector3 lastTickPosition;
|
||||
private int Fleck_MakeFleckTick; // 拖尾特效的计时器
|
||||
public int Fleck_MakeFleckTickMax = 1; // 拖尾特效的生成频率
|
||||
public IntRange Fleck_MakeFleckNum = new IntRange(1, 1); // 每次生成的粒子数量
|
||||
public FloatRange Fleck_Angle = new FloatRange(-180f, 180f); // 粒子角度
|
||||
public FloatRange Fleck_Scale = new FloatRange(1f, 1f); // 粒子大小
|
||||
public FloatRange Fleck_Speed = new FloatRange(0f, 0f); // 粒子速度
|
||||
public FloatRange Fleck_Rotation = new FloatRange(-180f, 180f); // 粒子旋转
|
||||
|
||||
private Wula_PathPierce_Extension Props => def.GetModExtension<Wula_PathPierce_Extension>();
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Values.Look(ref hitCounter, "hitCounter", 0);
|
||||
Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference);
|
||||
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition");
|
||||
if (alreadyDamaged == null)
|
||||
{
|
||||
alreadyDamaged = new List<Thing>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
|
||||
{
|
||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||
this.lastTickPosition = origin;
|
||||
this.alreadyDamaged.Clear();
|
||||
this.hitCounter = 0;
|
||||
// Friendly fire is prevented if EITHER the game setting is true OR the XML extension is true.
|
||||
this.preventFriendlyFire = preventFriendlyFire || (Props?.preventFriendlyFire ?? false);
|
||||
}
|
||||
|
||||
protected override void Tick()
|
||||
{
|
||||
Vector3 startPos = this.lastTickPosition;
|
||||
base.Tick();
|
||||
|
||||
if (this.Destroyed) return;
|
||||
|
||||
this.Fleck_MakeFleckTick++;
|
||||
// 只有当达到延迟时间后才开始生成Fleck
|
||||
if (this.Fleck_MakeFleckTick >= Props.fleckDelayTicks)
|
||||
{
|
||||
if (this.Fleck_MakeFleckTick >= (Props.fleckDelayTicks + this.Fleck_MakeFleckTickMax))
|
||||
{
|
||||
this.Fleck_MakeFleckTick = Props.fleckDelayTicks; // 重置计时器,从延迟时间开始循环
|
||||
}
|
||||
|
||||
Map map = base.Map;
|
||||
int randomInRange = this.Fleck_MakeFleckNum.RandomInRange;
|
||||
Vector3 currentPosition = this.ExactPosition; // Current position of the bullet
|
||||
Vector3 previousPosition = this.lastTickPosition; // Previous position of the bullet
|
||||
|
||||
for (int i = 0; i < randomInRange; i++)
|
||||
{
|
||||
float currentBulletAngle = ExactRotation.eulerAngles.y; // 使用子弹当前的水平旋转角度
|
||||
float fleckRotationAngle = currentBulletAngle; // Fleck 的旋转角度与子弹方向一致
|
||||
float velocityAngle = this.Fleck_Angle.RandomInRange + currentBulletAngle; // Fleck 的速度角度基于子弹方向加上随机偏移
|
||||
float randomInRange2 = this.Fleck_Scale.RandomInRange;
|
||||
float randomInRange3 = this.Fleck_Speed.RandomInRange;
|
||||
|
||||
if (Props?.tailFleckDef != null)
|
||||
{
|
||||
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, Props.tailFleckDef, randomInRange2);
|
||||
dataStatic.rotation = fleckRotationAngle;
|
||||
dataStatic.rotationRate = this.Fleck_Rotation.RandomInRange;
|
||||
dataStatic.velocityAngle = velocityAngle;
|
||||
dataStatic.velocitySpeed = randomInRange3;
|
||||
map.flecks.CreateFleck(dataStatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Destroyed) return;
|
||||
|
||||
Vector3 endPos = this.ExactPosition;
|
||||
|
||||
CheckPathForDamage(startPos, endPos);
|
||||
|
||||
this.lastTickPosition = endPos;
|
||||
}
|
||||
|
||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||
{
|
||||
CheckPathForDamage(lastTickPosition, this.ExactPosition);
|
||||
|
||||
if (hitThing != null && alreadyDamaged.Contains(hitThing))
|
||||
{
|
||||
base.Impact(null, blockedByShield);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Impact(hitThing, blockedByShield);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckPathForDamage(Vector3 startPos, Vector3 endPos)
|
||||
{
|
||||
if (startPos == endPos) return;
|
||||
|
||||
int maxHits = Props?.maxHits ?? 1;
|
||||
bool infinitePenetration = maxHits < 0;
|
||||
|
||||
if (!infinitePenetration && hitCounter >= maxHits) return;
|
||||
|
||||
Map map = this.Map;
|
||||
float distance = Vector3.Distance(startPos, endPos);
|
||||
Vector3 direction = (endPos - startPos).normalized;
|
||||
|
||||
for (float i = 0; i < distance; i += 0.8f)
|
||||
{
|
||||
if (!infinitePenetration && hitCounter >= maxHits) break;
|
||||
|
||||
Vector3 checkPos = startPos + direction * i;
|
||||
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkPos.ToIntVec3()));
|
||||
|
||||
foreach (Thing thing in thingsInCell)
|
||||
{
|
||||
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn))
|
||||
{
|
||||
bool shouldDamage = false;
|
||||
|
||||
// Case 1: Always damage the intended target if it's a pawn. This allows hunting.
|
||||
if (this.intendedTarget.Thing == pawn)
|
||||
{
|
||||
shouldDamage = true;
|
||||
}
|
||||
// Case 2: Always damage hostile pawns in the path.
|
||||
else if (pawn.HostileTo(this.launcher))
|
||||
{
|
||||
shouldDamage = true;
|
||||
}
|
||||
// Case 3: Damage non-hostiles (friendlies, neutrals) if the shot itself isn't marked to prevent friendly fire.
|
||||
else if (!this.preventFriendlyFire)
|
||||
{
|
||||
shouldDamage = true;
|
||||
}
|
||||
|
||||
if (shouldDamage)
|
||||
{
|
||||
ApplyPathDamage(pawn);
|
||||
if (!infinitePenetration && hitCounter >= maxHits) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyPathDamage(Pawn pawn)
|
||||
{
|
||||
Wula_PathPierce_Extension props = Props;
|
||||
float falloff = props?.damageFalloff ?? 0.25f;
|
||||
|
||||
// Damage falloff now applies universally, even for infinite penetration.
|
||||
float damageMultiplier = Mathf.Pow(1f - falloff, hitCounter);
|
||||
|
||||
int damageAmount = (int)(this.DamageAmount * damageMultiplier);
|
||||
if (damageAmount <= 0) return;
|
||||
|
||||
var dinfo = new DamageInfo(
|
||||
this.def.projectile.damageDef,
|
||||
damageAmount,
|
||||
this.ArmorPenetration * damageMultiplier,
|
||||
this.ExactRotation.eulerAngles.y,
|
||||
this.launcher,
|
||||
null,
|
||||
this.equipmentDef,
|
||||
DamageInfo.SourceCategory.ThingOrUnknown,
|
||||
this.intendedTarget.Thing);
|
||||
|
||||
pawn.TakeDamage(dinfo);
|
||||
alreadyDamaged.Add(pawn);
|
||||
hitCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Source/ArachnaeSwarm/Verbs/Projectiles/TrackingBulletDef.cs
Normal file
21
Source/ArachnaeSwarm/Verbs/Projectiles/TrackingBulletDef.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Verse;
|
||||
|
||||
namespace ArachnaeSwarm
|
||||
{
|
||||
public class TrackingBulletDef : DefModExtension
|
||||
{
|
||||
public float homingSpeed = 0.1f; // 追踪速度,值越大追踪越灵敏
|
||||
public float initRotateAngle = 0f; // 初始旋转角度
|
||||
public float impactThreshold = 0.5f; // 强制命中阈值,子弹与目标距离小于此值时强制命中
|
||||
|
||||
public FleckDef tailFleckDef; // 拖尾特效的FleckDef
|
||||
public int fleckMakeFleckTickMax = 1; // 拖尾特效的生成间隔(tick)
|
||||
public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间(tick)
|
||||
public IntRange fleckMakeFleckNum = new IntRange(1, 1); // 每次生成拖尾特效的数量
|
||||
public FloatRange fleckAngle = new FloatRange(-180f, 180f); // 拖尾特效的初始角度范围
|
||||
public FloatRange fleckScale = new FloatRange(1f, 1f); // 拖尾特效的缩放范围
|
||||
public FloatRange fleckSpeed = new FloatRange(0f, 0f); // 拖尾特效的初始速度范围
|
||||
public FloatRange fleckRotation = new FloatRange(-180f, 180f); // 拖尾特效的旋转速度范围
|
||||
public IntRange destroyTicksAfterLosingTrack = new IntRange(60, 120); // 失去追踪后多少tick自毁
|
||||
}
|
||||
}
|
||||
15
Source/ArachnaeSwarm/Verbs/VerbProperties_Excalibur.cs
Normal file
15
Source/ArachnaeSwarm/Verbs/VerbProperties_Excalibur.cs
Normal 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
|
||||
}
|
||||
}
|
||||
401
Source/ArachnaeSwarm/Verbs/Verb_ShootArc.cs
Normal file
401
Source/ArachnaeSwarm/Verbs/Verb_ShootArc.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
|
||||
585
Source/ArachnaeSwarm/Verbs/Verb_ShootMeltBeam.cs
Normal file
585
Source/ArachnaeSwarm/Verbs/Verb_ShootMeltBeam.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
63
Source/ArachnaeSwarm/Verbs/Verb_ShootShotgun.cs
Normal file
63
Source/ArachnaeSwarm/Verbs/Verb_ShootShotgun.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user