From 17cdfd07eba5c5f362e871905f93f125b6f14822 Mon Sep 17 00:00:00 2001 From: "ProjectKoi-Kalo\\Kalo" Date: Tue, 9 Sep 2025 12:44:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 1.6/1.6/Assemblies/ArachnaeSwarm.dll | Bin 143360 -> 144384 bytes 1.6/1.6/Defs/DamageDefs/ARA_Damages.xml | 11 + .../Defs/HediffDefs/ARA_Hediffs_Damage.xml | 31 +++ 1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml | 28 +++ .../PawnRenderTreeDefs/ARA_RenderTree.xml | 1 - .../Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml | 63 +---- .../ThingDef_Races/ARA_RaceDroneSwarm.xml | 172 +++++++++++++ .../Thing_Misc/Weapons/ARA_Weapom_Skyhive.xml | 112 +++++++++ .../{WULA_Weapon.xml => ARA_Weapon.xml} | 0 Source/ArachnaeSwarm/ArachnaeSwarm.csproj | 2 +- .../HediffComp_SpawnPawnOnRemoved.cs | 86 +++++++ .../Projectiles/BulletWithTrail.cs | 98 ++++++++ .../Projectiles/ExplosiveTrackingBulletDef.cs | 29 +++ ...Projectile_ConfigurableHellsphereCannon.cs | 93 +++++++ .../Projectiles/Projectile_CruiseMissile.cs | 194 +++++++++++++++ .../Projectile_ExplosiveTrackingBullet.cs | 185 ++++++++++++++ .../Projectile_ExplosiveWithTrail.cs | 98 ++++++++ .../Projectiles/Projectile_TrackingBullet.cs | 232 ++++++++++++++++++ .../Projectile_WulaPenetratingBeam.cs | 157 ++++++++++++ .../Projectile_WulaPenetratingBullet.cs | 201 +++++++++++++++ .../Projectiles/TrackingBulletDef.cs | 21 ++ 21 files changed, 1750 insertions(+), 64 deletions(-) create mode 100644 1.6/1.6/Defs/ThingDef_Races/ARA_RaceDroneSwarm.xml create mode 100644 1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapom_Skyhive.xml rename 1.6/1.6/Defs/Thing_Misc/Weapons/{WULA_Weapon.xml => ARA_Weapon.xml} (100%) create mode 100644 Source/ArachnaeSwarm/HediffComp_SpawnPawnOnRemoved.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/BulletWithTrail.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/ExplosiveTrackingBulletDef.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_ConfigurableHellsphereCannon.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_CruiseMissile.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveTrackingBullet.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveWithTrail.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_TrackingBullet.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBeam.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBullet.cs create mode 100644 Source/ArachnaeSwarm/Projectiles/TrackingBulletDef.cs diff --git a/1.6/1.6/Assemblies/ArachnaeSwarm.dll b/1.6/1.6/Assemblies/ArachnaeSwarm.dll index 81bed62d143a50783512b03fe7591757b63a4e2f..79815bccd86b4c8f9e536372a7e31d1b13baad4f 100644 GIT binary patch delta 39417 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 delta 38621 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 diff --git a/1.6/1.6/Defs/DamageDefs/ARA_Damages.xml b/1.6/1.6/Defs/DamageDefs/ARA_Damages.xml index ce0d7f0..bc6e200 100644 --- a/1.6/1.6/Defs/DamageDefs/ARA_Damages.xml +++ b/1.6/1.6/Defs/DamageDefs/ARA_Damages.xml @@ -44,4 +44,15 @@ + + ARA_SkyhiveBite + + +
  • + ARA_SkyhiveBite + 0.1 +
  • +
    +
    + \ No newline at end of file diff --git a/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Damage.xml b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Damage.xml index 25cd4eb..3daf27a 100644 --- a/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Damage.xml +++ b/1.6/1.6/Defs/HediffDefs/ARA_Hediffs_Damage.xml @@ -84,4 +84,35 @@ + + + ARA_SkyhiveBite + + 一只可爱的天巢种阿拉克涅虫族咬到了你的身上.这不会有太大问题...对吧? + + true + ArachnaeSwarm.HediffCurseFlame + +
  • + 1200 +
  • +
  • +
  • + ArachnaeBase_Race_Skyhive +
  • +
    + +
  • + Bite + 3~5 + 100 +
  • +
    + +
  • + 0.05 +
  • +
    + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml index 864f31e..ad26105 100644 --- a/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml +++ b/1.6/1.6/Defs/PawnKindDef/ARA_PawnKinds.xml @@ -366,4 +366,32 @@ + + + ArachnaeBase_Race_Skyhive + + ArachnaeBase_Race_Skyhive + Things/Pawn/Animal/Goose/Goose_Flying_ + 1.35 + 2 + 2 + false + +
  • + + Things/Pawn/Animal/Spelopede/Spelopede + 1 + (156,148,125) + + (0.4, 0.5, 0.37) + (0,0,-0.15) + + + + Things/Pawn/Animal/Spelopede/Dessicated_Spelopede + 1 + +
  • +
    +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/PawnRenderTreeDefs/ARA_RenderTree.xml b/1.6/1.6/Defs/PawnRenderTreeDefs/ARA_RenderTree.xml index f301223..8582cf4 100644 --- a/1.6/1.6/Defs/PawnRenderTreeDefs/ARA_RenderTree.xml +++ b/1.6/1.6/Defs/PawnRenderTreeDefs/ARA_RenderTree.xml @@ -42,7 +42,6 @@ -
  • Arachnae Flight Attachment ARA_Flight_Attachment diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml index 7f81e1c..03db058 100644 --- a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceBaseSwarm.xml @@ -166,65 +166,4 @@
  • - - ArachnaeBase_Race_Acid - - 阿拉克涅辅虫之一,智力低下,一般被作为活体炮弹打出,击中敌人后若是还没散架,就会继续依靠带酸液的颚撕咬敌军。 - - WarUrchinConstant - 0.5 - - BeetleLikeWithClaw - -
  • - EusocialInsectAdult - 0 - Pawn_Spelopede_Pain - Pawn_Spelopede_Death - Pawn_Spelopede_Call - Pawn_Spelopede_Angry -
  • -
    -
    - - 6 - - -
  • - 4400 - 寿命 - 这种特殊的阿拉克涅辅虫从出生起就走在死亡的道路上了——它们的寿命就是如此短暂。 - true -
  • -
    - -
  • - - -
  • ARA_AcidCut
  • - - 6 - 2.6 - HeadAttackTool - true - -
    -
    - - ARA_AcidCut - - - - ARA_AcidCut - ARA_AcidCut - - Verb_MeleeAttackDamage - ARA_AcidCut_Damage - - MeleeAttack - Maneuver_Slash_MeleeHit - Maneuver_Slash_MeleeDeflect - Maneuver_Slash_MeleeMiss - Maneuver_Slash_MeleeDodge - - \ No newline at end of file + diff --git a/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDroneSwarm.xml b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDroneSwarm.xml new file mode 100644 index 0000000..025a357 --- /dev/null +++ b/1.6/1.6/Defs/ThingDef_Races/ARA_RaceDroneSwarm.xml @@ -0,0 +1,172 @@ + + + + + + 1200 + 0 + 1 + 0 + -100 + 250 + 0 + 1 + 1 + + true + None + + true + Insectoid + false + false + false + None + 2500 + Filth_BloodInsect + true + None + 1000~2000 + 2 + SentryDroneConstant + true + ToolUser + Drone + true + + + + + + + + + ArachnaeBase_Race_Acid + + 阿拉克涅辅虫之一,智力低下,一般被作为活体炮弹打出,击中敌人后若是还没散架,就会继续依靠带酸液的颚撕咬敌军。 + + WarUrchinConstant + 0.5 + + BeetleLikeWithClaw + +
  • + EusocialInsectAdult + 0 + Pawn_Spelopede_Pain + Pawn_Spelopede_Death + Pawn_Spelopede_Call + Pawn_Spelopede_Angry +
  • +
    +
    + + 6 + + +
  • + 4400 + 寿命 + 这种特殊的阿拉克涅辅虫从出生起就走在死亡的道路上了——它们的寿命就是如此短暂。 + true +
  • +
    + +
  • + + +
  • ARA_AcidCut
  • + + 6 + 2.6 + HeadAttackTool + true + +
    +
    + + + ArachnaeBase_Race_Skyhive + + 阿拉克涅辅虫之一,智力低下,一般被作为活体炮弹打出,击中敌人后若是还没散架,就会继续依靠带酸液的颚撕咬敌军。 + + 1.0 + WarUrchinConstant + 0.5 + + BeetleLikeWithClaw + +
  • + EusocialInsectAdult + 0 + Pawn_Spelopede_Pain + Pawn_Spelopede_Death + Pawn_Spelopede_Call + Pawn_Spelopede_Angry +
  • +
    +
    + + 6 + + 9999 + 0 + + +
  • + 4400 + 寿命 + 这种特殊的阿拉克涅辅虫从出生起就走在死亡的道路上了——它们的寿命就是如此短暂。 + true +
  • +
    + +
  • + + +
  • ARA_Bite
  • + + 6 + 2.6 + HeadAttackTool + true + +
    +
    + + + ARA_AcidCut + + + + ARA_AcidCut + ARA_AcidCut + + Verb_MeleeAttackDamage + ARA_AcidCut_Damage + + MeleeAttack + Maneuver_Slash_MeleeHit + Maneuver_Slash_MeleeDeflect + Maneuver_Slash_MeleeMiss + Maneuver_Slash_MeleeDodge + + + + ARA_Bite + + + + ARA_Bite + ARA_Bite + + Verb_MeleeAttackDamage + Bite + + MeleeAttack + Maneuver_Slash_MeleeHit + Maneuver_Slash_MeleeDeflect + Maneuver_Slash_MeleeMiss + Maneuver_Slash_MeleeDodge + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapom_Skyhive.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapom_Skyhive.xml new file mode 100644 index 0000000..8ad85e8 --- /dev/null +++ b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapom_Skyhive.xml @@ -0,0 +1,112 @@ + + + + + ARA_RW_Basic_SkyHive_Gun + + 阿拉克涅虫群督虫使用基础远程武装器官,可以通过肌肉的瞬间加压喷出一团包含阿拉克涅酸液的液体团。这种酸液团的飞行速度很慢,但是能在目标地点炸开,并灼烧所有粘上酸液的敌人。 + Normal + Animal + + ArachnaeSwarm/Weapon/ARA_RW_Basic_Acid_Bladder_Gun + Graphic_Single + 1.2 + + SpitterSpawn + + + + UnfinishedWeapon + + + 1300 + + 3.5 + 0.5 + 0.6 + 0.45 + 0.3 + 2.5 + + +
  • + ArachnaeSwarm.Verb_ShootShotgun + true + false + 1.0 + 1 + Bullet_ARA_RW_Basic_SkyHive_Gun + true + false + 3 + 28 + 1 + SpitterSpit + + true + +
  • +
    + + 50 + + +
  • ARA_Armed_Organ
  • +
  • ARA_Armed_Organ_Ranged
  • +
  • ARA_Armed_Organ_T1
  • +
    + +
  • RewardStandardQualitySuper
  • +
    +
    + + Bullet_ARA_RW_Basic_SkyHive_Gun + + + Graphic_Single_AgeSecs + Things/Pawn/Animal/Spelopede/Spelopede_north + (156,148,125) + (1,1) + MoteGlow + + 0.8 + ArachnaeSwarm.Projectile_TrackingBullet + + True + 1 + ARA_SkyhiveBite + 20 + 20 + 15 + 1 + true + + +
  • + + 1.5 + + + 15 + + + 150 + + + 0.5 +
  • +
  • + 3 +
  • +
    + +
  • + Shell_AcidSpitStream +
  • +
  • + Shell_AcidSpitLaunched +
  • +
    +
    + +
    \ No newline at end of file diff --git a/1.6/1.6/Defs/Thing_Misc/Weapons/WULA_Weapon.xml b/1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml similarity index 100% rename from 1.6/1.6/Defs/Thing_Misc/Weapons/WULA_Weapon.xml rename to 1.6/1.6/Defs/Thing_Misc/Weapons/ARA_Weapon.xml diff --git a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj index 713ad63..8314a57 100644 --- a/Source/ArachnaeSwarm/ArachnaeSwarm.csproj +++ b/Source/ArachnaeSwarm/ArachnaeSwarm.csproj @@ -162,7 +162,7 @@ - + diff --git a/Source/ArachnaeSwarm/HediffComp_SpawnPawnOnRemoved.cs b/Source/ArachnaeSwarm/HediffComp_SpawnPawnOnRemoved.cs new file mode 100644 index 0000000..0cb3171 --- /dev/null +++ b/Source/ArachnaeSwarm/HediffComp_SpawnPawnOnRemoved.cs @@ -0,0 +1,86 @@ +using RimWorld; +using Verse; + +namespace ArachnaeSwarm +{ + /// + /// Defines the properties for HediffComp_SpawnPawnOnRemoved. + /// You must specify the pawnKindDef to spawn. + /// + public class HediffCompProperties_SpawnPawnOnRemoved : HediffCompProperties + { + public PawnKindDef pawnKindDef; + + public HediffCompProperties_SpawnPawnOnRemoved() + { + this.compClass = typeof(HediffComp_SpawnPawnOnRemoved); + } + } + + /// + /// Spawns a specified pawn when the parent hediff is removed. + /// The pawn's faction is determined by the hediff's instigator. + /// + public class HediffComp_SpawnPawnOnRemoved : HediffComp + { + // This field will store the faction of the original attacker. + private Faction casterFaction; + + public HediffCompProperties_SpawnPawnOnRemoved Props => (HediffCompProperties_SpawnPawnOnRemoved)this.props; + + /// + /// Called after the hediff is added. We use this to capture the instigator's faction. + /// + public override void CompPostPostAdd(DamageInfo? dinfo) + { + base.CompPostPostAdd(dinfo); + if (dinfo.HasValue && dinfo.Value.Instigator != null) + { + this.casterFaction = dinfo.Value.Instigator.Faction; + } + } + + /// + /// Called when the hediff is removed. This is where we spawn the pawn. + /// + public override void CompPostPostRemoved() + { + base.CompPostPostRemoved(); + + if (this.Pawn == null || this.Pawn.Map == null || Props.pawnKindDef == null) + { + Log.Warning("ArachnaeSwarm: HediffComp_SpawnPawnOnRemoved tried to spawn a pawn but required data was missing (Pawn, Map, or pawnKindDef)."); + return; + } + + Map map = this.Pawn.Map; + IntVec3 loc = this.Pawn.Position; + + Pawn newPawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest( + kind: Props.pawnKindDef, + faction: this.casterFaction, // Use the stored faction + context: PawnGenerationContext.NonPlayer, + tile: -1, + forceGenerateNewPawn: true + )); + + if (newPawn != null) + { + GenSpawn.Spawn(newPawn, loc, map, WipeMode.Vanish); + } + else + { + Log.Error($"ArachnaeSwarm: Failed to generate pawn of kind {Props.pawnKindDef.defName}."); + } + } + + /// + /// Ensures the casterFaction is saved and loaded with the game. + /// + public override void CompExposeData() + { + base.CompExposeData(); + Scribe_References.Look(ref this.casterFaction, "casterFaction"); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/BulletWithTrail.cs b/Source/ArachnaeSwarm/Projectiles/BulletWithTrail.cs new file mode 100644 index 0000000..e906b06 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/BulletWithTrail.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Reflection; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class BulletWithTrail : Bullet + { + private TrackingBulletDef trackingDefInt; + private int Fleck_MakeFleckTick; + private Vector3 lastTickPosition; + + 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 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); + lastTickPosition = origin; + } + + protected override void Tick() + { + base.Tick(); + + // 处理拖尾特效 + if (TrackingDef != null && TrackingDef.tailFleckDef != null) + { + Fleck_MakeFleckTick++; + if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks) + { + if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax)) + { + Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks; + } + + Map map = base.Map; + int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange; + Vector3 currentPosition = base.ExactPosition; + Vector3 previousPosition = lastTickPosition; + + for (int i = 0; i < randomInRange; i++) + { + float num = (currentPosition - previousPosition).AngleFlat(); + float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num; + float randomInRange2 = TrackingDef.fleckScale.RandomInRange; + float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange; + + FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2); + dataStatic.rotation = (currentPosition - previousPosition).AngleFlat(); + dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange; + dataStatic.velocityAngle = velocityAngle; + dataStatic.velocitySpeed = randomInRange3; + map.flecks.CreateFleck(dataStatic); + } + } + } + lastTickPosition = base.ExactPosition; + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref Fleck_MakeFleckTick, "Fleck_MakeFleckTick", 0); + Scribe_Values.Look(ref lastTickPosition, "lastTickPosition", Vector3.zero); + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + 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/ArachnaeSwarm/Projectiles/ExplosiveTrackingBulletDef.cs b/Source/ArachnaeSwarm/Projectiles/ExplosiveTrackingBulletDef.cs new file mode 100644 index 0000000..3271fa6 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/ExplosiveTrackingBulletDef.cs @@ -0,0 +1,29 @@ +using Verse; +using RimWorld; + +namespace ArachnaeSwarm +{ + public class ExplosiveTrackingBulletDef : DefModExtension + { + public float explosionRadius = 1.9f; + public DamageDef damageDef; + public int explosionDelay = 0; + public SoundDef soundExplode; + public FleckDef preExplosionFlash; + public ThingDef postExplosionSpawnThingDef; + public float postExplosionSpawnChance = 0f; + public int postExplosionSpawnThingCount = 1; + public GasType? gasType; // 修改为可空类型 + public ThingDef postExplosionSpawnThingDefWater; // 新增 + public ThingDef preExplosionSpawnThingDef; // 新增 + public float preExplosionSpawnChance = 0f; // 新增 + public int preExplosionSpawnThingCount = 0; // 新增 + public float screenShakeFactor = 1f; // 新增 + public bool applyDamageToExplosionCellsNeighbors = false; + public bool doExplosionDamageAfterThingDestroyed = false; + public float preExplosionSpawnMinMeleeThreat = -1f; + public float explosionChanceToStartFire = 0f; // 从bool改为float,并设置默认值 + public bool explosionDamageFalloff = false; + public bool doExplosionVFX = true; + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_ConfigurableHellsphereCannon.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_ConfigurableHellsphereCannon.cs new file mode 100644 index 0000000..f14a245 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_ConfigurableHellsphereCannon.cs @@ -0,0 +1,93 @@ +using Verse; +using RimWorld; +using System.Collections.Generic; + +namespace ArachnaeSwarm +{ + /// + /// ��������ڶ��������XML�� �ڵ������õ��Զ������ԡ� + /// + public class ProjectileProperties_ConfigurableHellsphereCannon : ProjectileProperties + { + // --- �������֣������� explosionChanceToStartFire --- + // ʹ�� 'new' �ؼ��������ز��滻�����ͬ���ֶ� + public new float explosionRadius = 4.9f; + public new float explosionChanceToStartFire = 0f; // <--- �������У�ʹ�����XML������ + public new bool applyDamageToExplosionCellsNeighbors = false; + public new ThingDef preExplosionSpawnThingDef = null; + public new float preExplosionSpawnChance = 0f; + public new int preExplosionSpawnThingCount = 1; + public new ThingDef postExplosionSpawnThingDef = null; + public new float postExplosionSpawnChance = 0f; + public new int postExplosionSpawnThingCount = 1; + public new GasType? postExplosionGasType = null; + public new float screenShakeFactor = 1f; + public new ThingDef postExplosionSpawnThingDefWater = null; + public new ThingDef postExplosionSpawnSingleThingDef = null; + public new ThingDef preExplosionSpawnSingleThingDef = null; + + // ��Щ�������ӵ�ȫ���ֶΣ�����Ҫ 'new' �ؼ��� + public SoundDef explosionSound = null; + public bool damageFalloff = false; + public bool doVisualEffects = true; + public bool doSoundEffects = true; + public float? postExplosionGasRadiusOverride = null; + public int postExplosionGasAmount = 255; + public float? direction = null; + public FloatRange? affectedAngle = null; + public float excludeRadius = 0f; + public SimpleCurve flammabilityChanceCurve = null; + } + + /// + /// ����Ͷ����ĺ����߼��࣬��XML�е� ָ���� + /// + public class Projectile_ConfigurableHellsphereCannon : Projectile + { + public ProjectileProperties_ConfigurableHellsphereCannon Props => (ProjectileProperties_ConfigurableHellsphereCannon)def.projectile; + + protected override void Impact(Thing hitThing, bool blockedByShield = false) + { + Map map = base.Map; + base.Impact(hitThing, blockedByShield); + + GenExplosion.DoExplosion( + center: base.Position, + map: map, + radius: Props.explosionRadius, + damType: base.DamageDef, + instigator: launcher, + damAmount: DamageAmount, + armorPenetration: ArmorPenetration, + explosionSound: Props.explosionSound, + weapon: equipmentDef, + projectile: def, + intendedTarget: intendedTarget.Thing, + postExplosionSpawnThingDef: Props.postExplosionSpawnThingDef, + postExplosionSpawnChance: Props.postExplosionSpawnChance, + postExplosionSpawnThingCount: Props.postExplosionSpawnThingCount, + postExplosionGasType: Props.postExplosionGasType, + postExplosionGasRadiusOverride: Props.postExplosionGasRadiusOverride, + postExplosionGasAmount: Props.postExplosionGasAmount, + applyDamageToExplosionCellsNeighbors: Props.applyDamageToExplosionCellsNeighbors, + preExplosionSpawnThingDef: Props.preExplosionSpawnThingDef, + preExplosionSpawnChance: Props.preExplosionSpawnChance, + preExplosionSpawnThingCount: Props.preExplosionSpawnThingCount, + // --- �������֣�ʹ������ȷ���ֶ� --- + chanceToStartFire: Props.explosionChanceToStartFire, // <--- �����˴��� + damageFalloff: Props.damageFalloff, + direction: Props.direction, + affectedAngle: Props.affectedAngle, + doVisualEffects: Props.doVisualEffects, + propagationSpeed: base.DamageDef.expolosionPropagationSpeed, + excludeRadius: Props.excludeRadius, + doSoundEffects: Props.doSoundEffects, + postExplosionSpawnThingDefWater: Props.postExplosionSpawnThingDefWater, + screenShakeFactor: Props.screenShakeFactor, + flammabilityChanceCurve: Props.flammabilityChanceCurve, + postExplosionSpawnSingleThingDef: Props.postExplosionSpawnSingleThingDef, + preExplosionSpawnSingleThingDef: Props.preExplosionSpawnSingleThingDef + ); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_CruiseMissile.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_CruiseMissile.cs new file mode 100644 index 0000000..ee8fdac --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_CruiseMissile.cs @@ -0,0 +1,194 @@ +using RimWorld; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + public class CruiseMissileProperties : DefModExtension + { + public DamageDef customDamageDef; + public int customDamageAmount = 5; + public float customExplosionRadius = 1.1f; + public SoundDef customSoundExplode; + + public bool useSubExplosions = true; + public int subExplosionCount = 3; + public float subExplosionRadius = 1.9f; + public int subExplosionDamage = 30; + public float subExplosionSpread = 6f; + public DamageDef subDamageDef; + public SoundDef subSoundExplode; + + // 新增的弹道配置参数 + public float bezierArcHeightFactor = 0.05f; // 贝塞尔曲线高度因子 + public float bezierMinArcHeight = 2f; // 贝塞尔曲线最小高度 + public float bezierMaxArcHeight = 6f; // 贝塞尔曲线最大高度 + public float bezierHorizontalOffsetFactor = 0.1f; // 贝塞尔曲线水平偏移因子 + public float bezierSideOffsetFactor = 0.2f; // 贝塞尔曲线侧向偏移因子 + public float bezierRandomOffsetScale = 0.5f; // 贝塞尔曲线随机偏移缩放 + + } + + public class Projectile_CruiseMissile : Projectile_Explosive + { + private CruiseMissileProperties settings; + private bool flag2; + private Vector3 Randdd; + private Vector3 position2; + public Vector3 ExPos; + + public override void SpawnSetup(Map map, bool respawningAfterLoad) + { + base.SpawnSetup(map, respawningAfterLoad); + settings = def.GetModExtension() ?? new CruiseMissileProperties(); + } + + private void RandFactor() + { + // 调整随机范围,用于控制C形弹道的随机偏移 + Randdd = new Vector3( + Rand.Range(-settings.bezierRandomOffsetScale, settings.bezierRandomOffsetScale), // X轴的随机偏移 + Rand.Range(0f, 0f), // Y轴(高度)不进行随机,保持平稳 + Rand.Range(-settings.bezierRandomOffsetScale, settings.bezierRandomOffsetScale) // Z轴的随机偏移 + ); + flag2 = true; + } + + public Vector3 BPos(float t) + { + if (!flag2) RandFactor(); + + // 计算水平距离 + float horizontalDistance = Vector3.Distance(new Vector3(origin.x, 0, origin.z), + new Vector3(destination.x, 0, destination.z)); + + // 动态调整控制点高度,使其更扁平,使用XML配置的高度因子 + float arcHeight = Mathf.Clamp(horizontalDistance * settings.bezierArcHeightFactor, settings.bezierMinArcHeight, settings.bezierMaxArcHeight); + + // 计算从起点到终点的方向向量 + Vector3 direction = (destination - origin).normalized; + // 计算垂直于方向向量的水平向量(用于侧向偏移),确保C形弯曲方向一致 + Vector3 perpendicularDirection = Vector3.Cross(direction, Vector3.up).normalized; + + // 调整控制点以形成扁平 C 形,使用XML配置的偏移因子 + // P1: 在起点附近,向前偏移,向上偏移,并向一侧偏移 + Vector3 p1 = origin + direction * horizontalDistance * settings.bezierHorizontalOffsetFactor + Vector3.up * arcHeight + perpendicularDirection * horizontalDistance * settings.bezierSideOffsetFactor + Randdd; + // P2: 在终点附近,向后偏移,向上偏移,并向同一侧偏移 + Vector3 p2 = destination - direction * horizontalDistance * settings.bezierHorizontalOffsetFactor + Vector3.up * arcHeight + perpendicularDirection * horizontalDistance * settings.bezierSideOffsetFactor + Randdd; + + return BezierCurve(origin, p1, p2, destination, t); + } + + private Vector3 BezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) + { + float u = 1 - t; + return u * u * u * p0 + + 3 * u * u * t * p1 + + 3 * u * t * t * p2 + + t * t * t * p3; + } + + private IEnumerable GetValidCells(Map map) + { + if (map == null || settings == null) yield break; + + var cells = GenRadial.RadialCellsAround( + base.Position, + settings.subExplosionSpread, + false + ).Where(c => c.InBounds(map)); + + var randomizedCells = cells.InRandomOrder().Take(settings.subExplosionCount); + + foreach (var cell in randomizedCells) + { + yield return cell; + } + } + + protected override void Impact(Thing hitThing, bool blockedByShield = false) + { + var map = base.Map; + base.Impact(hitThing, blockedByShield); + + DoExplosion( + base.Position, + map, + settings.customExplosionRadius, + settings.customDamageDef, + settings.customDamageAmount, + settings.customSoundExplode + ); + + if (settings.useSubExplosions) + { + foreach (var cell in GetValidCells(map)) + { + DoExplosion( + cell, + map, + settings.subExplosionRadius, + settings.subDamageDef, + settings.subExplosionDamage, + settings.subSoundExplode + ); + } + } + } + + private void DoExplosion(IntVec3 pos, Map map, float radius, DamageDef dmgDef, int dmgAmount, SoundDef sound) + { + GenExplosion.DoExplosion( + pos, + map, + radius, + dmgDef, + launcher, + dmgAmount, + ArmorPenetration, + sound + ); + } + + protected override void DrawAt(Vector3 position, bool flip = false) + { + position2 = BPos(DistanceCoveredFraction - 0.01f); + ExPos = position = BPos(DistanceCoveredFraction); + base.DrawAt(position, flip); + } + + protected override void Tick() + { + if (intendedTarget.Thing is Pawn pawn && pawn.Spawned && !pawn.Destroyed) + { + if ((pawn.Dead || pawn.Downed) && DistanceCoveredFraction < 0.6f) + { + FindNextTarget(pawn.DrawPos); + } + destination = pawn.DrawPos; + } + base.Tick(); + } + + private void FindNextTarget(Vector3 center) + { + var map = base.Map; + if (map == null) return; + + foreach (IntVec3 cell in GenRadial.RadialCellsAround(IntVec3.FromVector3(center), 7f, true)) + { + if (!cell.InBounds(map)) continue; + + Pawn target = cell.GetFirstPawn(map); + if (target != null && target.Faction.HostileTo(launcher?.Faction)) + { + intendedTarget = target; + return; + } + } + intendedTarget = CellRect.CenteredOn(IntVec3.FromVector3(center), 7).RandomCell; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveTrackingBullet.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveTrackingBullet.cs new file mode 100644 index 0000000..3f23eb8 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveTrackingBullet.cs @@ -0,0 +1,185 @@ +using RimWorld; +using UnityEngine; +using Verse; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class Projectile_ExplosiveTrackingBullet : Projectile_TrackingBullet + { + private ExplosiveTrackingBulletDef explosiveDefInt; + private int ticksToDetonation; + + public ExplosiveTrackingBulletDef ExplosiveDef + { + get + { + if (explosiveDefInt == null) + { + explosiveDefInt = def.GetModExtension(); + if (explosiveDefInt == null) + { + Log.ErrorOnce($"ExplosiveTrackingBulletDef for {this.def.defName} is null. Creating a default instance.", this.thingIDNumber ^ 0x12345679); + this.explosiveDefInt = new ExplosiveTrackingBulletDef(); + } + } + return explosiveDefInt; + } + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref this.ticksToDetonation, "ticksToDetonation", 0, false); + } + + protected override void Tick() + { + base.Tick(); // Call base Projectile_TrackingBullet Tick logic + bool flag = this.ticksToDetonation > 0; + if (flag) + { + this.ticksToDetonation--; + bool flag2 = this.ticksToDetonation <= 0; + if (flag2) + { + this.Explode(); + } + } + } + + protected override void Impact(Thing hitThing, bool blockedByShield = false) + { + bool flag = hitThing == null || blockedByShield || ExplosiveDef.explosionDelay == 0; // Use ExplosiveDef for explosionDelay + if (flag) + { + this.Explode(); + } + else + { + this.landed = true; + this.ticksToDetonation = ExplosiveDef.explosionDelay; // Use ExplosiveDef for explosionDelay + GenExplosion.NotifyNearbyPawnsOfDangerousExplosive(this, ExplosiveDef.damageDef ?? DamageDefOf.Bomb, this.launcher.Faction, this.launcher); // Use ExplosiveDef for damageDef + // 停止追踪并清空速度,确保子弹停止移动 + this.homing = false; + this.curSpeed = Vector3.zero; + } + } + + protected virtual void Explode() + { + Map map = base.Map; + // ModExtension_Cone modExtension = this.def.GetModExtension(); // Not used in this explosive logic + this.DoExplosion(map); // Call the helper DoExplosion with map + + // Cone explosion logic (if needed, based on ModExtension_Cone) - Currently not implemented for this class + // bool flag = modExtension != null; + // if (flag) + // { + // 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); + // } + + // Explosion effect (if needed, based on def.projectile.explosionEffect) - Currently not implemented for this class + // bool flag2 = this.def.projectile.explosionEffect != null; + // if (flag2) + // { + // Effecter effecter = this.def.projectile.explosionEffect.Spawn(); + // bool flag3 = this.def.projectile.explosionEffectLifetimeTicks != 0; + // if (flag3) + // { + // 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(); + // } + // } + this.Destroy(DestroyMode.Vanish); + } + + protected void DoExplosion(Map map) + { + IntVec3 position = base.Position; + float explosionRadius = ExplosiveDef.explosionRadius; // Use ExplosiveDef for explosionRadius + DamageDef damageDef = ExplosiveDef.damageDef ?? DamageDefOf.Bomb; // Use ExplosiveDef for damageDef + Thing launcher = this.launcher; + int damageAmount = this.DamageAmount; + float armorPenetration = this.ArmorPenetration; + SoundDef soundExplode = ExplosiveDef.soundExplode; // Use ExplosiveDef for soundExplode + ThingDef equipmentDef = this.equipmentDef; + ThingDef def = this.def; // This is the projectile's ThingDef + Thing thing = this.intendedTarget.Thing; + ThingDef postExplosionSpawnThingDef = ExplosiveDef.postExplosionSpawnThingDef; // Use ExplosiveDef for postExplosionSpawnThingDef + ThingDef postExplosionSpawnThingDefWater = ExplosiveDef.postExplosionSpawnThingDefWater; // Use ExplosiveDef for postExplosionSpawnThingDefWater + float postExplosionSpawnChance = ExplosiveDef.postExplosionSpawnChance; // Use ExplosiveDef for postExplosionSpawnChance + int postExplosionSpawnThingCount = ExplosiveDef.postExplosionSpawnThingCount; // Use ExplosiveDef for postExplosionSpawnThingCount + GasType? postExplosionGasType = ExplosiveDef.gasType; // Use ExplosiveDef for gasType + ThingDef preExplosionSpawnThingDef = ExplosiveDef.preExplosionSpawnThingDef; // Use ExplosiveDef for preExplosionSpawnThingDef + float preExplosionSpawnChance = ExplosiveDef.preExplosionSpawnChance; // Use ExplosiveDef for preExplosionSpawnChance + int preExplosionSpawnThingCount = ExplosiveDef.preExplosionSpawnThingCount; // Use ExplosiveDef for preExplosionSpawnThingCount + bool applyDamageToExplosionCellsNeighbors = ExplosiveDef.applyDamageToExplosionCellsNeighbors; // Use ExplosiveDef for applyDamageToExplosionCellsNeighbors + float explosionChanceToStartFire = ExplosiveDef.explosionChanceToStartFire; // Use ExplosiveDef for explosionChanceToStartFire + bool explosionDamageFalloff = ExplosiveDef.explosionDamageFalloff; // Use ExplosiveDef for explosionDamageFalloff + float? direction = new float?(this.origin.AngleToFlat(this.destination)); // This remains from original logic + float screenShakeFactor = ExplosiveDef.screenShakeFactor; // Use ExplosiveDef for screenShakeFactor + bool doExplosionVFX = ExplosiveDef.doExplosionVFX; // Use ExplosiveDef for doExplosionVFX + + GenExplosion.DoExplosion( + center: ExactPosition.ToIntVec3(), // 爆炸中心 + map: map, // 地图 + radius: explosionRadius, // 爆炸半径 + damType: damageDef, // 伤害类型 + instigator: launcher, // 制造者 + damAmount: damageAmount, // 伤害量 + armorPenetration: armorPenetration, // 护甲穿透 + explosionSound: soundExplode, // 爆炸音效 + weapon: equipmentDef, // 武器 + projectile: def, // 弹药定义 + intendedTarget: thing, // 预期目标 + postExplosionSpawnThingDef: postExplosionSpawnThingDef, // 爆炸后生成物 + postExplosionSpawnChance: postExplosionSpawnChance, // 爆炸后生成几率 + postExplosionSpawnThingCount: postExplosionSpawnThingCount, // 爆炸后生成数量 + postExplosionGasType: postExplosionGasType, // 气体类型 + postExplosionGasRadiusOverride: null, // 爆炸气体半径覆盖 + postExplosionGasAmount: 255, // 爆炸气体数量 + applyDamageToExplosionCellsNeighbors: applyDamageToExplosionCellsNeighbors, // 是否对爆炸单元格邻居造成伤害 + preExplosionSpawnThingDef: preExplosionSpawnThingDef, // 爆炸前生成物 + preExplosionSpawnChance: preExplosionSpawnChance, // 爆炸前生成几率 + preExplosionSpawnThingCount: preExplosionSpawnThingCount, // 爆炸前生成数量 + chanceToStartFire: explosionChanceToStartFire, // 是否有几率点燃 + damageFalloff: explosionDamageFalloff, // 爆炸伤害衰减 + direction: direction, // 方向 + ignoredThings: null, // 忽略的物体 + affectedAngle: null, // 受影响角度 + doVisualEffects: doExplosionVFX, // 是否显示视觉效果 + propagationSpeed: 1f, // 传播速度 + excludeRadius: 0f, // 排除半径 + doSoundEffects: true, // 是否播放音效 + postExplosionSpawnThingDefWater: postExplosionSpawnThingDefWater, // 爆炸后在水中生成物 + screenShakeFactor: screenShakeFactor, // 屏幕震动因子 + flammabilityChanceCurve: null, // 易燃性几率曲线 + overrideCells: null, // 覆盖单元格 + postExplosionSpawnSingleThingDef: null, // 爆炸后生成单个物体 + preExplosionSpawnSingleThingDef: null // 爆炸前生成单个物体 + ); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveWithTrail.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveWithTrail.cs new file mode 100644 index 0000000..4ff6dde --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_ExplosiveWithTrail.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Reflection; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class Projectile_ExplosiveWithTrail : Projectile_Explosive + { + private TrackingBulletDef trackingDefInt; + private int Fleck_MakeFleckTick; + private Vector3 lastTickPosition; + + 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 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); + lastTickPosition = origin; + } + + protected override void Tick() + { + base.Tick(); + + // 处理拖尾特效 + if (TrackingDef != null && TrackingDef.tailFleckDef != null) + { + Fleck_MakeFleckTick++; + if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks) + { + if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax)) + { + Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks; + } + + Map map = base.Map; + int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange; + Vector3 currentPosition = base.ExactPosition; + Vector3 previousPosition = lastTickPosition; + + for (int i = 0; i < randomInRange; i++) + { + float num = (currentPosition - previousPosition).AngleFlat(); + float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num; + float randomInRange2 = TrackingDef.fleckScale.RandomInRange; + float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange; + + FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2); + dataStatic.rotation = (currentPosition - previousPosition).AngleFlat(); + dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange; + dataStatic.velocityAngle = velocityAngle; + dataStatic.velocitySpeed = randomInRange3; + map.flecks.CreateFleck(dataStatic); + } + } + } + lastTickPosition = base.ExactPosition; + } + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref Fleck_MakeFleckTick, "Fleck_MakeFleckTick", 0); + Scribe_Values.Look(ref lastTickPosition, "lastTickPosition", Vector3.zero); + if (Scribe.mode == LoadSaveMode.PostLoadInit) + { + 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/ArachnaeSwarm/Projectiles/Projectile_TrackingBullet.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_TrackingBullet.cs new file mode 100644 index 0000000..4116a2d --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_TrackingBullet.cs @@ -0,0 +1,232 @@ +using System.Collections.Generic; +using System.Reflection; +using RimWorld; +using UnityEngine; +using Verse; +using Verse.AI; +using Verse.Sound; + +namespace ArachnaeSwarm +{ + public class Projectile_TrackingBullet : Bullet + { + private TrackingBulletDef trackingDefInt; + + protected Vector3 exactPositionInt; + public Vector3 curSpeed; + public bool homing = true; + private int destroyTicksAfterLosingTrack = -1; // 失去追踪后多少tick自毁,-1表示不自毁 + private int Fleck_MakeFleckTick; // 拖尾特效的计时器 + private Vector3 lastTickPosition; // 记录上一帧的位置,用于计算移动方向 + + 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(); + lastTickPosition = origin; // 初始化 lastTickPosition + } + + 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) + { + // 首先检查目标是否是一个有效的 Thing + if (!intendedTarget.HasThing) + { + homing = false; // 如果目标是地面,则禁用追踪 + } + // 如果目标消失或距离太远,停止追踪 + else if (!intendedTarget.IsValid || !intendedTarget.Thing.Spawned || (intendedTarget.Cell.ToVector3() - ExactPosition).magnitude > def.projectile.speed * 2f) // 假设2倍速度为最大追踪距离 + { + homing = false; + destroyTicksAfterLosingTrack = TrackingDef.destroyTicksAfterLosingTrack.RandomInRange; // 失去追踪后根据XML配置的范围自毁 + } + else + { + // 计算需要转向的方向 + 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; // 更新位置 + } + + 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); + Scribe_Values.Look(ref destroyTicksAfterLosingTrack, "destroyTicksAfterLosingTrack", -1); + 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(); + } + } + } + } + + protected override void Tick() + { + base.Tick(); // 调用父类Bullet的Tick,处理 ticksToImpact 减少和最终命中 + + if (destroyTicksAfterLosingTrack > 0) + { + destroyTicksAfterLosingTrack--; + if (destroyTicksAfterLosingTrack <= 0) + { + Destroy(); // 如果自毁计时器归零,直接销毁 + return; + } + } + + // 处理拖尾特效 + if (TrackingDef != null && TrackingDef.tailFleckDef != null) + { + Fleck_MakeFleckTick++; + // 只有当达到延迟时间后才开始生成Fleck + if (Fleck_MakeFleckTick >= TrackingDef.fleckDelayTicks) + { + if (Fleck_MakeFleckTick >= (TrackingDef.fleckDelayTicks + TrackingDef.fleckMakeFleckTickMax)) + { + Fleck_MakeFleckTick = TrackingDef.fleckDelayTicks; // 重置计时器,从延迟时间开始循环 + } + + Map map = base.Map; + int randomInRange = TrackingDef.fleckMakeFleckNum.RandomInRange; + Vector3 currentPosition = ExactPosition; + Vector3 previousPosition = lastTickPosition; + + for (int i = 0; i < randomInRange; i++) + { + float num = (currentPosition - previousPosition).AngleFlat(); + float velocityAngle = TrackingDef.fleckAngle.RandomInRange + num; + float randomInRange2 = TrackingDef.fleckScale.RandomInRange; + float randomInRange3 = TrackingDef.fleckSpeed.RandomInRange; + + FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, TrackingDef.tailFleckDef, randomInRange2); + dataStatic.rotation = (currentPosition - previousPosition).AngleFlat(); + dataStatic.rotationRate = TrackingDef.fleckRotation.RandomInRange; + dataStatic.velocityAngle = velocityAngle; + dataStatic.velocitySpeed = randomInRange3; + map.flecks.CreateFleck(dataStatic); + } + } + } + lastTickPosition = ExactPosition; // 更新上一帧位置 + + // 保存移动前的精确位置 + Vector3 exactPositionBeforeMove = exactPositionInt; + + MovementTick(); // 调用追踪移动逻辑,更新 exactPositionInt (即新的 ExactPosition) + + // 检查是否超出地图边界 + if (!ExactPosition.InBounds(base.Map)) + { + // 如果超出地图,直接销毁,不触发 ImpactSomething() + Destroy(); + return; + } + + // 在调用 ProjectileCheckForFreeInterceptBetween 之前,添加近距离命中检测 + if (intendedTarget != null && intendedTarget.Thing != null && intendedTarget.Thing.Spawned) + { + float distanceToTarget = (ExactPosition - intendedTarget.Thing.DrawPos).magnitude; + if (distanceToTarget <= TrackingDef.impactThreshold) + { + Impact(intendedTarget.Thing); // 强制命中目标 + return; // 命中后立即返回,不再执行后续逻辑 + } + } + + // 检查是否有东西在路径上拦截 + // ProjectileCheckForFreeInterceptBetween 会在内部处理命中,并调用 ImpactSomething() + // 所以这里不需要额外的 ImpactSomething() 调用 + object[] parameters = new object[2] { exactPositionBeforeMove, exactPositionInt }; // 传入移动前和移动后的位置 + + // 调用 ProjectileCheckForFreeInterceptBetween + // 如果它返回 true,说明有拦截,并且拦截逻辑已在内部处理。 + // 如果返回 false,说明没有拦截,子弹继续飞行。 + NonPublicFields.ProjectileCheckForFreeInterceptBetween.Invoke(this, parameters); + } + + protected override void Impact(Thing hitThing, bool blockedByShield = false) + { + // 默认Impact逻辑,可以根据需要扩展 + base.Impact(hitThing, blockedByShield); + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBeam.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBeam.cs new file mode 100644 index 0000000..2fd1d98 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBeam.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + // A new, dedicated extension class for the penetrating beam. + public class Wula_BeamPierce_Extension : DefModExtension + { + public int maxHits = 3; + public float damageFalloff = 0.25f; + public bool preventFriendlyFire = false; + public ThingDef beamMoteDef; + public float beamWidth = 1f; + public float beamStartOffset = 0f; + } + + public class Projectile_WulaPenetratingBeam : Bullet + { + private int hitCounter = 0; + private List alreadyDamaged = new List(); + + // It now gets its properties from the new, dedicated extension. + private Wula_BeamPierce_Extension Props => def.GetModExtension(); + + public override Vector3 ExactPosition => destination + Vector3.up * def.Altitude; + + 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); + + Wula_BeamPierce_Extension props = Props; + if (props == null) + { + Log.Error("Projectile_WulaBeam requires a Wula_BeamPierce_Extension in its def."); + Destroy(DestroyMode.Vanish); + return; + } + + this.hitCounter = 0; + this.alreadyDamaged.Clear(); + + bool shouldPreventFriendlyFire = preventFriendlyFire || props.preventFriendlyFire; + + Map map = this.Map; + // --- Corrected Start Position Calculation --- + // The beam should start from the gun's muzzle, not the pawn's center. + Vector3 endPosition = usedTarget.Cell.ToVector3Shifted(); + Vector3 castPosition = origin + (endPosition - origin).Yto0().normalized * props.beamStartOffset; + + // --- Vanilla Beam Drawing Logic --- + if (props.beamMoteDef != null) + { + // Calculate the offset exactly like the vanilla Beam class does. + // The offset for the mote is calculated from the launcher's true position, not the cast position. + Vector3 moteOffset = (endPosition - launcher.Position.ToVector3Shifted()).Yto0().normalized * props.beamStartOffset; + MoteMaker.MakeInteractionOverlay(props.beamMoteDef, launcher, usedTarget.ToTargetInfo(map), moteOffset, Vector3.zero); + } + + float distance = Vector3.Distance(castPosition, endPosition); + Vector3 direction = (endPosition - castPosition).normalized; + + var thingsOnPath = new HashSet(); + for (float i = 0; i < distance; i += 1.0f) + { + IntVec3 cell = (castPosition + direction * i).ToIntVec3(); + if (cell.InBounds(map)) + { + thingsOnPath.AddRange(map.thingGrid.ThingsListAt(cell)); + } + } + // CRITICAL FIX: Manually add the intended target to the list. + // This guarantees the primary target is always processed, even if the loop sampling misses its exact cell. + if (intendedTarget.HasThing) + { + thingsOnPath.Add(intendedTarget.Thing); + } + + int maxHits = props.maxHits; + bool infinitePenetration = maxHits < 0; + + foreach (Thing thing in thingsOnPath) + { + if (!infinitePenetration && hitCounter >= maxHits) break; + + // 统一处理 Pawn 和 Building 的伤害逻辑 + // 确保 Thing 未被伤害过且不是发射者 + if (thing != launcher && !alreadyDamaged.Contains(thing)) + { + bool shouldDamage = false; + Pawn pawn = thing as Pawn; + Building building = thing as Building; + + if (pawn != null) // 如果是 Pawn + { + if (intendedTarget.Thing == pawn) shouldDamage = true; + else if (pawn.HostileTo(launcher)) shouldDamage = true; + else if (!shouldPreventFriendlyFire) shouldDamage = true; + } + else if (building != null) // 如果是 Building + { + shouldDamage = true; // 默认对 Building 造成伤害 + } + + if (shouldDamage) + { + ApplyPathDamage(thing, props); // 传递 Thing + } + } + // 只有当遇到完全阻挡的 Thing 且不是 Pawn 或 Building 时才停止穿透 + else if (thing.def.Fillage == FillCategory.Full && thing.def.blockLight && !(thing is Pawn) && !(thing is Building)) + { + break; + } + } + + this.Destroy(DestroyMode.Vanish); + } + + private void ApplyPathDamage(Thing targetThing, Wula_BeamPierce_Extension props) // 接受 Thing 参数 + { + float damageMultiplier = 1f; + if (targetThing is Pawn) // 只有 Pawn 才计算穿透衰减 + { + damageMultiplier = Mathf.Pow(1f - props.damageFalloff, hitCounter); + } + // Building 不受穿透衰减影响,或者 Building 的穿透衰减始终为 1 (不衰减) + + 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); + + targetThing.TakeDamage(dinfo); // 对 targetThing 造成伤害 + alreadyDamaged.Add(targetThing); + + if (targetThing is Pawn) // 只有 Pawn 才增加 hitCounter + { + hitCounter++; + } + } + + protected override void Tick() { } + protected override void Impact(Thing hitThing, bool blockedByShield = false) { } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBullet.cs b/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBullet.cs new file mode 100644 index 0000000..1481ad4 --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/Projectile_WulaPenetratingBullet.cs @@ -0,0 +1,201 @@ +using System.Collections.Generic; +using RimWorld; +using UnityEngine; +using Verse; + +namespace ArachnaeSwarm +{ + // 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; + // If true, this projectile will never cause friendly fire, regardless of game settings. + public bool preventFriendlyFire = false; + public FleckDef tailFleckDef; // 用于配置拖尾特效的 FleckDef + public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间(tick) + } + + public class Projectile_WulaLineAttack : Bullet + { + private int hitCounter = 0; + private List alreadyDamaged = new List(); + private Vector3 lastTickPosition; + private int Fleck_MakeFleckTick; // 拖尾特效的计时器 + public int Fleck_MakeFleckTickMax = 1; // 拖尾特效的生成频率 + public IntRange Fleck_MakeFleckNum = new IntRange(1, 1); // 每次生成的粒子数量 + public FloatRange Fleck_Angle = new FloatRange(-180f, 180f); // 粒子角度 + public FloatRange Fleck_Scale = new FloatRange(1f, 1f); // 粒子大小 + public FloatRange Fleck_Speed = new FloatRange(0f, 0f); // 粒子速度 + public FloatRange Fleck_Rotation = new FloatRange(-180f, 180f); // 粒子旋转 + + private Wula_PathPierce_Extension Props => def.GetModExtension(); + + public override void ExposeData() + { + base.ExposeData(); + Scribe_Values.Look(ref hitCounter, "hitCounter", 0); + Scribe_Collections.Look(ref alreadyDamaged, "alreadyDamaged", LookMode.Reference); + Scribe_Values.Look(ref lastTickPosition, "lastTickPosition"); + if (alreadyDamaged == null) + { + alreadyDamaged = new List(); + } + } + + 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); + this.lastTickPosition = origin; + this.alreadyDamaged.Clear(); + this.hitCounter = 0; + // Friendly fire is prevented if EITHER the game setting is true OR the XML extension is true. + this.preventFriendlyFire = preventFriendlyFire || (Props?.preventFriendlyFire ?? false); + } + + protected override void Tick() + { + Vector3 startPos = this.lastTickPosition; + base.Tick(); + + if (this.Destroyed) return; + + this.Fleck_MakeFleckTick++; + // 只有当达到延迟时间后才开始生成Fleck + if (this.Fleck_MakeFleckTick >= Props.fleckDelayTicks) + { + if (this.Fleck_MakeFleckTick >= (Props.fleckDelayTicks + this.Fleck_MakeFleckTickMax)) + { + this.Fleck_MakeFleckTick = Props.fleckDelayTicks; // 重置计时器,从延迟时间开始循环 + } + + Map map = base.Map; + int randomInRange = this.Fleck_MakeFleckNum.RandomInRange; + Vector3 currentPosition = this.ExactPosition; // Current position of the bullet + Vector3 previousPosition = this.lastTickPosition; // Previous position of the bullet + + for (int i = 0; i < randomInRange; i++) + { + float currentBulletAngle = ExactRotation.eulerAngles.y; // 使用子弹当前的水平旋转角度 + float fleckRotationAngle = currentBulletAngle; // Fleck 的旋转角度与子弹方向一致 + float velocityAngle = this.Fleck_Angle.RandomInRange + currentBulletAngle; // Fleck 的速度角度基于子弹方向加上随机偏移 + float randomInRange2 = this.Fleck_Scale.RandomInRange; + float randomInRange3 = this.Fleck_Speed.RandomInRange; + + if (Props?.tailFleckDef != null) + { + FleckCreationData dataStatic = FleckMaker.GetDataStatic(currentPosition, map, Props.tailFleckDef, randomInRange2); + dataStatic.rotation = fleckRotationAngle; + dataStatic.rotationRate = this.Fleck_Rotation.RandomInRange; + dataStatic.velocityAngle = velocityAngle; + dataStatic.velocitySpeed = randomInRange3; + map.flecks.CreateFleck(dataStatic); + } + } + } + + if (this.Destroyed) return; + + Vector3 endPos = this.ExactPosition; + + CheckPathForDamage(startPos, endPos); + + this.lastTickPosition = endPos; + } + + protected override void Impact(Thing hitThing, bool blockedByShield = false) + { + CheckPathForDamage(lastTickPosition, this.ExactPosition); + + if (hitThing != null && alreadyDamaged.Contains(hitThing)) + { + base.Impact(null, blockedByShield); + } + else + { + base.Impact(hitThing, blockedByShield); + } + } + + private void CheckPathForDamage(Vector3 startPos, Vector3 endPos) + { + if (startPos == endPos) return; + + int maxHits = Props?.maxHits ?? 1; + bool infinitePenetration = maxHits < 0; + + if (!infinitePenetration && hitCounter >= maxHits) return; + + Map map = this.Map; + float distance = Vector3.Distance(startPos, endPos); + Vector3 direction = (endPos - startPos).normalized; + + for (float i = 0; i < distance; i += 0.8f) + { + if (!infinitePenetration && hitCounter >= maxHits) break; + + Vector3 checkPos = startPos + direction * i; + var thingsInCell = new HashSet(map.thingGrid.ThingsListAt(checkPos.ToIntVec3())); + + foreach (Thing thing in thingsInCell) + { + if (thing is Pawn pawn && pawn != this.launcher && !alreadyDamaged.Contains(pawn)) + { + bool shouldDamage = false; + + // Case 1: Always damage the intended target if it's a pawn. This allows hunting. + if (this.intendedTarget.Thing == pawn) + { + shouldDamage = true; + } + // Case 2: Always damage hostile pawns in the path. + else if (pawn.HostileTo(this.launcher)) + { + shouldDamage = true; + } + // Case 3: Damage non-hostiles (friendlies, neutrals) if the shot itself isn't marked to prevent friendly fire. + else if (!this.preventFriendlyFire) + { + shouldDamage = true; + } + + if (shouldDamage) + { + ApplyPathDamage(pawn); + if (!infinitePenetration && hitCounter >= maxHits) break; + } + } + } + } + } + + private void ApplyPathDamage(Pawn pawn) + { + Wula_PathPierce_Extension props = Props; + float falloff = props?.damageFalloff ?? 0.25f; + + // Damage falloff now applies universally, even for infinite penetration. + float damageMultiplier = Mathf.Pow(1f - falloff, hitCounter); + + int damageAmount = (int)(this.DamageAmount * damageMultiplier); + if (damageAmount <= 0) return; + + var dinfo = new DamageInfo( + this.def.projectile.damageDef, + damageAmount, + this.ArmorPenetration * damageMultiplier, + this.ExactRotation.eulerAngles.y, + this.launcher, + null, + this.equipmentDef, + DamageInfo.SourceCategory.ThingOrUnknown, + this.intendedTarget.Thing); + + pawn.TakeDamage(dinfo); + alreadyDamaged.Add(pawn); + hitCounter++; + } + } +} \ No newline at end of file diff --git a/Source/ArachnaeSwarm/Projectiles/TrackingBulletDef.cs b/Source/ArachnaeSwarm/Projectiles/TrackingBulletDef.cs new file mode 100644 index 0000000..fe465ad --- /dev/null +++ b/Source/ArachnaeSwarm/Projectiles/TrackingBulletDef.cs @@ -0,0 +1,21 @@ +using Verse; + +namespace ArachnaeSwarm +{ + public class TrackingBulletDef : DefModExtension + { + public float homingSpeed = 0.1f; // 追踪速度,值越大追踪越灵敏 + public float initRotateAngle = 0f; // 初始旋转角度 + public float impactThreshold = 0.5f; // 强制命中阈值,子弹与目标距离小于此值时强制命中 + + public FleckDef tailFleckDef; // 拖尾特效的FleckDef + public int fleckMakeFleckTickMax = 1; // 拖尾特效的生成间隔(tick) + public int fleckDelayTicks = 10; // 拖尾特效延迟生成时间(tick) + public IntRange fleckMakeFleckNum = new IntRange(1, 1); // 每次生成拖尾特效的数量 + public FloatRange fleckAngle = new FloatRange(-180f, 180f); // 拖尾特效的初始角度范围 + public FloatRange fleckScale = new FloatRange(1f, 1f); // 拖尾特效的缩放范围 + public FloatRange fleckSpeed = new FloatRange(0f, 0f); // 拖尾特效的初始速度范围 + public FloatRange fleckRotation = new FloatRange(-180f, 180f); // 拖尾特效的旋转速度范围 + public IntRange destroyTicksAfterLosingTrack = new IntRange(60, 120); // 失去追踪后多少tick自毁 + } +} \ No newline at end of file