穿透弹

This commit is contained in:
2025-08-17 17:27:22 +08:00
parent 3f4d91fece
commit 8b394062fc
5 changed files with 80 additions and 48 deletions

View File

@@ -98,16 +98,6 @@ namespace WulaFallenEmpire
}
}
public override void PostDeSpawn(Map map, DestroyMode mode = DestroyMode.Vanish)
{
base.PostDeSpawn(map, mode);
// This handles cases like uninstalling where the pod is removed from the map
// without being "destroyed". We still need to eject the occupant.
Log.Warning($"[WulaPodDebug] Pod despawned. Ejecting pawn.");
EjectPawn();
}
// ===================== IThingHolder Implementation =====================
public void GetChildHolders(List<IThingHolder> outChildren)
{

View File

@@ -5,14 +5,27 @@ using Verse;
namespace WulaFallenEmpire
{
// 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;
}
public class Projectile_WulaLineAttack : Projectile
{
private int hitCounter = 0;
private List<Thing> alreadyDamaged = new List<Thing>();
private Vector3 lastTickPosition;
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)
@@ -26,70 +39,93 @@ namespace WulaFallenEmpire
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
this.lastTickPosition = origin;
this.alreadyDamaged.Clear();
this.hitCounter = 0;
}
protected override void Tick()
{
Vector3 startPos = this.lastTickPosition;
base.Tick(); // 这会更新弹丸的位置并可能调用Impact()
base.Tick();
if (this.Destroyed) return;
if (this.Destroyed)
{
return;
}
Vector3 endPos = this.ExactPosition;
CheckPathForDamage(startPos, endPos);
// 调用路径伤害检测
DamageMissedPawns(startPos, endPos);
// 为下一帧更新位置
this.lastTickPosition = endPos;
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
// 在最终碰撞前,最后一次检查从上一帧到当前碰撞点的路径
DamageMissedPawns(this.lastTickPosition, this.ExactPosition);
CheckPathForDamage(lastTickPosition, this.ExactPosition);
// 如果最终目标还没被路径伤害击中,在这里造成一次伤害
if (hitThing != null && !alreadyDamaged.Contains(hitThing))
if (hitThing != null && alreadyDamaged.Contains(hitThing))
{
DamageInfo dinfo = new DamageInfo(this.def.projectile.damageDef, (float)this.DamageAmount, this.ArmorPenetration, this.ExactRotation.eulerAngles.y, this.launcher, null, this.equipmentDef, DamageInfo.SourceCategory.ThingOrUnknown, this.intendedTarget.Thing);
hitThing.TakeDamage(dinfo);
base.Impact(null, blockedByShield);
}
else
{
base.Impact(hitThing, blockedByShield);
}
// 调用基类方法来处理XML中定义的爆炸等最终效果
base.Impact(hitThing, blockedByShield);
}
private void DamageMissedPawns(Vector3 startPos, Vector3 endPos)
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.5f)
for (float i = 0; i < distance; i += 0.8f)
{
Vector3 checkPos = startPos + direction * i;
IntVec3 checkCell = new IntVec3(checkPos);
if (!infinitePenetration && hitCounter >= maxHits) break;
Vector3 checkPos = startPos + direction * i;
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkPos.ToIntVec3()));
if (!checkCell.InBounds(map)) continue;
var thingsInCell = new HashSet<Thing>(map.thingGrid.ThingsListAt(checkCell));
foreach (Thing thing in thingsInCell)
{
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn) && GenHostility.HostileTo(pawn, this.launcher.Faction))
{
var dinfo = new DamageInfo(this.def.projectile.damageDef, (float)this.DamageAmount, this.ArmorPenetration, this.ExactRotation.eulerAngles.y, this.launcher, null, this.equipmentDef, DamageInfo.SourceCategory.ThingOrUnknown, this.intendedTarget.Thing);
pawn.TakeDamage(dinfo);
alreadyDamaged.Add(pawn);
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++;
}
}
}