This commit is contained in:
2025-08-20 14:33:45 +08:00
parent 617f8da51e
commit 608266e614
11 changed files with 474 additions and 760 deletions

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using RimWorld;
@@ -9,317 +8,319 @@ using Verse.Sound;
namespace WulaFallenEmpire
{
public class Projectile_Homing : Bullet
{
public HomingProjectileDef HomingDef
{
get
{
bool flag = this.homingDefInt == null;
if (flag)
{
this.homingDefInt = this.def.GetModExtension<HomingProjectileDef>();
}
return this.homingDefInt;
}
}
public class Projectile_Homing : Bullet
{
private HomingProjectileDef homingDefInt;
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
{
bool flag = false;
bool flag2 = usedTarget.HasThing && usedTarget.Thing is IAttackTarget;
if (flag2)
{
bool flag3 = Rand.Chance(this.GetHitChance(usedTarget.Thing));
if (flag3)
{
hitFlags |= ProjectileHitFlags.IntendedTarget;
intendedTarget = usedTarget;
flag = true;
}
}
else
{
bool flag4 = Rand.Chance(this.GetHitChance(intendedTarget.Thing));
if (flag4)
{
hitFlags |= ProjectileHitFlags.IntendedTarget;
usedTarget = intendedTarget;
flag = true;
}
}
bool flag5 = flag;
if (flag5)
{
hitFlags &= ~ProjectileHitFlags.IntendedTarget;
}
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
this.exactPositionInt = origin.Yto0() + Vector3.up * this.def.Altitude;
Vector3 normalized = (this.destination - origin).Yto0().normalized;
float degrees = Rand.Range(-this.HomingDef.initRotateAngle, this.HomingDef.initRotateAngle);
Vector2 vector = new Vector2(normalized.x, normalized.z);
vector = vector.RotatedBy(degrees);
Vector3 a = new Vector3(vector.x, 0f, vector.y);
bool flag6 = this.HomingDef.speedRangeOverride == null;
if (flag6)
{
this.curSpeed = a * this.def.projectile.SpeedTilesPerTick;
}
else
{
this.curSpeed = a * this.HomingDef.SpeedRangeTilesPerTickOverride.RandomInRange;
}
this.ticksToImpact = int.MaxValue;
this.lifetime = int.MaxValue;
this.ReflectInit();
}
private Sustainer ambientSustainer;
protected void ReflectInit()
{
bool flag = !this.def.projectile.soundAmbient.NullOrUndefined();
if (flag)
{
this.ambientSustainer = (Sustainer)NonPublicFields.Projectile_AmbientSustainer.GetValue(this);
}
this.comps = (List<ThingComp>)NonPublicFields.ThingWithComps_comps.GetValue(this);
}
private List<ThingComp> comps;
public float GetHitChance(Thing thing)
{
float num = this.HomingDef.hitChance;
bool flag = thing == null;
float result;
if (flag)
{
result = num;
}
else
{
Pawn pawn = thing as Pawn;
bool flag2 = pawn != null;
if (flag2)
{
num *= Mathf.Clamp(pawn.BodySize, 0.5f, 1.5f);
bool flag3 = pawn.GetPosture() > PawnPosture.Standing;
if (flag3)
{
num *= 0.5f;
}
float num2 = 1f;
switch (this.equipmentQuality)
{
case QualityCategory.Awful:
num2 = 0.5f;
goto IL_DD;
case QualityCategory.Poor:
num2 = 0.75f;
goto IL_DD;
case QualityCategory.Normal:
num2 = 1f;
goto IL_DD;
case QualityCategory.Excellent:
num2 = 1.1f;
goto IL_DD;
case QualityCategory.Masterwork:
num2 = 1.2f;
goto IL_DD;
case QualityCategory.Legendary:
num2 = 1.3f;
goto IL_DD;
}
Log.Message("Unknown QualityCategory, returning default qualityFactor = 1");
IL_DD:
num *= num2;
}
else
{
num *= 1.5f * thing.def.fillPercent;
}
result = Mathf.Clamp(num, 0f, 1f);
}
return result;
}
protected Vector3 exactPositionInt;
public override Vector3 ExactPosition
{
get
{
return this.exactPositionInt;
}
}
public Vector3 curSpeed;
public override Quaternion ExactRotation
{
get
{
return Quaternion.LookRotation(this.curSpeed);
}
}
public bool homing = true;
private int Fleck_MakeFleckTick; // 拖尾特效的计时器
private Vector3 lastTickPosition; // 记录上一帧的位置,用于计算移动方向
public virtual void MovementTick()
{
Vector3 vect = this.ExactPosition + this.curSpeed;
ShootLine shootLine = new ShootLine(this.ExactPosition.ToIntVec3(), vect.ToIntVec3());
Vector3 vector = (this.intendedTarget.Cell.ToVector3() - this.ExactPosition).Yto0();
bool flag = this.homing;
if (flag)
{
Vector3 a = vector.normalized - this.curSpeed.normalized;
bool flag2 = a.sqrMagnitude >= 1.414f;
if (flag2)
{
this.homing = false;
this.lifetime = this.HomingDef.destroyTicksAfterLosingTrack.RandomInRange;
this.ticksToImpact = this.lifetime;
base.HitFlags &= ~ProjectileHitFlags.IntendedTarget;
base.HitFlags |= ProjectileHitFlags.NonTargetPawns;
base.HitFlags |= ProjectileHitFlags.NonTargetWorld;
}
else
{
this.curSpeed += a * this.HomingDef.homingSpeed * this.curSpeed.magnitude;
}
}
foreach (IntVec3 b in shootLine.Points())
{
bool flag3 = (this.intendedTarget.Cell - b).SqrMagnitude <= this.HomingDef.proximityFuseRange * this.HomingDef.proximityFuseRange;
if (flag3)
{
this.homing = false;
this.lifetime = this.HomingDef.destroyTicksAfterLosingTrack.RandomInRange;
bool flag4 = (base.HitFlags & ProjectileHitFlags.IntendedTarget) == ProjectileHitFlags.IntendedTarget || this.HomingDef.proximityFuseRange > 0f;
if (flag4)
{
this.lifetime = 0;
this.ticksToImpact = 0;
vect = b.ToVector3();
bool flag5 = Find.TickManager.CurTimeSpeed == TimeSpeed.Normal && this.def.projectile.soundImpactAnticipate != null;
if (flag5)
{
this.def.projectile.soundImpactAnticipate.PlayOneShot(this);
}
}
}
}
this.exactPositionInt = vect;
this.curSpeed *= (this.curSpeed.magnitude + this.HomingDef.SpeedChangeTilesPerTickOverride) / this.curSpeed.magnitude;
}
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);
}
protected override void Tick()
{
this.ThingWithCompsTick();
this.lifetime--;
if (this.HomingDef.tailFleckDef != null)
{
FleckMaker.Static(this.ExactPosition, base.Map, this.HomingDef.tailFleckDef, 1f);
}
bool landed = this.landed;
if (!landed)
{
Vector3 exactPosition = this.ExactPosition;
this.ticksToImpact--;
this.MovementTick();
bool flag = !this.ExactPosition.InBounds(base.Map);
if (flag)
{
base.Position = exactPosition.ToIntVec3();
this.Destroy(DestroyMode.Vanish);
}
else
{
Vector3 exactPosition2 = this.ExactPosition;
object[] parameters = new object[]
{
exactPosition,
exactPosition2
};
bool flag2 = (bool)Projectile_Homing.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters);
if (!flag2)
{
base.Position = this.ExactPosition.ToIntVec3();
bool flag3 = this.ticksToImpact == 60 && Find.TickManager.CurTimeSpeed == TimeSpeed.Normal && this.def.projectile.soundImpactAnticipate != null;
if (flag3)
{
this.def.projectile.soundImpactAnticipate.PlayOneShot(this);
}
bool flag4 = this.ticksToImpact <= 0;
if (flag4)
{
this.ImpactSomething();
}
else
{
bool flag5 = this.ambientSustainer != null;
if (flag5)
{
this.ambientSustainer.Maintain();
}
}
}
}
}
}
public HomingProjectileDef HomingDef
{
get
{
if (homingDefInt == null)
{
homingDefInt = def.GetModExtension<HomingProjectileDef>();
if (homingDefInt == null)
{
Log.ErrorOnce($"HomingProjectileDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
this.homingDefInt = new HomingProjectileDef();
}
}
return homingDefInt;
}
}
private void ThingWithCompsTick()
{
bool flag = this.comps != null;
if (flag)
{
int i = 0;
int count = this.comps.Count;
while (i < count)
{
this.comps[i].CompTick();
i++;
}
}
}
public override Vector3 ExactPosition => exactPositionInt;
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
Map map = base.Map;
IntVec3 position = base.Position;
base.Impact(hitThing, blockedByShield);
bool flag = this.HomingDef.extraProjectile != null;
if (flag)
{
bool flag2 = hitThing != null && hitThing.Spawned;
if (flag2)
{
((Projectile)GenSpawn.Spawn(this.HomingDef.extraProjectile, base.Position, map, WipeMode.Vanish)).Launch(this.launcher, this.ExactPosition, hitThing, hitThing, ProjectileHitFlags.All, false, null, null);
}
else
{
((Projectile)GenSpawn.Spawn(this.HomingDef.extraProjectile, base.Position, map, WipeMode.Vanish)).Launch(this.launcher, this.ExactPosition, position, position, ProjectileHitFlags.All, false, null, null);
}
}
}
public override Quaternion ExactRotation => Quaternion.LookRotation(curSpeed);
public override void ExposeData()
{
base.ExposeData();
Scribe_Values.Look<Vector3>(ref this.exactPositionInt, "exactPosition", default(Vector3), false);
Scribe_Values.Look<Vector3>(ref this.curSpeed, "curSpeed", default(Vector3), false);
Scribe_Values.Look<bool>(ref this.homing, "homing", false, false);
bool flag = Scribe.mode == LoadSaveMode.PostLoadInit;
if (flag)
{
this.ReflectInit();
}
}
public override void Launch(Thing launcher, Vector3 origin, LocalTargetInfo usedTarget, LocalTargetInfo intendedTarget, ProjectileHitFlags hitFlags, bool preventFriendlyFire = false, Thing equipment = null, ThingDef targetCoverDef = null)
{
bool flag = false;
if (usedTarget.HasThing && usedTarget.Thing is IAttackTarget)
{
if (Rand.Chance(GetHitChance(usedTarget.Thing)))
{
hitFlags |= ProjectileHitFlags.IntendedTarget;
intendedTarget = usedTarget;
flag = true;
}
}
else if (Rand.Chance(GetHitChance(intendedTarget.Thing)))
{
hitFlags |= ProjectileHitFlags.IntendedTarget;
usedTarget = intendedTarget;
flag = true;
}
if (flag)
{
hitFlags &= ~ProjectileHitFlags.IntendedTarget;
}
base.Launch(launcher, origin, usedTarget, intendedTarget, hitFlags, preventFriendlyFire, equipment, targetCoverDef);
exactPositionInt = origin.Yto0() + Vector3.up * def.Altitude;
lastTickPosition = origin; // 初始化 lastTickPosition
Vector3 normalized = (destination - origin).Yto0().normalized;
float degrees = Rand.Range(0f - HomingDef.initRotateAngle, HomingDef.initRotateAngle);
Vector2 v = new Vector2(normalized.x, normalized.z);
v = v.RotatedBy(degrees);
Vector3 vector = new Vector3(v.x, 0f, v.y);
// 检查 HomingDef.speedRangeOverride 是否有值
if (!HomingDef.speedRangeOverride.HasValue)
{
curSpeed = vector * def.projectile.SpeedTilesPerTick;
}
else
{
curSpeed = vector * HomingDef.SpeedRangeTilesPerTickOverride.RandomInRange;
}
ticksToImpact = int.MaxValue;
lifetime = int.MaxValue;
ReflectInit();
}
private HomingProjectileDef homingDefInt;
protected void ReflectInit()
{
if (!def.projectile.soundAmbient.NullOrUndefined())
{
ambientSustainer = (Sustainer)NonPublicFields.Projectile_AmbientSustainer.GetValue(this);
}
comps = (List<ThingComp>)NonPublicFields.ThingWithComps_comps.GetValue(this);
}
private Sustainer ambientSustainer;
public float GetHitChance(Thing thing)
{
if (this.HomingDef == null)
{
Log.ErrorOnce("HomingDef is null for projectile " + this.def.defName + ". Returning default hitChance.", this.thingIDNumber ^ 0x12345678);
return 0.7f;
}
private List<ThingComp> comps;
float hitChance = HomingDef.hitChance;
if (thing == null)
{
return hitChance;
}
if (thing is Pawn pawn)
{
hitChance *= Mathf.Clamp(pawn.BodySize, 0.5f, 1.5f);
if (pawn.GetPosture() != 0)
{
hitChance *= 0.5f;
}
float num = 1f;
switch (equipmentQuality)
{
case QualityCategory.Awful:
num = 0.5f;
break;
case QualityCategory.Poor:
num = 0.75f;
break;
case QualityCategory.Normal:
num = 1f;
break;
case QualityCategory.Excellent:
num = 1.1f;
break;
case QualityCategory.Masterwork:
num = 1.2f;
break;
case QualityCategory.Legendary:
num = 1.3f;
break;
default:
Log.Message("Unknown QualityCategory, returning default qualityFactor = 1");
break;
}
hitChance *= num;
}
else
{
hitChance *= 1.5f * thing.def.fillPercent;
}
return Mathf.Clamp(hitChance, 0f, 1f);
}
protected Vector3 exactPositionInt;
public virtual void MovementTick()
{
Vector3 vect = ExactPosition + curSpeed;
ShootLine shootLine = new ShootLine(ExactPosition.ToIntVec3(), vect.ToIntVec3());
Vector3 vector = (intendedTarget.Cell.ToVector3() - ExactPosition).Yto0();
if (homing)
{
Vector3 vector2 = vector.normalized - curSpeed.normalized;
if (vector2.sqrMagnitude >= 1.414f)
{
homing = false;
lifetime = HomingDef.destroyTicksAfterLosingTrack.RandomInRange;
ticksToImpact = lifetime;
base.HitFlags &= ~ProjectileHitFlags.IntendedTarget;
base.HitFlags |= ProjectileHitFlags.NonTargetPawns;
base.HitFlags |= ProjectileHitFlags.NonTargetWorld;
}
else
{
curSpeed += vector2 * HomingDef.homingSpeed * curSpeed.magnitude;
}
}
foreach (IntVec3 item in shootLine.Points())
{
if (!((intendedTarget.Cell - item).SqrMagnitude <= HomingDef.proximityFuseRange * HomingDef.proximityFuseRange))
{
continue;
}
homing = false;
lifetime = HomingDef.destroyTicksAfterLosingTrack.RandomInRange;
if ((base.HitFlags & ProjectileHitFlags.IntendedTarget) == ProjectileHitFlags.IntendedTarget || HomingDef.proximityFuseRange > 0f)
{
lifetime = 0;
ticksToImpact = 0;
vect = item.ToVector3();
if (Find.TickManager.CurTimeSpeed == TimeSpeed.Normal && def.projectile.soundImpactAnticipate != null)
{
def.projectile.soundImpactAnticipate.PlayOneShot(this);
}
}
}
exactPositionInt = vect;
curSpeed *= (curSpeed.magnitude + HomingDef.SpeedChangeTilesPerTickOverride) / curSpeed.magnitude;
}
public Vector3 curSpeed;
protected override void Tick()
{
ThingWithCompsTick();
lifetime--;
// 处理拖尾特效
if (HomingDef != null && HomingDef.tailFleckDef != null)
{
Fleck_MakeFleckTick++;
if (Fleck_MakeFleckTick >= HomingDef.fleckMakeFleckTickMax)
{
Fleck_MakeFleckTick = 0;
Map map = base.Map;
int randomInRange = HomingDef.fleckMakeFleckNum.RandomInRange;
Vector3 currentPosition = ExactPosition;
Vector3 previousPosition = lastTickPosition;
public bool homing = true;
for (int i = 0; i < randomInRange; i++)
{
float num = (currentPosition - previousPosition).AngleFlat();
float velocityAngle = HomingDef.fleckAngle.RandomInRange + num;
float randomInRange2 = HomingDef.fleckScale.RandomInRange;
float randomInRange3 = HomingDef.fleckSpeed.RandomInRange;
FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, HomingDef.tailFleckDef, randomInRange2);
dataStatic.rotation = (currentPosition - previousPosition).AngleFlat();
dataStatic.rotationRate = HomingDef.fleckRotation.RandomInRange;
dataStatic.velocityAngle = velocityAngle;
dataStatic.velocitySpeed = randomInRange3;
map.flecks.CreateFleck(dataStatic);
}
}
}
lastTickPosition = ExactPosition; // 更新上一帧位置
private static MethodInfo ProjectileCheckForFreeInterceptBetween = typeof(Projectile).GetMethod("CheckForFreeInterceptBetween", BindingFlags.Instance | BindingFlags.NonPublic);
}
if (landed)
{
return;
}
Vector3 exactPosition = ExactPosition;
ticksToImpact--;
MovementTick();
if (!ExactPosition.InBounds(base.Map))
{
base.Position = exactPosition.ToIntVec3();
Destroy();
return;
}
Vector3 exactPosition2 = ExactPosition;
object[] parameters = new object[2] { exactPosition, exactPosition2 };
if (!(bool)NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters))
{
base.Position = ExactPosition.ToIntVec3();
if (ticksToImpact == 60 && Find.TickManager.CurTimeSpeed == TimeSpeed.Normal && def.projectile.soundImpactAnticipate != null)
{
def.projectile.soundImpactAnticipate.PlayOneShot(this);
}
if (ticksToImpact <= 0)
{
ImpactSomething();
}
else if (ambientSustainer != null)
{
ambientSustainer.Maintain();
}
}
}
private void ThingWithCompsTick()
{
if (comps != null)
{
int i = 0;
for (int count = comps.Count; i < count; i++)
{
comps[i].CompTick();
}
}
}
protected override void Impact(Thing hitThing, bool blockedByShield = false)
{
Map map = base.Map;
IntVec3 position = base.Position;
base.Impact(hitThing, blockedByShield);
if (HomingDef.extraProjectile != null)
{
if (hitThing != null && hitThing.Spawned)
{
((Projectile)GenSpawn.Spawn(HomingDef.extraProjectile, base.Position, map, WipeMode.Vanish)).Launch(launcher, ExactPosition, hitThing, hitThing, ProjectileHitFlags.All, false, null, null);
}
else
{
((Projectile)GenSpawn.Spawn(HomingDef.extraProjectile, base.Position, map, WipeMode.Vanish)).Launch(launcher, ExactPosition, position, position, ProjectileHitFlags.All, false, null, null);
}
}
}
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: false);
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
ReflectInit();
if (this.homingDefInt == null)
{
this.homingDefInt = this.def.GetModExtension<HomingProjectileDef>();
if (this.homingDefInt == null)
{
Log.ErrorOnce($"HomingProjectileDef is null for projectile {this.def.defName} after PostLoadInit. Creating a default instance.", this.thingIDNumber ^ 0x12345678);
this.homingDefInt = new HomingProjectileDef();
}
}
}
}
}
}