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 @@ + +