diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll
index 5993a73f..ef72ddc5 100644
Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ
diff --git a/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_Homing.xml b/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_Homing.xml
index 0fdfeb62..3cf0c030 100644
--- a/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_Homing.xml
+++ b/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_Homing.xml
@@ -67,41 +67,18 @@
Things/Projectile/Bullet_Big
Graphic_Single
- WulaFallenEmpire.Projectile_Homing
+ WulaFallenEmpire.Projectile_TrackingBullet
30
Bullet
15
- 1.5
0.5
- Explosion_Blasting
- Impact_Metal
- false
- 0.5
- 0.5
- 0
- 0.5
- false
- 0.5
- 1
- 0.1
- true
- true
+ 1.5
-
+
+ 0.5
10
- 0.05
- 60
- 1.5
- 0.8
- Bullet_ExampleHoming
- 0.05
-
- 20
- 40
-
- WULA_GunTail_Smoke
diff --git a/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_HomingExplosive.xml b/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_HomingExplosive.xml
index 153f8455..b6c7a27f 100644
--- a/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_HomingExplosive.xml
+++ b/1.6/1.6/Defs/ThingDefs_Misc/Weapons/WULA_Weapon_Homing_Examples_Bullet_HomingExplosive.xml
@@ -68,18 +68,15 @@
Things/Projectile/Bullet_Big
Graphic_Single
- WulaFallenEmpire.Projectile_Homing_Explosive
+ WulaFallenEmpire.Projectile_TrackingBullet
25
Bomb
30
- 2.0
0.7
- Explosion_Bomb
- Impact_Metal
- true
- 0.8
+ 2.0
3.0
+ 60
1.0
true
1.0
@@ -89,19 +86,10 @@
true
-
+
+ 0.5
5
- 0.03
- 90
- 2.0
- 0.7
- Bullet_ExampleHomingExplosive
- 0.03
-
- 20
- 30
-
- WULA_GunTail_Smoke
+
diff --git a/Source/WulaFallenEmpire/Projectile_Homing.cs b/Source/WulaFallenEmpire/Projectile_Homing.cs
index 9243a528..fbf42bcd 100644
--- a/Source/WulaFallenEmpire/Projectile_Homing.cs
+++ b/Source/WulaFallenEmpire/Projectile_Homing.cs
@@ -91,8 +91,6 @@ namespace WulaFallenEmpire
{
curSpeed = vector * HomingDef.SpeedRangeTilesPerTickOverride.RandomInRange;
}
- ticksToImpact = int.MaxValue;
- lifetime = int.MaxValue;
ReflectInit();
}
@@ -208,6 +206,12 @@ namespace WulaFallenEmpire
{
ThingWithCompsTick();
lifetime--;
+
+ if (lifetime <= 0)
+ {
+ Destroy();
+ return;
+ }
// 处理拖尾特效
if (HomingDef != null && HomingDef.tailFleckDef != null)
@@ -239,10 +243,7 @@ namespace WulaFallenEmpire
}
lastTickPosition = ExactPosition; // 更新上一帧位置
- if (landed)
- {
- return;
- }
+ // 移除 if (landed) return; 以确保子弹落地后也能正常销毁
Vector3 exactPosition = ExactPosition;
ticksToImpact--;
MovementTick();
diff --git a/Source/WulaFallenEmpire/Projectile_PoiBullet.cs b/Source/WulaFallenEmpire/Projectile_PoiBullet.cs
new file mode 100644
index 00000000..d62e8452
--- /dev/null
+++ b/Source/WulaFallenEmpire/Projectile_PoiBullet.cs
@@ -0,0 +1,726 @@
+using System;
+using System.Collections.Generic;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+using System.Reflection;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ public class Projectile_PoiBullet : Bullet
+ {
+ // Projectile_Homing 的字段
+ private HomingProjectileDef homingDefInt;
+ private Sustainer ambientSustainer;
+ private List comps;
+ protected Vector3 exactPositionInt;
+ public Vector3 curSpeed;
+ public bool homing = true;
+ private int Fleck_MakeFleckTick;
+ private Vector3 lastTickPosition;
+
+ // Projectile_Homing_Explosive 的字段
+ private int ticksToDetonation;
+
+ 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 HomingProjectileDef HomingDef
+ {
+ get
+ {
+ if (homingDefInt == null)
+ {
+ homingDefInt = def.GetModExtension();
+ 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;
+ }
+ }
+
+ 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)
+ {
+ 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;
+ 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);
+ if (!HomingDef.speedRangeOverride.HasValue)
+ {
+ curSpeed = vector * def.projectile.SpeedTilesPerTick;
+ }
+ else
+ {
+ curSpeed = vector * HomingDef.SpeedRangeTilesPerTickOverride.RandomInRange;
+ }
+ ReflectInit();
+
+ // Projectile_PoiBullet 原始逻辑中的部分初始化
+ this.flag2 = false; // 重置RandFactor的标志
+ this.flag3 = true; // 重置CanHitTarget的标志
+ this.CalHit = false; // 重置命中计算结果
+ }
+
+ protected void ReflectInit()
+ {
+ if (!def.projectile.soundAmbient.NullOrUndefined())
+ {
+ ambientSustainer = (Sustainer)NonPublicFields.Projectile_AmbientSustainer.GetValue(this);
+ }
+ comps = (List)NonPublicFields.ThingWithComps_comps.GetValue(this);
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+
+ 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;
+ }
+
+ protected override void Tick()
+ {
+ // Projectile_Homing 的 Tick 逻辑
+ ThingWithCompsTick();
+ lifetime--;
+
+ if (lifetime <= 0)
+ {
+ Destroy();
+ return;
+ }
+
+ // 处理拖尾特效
+ 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;
+
+ 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;
+
+ // Projectile_Homing_Explosive 的 Tick 逻辑
+ if (HomingDef.isExplosive && HomingDef.explosionDelay > 0)
+ {
+ if (ticksToDetonation > 0)
+ {
+ ticksToDetonation--;
+ if (ticksToDetonation <= 0)
+ {
+ Explode();
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+
+ // Projectile_PoiBullet 原始逻辑中的部分Tick
+ this.tickcount++;
+ bool flag = this.flag3;
+ if (flag)
+ {
+ this.CalHit = this.CanHitTarget_Poi(); // 使用重命名后的方法
+ this.flag3 = false;
+ }
+ bool flag2 = !this.CalHit;
+ if (flag2)
+ {
+ this.FindRandCell(this.intendedTarget.CenterVector3);
+ }
+ bool flag3_poi = this.intendedTarget.Thing != null;
+ if (flag3_poi)
+ {
+ this.destination = this.intendedTarget.Thing.DrawPos;
+ }
+ this.Fleck_MakeFleckTick_Poi++; // 使用重命名后的字段
+ bool flag4 = this.Fleck_MakeFleckTick_Poi >= this.Fleck_MakeFleckTickMax_Poi; // 使用重命名后的字段
+ bool flag5 = flag4 && this.tickcount >= 8;
+ if (flag5)
+ {
+ this.Fleck_MakeFleckTick_Poi = 0;
+ Map map = base.Map;
+ int randomInRange = this.Fleck_MakeFleckNum_Poi.RandomInRange;
+ Vector3 vector = this.BPos(base.DistanceCoveredFraction - 0.01f);
+ Vector3 vector2 = this.BPos(base.DistanceCoveredFraction - 0.02f);
+ for (int i = 0; i < randomInRange; i++)
+ {
+ float num = (vector - this.intendedTarget.CenterVector3).AngleFlat();
+ float velocityAngle = this.Fleck_Angle_Poi.RandomInRange + num;
+ float randomInRange2 = this.Fleck_Scale_Poi.RandomInRange;
+ float randomInRange3 = this.Fleck_Speed_Poi.RandomInRange;
+ float randomInRange4 = this.Fleck_Speed2_Poi.RandomInRange;
+ FleckCreationData dataStatic = FleckMaker.GetDataStatic(vector, map, this.FleckDef_Poi, randomInRange2);
+ FleckCreationData dataStatic2 = FleckMaker.GetDataStatic(vector2, map, this.FleckDef2_Poi, randomInRange2);
+ dataStatic.rotation = (vector - vector2).AngleFlat();
+ dataStatic.rotationRate = this.Fleck_Rotation_Poi.RandomInRange;
+ dataStatic.velocityAngle = velocityAngle;
+ dataStatic.velocitySpeed = randomInRange3;
+ dataStatic2.rotation = (vector - vector2).AngleFlat();
+ dataStatic2.rotationRate = this.Fleck_Rotation_Poi.RandomInRange;
+ dataStatic2.velocityAngle = velocityAngle;
+ dataStatic2.velocitySpeed = randomInRange4;
+ map.flecks.CreateFleck(dataStatic2);
+ map.flecks.CreateFleck(dataStatic);
+ }
+ }
+ // 移除原始的 base.Tick(); 因为 Projectile_Homing 的 Tick 已经包含了其父类的逻辑
+ }
+
+ 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;
+
+ // Projectile_Homing_Explosive 的 Impact 逻辑
+ if (HomingDef.isExplosive)
+ {
+ bool flag = blockedByShield || HomingDef.explosionDelay == 0;
+ if (flag)
+ {
+ Explode();
+ }
+ else
+ {
+ landed = true;
+ ticksToDetonation = HomingDef.explosionDelay;
+ GenExplosion.NotifyNearbyPawnsOfDangerousExplosive(this, def.projectile.damageDef, launcher.Faction, launcher);
+ }
+ }
+ else // Projectile_Homing 的 Impact 逻辑
+ {
+ 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);
+ }
+ }
+ }
+
+ // Projectile_PoiBullet 原始逻辑中的 Impact
+ bool flag_poi = this.intendedTarget.Thing is Pawn;
+ if (flag_poi)
+ {
+ hitThing = this.intendedTarget.Thing;
+ }
+ // 原始的 base.Impact(hitThing, blockedByShield); 已经被上面的 Homing 和 Explosive 逻辑覆盖,需要确保正确调用或移除
+ // 这里我们已经调用了 base.Impact(hitThing, blockedByShield); 在 Projectile_Homing 的 Impact 逻辑中,所以这里不再重复调用。
+
+ BattleLogEntry_RangedImpact battleLogEntry_RangedImpact = new BattleLogEntry_RangedImpact(this.launcher, hitThing, this.intendedTarget.Thing, this.equipmentDef, this.def, this.targetCoverDef);
+ Find.BattleLog.Add(battleLogEntry_RangedImpact);
+ this.NotifyImpact_Poi(hitThing, map, position); // 使用重命名后的方法
+ bool flag2 = hitThing != null && !blockedByShield;
+ if (flag2)
+ {
+ Pawn pawn;
+ bool instigatorGuilty = (pawn = (this.launcher as Pawn)) == null || !pawn.Drafted;
+ 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, instigatorGuilty, true, QualityCategory.Normal, true);
+ hitThing.TakeDamage(dinfo).AssociateWithLog(battleLogEntry_RangedImpact);
+ Pawn pawn2 = hitThing as Pawn;
+ bool flag3 = pawn2 != null && pawn2.stances != null;
+ if (flag3)
+ {
+ pawn2.stances.stagger.Notify_BulletImpact(this);
+ }
+ bool flag4 = this.def.projectile.extraDamages != null;
+ if (flag4)
+ {
+ foreach (ExtraDamage extraDamage in this.def.projectile.extraDamages)
+ {
+ bool flag5 = Rand.Chance(extraDamage.chance);
+ if (flag5)
+ {
+ DamageInfo dinfo2 = new DamageInfo(extraDamage.def, extraDamage.amount, extraDamage.AdjustedArmorPenetration(), this.ExactRotation.eulerAngles.y, this.launcher, null, this.equipmentDef, DamageInfo.SourceCategory.ThingOrUnknown, this.intendedTarget.Thing, instigatorGuilty, true, QualityCategory.Normal, true);
+ hitThing.TakeDamage(dinfo2).AssociateWithLog(battleLogEntry_RangedImpact);
+ }
+ }
+ }
+ bool flag6 = Rand.Chance(this.def.projectile.bulletChanceToStartFire) && (pawn2 == null || Rand.Chance(FireUtility.ChanceToAttachFireFromEvent(pawn2)));
+ if (flag6)
+ {
+ hitThing.TryAttachFire(this.def.projectile.bulletFireSizeRange.RandomInRange, this);
+ }
+ }
+ else
+ {
+ bool flag7 = !blockedByShield;
+ if (flag7)
+ {
+ SoundDefOf.BulletImpact_Ground.PlayOneShot(new TargetInfo(base.Position, map, false));
+ bool takeSplashes = base.Position.GetTerrain(map).takeSplashes;
+ if (takeSplashes)
+ {
+ FleckMaker.WaterSplash(this.ExactPosition, map, Mathf.Sqrt((float)this.DamageAmount) * 1f, 4f);
+ }
+ else
+ {
+ FleckMaker.Static(this.ExactPosition, map, FleckDefOf.ShotHit_Dirt, 1f);
+ }
+ }
+ bool flag8 = Rand.Chance(this.def.projectile.bulletChanceToStartFire);
+ if (flag8)
+ {
+ FireUtility.TryStartFireIn(base.Position, map, this.def.projectile.bulletFireSizeRange.RandomInRange, this, null);
+ }
+ }
+ }
+
+ protected virtual void Explode()
+ {
+ Map map = base.Map;
+ ModExtension_Cone modExtension = this.def.GetModExtension();
+ DoExplosion();
+ if (modExtension != null)
+ {
+ 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);
+ }
+ if (this.def.projectile.explosionEffect != null)
+ {
+ Effecter effecter = this.def.projectile.explosionEffect.Spawn();
+ if (this.def.projectile.explosionEffectLifetimeTicks != 0)
+ {
+ 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();
+ }
+ }
+ Destroy(DestroyMode.Vanish);
+ }
+
+ protected void DoExplosion()
+ {
+ IntVec3 position = base.Position;
+ float explosionRadius = this.def.projectile.explosionRadius;
+ DamageDef damageDef = this.def.projectile.damageDef;
+ Thing launcher = this.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 thingDef = this.def.projectile.postExplosionSpawnThingDef ?? this.def.projectile.filth;
+ ThingDef postExplosionSpawnThingDefWater = this.def.projectile.postExplosionSpawnThingDefWater;
+ float postExplosionSpawnChance = this.def.projectile.postExplosionSpawnChance;
+ int postExplosionSpawnThingCount = this.def.projectile.postExplosionSpawnThingCount;
+ GasType? postExplosionGasType = this.def.projectile.postExplosionGasType;
+ ThingDef preExplosionSpawnThingDef = this.def.projectile.preExplosionSpawnThingDef;
+ float preExplosionSpawnChance = this.def.projectile.preExplosionSpawnChance;
+ int preExplosionSpawnThingCount = this.def.projectile.preExplosionSpawnThingCount;
+ bool applyDamageToExplosionCellsNeighbors = this.def.projectile.applyDamageToExplosionCellsNeighbors;
+ ThingDef preExplosionSpawnThingDef2 = preExplosionSpawnThingDef;
+ float preExplosionSpawnChance2 = preExplosionSpawnChance;
+ int preExplosionSpawnThingCount2 = preExplosionSpawnThingCount;
+ float explosionChanceToStartFire = this.def.projectile.explosionChanceToStartFire;
+ bool explosionDamageFalloff = this.def.projectile.explosionDamageFalloff;
+ float? direction = new float?(this.origin.AngleToFlat(this.destination));
+ FloatRange? affectedAngle = null;
+ float expolosionPropagationSpeed = this.def.projectile.damageDef.expolosionPropagationSpeed;
+ float screenShakeFactor = this.def.projectile.screenShakeFactor;
+ IntVec3 center = position;
+ Map map = base.Map;
+ float radius = explosionRadius;
+ DamageDef damType = damageDef;
+ Thing instigator = launcher;
+ int damAmount = damageAmount;
+ float armorPenetration2 = armorPenetration;
+ SoundDef explosionSound = soundExplode;
+ ThingDef weapon = equipmentDef;
+ ThingDef projectile = def;
+ Thing intendedTarget = thing;
+ ThingDef postExplosionSpawnThingDef = thingDef;
+ float postExplosionSpawnChance2 = postExplosionSpawnChance;
+ int postExplosionSpawnThingCount2 = postExplosionSpawnThingCount;
+ GasType? postExplosionGasType2 = postExplosionGasType;
+ bool doExplosionVFX = this.def.projectile.doExplosionVFX;
+ ThingDef postExplosionSpawnThingDefWater2 = postExplosionSpawnThingDefWater;
+ GenExplosion.DoExplosion(center, map, radius, damType, instigator, damAmount, armorPenetration2, explosionSound, weapon, projectile, intendedTarget, postExplosionSpawnThingDef, postExplosionSpawnChance2, postExplosionSpawnThingCount2, postExplosionGasType2, null, 255, applyDamageToExplosionCellsNeighbors, preExplosionSpawnThingDef2, preExplosionSpawnChance2, preExplosionSpawnThingCount2, explosionChanceToStartFire, explosionDamageFalloff, direction, null, affectedAngle, doExplosionVFX, expolosionPropagationSpeed, 0f, true, postExplosionSpawnThingDefWater2, screenShakeFactor, null, null, 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);
+ Scribe_Values.Look(ref ticksToDetonation, "ticksToDetonation", 0, false); // 爆炸弹字段
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ ReflectInit();
+ if (this.homingDefInt == null)
+ {
+ this.homingDefInt = this.def.GetModExtension();
+ 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();
+ }
+ }
+ }
+ }
+
+ // Projectile_PoiBullet 原始逻辑
+ private void RandFactor()
+ {
+ FloatRange floatRange = new FloatRange(-0.5f, 0.5f);
+ FloatRange floatRange2 = new FloatRange(-0.5f, 0.5f);
+ this.Randdd.x = floatRange.RandomInRange;
+ this.Randdd.z = floatRange2.RandomInRange;
+ this.flag2 = true;
+ }
+
+ public Vector3 BPos(float t)
+ {
+ bool flag = !this.flag2;
+ if (flag)
+ {
+ this.RandFactor();
+ }
+ Vector3 origin = this.origin;
+ Vector3 a = (this.origin + this.destination) / 2f;
+ a += this.Randdd;
+ a.y = this.destination.y;
+ Vector3 destination = this.destination;
+ return (1f - t) * (1f - t) * origin + 2f * t * (1f - t) * a + t * t * destination;
+ }
+
+ private void FindRandCell(Vector3 d)
+ {
+ IntVec3 center = IntVec3.FromVector3(d);
+ this.intendedTarget = CellRect.CenteredOn(center, 2).RandomCell;
+ }
+
+ protected override void DrawAt(Vector3 position, bool flip = false)
+ {
+ Vector3 b = this.BPos(base.DistanceCoveredFraction - 0.01f);
+ position = this.BPos(base.DistanceCoveredFraction);
+ Quaternion rotation = Quaternion.LookRotation(position - b);
+ bool flag = this.tickcount >= 4;
+ if (flag)
+ {
+ Vector3 position2 = position;
+ position2.y = AltitudeLayer.Projectile.AltitudeFor();
+ Graphics.DrawMesh(MeshPool.GridPlane(this.def.graphicData.drawSize), position2, rotation, this.DrawMat, 0);
+ base.Comps_PostDraw();
+ }
+ }
+
+ private bool CanHitTarget_Poi() // 重命名以避免冲突
+ {
+ bool flag = this.launcher is Pawn;
+ bool result;
+ if (flag)
+ {
+ float num = this.Hitchance_Poi(); // 使用重命名后的方法
+ bool flag2 = (float)Rand.RangeInclusive(0, 100) <= num * 100f;
+ Pawn pawn = this.intendedTarget.Thing as Pawn;
+ bool flag3 = pawn != null;
+ if (flag3)
+ {
+ bool downed = pawn.Downed;
+ if (downed)
+ {
+ flag2 = (Rand.RangeInclusive(0, 100) <= 30);
+ }
+ }
+ result = flag2;
+ }
+ else
+ {
+ result = (Rand.RangeInclusive(0, 100) <= 85);
+ }
+ return result;
+ }
+
+ public float Hitchance_Poi() // 重命名以避免冲突
+ {
+ Pawn pawn = this.launcher as Pawn;
+ bool flag = pawn != null;
+ float result;
+ if (flag)
+ {
+ SkillDef named = DefDatabase.GetNamed("Intellectual", true);
+ SkillRecord skill = pawn.skills.GetSkill(named);
+ bool flag2 = skill != null;
+ if (flag2)
+ {
+ int level = skill.GetLevel(true);
+ float num = Mathf.Min(1f, (float)level * 0.05f);
+ result = num;
+ }
+ else
+ {
+ result = 0.5f;
+ }
+ }
+ else
+ {
+ result = 0.2f;
+ }
+ return result;
+ }
+
+ private void NotifyImpact_Poi(Thing hitThing, Map map, IntVec3 position) // 重命名以避免冲突
+ {
+ BulletImpactData impactData = new BulletImpactData
+ {
+ bullet = this,
+ hitThing = hitThing,
+ impactPosition = position
+ };
+ bool flag = hitThing != null;
+ if (flag)
+ {
+ hitThing.Notify_BulletImpactNearby(impactData);
+ }
+ int num = 9;
+ for (int i = 0; i < num; i++)
+ {
+ IntVec3 c = position + GenRadial.RadialPattern[i];
+ bool flag2 = c.InBounds(map);
+ if (flag2)
+ {
+ List thingList = c.GetThingList(map);
+ for (int j = 0; j < thingList.Count; j++)
+ {
+ bool flag3 = thingList[j] != hitThing;
+ if (flag3)
+ {
+ thingList[j].Notify_BulletImpactNearby(impactData);
+ }
+ }
+ }
+ }
+ }
+
+ private bool flag2 = false;
+ private bool flag3 = true;
+ private bool CalHit = false;
+ private Vector3 Randdd;
+ private int tickcount;
+
+ // Projectile_PoiBullet 原始的 Fleck 字段,重命名以避免冲突
+ public FleckDef FleckDef_Poi = DefDatabase.GetNamed("CMC_SparkFlash_Blue_Small", true);
+ public FleckDef FleckDef2_Poi = DefDatabase.GetNamed("CMC_SparkFlash_Blue_LongLasting_Small", true);
+ public int Fleck_MakeFleckTickMax_Poi = 1;
+ public IntRange Fleck_MakeFleckNum_Poi = new IntRange(2, 2);
+ public FloatRange Fleck_Angle_Poi = new FloatRange(-180f, 180f);
+ public FloatRange Fleck_Scale_Poi = new FloatRange(1.6f, 1.7f);
+ public FloatRange Fleck_Speed_Poi = new FloatRange(5f, 7f);
+ public FloatRange Fleck_Speed2_Poi = new FloatRange(0.1f, 0.2f);
+ public FloatRange Fleck_Rotation_Poi = new FloatRange(-180f, 180f);
+ public int Fleck_MakeFleckTick_Poi;
+ }
+}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/Projectile_TrackingBullet.cs b/Source/WulaFallenEmpire/Projectile_TrackingBullet.cs
new file mode 100644
index 00000000..4dc05f36
--- /dev/null
+++ b/Source/WulaFallenEmpire/Projectile_TrackingBullet.cs
@@ -0,0 +1,159 @@
+using System.Collections.Generic;
+using System.Reflection;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using Verse.Sound;
+
+namespace WulaFallenEmpire
+{
+ public class Projectile_TrackingBullet : Bullet
+ {
+ private TrackingBulletDef trackingDefInt;
+
+ protected Vector3 exactPositionInt;
+ public Vector3 curSpeed;
+ public bool homing = true;
+
+ 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();
+ 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();
+ }
+
+ 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)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)
+ {
+ // 计算需要转向的方向
+ 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; // 更新位置
+ }
+
+ protected override void Tick()
+ {
+ base.Tick(); // 调用父类Bullet的Tick,处理一些基本逻辑,如lifetime, ticksToImpact
+
+ MovementTick(); // 调用追踪移动逻辑
+
+ // 检查是否撞到东西或超出地图
+ Vector3 exactPosition = ExactPosition; // 之前的ExactPosition
+ ticksToImpact--; // 减少impact计时器
+
+ if (!ExactPosition.InBounds(base.Map)) // 超出地图边界
+ {
+ base.Position = exactPosition.ToIntVec3(); // 设回旧位置,然后销毁
+ Destroy();
+ return;
+ }
+
+ // 检查是否有东西在路径上拦截
+ Vector3 exactPositionAfterMove = ExactPosition; // 移动后的ExactPosition
+ object[] parameters = new object[2] { exactPosition, exactPositionAfterMove };
+ if (!(bool)NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters))
+ {
+ base.Position = ExactPosition.ToIntVec3(); // 更新位置到当前精确位置
+ if (ticksToImpact <= 0) // 达到impact时间
+ {
+ ImpactSomething(); // 触发Impact
+ }
+ }
+ }
+
+ protected override void Impact(Thing hitThing, bool blockedByShield = false)
+ {
+ // 默认Impact逻辑,可以根据需要扩展
+ base.Impact(hitThing, blockedByShield);
+ }
+
+ 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);
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ ReflectInit();
+ if (this.trackingDefInt == null)
+ {
+ this.trackingDefInt = this.def.GetModExtension();
+ 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();
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/TrackingBulletDef.cs b/Source/WulaFallenEmpire/TrackingBulletDef.cs
new file mode 100644
index 00000000..43adcf11
--- /dev/null
+++ b/Source/WulaFallenEmpire/TrackingBulletDef.cs
@@ -0,0 +1,10 @@
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class TrackingBulletDef : DefModExtension
+ {
+ public float homingSpeed = 0.1f; // 追踪速度,值越大追踪越灵敏
+ public float initRotateAngle = 0f; // 初始旋转角度
+ }
+}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
index 82d1a4be..0fc83f50 100644
--- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
+++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
@@ -126,6 +126,8 @@
+
+