穿透弹
This commit is contained in:
Binary file not shown.
@@ -1219,7 +1219,7 @@
|
|||||||
<AccuracyLong>0.6</AccuracyLong>
|
<AccuracyLong>0.6</AccuracyLong>
|
||||||
<RangedWeapon_Cooldown>3</RangedWeapon_Cooldown>
|
<RangedWeapon_Cooldown>3</RangedWeapon_Cooldown>
|
||||||
</statBases>
|
</statBases>
|
||||||
<verbs>x
|
<verbs>
|
||||||
<li Class="WulaFallenEmpire.VerbPropertiesExplosiveBeam">
|
<li Class="WulaFallenEmpire.VerbPropertiesExplosiveBeam">
|
||||||
<verbClass>WulaFallenEmpire.Verb_ShootBeamExplosive</verbClass>
|
<verbClass>WulaFallenEmpire.Verb_ShootBeamExplosive</verbClass>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<!-- Penetrating Rifle (No Explosion) -->
|
<!-- Penetrating Rifle (No Explosion) -->
|
||||||
<ThingDef ParentName="BaseHumanMakeableGun">
|
<ThingDef ParentName="BaseHumanMakeableGun">
|
||||||
<defName>WULA_RW_Penetrating_Rifle</defName>
|
<defName>WULA_RW_Penetrating_Rifle</defName>
|
||||||
<label>SLr-15 "长钉" (穿透型)</label>
|
<label>SLr-15 "长钉"</label>
|
||||||
<description>一把经过实验性改造的“蓝锥”步枪,能够发射一种特殊的钢针,利用过载的能量使其在击中第一个目标后仍能继续飞行,对路径上的多个敌人造成伤害。</description>
|
<description>一把经过实验性改造的“蓝锥”步枪,能够发射一种特殊的钢针,利用过载的能量使其在击中第一个目标后仍能继续飞行,对路径上的多个敌人造成伤害。</description>
|
||||||
<techLevel>Spacer</techLevel>
|
<techLevel>Spacer</techLevel>
|
||||||
<graphicData>
|
<graphicData>
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
<statBases>
|
<statBases>
|
||||||
<WorkToMake>1300</WorkToMake>
|
<WorkToMake>1300</WorkToMake>
|
||||||
<Mass>3.5</Mass>
|
<Mass>3.5</Mass>
|
||||||
<AccuracyTouch>0.3</AccuracyTouch>
|
<AccuracyTouch>1.0</AccuracyTouch>
|
||||||
<AccuracyShort>0.8</AccuracyShort>
|
<AccuracyShort>1.0</AccuracyShort>
|
||||||
<AccuracyMedium>0.9</AccuracyMedium>
|
<AccuracyMedium>1.0</AccuracyMedium>
|
||||||
<AccuracyLong>0.8</AccuracyLong>
|
<AccuracyLong>1.0</AccuracyLong>
|
||||||
<RangedWeapon_Cooldown>0.8</RangedWeapon_Cooldown>
|
<RangedWeapon_Cooldown>0.8</RangedWeapon_Cooldown>
|
||||||
</statBases>
|
</statBases>
|
||||||
<costList Inherit="False">
|
<costList Inherit="False">
|
||||||
@@ -59,7 +59,13 @@
|
|||||||
<ThingDef ParentName="BaseBullet">
|
<ThingDef ParentName="BaseBullet">
|
||||||
<defName>Bullet_WULA_RW_Penetrating_Rifle</defName>
|
<defName>Bullet_WULA_RW_Penetrating_Rifle</defName>
|
||||||
<label>穿透钢针弹</label>
|
<label>穿透钢针弹</label>
|
||||||
<thingClass>WulaFallenEmpire.Projectile_WulaLineAttack</thingClass> <!-- 使用我们的自定义穿透弹丸类 -->
|
<thingClass>WulaFallenEmpire.Projectile_WulaLineAttack</thingClass>
|
||||||
|
<modExtensions>
|
||||||
|
<li Class="WulaFallenEmpire.Wula_PathPierce_Extension">
|
||||||
|
<maxHits>-1</maxHits> <!-- 无限穿透 -->
|
||||||
|
<damageFalloff>0</damageFalloff> <!-- 无伤害衰减 -->
|
||||||
|
</li>
|
||||||
|
</modExtensions>
|
||||||
<tickerType>Normal</tickerType>
|
<tickerType>Normal</tickerType>
|
||||||
<neverMultiSelect>True</neverMultiSelect>
|
<neverMultiSelect>True</neverMultiSelect>
|
||||||
<graphicData>
|
<graphicData>
|
||||||
|
|||||||
@@ -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 =====================
|
// ===================== IThingHolder Implementation =====================
|
||||||
public void GetChildHolders(List<IThingHolder> outChildren)
|
public void GetChildHolders(List<IThingHolder> outChildren)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,14 +5,27 @@ using Verse;
|
|||||||
|
|
||||||
namespace WulaFallenEmpire
|
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
|
public class Projectile_WulaLineAttack : Projectile
|
||||||
{
|
{
|
||||||
|
private int hitCounter = 0;
|
||||||
private List<Thing> alreadyDamaged = new List<Thing>();
|
private List<Thing> alreadyDamaged = new List<Thing>();
|
||||||
private Vector3 lastTickPosition;
|
private Vector3 lastTickPosition;
|
||||||
|
|
||||||
|
private Wula_PathPierce_Extension Props => def.GetModExtension<Wula_PathPierce_Extension>();
|
||||||
|
|
||||||
public override void ExposeData()
|
public override void ExposeData()
|
||||||
{
|
{
|
||||||
base.ExposeData();
|
base.ExposeData();
|
||||||
|
Scribe_Values.Look(ref hitCounter, "hitCounter", 0);
|
||||||
Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference);
|
Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference);
|
||||||
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition");
|
Scribe_Values.Look(ref lastTickPosition, "lastTickPosition");
|
||||||
if (alreadyDamaged == null)
|
if (alreadyDamaged == null)
|
||||||
@@ -26,70 +39,93 @@ namespace WulaFallenEmpire
|
|||||||
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
|
||||||
this.lastTickPosition = origin;
|
this.lastTickPosition = origin;
|
||||||
this.alreadyDamaged.Clear();
|
this.alreadyDamaged.Clear();
|
||||||
|
this.hitCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Tick()
|
protected override void Tick()
|
||||||
{
|
{
|
||||||
Vector3 startPos = this.lastTickPosition;
|
Vector3 startPos = this.lastTickPosition;
|
||||||
|
base.Tick();
|
||||||
base.Tick(); // 这会更新弹丸的位置,并可能调用Impact()
|
|
||||||
|
if (this.Destroyed) return;
|
||||||
|
|
||||||
if (this.Destroyed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 endPos = this.ExactPosition;
|
Vector3 endPos = this.ExactPosition;
|
||||||
|
|
||||||
|
CheckPathForDamage(startPos, endPos);
|
||||||
|
|
||||||
// 调用路径伤害检测
|
|
||||||
DamageMissedPawns(startPos, endPos);
|
|
||||||
|
|
||||||
// 为下一帧更新位置
|
|
||||||
this.lastTickPosition = endPos;
|
this.lastTickPosition = endPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
protected override void Impact(Thing hitThing, bool blockedByShield = false)
|
||||||
{
|
{
|
||||||
// 在最终碰撞前,最后一次检查从上一帧到当前碰撞点的路径
|
CheckPathForDamage(lastTickPosition, this.ExactPosition);
|
||||||
DamageMissedPawns(this.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);
|
base.Impact(null, blockedByShield);
|
||||||
hitThing.TakeDamage(dinfo);
|
}
|
||||||
|
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;
|
if (startPos == endPos) return;
|
||||||
|
|
||||||
|
int maxHits = Props?.maxHits ?? 1;
|
||||||
|
bool infinitePenetration = maxHits < 0;
|
||||||
|
|
||||||
|
if (!infinitePenetration && hitCounter >= maxHits) return;
|
||||||
|
|
||||||
Map map = this.Map;
|
Map map = this.Map;
|
||||||
float distance = Vector3.Distance(startPos, endPos);
|
float distance = Vector3.Distance(startPos, endPos);
|
||||||
Vector3 direction = (endPos - startPos).normalized;
|
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;
|
if (!infinitePenetration && hitCounter >= maxHits) break;
|
||||||
IntVec3 checkCell = new IntVec3(checkPos);
|
|
||||||
|
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)
|
foreach (Thing thing in thingsInCell)
|
||||||
{
|
{
|
||||||
if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn) && GenHostility.HostileTo(pawn, this.launcher.Faction))
|
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);
|
ApplyPathDamage(pawn);
|
||||||
pawn.TakeDamage(dinfo);
|
if (!infinitePenetration && hitCounter >= maxHits) break;
|
||||||
alreadyDamaged.Add(pawn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user