From 96bc1d4c5a3d65de61ca494be065e5e903ae4f49 Mon Sep 17 00:00:00 2001
From: Tourswen <565033799@qq.com>
Date: Tue, 24 Feb 2026 12:02:38 +0800
Subject: [PATCH] zc
---
.../Defs/JobDefs/Jobs_WULA_AutonomousMech.xml | 12 -
1.6/1.6/Defs/JobDefs/WULA_JobDefs.xml | 76 ++
.../MentalStates_WULA_Broken.xml | 22 +
.../WULA_Turret_Buildings.xml | 7 -
...CompAbilityEffect_LaunchMultiProjectile.cs | 44 -
.../Ability/LightningBombardment.cs | 301 -----
.../WULA_AbilityEnergyLance/EnergyLance.cs | 2 +-
...CompAbilityEffect_LaunchMultiProjectile.cs | 358 ++++++
...Properties_AbilityLaunchMultiProjectile.cs | 47 +
...iver_CastAbilityMaintainMultiProjectile.cs | 115 ++
.../CompAbilityEffect_RequiresNonHostility.cs | 0
.../CompAbilityEffect_ResearchPrereq.cs | 0
.../Building_ExtraGraphics.cs | 0
.../Building_MapObserver.cs | 0
.../Building_TurretGunHasSpeed.cs | 0
.../CompPathCostUpdater.cs | 0
.../CompProperties_TransformAtFullCapacity.cs | 21 -
.../CompProperties_TransformIntoBuilding.cs | 21 -
.../CompTransformAtFullCapacity.cs | 170 ---
.../CompTransformIntoBuilding.cs | 294 -----
.../TransformValidationUtility.cs | 220 ----
.../Building_GlobalWorkTable.cs | 4 +-
.../WULA_MechUnit/Patch_ColonistBarMech.cs | 181 +++
.../WULA_MechUnit/Patch_MechSpecificWeapon.cs | 74 ++
.../WULA_MechUnit/Patch_RomanceFix.cs | 130 +++
.../WULA_MechUnit/Patch_TakeDamage.cs | 230 ++++
.../WULA_MechUnit/Patch_mechunit.cs | 285 +++++
.../WULA_MechUnit/Patche_SkillSystem.cs | 28 +
.../Job/JobGiver_InspectBuilding.cs | 6 +-
Source/WulaFallenEmpire/Pawn/Mechunit.cs | 166 +++
.../Pawn/WULA_Energy/Need_WulaEnergy.cs | 4 +-
.../WULA_Maintenance/CompMaintenancePod.cs | 4 +-
.../WorkGiver_DoMaintenance.cs | 2 +-
.../DefaultPilotEntry/CompMechDefaultPilot.cs | 236 ++++
.../CompProperties_MechDefaultPilot.cs | 127 +++
.../CompHediffGiverByKind.cs | 157 +++
.../CompProperties_HediffGiverByKind.cs | 77 ++
.../Pawn_Comps/MechArmor/CompMechArmor.cs | 199 ++++
.../Pawn_Comps/MechFuel/CompMechFuel.cs | 446 ++++++++
.../MechFuel/CompProperties_MechFuel.cs | 56 +
.../MechFuel/Gizmo_MechFuelStatus.cs | 127 +++
.../CompMechInherentWeapon.cs | 96 ++
.../CompProperties_MechInherentWeapon.cs | 16 +
.../CompMechMovementSound.cs | 359 ++++++
.../CompProperties_MechMovementSound.cs | 66 ++
.../MechPilotHolder/CompMechPilotHolder.cs | 1015 +++++++++++++++++
.../MechRepairable/CompMechRepairable.cs | 289 +++++
.../MechSelfDestruct/CompMechSelfDestruct.cs | 325 ++++++
.../CompProperties_MechSelfDestruct.cs | 88 ++
.../CompMechSkillInheritance.cs | 124 ++
.../CompMoteEmitterNorthward.cs | 476 ++++++++
.../MultiTurretGun/CompMultiTurretGun.cs | 70 ++
.../CompAndPatch_GiveHediffOnShot.cs | 57 +-
.../CompMechSpecificWeapon.cs | 39 +
.../CompPlaySoundOnSpawn.cs | 12 +-
Source/WulaFallenEmpire/WulaDefOf.cs | 56 +-
.../WulaFallenEmpire/WulaFallenEmpire.csproj | 428 ++++++-
57 files changed, 6595 insertions(+), 1170 deletions(-)
delete mode 100644 1.6/1.6/Defs/JobDefs/Jobs_WULA_AutonomousMech.xml
delete mode 100644 Source/WulaFallenEmpire/Ability/CompAbilityEffect_LaunchMultiProjectile.cs
delete mode 100644 Source/WulaFallenEmpire/Ability/LightningBombardment.cs
create mode 100644 Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompAbilityEffect_LaunchMultiProjectile.cs
create mode 100644 Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompProperties_AbilityLaunchMultiProjectile.cs
create mode 100644 Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/JobDriver_CastAbilityMaintainMultiProjectile.cs
rename Source/WulaFallenEmpire/Ability/{ => WULA_RequiresNonHostility}/CompAbilityEffect_RequiresNonHostility.cs (100%)
rename Source/WulaFallenEmpire/Ability/{ => WULA_ResearchPrereq}/CompAbilityEffect_ResearchPrereq.cs (100%)
rename Source/WulaFallenEmpire/{BuildingComp => Building}/Building_ExtraGraphics.cs (100%)
rename Source/WulaFallenEmpire/{BuildingComp => Building}/Building_MapObserver.cs (100%)
rename Source/WulaFallenEmpire/{BuildingComp => Building}/Building_TurretGunHasSpeed.cs (100%)
rename Source/WulaFallenEmpire/BuildingComp/{ => WULA_PathCostUpdater}/CompPathCostUpdater.cs (100%)
delete mode 100644 Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformAtFullCapacity.cs
delete mode 100644 Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformIntoBuilding.cs
delete mode 100644 Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformAtFullCapacity.cs
delete mode 100644 Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformIntoBuilding.cs
delete mode 100644 Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/TransformValidationUtility.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_ColonistBarMech.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_MechSpecificWeapon.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_RomanceFix.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_TakeDamage.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_mechunit.cs
create mode 100644 Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patche_SkillSystem.cs
create mode 100644 Source/WulaFallenEmpire/Pawn/Mechunit.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompMechDefaultPilot.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompProperties_MechDefaultPilot.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompHediffGiverByKind.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompProperties_HediffGiverByKind.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompProperties_MechFuel.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechFuel/Gizmo_MechFuelStatus.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompMechInherentWeapon.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompProperties_MechInherentWeapon.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompMechMovementSound.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompProperties_MechMovementSound.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechPilotHolder/CompMechPilotHolder.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechRepairable/CompMechRepairable.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompMechSelfDestruct.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompProperties_MechSelfDestruct.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MechSkillInheritance/CompMechSkillInheritance.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MoteEmitterNorthward/CompMoteEmitterNorthward.cs
create mode 100644 Source/WulaFallenEmpire/Pawn_Comps/MultiTurretGun/CompMultiTurretGun.cs
rename Source/WulaFallenEmpire/ThingComp/{ => WULA_GiveHediffOnShot}/CompAndPatch_GiveHediffOnShot.cs (51%)
create mode 100644 Source/WulaFallenEmpire/ThingComp/WULA_MechSpecificWeapon/CompMechSpecificWeapon.cs
diff --git a/1.6/1.6/Defs/JobDefs/Jobs_WULA_AutonomousMech.xml b/1.6/1.6/Defs/JobDefs/Jobs_WULA_AutonomousMech.xml
deleted file mode 100644
index ece1f6a1..00000000
--- a/1.6/1.6/Defs/JobDefs/Jobs_WULA_AutonomousMech.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/1.6/1.6/Defs/JobDefs/WULA_JobDefs.xml b/1.6/1.6/Defs/JobDefs/WULA_JobDefs.xml
index 341755d8..7d85f771 100644
--- a/1.6/1.6/Defs/JobDefs/WULA_JobDefs.xml
+++ b/1.6/1.6/Defs/JobDefs/WULA_JobDefs.xml
@@ -55,4 +55,80 @@
true
1
+
+
+
+ WULA_Launch_Proj
+ WulaFallenEmpire.JobDriver_CastAbilityMaintainMultiProjectile
+ 发射中。
+ true
+ true
+ true
+ false
+ Always
+ false
+
+
+
+
+ WULA_EnterMech
+ WulaFallenEmpire.JobDriver_EnterMech
+ Entering mech.
+
+ false
+
+
+ WULA_RefuelMech
+ WulaFallenEmpire.JobDriver_RefuelMech
+ Refuleing TargetA.
+ false
+
+
+
+ WULA_Refuel
+
+ WulaFallenEmpire.WorkGiver_RefuelMech
+ Hauling
+ refuel
+ refueling
+ 140
+
+ Manipulation
+
+ true
+
+
+ WULA_RepairMech
+ WulaFallenEmpire.JobDriver_RepairMech
+ Repairing TargetA.
+ false
+ false
+
+
+ WULA_RepairMech
+ Smithing
+ WulaFallenEmpire.WorkGiver_RepairMech
+ 50
+
+ Manipulation
+
+ Repair
+ Repair Mech
+
+
+
+ WULA_ForceEjectPilot
+ WulaFallenEmpire.JobDriver_ForceEjectPilot
+ Prise TargetA.
+ false
+
+
+
+ WULA_CarryToMech
+ WulaFallenEmpire.JobDriver_CarryToMech
+ carrying TargetA to TargetB.
+ false
+
\ No newline at end of file
diff --git a/1.6/1.6/Defs/MentalStateDefs/MentalStates_WULA_Broken.xml b/1.6/1.6/Defs/MentalStateDefs/MentalStates_WULA_Broken.xml
index 50b03e76..67a4372c 100644
--- a/1.6/1.6/Defs/MentalStateDefs/MentalStates_WULA_Broken.xml
+++ b/1.6/1.6/Defs/MentalStateDefs/MentalStates_WULA_Broken.xml
@@ -43,4 +43,26 @@
true
true
+
+
+ WULA_MechNoPilot
+
+ WulaFallenEmpire.MentalState_MechNoPilot
+ Misc
+ (0.65, 0.9, 0.93)
+ No driver
+
+ true
+ true
+ false
+ false
+ true
+ true
+
+
+
+
+ 10000
+ 999999
+
diff --git a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
index 8914f2d2..cb08025e 100644
--- a/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
+++ b/1.6/1.6/Defs/ThingDefs_Buildings/WULA_Turret_Buildings.xml
@@ -481,13 +481,6 @@
-
true
diff --git a/Source/WulaFallenEmpire/Ability/CompAbilityEffect_LaunchMultiProjectile.cs b/Source/WulaFallenEmpire/Ability/CompAbilityEffect_LaunchMultiProjectile.cs
deleted file mode 100644
index 3fd32ecf..00000000
--- a/Source/WulaFallenEmpire/Ability/CompAbilityEffect_LaunchMultiProjectile.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using Verse;
-using RimWorld;
-
-namespace WulaFallenEmpire
-{
- public class CompProperties_AbilityLaunchMultiProjectile : CompProperties_AbilityLaunchProjectile
- {
- public int numProjectiles = 1;
-
- public CompProperties_AbilityLaunchMultiProjectile()
- {
- compClass = typeof(CompAbilityEffect_LaunchMultiProjectile);
- }
- }
-
- public class CompAbilityEffect_LaunchMultiProjectile : CompAbilityEffect
- {
- public new CompProperties_AbilityLaunchMultiProjectile Props => (CompProperties_AbilityLaunchMultiProjectile)props;
-
- public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
- {
- base.Apply(target, dest);
- for (int i = 0; i < Props.numProjectiles; i++)
- {
- LaunchProjectile(target);
- }
- }
-
- private void LaunchProjectile(LocalTargetInfo target)
- {
- if (Props.projectileDef != null)
- {
- Pawn pawn = parent.pawn;
- Projectile projectile = (Projectile)GenSpawn.Spawn(Props.projectileDef, pawn.Position, pawn.Map);
- projectile.Launch(pawn, pawn.DrawPos, target, target, ProjectileHitFlags.IntendedTarget, parent.verb.preventFriendlyFire);
- }
- }
-
- public override bool AICanTargetNow(LocalTargetInfo target)
- {
- return target.Pawn != null;
- }
- }
-}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/Ability/LightningBombardment.cs b/Source/WulaFallenEmpire/Ability/LightningBombardment.cs
deleted file mode 100644
index 384ecf50..00000000
--- a/Source/WulaFallenEmpire/Ability/LightningBombardment.cs
+++ /dev/null
@@ -1,301 +0,0 @@
-using System;
-using System.Collections.Generic;
-using RimWorld;
-using UnityEngine;
-using Verse;
-using Verse.Sound;
-
-namespace WulaFallenEmpire
-{
- public class CompAbilityEffect_DRM_LightningBombardment : CompAbilityEffect
- {
- public new CompProperties_AbilityDRM_LightningBombardment Props
- {
- get => (CompProperties_AbilityDRM_LightningBombardment)props;
- }
-
- public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
- {
- base.Apply(target, dest);
- Map map = parent.pawn.MapHeld;
-
- // 获取或创建地图组件
- MapComponent_LightningBombardment comp = GetOrCreateMapComponent(map);
-
- // 启动轰炸任务
- comp.StartBombardment(
- target: target.Cell,
- instigator: parent.pawn,
- explosionCount: Props.explosionCount,
- bombIntervalTicks: Props.bombIntervalTicks,
- impactAreaRadius: Props.impactAreaRadius,
- explosionRadiusRange: Props.explosionRadiusRange,
- damageDef: Props.damageDef,
- damageAmount: Props.damageAmount,
- armorPenetration: Props.armorPenetration,
- postExplosionSpawnThingDef: Props.postExplosionSpawnThingDef,
- postExplosionSpawnChance: Props.postExplosionSpawnChance,
- postExplosionSpawnThingCount: Props.postExplosionSpawnThingCount
- );
-
- // 播放启动音效
- SoundDefOf.Thunder_OffMap.PlayOneShotOnCamera(map);
- }
-
- private MapComponent_LightningBombardment GetOrCreateMapComponent(Map map)
- {
- MapComponent_LightningBombardment comp = map.GetComponent();
- if (comp == null)
- {
- comp = new MapComponent_LightningBombardment(map);
- map.components.Add(comp);
- }
- return comp;
- }
-
- public override void DrawEffectPreview(LocalTargetInfo target)
- {
- // 显示施法范围
- float castingRange = parent.verb.verbProps.range;
- GenDraw.DrawRadiusRing(parent.pawn.Position, castingRange, Color.white);
-
- // 显示爆炸作用范围
- GenDraw.DrawRadiusRing(target.Cell, Props.impactAreaRadius, Color.white);
- if (target.IsValid) GenDraw.DrawTargetHighlight(target);
- }
- }
-
- public class CompProperties_AbilityDRM_LightningBombardment : CompProperties_AbilityEffect
- {
- public CompProperties_AbilityDRM_LightningBombardment()
- {
- compClass = typeof(CompAbilityEffect_DRM_LightningBombardment);
- }
-
- public float impactAreaRadius = 10f;
- public FloatRange explosionRadiusRange = new FloatRange(3f, 4f);
- public int bombIntervalTicks = 30;
- public int explosionCount = 3;
-
- public DamageDef damageDef;
- public int damageAmount = 30;
- public float armorPenetration = 0.8f;
-
- public ThingDef postExplosionSpawnThingDef;
- public float postExplosionSpawnChance;
- public int postExplosionSpawnThingCount;
- }
-
- // 地图组件管理轰炸任务
- public class MapComponent_LightningBombardment : MapComponent
- {
- private readonly List activeCoroutines = new List();
- private const int MAX_CONCURRENT_BOMBARDMENTS = 5;
-
- public MapComponent_LightningBombardment(Map map) : base(map) { }
-
- public void StartBombardment(
- IntVec3 target,
- Pawn instigator,
- int explosionCount,
- int bombIntervalTicks,
- float impactAreaRadius,
- FloatRange explosionRadiusRange,
- DamageDef damageDef,
- int damageAmount,
- float armorPenetration,
- ThingDef postExplosionSpawnThingDef,
- float postExplosionSpawnChance,
- int postExplosionSpawnThingCount)
- {
- // 防止过多任务影响性能
- if (activeCoroutines.Count >= MAX_CONCURRENT_BOMBARDMENTS)
- {
- WulaLog.Debug($"Too many concurrent bombardments on map {map}, max is {MAX_CONCURRENT_BOMBARDMENTS}");
- return;
- }
-
- activeCoroutines.Add(new BombardmentCoroutine(
- target: target,
- map: map,
- instigator: instigator,
- explosionCount: explosionCount,
- bombIntervalTicks: bombIntervalTicks,
- impactAreaRadius: impactAreaRadius,
- explosionRadiusRange: explosionRadiusRange,
- damageDef: damageDef,
- damageAmount: damageAmount,
- armorPenetration: armorPenetration,
- postExplosionSpawnThingDef: postExplosionSpawnThingDef,
- postExplosionSpawnChance: postExplosionSpawnChance,
- postExplosionSpawnThingCount: postExplosionSpawnThingCount
- ));
- }
-
- public override void MapComponentTick()
- {
- try
- {
- for (int i = activeCoroutines.Count - 1; i >= 0; i--)
- {
- if (!activeCoroutines[i].Tick())
- {
- activeCoroutines.RemoveAt(i);
- }
- }
- }
- catch (Exception ex)
- {
- WulaLog.Debug($"Lightning bombardment error: {ex}");
- activeCoroutines.Clear();
- }
- }
- }
-
- [StaticConstructorOnStartup]
- // 轰炸任务协程
- public class BombardmentCoroutine
- {
- private readonly IntVec3 target;
- private readonly Map map;
- private readonly Pawn instigator;
- private readonly int explosionCount;
- private readonly FloatRange explosionRadiusRange;
- private readonly float impactAreaRadius;
- private readonly int bombIntervalTicks;
-
- public DamageDef damageDef;
- public int damageAmount;
- public float armorPenetration;
- public ThingDef postExplosionSpawnThingDef;
- public float postExplosionSpawnChance;
- public int postExplosionSpawnThingCount;
-
- private int nextBombTick;
- private int explosionsRemaining;
-
- private static readonly Material LightningMat = MatLoader.LoadMat("Weather/LightningBolt", -1);
-
- public BombardmentCoroutine(
- IntVec3 target,
- Map map,
- Pawn instigator,
- int explosionCount,
- int bombIntervalTicks,
- float impactAreaRadius,
- FloatRange explosionRadiusRange,
- DamageDef damageDef,
- int damageAmount,
- float armorPenetration,
- ThingDef postExplosionSpawnThingDef,
- float postExplosionSpawnChance,
- int postExplosionSpawnThingCount)
- {
- this.target = target;
- this.map = map;
- this.instigator = instigator;
- this.explosionCount = explosionCount;
- this.bombIntervalTicks = bombIntervalTicks;
- this.impactAreaRadius = impactAreaRadius;
- this.explosionRadiusRange = explosionRadiusRange;
- this.damageDef = damageDef;
- this.damageAmount = damageAmount;
- this.armorPenetration = armorPenetration;
- this.postExplosionSpawnThingDef = postExplosionSpawnThingDef;
- this.postExplosionSpawnChance = postExplosionSpawnChance;
- this.postExplosionSpawnThingCount = postExplosionSpawnThingCount;
-
- explosionsRemaining = explosionCount;
- nextBombTick = Find.TickManager.TicksGame + bombIntervalTicks;
- }
-
- public bool Tick()
- {
- if (Find.TickManager.TicksGame >= nextBombTick)
- {
- ExecuteBomb();
- explosionsRemaining--;
-
- if (explosionsRemaining > 0)
- {
- nextBombTick += bombIntervalTicks;
- }
- }
- return explosionsRemaining > 0;
- }
-
- private void ExecuteBomb()
- {
- // 在轰炸区域内随机选择目标点
- IntVec3 bombTarget = GetRandomCellInRadius(target, map, impactAreaRadius);
-
- // 创建闪电视觉效果
- CreateLightningEffect(bombTarget);
-
- // 执行爆炸
- GenExplosion.DoExplosion(
- center: bombTarget,
- map: map,
- radius: explosionRadiusRange.RandomInRange,
- damType: damageDef,
- instigator: instigator,
- damAmount: damageAmount,
- armorPenetration: armorPenetration,
- postExplosionSpawnThingDef: postExplosionSpawnThingDef,
- postExplosionSpawnChance: postExplosionSpawnChance,
- postExplosionSpawnThingCount: postExplosionSpawnThingCount,
- applyDamageToExplosionCellsNeighbors: true,
- chanceToStartFire: 0.4f,
- damageFalloff: true
- );
- }
-
- private IntVec3 GetRandomCellInRadius(IntVec3 center, Map map, float radius)
- {
- if (radius <= 0.0f) return center;
-
- if (CellFinder.TryFindRandomCellNear(
- center,
- map,
- Mathf.CeilToInt(radius),
- c => c.DistanceTo(center) <= radius && c.Standable(map),
- out IntVec3 result))
- {
- return result;
- }
- return center; // 备用方案
- }
-
- private void CreateLightningEffect(IntVec3 strikeLoc)
- {
- if (strikeLoc.Fogged(map)) return;
-
- Vector3 position = strikeLoc.ToVector3Shifted();
-
- // 1. 播放声音
- SoundDefOf.Thunder_OffMap.PlayOneShotOnCamera(map);
-
- // 2. 生成粒子效果
- for (int i = 0; i < 4; i++)
- {
- FleckMaker.ThrowSmoke(position, map, 1.5f);
- FleckMaker.ThrowMicroSparks(position, map);
- FleckMaker.ThrowLightningGlow(position, map, 1.5f);
- }
-
- // 3. 绘制闪电网格
- Mesh boltMesh = LightningBoltMeshPool.RandomBoltMesh;
- Graphics.DrawMesh(
- boltMesh,
- strikeLoc.ToVector3ShiftedWithAltitude(AltitudeLayer.Weather),
- Quaternion.identity,
- FadedMaterialPool.FadedVersionOf(LightningMat, 1f),
- 0
- );
-
- // 4. 播放局部雷声
- SoundInfo soundInfo = SoundInfo.InMap(new TargetInfo(strikeLoc, map));
- SoundDefOf.Thunder_OnMap.PlayOneShot(soundInfo);
- }
- }
-}
diff --git a/Source/WulaFallenEmpire/Ability/WULA_AbilityEnergyLance/EnergyLance.cs b/Source/WulaFallenEmpire/Ability/WULA_AbilityEnergyLance/EnergyLance.cs
index edf1d8f4..4187f0c6 100644
--- a/Source/WulaFallenEmpire/Ability/WULA_AbilityEnergyLance/EnergyLance.cs
+++ b/Source/WulaFallenEmpire/Ability/WULA_AbilityEnergyLance/EnergyLance.cs
@@ -367,7 +367,7 @@ namespace WulaFallenEmpire
Find.BattleLog.Add(battleLogEntry_DamageTaken);
}
- DamageInfo damageInfo = new DamageInfo(WulaDamageDefOf.Wula_Dark_Matter_Flame, num, 2f, -1f, instigator, null, weaponDef);
+ DamageInfo damageInfo = new DamageInfo(Wula_DamageDefOf.Wula_Dark_Matter_Flame, num, 2f, -1f, instigator, null, weaponDef);
tmpThings[i].TakeDamage(damageInfo).AssociateWithLog(battleLogEntry_DamageTaken);
}
diff --git a/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompAbilityEffect_LaunchMultiProjectile.cs b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompAbilityEffect_LaunchMultiProjectile.cs
new file mode 100644
index 00000000..7147b404
--- /dev/null
+++ b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompAbilityEffect_LaunchMultiProjectile.cs
@@ -0,0 +1,358 @@
+using System.Collections.Generic;
+using Verse;
+using RimWorld;
+using UnityEngine;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ public class CompAbilityEffect_LaunchMultiProjectile : CompAbilityEffect
+ {
+ private bool isActive;
+ private int startTick;
+ private int nextProjectileTick;
+ private int projectilesFired;
+ private IntVec3? targetCell;
+ private bool parametersInitialized;
+
+ // 缓存当前状态的参数
+ private int currentNumProjectiles;
+ private ThingDef currentProjectileDef;
+ private float currentOffsetRadius;
+ private int currentShotIntervalTicks;
+
+ public new CompProperties_AbilityLaunchMultiProjectile Props => (CompProperties_AbilityLaunchMultiProjectile)props;
+
+ public bool IsActive => isActive;
+ public IntVec3? TargetCell => targetCell;
+
+ public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
+ {
+ base.Apply(target, dest);
+
+ if (parent.pawn == null || parent.pawn.MapHeld == null || !target.IsValid)
+ {
+ return;
+ }
+
+ // 初始化参数
+ InitializeParameters();
+
+ // 设置目标
+ targetCell = target.Cell.ClampInsideMap(parent.pawn.MapHeld);
+
+ // 开始发射
+ isActive = true;
+ startTick = Find.TickManager.TicksGame;
+ nextProjectileTick = startTick + Mathf.Max(0, Props.startDelayTicks);
+ projectilesFired = 0;
+
+ // 如果是持续模式,需要启动持续Job
+ if (Props.useSustainedJob)
+ {
+ StartSustainedJob(target);
+ }
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (!isActive)
+ {
+ return;
+ }
+
+ // 终止条件检查
+ if (parent.pawn == null || parent.pawn.Dead || !parent.pawn.Spawned || parent.pawn.MapHeld == null)
+ {
+ StopMultiProjectile();
+ return;
+ }
+
+ // 检查是否还在持续施法状态
+ if (Props.useSustainedJob && !IsPawnMaintainingJob())
+ {
+ StopMultiProjectile();
+ return;
+ }
+
+ // 检查最大持续时间
+ if (Props.maxSustainTicks > 0 && (Find.TickManager.TicksGame - startTick) >= Props.maxSustainTicks)
+ {
+ StopMultiProjectile();
+ return;
+ }
+
+ // 检查是否已经发射完所有射弹
+ if (projectilesFired >= currentNumProjectiles)
+ {
+ StopMultiProjectile();
+ return;
+ }
+
+ int currentTick = Find.TickManager.TicksGame;
+
+ // 发射下一发
+ if (currentTick >= nextProjectileTick)
+ {
+ if (targetCell.HasValue)
+ {
+ LaunchProjectile(new LocalTargetInfo(targetCell.Value));
+ }
+
+ projectilesFired++;
+
+ // 如果还有剩余射弹,设置下一次发射时间
+ if (projectilesFired < currentNumProjectiles)
+ {
+ nextProjectileTick = currentTick + Mathf.Max(1, currentShotIntervalTicks);
+ }
+ else
+ {
+ // 所有射弹已发射完毕
+ if (!Props.useSustainedJob)
+ {
+ StopMultiProjectile();
+ }
+ }
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+
+ Scribe_Values.Look(ref isActive, "isActive", false);
+ Scribe_Values.Look(ref startTick, "startTick", 0);
+ Scribe_Values.Look(ref nextProjectileTick, "nextProjectileTick", 0);
+ Scribe_Values.Look(ref projectilesFired, "projectilesFired", 0);
+
+ if (Scribe.mode == LoadSaveMode.PostLoadInit && isActive)
+ {
+ InitializeParameters();
+ }
+ }
+
+ ///
+ /// 初始化当前参数
+ ///
+ private void InitializeParameters()
+ {
+ if (parametersInitialized)
+ return;
+
+ var state = GetCurrentState();
+
+ currentNumProjectiles = state?.numProjectiles ?? Props.numProjectiles;
+ currentProjectileDef = state?.projectileDef ?? Props.projectileDef;
+ currentOffsetRadius = state?.offsetRadius ?? Props.offsetRadius;
+ currentShotIntervalTicks = state?.shotIntervalTicks ?? Props.shotIntervalTicks;
+
+ parametersInitialized = true;
+ }
+
+ ///
+ /// 启动持续发射Job
+ ///
+ private void StartSustainedJob(LocalTargetInfo target)
+ {
+ // 创建一个持续施法的工作
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_Launch_Proj, target);
+ job.ability = parent;
+ job.verbToUse = parent.verb;
+ parent.pawn.jobs.StartJob(job, JobCondition.InterruptForced, null, false, true, null, null, false);
+ }
+
+ ///
+ /// 检查pawn是否还在维持发射工作
+ ///
+ private bool IsPawnMaintainingJob()
+ {
+ if (parent.pawn?.jobs?.curJob == null)
+ return false;
+
+ var curJob = parent.pawn.jobs.curJob;
+ return curJob.ability == parent && curJob.def != null && curJob.def.abilityCasting;
+ }
+
+ private void LaunchProjectile(LocalTargetInfo target)
+ {
+ if (currentProjectileDef == null)
+ return;
+
+ Pawn pawn = parent.pawn;
+ LocalTargetInfo finalTarget = target;
+
+ // 如果有偏移范围,计算偏移后的目标
+ if (Props.useRandomOffset && currentOffsetRadius > 0)
+ {
+ finalTarget = GetOffsetTarget(target, currentOffsetRadius);
+ }
+
+ Projectile projectile = (Projectile)GenSpawn.Spawn(currentProjectileDef, pawn.Position, pawn.Map);
+ projectile.Launch(pawn, pawn.DrawPos, finalTarget, finalTarget,
+ ProjectileHitFlags.IntendedTarget, parent.verb.preventFriendlyFire);
+ }
+
+ ///
+ /// 停止多射弹发射
+ ///
+ public void StopMultiProjectile()
+ {
+ isActive = false;
+ startTick = 0;
+ nextProjectileTick = 0;
+ projectilesFired = 0;
+ targetCell = null;
+ parametersInitialized = false;
+ }
+
+ ///
+ /// 获取偏移后的目标点
+ ///
+ private LocalTargetInfo GetOffsetTarget(LocalTargetInfo originalTarget, float offsetRadius)
+ {
+ if (!Props.useRandomOffset || offsetRadius <= 0)
+ return originalTarget;
+
+ Vector3 basePos = originalTarget.Cell.ToVector3Shifted();
+ Map map = parent.pawn.Map;
+
+ // 生成随机偏移
+ Vector3 offset = Vector3.zero;
+
+ if (Props.offsetInLineOnly)
+ {
+ // 只在线条方向偏移(从施法者到目标的方向)
+ Vector3 casterPos = parent.pawn.Position.ToVector3Shifted();
+ Vector3 direction = (basePos - casterPos).normalized;
+
+ // 使用offsetRange或offsetRadius
+ float offsetDistance = Props.offsetRange.RandomInRange;
+ if (Mathf.Abs(offsetDistance) < Props.minOffsetDistance)
+ {
+ offsetDistance = Mathf.Sign(offsetDistance) * Props.minOffsetDistance;
+ }
+
+ offset = direction * offsetDistance;
+ }
+ else if (Props.offsetInCircle)
+ {
+ // 在圆形范围内随机偏移
+ float angle = Rand.Range(0f, 360f);
+ float distance = Rand.Range(0f, offsetRadius);
+
+ // 确保最小距离
+ distance = Mathf.Max(distance, Props.minOffsetDistance);
+
+ offset = new Vector3(
+ Mathf.Cos(angle * Mathf.Deg2Rad) * distance,
+ 0f,
+ Mathf.Sin(angle * Mathf.Deg2Rad) * distance
+ );
+ }
+ else
+ {
+ // 在矩形范围内随机偏移
+ offset = new Vector3(
+ Props.offsetRange.RandomInRange,
+ 0f,
+ Props.offsetRange.RandomInRange
+ );
+
+ // 限制最大偏移距离
+ if (offsetRadius > 0 && offset.magnitude > offsetRadius)
+ {
+ offset = offset.normalized * offsetRadius;
+ }
+ }
+
+ // 计算最终目标点
+ IntVec3 targetCell = (basePos + offset).ToIntVec3();
+ targetCell = targetCell.ClampInsideMap(map);
+
+ return new LocalTargetInfo(targetCell);
+ }
+
+ ///
+ /// 绘制效果预览(显示偏移范围)
+ ///
+ public override void DrawEffectPreview(LocalTargetInfo target)
+ {
+ base.DrawEffectPreview(target);
+
+ float currentRadius = GetCurrentOffsetRadius();
+ if (Props.useRandomOffset && currentRadius > 0)
+ {
+ // 绘制偏移范围
+ GenDraw.DrawRadiusRing(target.Cell, currentRadius);
+ }
+ }
+
+ public override bool AICanTargetNow(LocalTargetInfo target)
+ {
+ return target.Pawn != null;
+ }
+
+ #region 状态获取方法
+
+ ///
+ /// 获取当前状态的射弹数量
+ ///
+ private int GetCurrentNumProjectiles()
+ {
+ var state = GetCurrentState();
+ return state?.numProjectiles ?? Props.numProjectiles;
+ }
+
+ ///
+ /// 获取当前状态的射弹类型
+ ///
+ private ThingDef GetCurrentProjectileDef()
+ {
+ var state = GetCurrentState();
+ return state?.projectileDef ?? Props.projectileDef;
+ }
+
+ ///
+ /// 获取当前状态的偏移半径
+ ///
+ private float GetCurrentOffsetRadius()
+ {
+ var state = GetCurrentState();
+ return state?.offsetRadius ?? Props.offsetRadius;
+ }
+
+ ///
+ /// 获取当前状态的发射间隔
+ ///
+ private int GetCurrentShotIntervalTicks()
+ {
+ var state = GetCurrentState();
+ return state?.shotIntervalTicks ?? Props.shotIntervalTicks;
+ }
+
+ ///
+ /// 获取当前状态(基于施法者的Hediff)
+ ///
+ private ProjectileState GetCurrentState()
+ {
+ if (parent.pawn == null || parent.pawn.health?.hediffSet == null || Props.states == null)
+ return null;
+
+ // 检查施法者是否有匹配的Hediff
+ foreach (var state in Props.states)
+ {
+ if (state.hediffDef != null && parent.pawn.health.hediffSet.HasHediff(state.hediffDef))
+ {
+ return state;
+ }
+ }
+
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompProperties_AbilityLaunchMultiProjectile.cs b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompProperties_AbilityLaunchMultiProjectile.cs
new file mode 100644
index 00000000..71d7600b
--- /dev/null
+++ b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/CompProperties_AbilityLaunchMultiProjectile.cs
@@ -0,0 +1,47 @@
+using Verse;
+using RimWorld;
+using System.Collections.Generic;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_AbilityLaunchMultiProjectile : CompProperties_AbilityLaunchProjectile
+ {
+ public int numProjectiles = 1;
+
+ // 发射间隔控制
+ public int shotIntervalTicks = 0; // 每两发射弹的间隔时间(tick)
+ public bool useSustainedJob = false; // 是否使用持续Job
+
+ // 偏移范围
+ public float offsetRadius = 0f;
+ public bool useRandomOffset = false;
+ public FloatRange offsetRange = new FloatRange(-1f, 1f);
+ public bool offsetInLineOnly = false;
+ public bool offsetInCircle = true;
+ public float minOffsetDistance = 0f;
+ public bool avoidOverlap = false;
+ public float minProjectileSpacing = 1f;
+
+ // 持续发射控制
+ public int maxSustainTicks = 180; // 最大持续时间(参考火焰技能)
+ public int startDelayTicks = 10; // 开始发射前的延迟
+
+ // 状态定义
+ public List states;
+
+ public CompProperties_AbilityLaunchMultiProjectile()
+ {
+ compClass = typeof(CompAbilityEffect_LaunchMultiProjectile);
+ }
+ }
+
+ // 射弹状态参数
+ public class ProjectileState
+ {
+ public HediffDef hediffDef; // 触发的hediff
+ public int numProjectiles = 1; // 射弹数量(可选)
+ public ThingDef projectileDef; // 射弹类型(可选)
+ public float offsetRadius = 0f; // 偏移半径(可选)
+ public int shotIntervalTicks = 0; // 发射间隔(可选)
+ }
+}
diff --git a/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/JobDriver_CastAbilityMaintainMultiProjectile.cs b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/JobDriver_CastAbilityMaintainMultiProjectile.cs
new file mode 100644
index 00000000..6ac274ca
--- /dev/null
+++ b/Source/WulaFallenEmpire/Ability/WULA_LaunchMultiProjectile/JobDriver_CastAbilityMaintainMultiProjectile.cs
@@ -0,0 +1,115 @@
+using System.Collections.Generic;
+using Verse;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ public class JobDriver_CastAbilityMaintainMultiProjectile : JobDriver_CastAbility
+ {
+ private CompAbilityEffect_LaunchMultiProjectile MultiProjectileComp
+ {
+ get
+ {
+ if (job?.ability?.EffectComps == null)
+ {
+ return null;
+ }
+
+ foreach (var comp in job.ability.EffectComps)
+ {
+ if (comp is CompAbilityEffect_LaunchMultiProjectile multiComp)
+ {
+ return multiComp;
+ }
+ }
+ return null;
+ }
+ }
+
+ protected override IEnumerable MakeNewToils()
+ {
+ this.FailOnDespawnedOrNull(TargetIndex.A);
+ this.FailOn(() => job.ability == null || (!job.ability.CanCast && !job.ability.Casting));
+
+ AddFinishAction(delegate
+ {
+ if (job.ability != null && job.def.abilityCasting)
+ {
+ job.ability.StartCooldown(job.ability.def.cooldownTicksRange.RandomInRange);
+ }
+
+ // 停止多射弹发射
+ MultiProjectileComp?.StopMultiProjectile();
+ });
+
+ // 停止移动
+ Toil stopToil = ToilMaker.MakeToil("StopBeforeMultiProjectileCast");
+ stopToil.initAction = delegate
+ {
+ pawn.pather.StopDead();
+ };
+ stopToil.defaultCompleteMode = ToilCompleteMode.Instant;
+ yield return stopToil;
+
+ // 施法动作
+ Toil castToil = Toils_Combat.CastVerb(TargetIndex.A, TargetIndex.B, canHitNonTargetPawns: false);
+ if (job.ability != null && job.ability.def.showCastingProgressBar && job.verbToUse != null)
+ {
+ castToil.WithProgressBar(TargetIndex.A, () => job.verbToUse.WarmupProgress);
+ }
+ yield return castToil;
+
+ // 持续发射阶段
+ Toil maintainToil = ToilMaker.MakeToil("MaintainMultiProjectile");
+ maintainToil.initAction = delegate
+ {
+ pawn.pather.StopDead();
+ rotateToFace = TargetIndex.A;
+
+ // 如果组件有目标单元格,更新job的目标
+ var multiComp = MultiProjectileComp;
+ if (multiComp != null && multiComp.TargetCell.HasValue)
+ {
+ job.targetA = new LocalTargetInfo(multiComp.TargetCell.Value);
+ }
+ };
+ maintainToil.tickAction = delegate
+ {
+ pawn.pather.StopDead();
+
+ var multiComp = MultiProjectileComp;
+ if (multiComp == null || !multiComp.IsActive)
+ {
+ EndJobWith(JobCondition.Succeeded);
+ return;
+ }
+
+ // 更新目标(如果有必要)- 修正类型转换
+ if (multiComp.TargetCell.HasValue && multiComp.TargetCell.Value.IsValid)
+ {
+ // 将 IntVec3? 转换为 LocalTargetInfo
+ job.targetA = new LocalTargetInfo(multiComp.TargetCell.Value);
+ }
+
+ // 继续发射射弹(通过组件的Tick方法)
+ // 组件会在其CompTick中处理发射逻辑
+ };
+
+
+ maintainToil.FailOn(() => pawn.Dead || pawn.Downed || !pawn.Spawned);
+ maintainToil.handlingFacing = true;
+ maintainToil.defaultCompleteMode = ToilCompleteMode.Never;
+ yield return maintainToil;
+ }
+
+ public override string GetReport()
+ {
+ if (job?.ability != null)
+ {
+ return "UsingVerbNoTarget".Translate(job.verbToUse.ReportLabel);
+ }
+
+ return base.GetReport();
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Ability/CompAbilityEffect_RequiresNonHostility.cs b/Source/WulaFallenEmpire/Ability/WULA_RequiresNonHostility/CompAbilityEffect_RequiresNonHostility.cs
similarity index 100%
rename from Source/WulaFallenEmpire/Ability/CompAbilityEffect_RequiresNonHostility.cs
rename to Source/WulaFallenEmpire/Ability/WULA_RequiresNonHostility/CompAbilityEffect_RequiresNonHostility.cs
diff --git a/Source/WulaFallenEmpire/Ability/CompAbilityEffect_ResearchPrereq.cs b/Source/WulaFallenEmpire/Ability/WULA_ResearchPrereq/CompAbilityEffect_ResearchPrereq.cs
similarity index 100%
rename from Source/WulaFallenEmpire/Ability/CompAbilityEffect_ResearchPrereq.cs
rename to Source/WulaFallenEmpire/Ability/WULA_ResearchPrereq/CompAbilityEffect_ResearchPrereq.cs
diff --git a/Source/WulaFallenEmpire/BuildingComp/Building_ExtraGraphics.cs b/Source/WulaFallenEmpire/Building/Building_ExtraGraphics.cs
similarity index 100%
rename from Source/WulaFallenEmpire/BuildingComp/Building_ExtraGraphics.cs
rename to Source/WulaFallenEmpire/Building/Building_ExtraGraphics.cs
diff --git a/Source/WulaFallenEmpire/BuildingComp/Building_MapObserver.cs b/Source/WulaFallenEmpire/Building/Building_MapObserver.cs
similarity index 100%
rename from Source/WulaFallenEmpire/BuildingComp/Building_MapObserver.cs
rename to Source/WulaFallenEmpire/Building/Building_MapObserver.cs
diff --git a/Source/WulaFallenEmpire/BuildingComp/Building_TurretGunHasSpeed.cs b/Source/WulaFallenEmpire/Building/Building_TurretGunHasSpeed.cs
similarity index 100%
rename from Source/WulaFallenEmpire/BuildingComp/Building_TurretGunHasSpeed.cs
rename to Source/WulaFallenEmpire/Building/Building_TurretGunHasSpeed.cs
diff --git a/Source/WulaFallenEmpire/BuildingComp/CompPathCostUpdater.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_PathCostUpdater/CompPathCostUpdater.cs
similarity index 100%
rename from Source/WulaFallenEmpire/BuildingComp/CompPathCostUpdater.cs
rename to Source/WulaFallenEmpire/BuildingComp/WULA_PathCostUpdater/CompPathCostUpdater.cs
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformAtFullCapacity.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformAtFullCapacity.cs
deleted file mode 100644
index f0e7039e..00000000
--- a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformAtFullCapacity.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// CompProperties_TransformAtFullCapacity.cs
-using RimWorld;
-using Verse;
-
-namespace WulaFallenEmpire
-{
- public class CompProperties_TransformAtFullCapacity : CompProperties
- {
- public PawnKindDef targetPawnKind;
- public int requiredCapacity = 5;
-
- public string gizmoLabel = "转换为机械单位";
- public string gizmoDesc = "将储存的机械族转换为一个强大的机械单位。";
- public string gizmoIconPath;
-
- public CompProperties_TransformAtFullCapacity()
- {
- compClass = typeof(CompTransformAtFullCapacity);
- }
- }
-}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformIntoBuilding.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformIntoBuilding.cs
deleted file mode 100644
index aaa27041..00000000
--- a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompProperties_TransformIntoBuilding.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-
-// CompProperties_TransformIntoBuilding.cs
-using RimWorld;
-using Verse;
-
-namespace WulaFallenEmpire
-{
- public class CompProperties_TransformIntoBuilding : CompProperties
- {
- public ThingDef targetBuildingDef;
-
- public string gizmoLabel = "部署为建筑";
- public string gizmoDesc = "转换为功能建筑形态。";
- public string gizmoIconPath;
-
- public CompProperties_TransformIntoBuilding()
- {
- compClass = typeof(CompTransformIntoBuilding);
- }
- }
-}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformAtFullCapacity.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformAtFullCapacity.cs
deleted file mode 100644
index 93d7d529..00000000
--- a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformAtFullCapacity.cs
+++ /dev/null
@@ -1,170 +0,0 @@
-using RimWorld;
-using System.Collections.Generic;
-using UnityEngine;
-using Verse;
-
-namespace WulaFallenEmpire
-{
- public class CompTransformAtFullCapacity : ThingComp
- {
- private CompProperties_TransformAtFullCapacity Props => (CompProperties_TransformAtFullCapacity)props;
-
- // 移除存储计数的字段,不再进行数量传递
-
- public Building_MechanoidRecycler Recycler => parent as Building_MechanoidRecycler;
- public bool IsCooldownActive => Recycler?.IsCooldownActive ?? false;
- public bool IsAtFullCapacity => Recycler?.StoredCount >= Props.requiredCapacity;
-
- public override void Initialize(CompProperties props)
- {
- base.Initialize(props);
- }
-
- public override void PostExposeData()
- {
- base.PostExposeData();
- // 移除存储计数的保存
- }
-
- public override IEnumerable CompGetGizmosExtra()
- {
- if (parent.Faction == Faction.OfPlayer && Recycler != null)
- {
- Command_Action command = new Command_Action
- {
- defaultLabel = Props.gizmoLabel.Translate(),
- defaultDesc = GetGizmoDescription(),
- icon = GetGizmoIcon(),
- action = TransformToPawn
- };
-
- // 禁用条件
- if (IsCooldownActive)
- {
- command.Disable("WULA_BuildingCooldown".Translate(Recycler.GetRemainingCooldownHours().ToString("F1")));
- }
- else if (!IsAtFullCapacity)
- {
- command.Disable("WULA_NeedMoreMechs".Translate(Props.requiredCapacity, Recycler.StoredCount, Props.requiredCapacity));
- }
-
- yield return command;
- }
- }
-
- private string GetGizmoDescription()
- {
- string desc = Props.gizmoDesc.Translate();
- if (IsCooldownActive)
- {
- desc += "\n\n" + "WULA_CooldownRemaining".Translate(Recycler.GetRemainingCooldownHours().ToString("F1"));
- }
- desc += "\n" + "WULA_TargetUnit".Translate(Props.targetPawnKind.LabelCap);
- desc += "\n" + "WULA_MechsRequired".Translate(Props.requiredCapacity);
- return desc;
- }
-
- private Texture2D GetGizmoIcon()
- {
- if (!Props.gizmoIconPath.NullOrEmpty())
- {
- return ContentFinder.Get(Props.gizmoIconPath);
- }
- return TexCommand.ReleaseAnimals;
- }
-
- public void NotifyStorageUpdated()
- {
- // 当存储更新时,可以触发视觉效果
- if (IsAtFullCapacity && !IsCooldownActive)
- {
- // 播放满容量提示效果
- //MoteMaker.ThrowLightningGlow(parent.DrawPos, parent.Map, 2f);
- }
- }
-
- private void PlayTransformEffects(IntVec3 position, Map map)
- {
- //// 播放转换视觉效果
- //for (int i = 0; i < 3; i++)
- //{
- // MoteMaker.ThrowSmoke(position.ToVector3Shifted() + new Vector3(0, 0, 0.5f), map, 1.5f);
- // MoteMaker.ThrowLightningGlow(position.ToVector3Shifted(), map, 2f);
- //}
-
- //// 播放音效
- //SoundDefOf.PsychicPulseGlobal.PlayOneShot(new TargetInfo(position, map));
- }
-
- public void TransformToPawn()
- {
- if (Recycler == null || !parent.Spawned)
- return;
- Map map = parent.Map;
- IntVec3 position = parent.Position;
- Faction faction = parent.Faction;
- // 记录建筑定义用于后续更新
- ThingDef buildingDef = parent.def;
- // 消耗存储的机械族
- if (!Recycler.ConsumeMechanoids(Props.requiredCapacity))
- {
- Messages.Message("WULA_NotEnoughMechs".Translate(), MessageTypeDefOf.RejectInput);
- return;
- }
- // 生成目标Pawn
- PawnGenerationRequest request = new PawnGenerationRequest(
- Props.targetPawnKind,
- faction,
- PawnGenerationContext.NonPlayer,
- -1,
- forceGenerateNewPawn: true,
- allowDead: false,
- allowDowned: false,
- canGeneratePawnRelations: false,
- mustBeCapableOfViolence: true
- );
- Pawn newPawn = PawnGenerator.GeneratePawn(request);
-
- // 关键修改:传递当前的机械族数量(6个)
- var transformComp = newPawn.GetComp();
- if (transformComp != null)
- {
- // 传递建筑定义和机械族数量
- transformComp.SetRestoreData(parent.def, Props.requiredCapacity);
- }
- else
- {
- // 动态添加组件
- var compProps = new CompProperties_TransformIntoBuilding
- {
- targetBuildingDef = parent.def
- };
- transformComp = new CompTransformIntoBuilding();
- transformComp.parent = newPawn;
- transformComp.props = compProps;
- newPawn.AllComps.Add(transformComp);
- transformComp.Initialize(compProps);
- // 传递建筑定义和机械族数量
- transformComp.SetRestoreData(parent.def, Props.requiredCapacity);
- }
- // 移除建筑
- parent.DeSpawn(DestroyMode.Vanish);
-
- // 生成Pawn
- GenSpawn.Spawn(newPawn, position, map, WipeMode.Vanish);
-
- // 选中新生成的Pawn
- if (Find.Selector.IsSelected(parent))
- {
- Find.Selector.Select(newPawn);
- }
- Messages.Message("WULA_BuildingTransformedToPawn".Translate(parent.Label, newPawn.LabelCap, Props.requiredCapacity),
- MessageTypeDefOf.PositiveEvent);
-
- // 播放转换效果
- PlayTransformEffects(position, map);
-
- WulaLog.Debug($"[TransformSystem] Building -> Pawn transformation completed at {position}. Path grid updated.");
- }
- }
-}
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformIntoBuilding.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformIntoBuilding.cs
deleted file mode 100644
index b85f2908..00000000
--- a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/CompTransformIntoBuilding.cs
+++ /dev/null
@@ -1,294 +0,0 @@
-using RimWorld;
-using System.Collections.Generic;
-using UnityEngine;
-using Verse;
-using System.Text;
-
-namespace WulaFallenEmpire
-{
- public class CompTransformIntoBuilding : ThingComp
- {
- private CompProperties_TransformIntoBuilding Props => (CompProperties_TransformIntoBuilding)props;
- private Pawn Pawn => (Pawn)parent;
-
- // 恢复数据 - 存储建筑定义和机械族数量
- private ThingDef restoreBuildingDef;
- private int restoreMechCount = 6; // 默认6个,符合你的需求
-
- // 缓存校验结果
- private bool? lastValidationResult = null;
- private string lastValidationReason = null;
- private int lastValidationTick = 0;
-
- public override void Initialize(CompProperties props)
- {
- base.Initialize(props);
- }
-
- public override void PostExposeData()
- {
- base.PostExposeData();
- Scribe_Defs.Look(ref restoreBuildingDef, "restoreBuildingDef");
- Scribe_Values.Look(ref restoreMechCount, "restoreMechCount", 6); // 默认6个
- }
-
- // 设置恢复数据 - 设置建筑定义和机械族数量
- public void SetRestoreData(ThingDef buildingDef, int mechCount = 6)
- {
- restoreBuildingDef = buildingDef;
- restoreMechCount = mechCount;
- }
-
- public override IEnumerable CompGetGizmosExtra()
- {
- if (parent.Faction == Faction.OfPlayer && Pawn != null)
- {
- Command_Action command = new Command_Action
- {
- defaultLabel = Props.gizmoLabel.Translate(),
- defaultDesc = GetGizmoDescription(),
- icon = GetGizmoIcon(),
- action = TransformToBuilding
- };
-
- // 检查是否可以转换
- string failReason;
- bool canTransform = CanTransformNow(out failReason);
-
- if (!canTransform)
- {
- command.Disable(failReason);
- }
-
- yield return command;
- }
- }
-
- private string GetGizmoDescription()
- {
- StringBuilder sb = new StringBuilder();
- sb.Append(Props.gizmoDesc.Translate());
-
- if (restoreBuildingDef != null)
- {
- sb.AppendLine();
- sb.AppendLine();
- sb.Append("WULA_WillRestoreTo".Translate(restoreBuildingDef.LabelCap));
-
- // 显示恢复的机械族数量
- sb.AppendLine();
-
- // 显示目标建筑的最大存储容量
- var recyclerProps = restoreBuildingDef.GetCompProperties();
- if (recyclerProps != null)
- {
- sb.AppendLine();
- sb.Append("WULA_MaxStorageCapacity".Translate(recyclerProps.maxStorageCapacity));
- }
- }
-
- // 添加空间校验信息
- string failReason;
- bool isValid = CanTransformNow(out failReason);
-
- sb.AppendLine();
- sb.AppendLine();
- if (isValid)
- {
- sb.Append("WULA_PositionValid".Translate());
- }
- else
- {
- sb.Append("WULA_PositionInvalid".Translate(failReason));
- }
-
- return sb.ToString();
- }
-
- private Texture2D GetGizmoIcon()
- {
- if (!Props.gizmoIconPath.NullOrEmpty())
- {
- return ContentFinder.Get(Props.gizmoIconPath);
- }
- return TexCommand.Install;
- }
-
- ///
- /// 检查是否可以转换(带详细失败原因)
- ///
- private bool CanTransformNow(out string failReason)
- {
- failReason = null;
-
- if (parent == null || !parent.Spawned)
- {
- failReason = "WULA_UnitNotSpawned".Translate();
- return false;
- }
-
- // 确定要生成的建筑类型
- ThingDef buildingDef = restoreBuildingDef ?? Props.targetBuildingDef;
- if (buildingDef == null)
- {
- failReason = "WULA_CannotDetermineBuildingType".Translate();
- return false;
- }
-
- // 使用缓存优化性能(每60 tick检查一次)
- if (lastValidationResult.HasValue && Find.TickManager.TicksGame - lastValidationTick < 60)
- {
- failReason = lastValidationReason;
- return lastValidationResult.Value;
- }
-
- // 执行完整的空间校验(排除被转换的Pawn本身)
- bool isValid = TransformValidationUtility.CanPlaceBuildingAt(
- buildingDef,
- Pawn.Position,
- Pawn.Map,
- Pawn.Faction,
- Pawn, // 排除被转换的Pawn本身
- out failReason
- );
-
- // 更新缓存
- lastValidationResult = isValid;
- lastValidationReason = failReason;
- lastValidationTick = Find.TickManager.TicksGame;
-
- return isValid;
- }
-
- ///
- /// 查找最近的可用位置
- ///
- private bool TryFindNearbyValidPosition(out IntVec3 validPosition, out string failReason)
- {
- validPosition = IntVec3.Invalid;
- failReason = null;
-
- ThingDef buildingDef = restoreBuildingDef ?? Props.targetBuildingDef;
- if (buildingDef == null)
- {
- failReason = "WULA_CannotDetermineBuildingType".Translate();
- return false;
- }
-
- // 在周围搜索可用位置
- for (int radius = 1; radius <= 5; radius++)
- {
- foreach (IntVec3 cell in GenRadial.RadialPatternInRadius(radius))
- {
- IntVec3 checkPos = Pawn.Position + cell;
-
- if (TransformValidationUtility.CanPlaceBuildingAt(buildingDef, checkPos, Pawn.Map, Pawn.Faction, Pawn, out failReason))
- {
- validPosition = checkPos;
- return true;
- }
- }
- }
-
- failReason = "WULA_NoSuitablePositionFound".Translate();
- return false;
- }
-
- private void PlayTransformEffects(IntVec3 position, Map map)
- {
- // 播放转换视觉效果
- //for (int i = 0; i < 3; i++)
- //{
- // MoteMaker.ThrowSmoke(position.ToVector3Shifted() + new Vector3(0, 0, 0.5f), map, 1.5f);
- // MoteMaker.ThrowMicroSparks(position.ToVector3Shifted(), map);
- //}
- }
-
- // 每tick更新校验状态(用于实时反馈)
- public override void CompTick()
- {
- base.CompTick();
-
- // 每60 tick清除一次缓存,确保校验结果实时更新
- if (Find.TickManager.TicksGame % 60 == 0)
- {
- lastValidationResult = null;
- }
- }
-
- public void TransformToBuilding()
- {
- if (Pawn == null || !Pawn.Spawned)
- return;
- Map map = Pawn.Map;
- IntVec3 desiredPosition = Pawn.Position;
- Faction faction = Pawn.Faction;
- // 确定要生成的建筑类型
- ThingDef buildingDef = restoreBuildingDef ?? Props.targetBuildingDef;
- if (buildingDef == null)
- {
- Messages.Message("WULA_CannotDetermineBuildingType".Translate(), MessageTypeDefOf.RejectInput);
- return;
- }
- // 最终校验(排除被转换的Pawn本身)
- string failReason;
- if (!TransformValidationUtility.CanPlaceBuildingAt(buildingDef, desiredPosition, map, faction, Pawn, out failReason))
- {
- // 尝试寻找附近的位置
- IntVec3 alternativePosition;
- if (TryFindNearbyValidPosition(out alternativePosition, out failReason))
- {
- desiredPosition = alternativePosition;
- Messages.Message("WULA_DeployingAtNearbyPosition".Translate(desiredPosition), MessageTypeDefOf.NeutralEvent);
- }
- else
- {
- Messages.Message("WULA_CannotDeployBuilding".Translate(failReason), MessageTypeDefOf.RejectInput);
- return;
- }
- }
- // 移除Pawn
- Pawn.DeSpawn(DestroyMode.Vanish);
- // 生成建筑
- Building newBuilding = (Building)GenSpawn.Spawn(buildingDef, desiredPosition, map, WipeMode.Vanish);
- newBuilding.SetFaction(faction);
- // 关键修改:恢复机械族数量
- var recycler = newBuilding as Building_MechanoidRecycler;
- if (recycler != null)
- {
- recycler.SetMechanoidCount(restoreMechCount);
- }
- // 添加建筑转换组件
- var transformComp = newBuilding.TryGetComp();
- if (transformComp == null)
- {
- // 动态添加组件
- var compProps = new CompProperties_TransformAtFullCapacity
- {
- targetPawnKind = Pawn.kindDef
- };
- transformComp = new CompTransformAtFullCapacity();
- transformComp.parent = newBuilding;
- transformComp.props = compProps;
- newBuilding.AllComps.Add(transformComp);
- transformComp.Initialize(compProps);
- }
- // 选中新生成的建筑
- if (Find.Selector.IsSelected(Pawn))
- {
- Find.Selector.Select(newBuilding);
- }
- Messages.Message("WULA_PawnDeployedAsBuilding".Translate(Pawn.LabelCap, newBuilding.Label, restoreMechCount),
- MessageTypeDefOf.PositiveEvent);
-
- // 播放转换效果
- PlayTransformEffects(desiredPosition, map);
-
- // 清除缓存
- lastValidationResult = null;
- lastValidationReason = null;
-
- WulaLog.Debug($"[TransformSystem] Pawn -> Building transformation completed at {desiredPosition}. Path grid updated.");
- }
- }
-}
diff --git a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/TransformValidationUtility.cs b/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/TransformValidationUtility.cs
deleted file mode 100644
index b2661c20..00000000
--- a/Source/WulaFallenEmpire/BuildingComp/WULA_TransformAtFullCapacity/TransformValidationUtility.cs
+++ /dev/null
@@ -1,220 +0,0 @@
-using RimWorld;
-using System.Collections.Generic;
-using System.Text;
-using UnityEngine;
-using Verse;
-using System.Linq;
-
-namespace WulaFallenEmpire
-{
- public static class TransformValidationUtility
- {
- ///
- /// 检查位置是否可以放置目标建筑(通用版本)
- ///
- public static bool CanPlaceBuildingAt(ThingDef buildingDef, IntVec3 center, Map map, Faction faction, out string failReason)
- {
- return CanPlaceBuildingAt(buildingDef, center, map, faction, null, out failReason);
- }
-
- ///
- /// 检查位置是否可以放置目标建筑(带忽略物体版本)
- ///
- public static bool CanPlaceBuildingAt(ThingDef buildingDef, IntVec3 center, Map map, Faction faction, Thing thingToIgnore, out string failReason)
- {
- failReason = null;
-
- // 1. 检查建筑定义
- if (buildingDef == null)
- {
- failReason = "目标建筑定义为空";
- return false;
- }
-
- // 2. 检查建筑尺寸是否为奇数(根据您的需求)
- if (!IsOddSizedBuilding(buildingDef))
- {
- failReason = $"建筑 {buildingDef.LabelCap} 的尺寸不是奇数,不符合放置要求";
- return false;
- }
-
- // 3. 检查地图边界
- if (!IsWithinMapBounds(buildingDef, center, map, out failReason))
- return false;
-
- // 4. 检查地形是否可建造
- if (!HasBuildableTerrain(buildingDef, center, map, out failReason))
- return false;
-
- // 5. 检查其他建筑挤占(排除要忽略的物体)
- if (!IsAreaClearOfBlockingBuildings(buildingDef, center, map, thingToIgnore, out failReason))
- return false;
-
- // 6. 检查特殊放置条件(如不可放置在水上等)
- if (!MeetsSpecialPlacementConditions(buildingDef, center, map, out failReason))
- return false;
-
- // 7. 使用RimWorld原生的放置检查(最终验证)- 移除派系限制
- if (!GenConstruct.CanPlaceBlueprintAt(buildingDef, center, Rot4.North, map, false, null, null, null))
- {
- failReason = "该位置不符合建筑放置要求";
- return false;
- }
-
- return true;
- }
-
- ///
- /// 检查建筑尺寸是否为奇数
- ///
- public static bool IsOddSizedBuilding(ThingDef buildingDef)
- {
- return buildingDef.Size.x % 2 == 1 && buildingDef.Size.z % 2 == 1;
- }
-
- ///
- /// 检查地图边界
- ///
- private static bool IsWithinMapBounds(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
- {
- CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
-
- if (!occupiedRect.InBounds(map))
- {
- failReason = "建筑超出地图边界";
- return false;
- }
-
- failReason = null;
- return true;
- }
-
- ///
- /// 检查地形是否可建造 - 重新设计的逻辑
- ///
- private static bool HasBuildableTerrain(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
- {
- CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
-
- foreach (IntVec3 cell in occupiedRect)
- {
- if (!cell.InBounds(map))
- continue;
-
- TerrainDef terrain = map.terrainGrid.TerrainAt(cell);
- if (terrain == null)
- {
- failReason = "未知地形";
- return false;
- }
-
- // 检查是否在水体上
- if (terrain.IsWater)
- {
- failReason = $"无法在水体 ({terrain.LabelCap}) 上放置建筑";
- return false;
- }
-
- // 检查affordance - 使用更宽松的逻辑
- if (!HasValidAffordance(buildingDef, terrain))
- {
- failReason = $"地形 {terrain.LabelCap} 不支持放置 {buildingDef.LabelCap}";
- return false;
- }
- }
-
- failReason = null;
- return true;
- }
-
- ///
- /// 检查affordance兼容性 - 重新设计的逻辑
- ///
- private static bool HasValidAffordance(ThingDef buildingDef, TerrainDef terrain)
- {
- // 如果建筑没有指定affordance要求,则跳过检查
- if (buildingDef.terrainAffordanceNeeded == null)
- return true;
-
- // 如果地形没有affordances列表,则检查失败
- if (terrain.affordances == null || terrain.affordances.Count == 0)
- return false;
-
- // 检查地形是否提供建筑所需的affordance
- return terrain.affordances.Contains(buildingDef.terrainAffordanceNeeded);
- }
-
- ///
- /// 检查区域是否被其他建筑阻挡(排除指定物体)
- ///
- private static bool IsAreaClearOfBlockingBuildings(ThingDef buildingDef, IntVec3 center, Map map, Thing thingToIgnore, out string failReason)
- {
- CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
- List blockingThings = new List();
-
- foreach (IntVec3 cell in occupiedRect)
- {
- if (!cell.InBounds(map))
- continue;
-
- // 检查该单元格上的所有建筑
- List thingList = map.thingGrid.ThingsListAt(cell);
- foreach (Thing thing in thingList)
- {
- // 跳过要忽略的物体(如被转换的Pawn本身)
- if (thing == thingToIgnore)
- continue;
-
- if (thing.def.category == ThingCategory.Building || thing.def.category == ThingCategory.Pawn)
- {
- // 忽略蓝图和框架
- if (thing.def.IsBlueprint || thing.def.IsFrame)
- continue;
-
- // 忽略被转换的Pawn本身
- if (thing == thingToIgnore)
- continue;
-
- blockingThings.Add(thing);
- }
- }
- }
-
- if (blockingThings.Count > 0)
- {
- StringBuilder sb = new StringBuilder();
- sb.AppendLine("以下物体阻挡了建筑放置:");
-
- foreach (Thing thing in blockingThings)
- {
- sb.AppendLine($" - {thing.LabelCap}");
- }
-
- failReason = sb.ToString().TrimEnd();
- return false;
- }
-
- failReason = null;
- return true;
- }
-
- ///
- /// 检查特殊放置条件
- ///
- private static bool MeetsSpecialPlacementConditions(ThingDef buildingDef, IntVec3 center, Map map, out string failReason)
- {
- CellRect occupiedRect = GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size);
-
- failReason = null;
- return true;
- }
-
- ///
- /// 获取建筑占用的所有单元格
- ///
- public static List GetOccupiedCells(ThingDef buildingDef, IntVec3 center)
- {
- return GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size).Cells.ToList();
- }
- }
-}
diff --git a/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs b/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
index 47987e67..88d55940 100644
--- a/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
+++ b/Source/WulaFallenEmpire/GlobalWorkTable/Building_GlobalWorkTable.cs
@@ -22,7 +22,7 @@ namespace WulaFallenEmpire
private static readonly Dictionary StuffCategoryMapping = new Dictionary
{
{ StuffCategoryDefOf.Metallic, ThingDefOf.Plasteel }, // 金属类 -> 玻璃钢
- { StuffCategoryDefOf.Fabric, ThingDefOf_WULA.Hyperweave } // 布革类 -> 超织物
+ { StuffCategoryDefOf.Fabric, Wula_ThingDefOf.Hyperweave } // 布革类 -> 超织物
};
public Building_GlobalWorkTable()
@@ -628,7 +628,7 @@ namespace WulaFallenEmpire
else if (fabricCategory != null)
{
// 布革类 -> 超织物
- selectedStuff = ThingDefOf_WULA.Hyperweave;
+ selectedStuff = Wula_ThingDefOf.Hyperweave;
}
// 创建带有指定材质的物品
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_ColonistBarMech.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_ColonistBarMech.cs
new file mode 100644
index 00000000..cdcec392
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_ColonistBarMech.cs
@@ -0,0 +1,181 @@
+// File: Patches/ColonistBarMechPatch_Fixed.cs
+using HarmonyLib;
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using Verse;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ [HarmonyPatch(typeof(ColonistBar), "CheckRecacheEntries")]
+ public static class Patch_ColonistBarMech_Fixed
+ {
+ [HarmonyPostfix]
+ public static void Postfix(ref List ___cachedEntries)
+ {
+ // 安全检查:只在玩家派系存在时运行
+ if (Faction.OfPlayer == null)
+ return;
+
+ try
+ {
+ // 建立机甲和驾驶员的映射关系
+ var mechToPilots = new Dictionary>();
+ var pilotToMech = new Dictionary();
+ var mechEntries = new HashSet();
+
+ // 只扫描玩家殖民地的地图
+ foreach (var map in Find.Maps.Where(m => m.IsPlayerHome))
+ {
+ foreach (var pawn in map.mapPawns.AllPawnsSpawned)
+ {
+ // 只处理玩家殖民地的机甲(Wulamechunit)
+ if (pawn is Wulamechunit mech &&
+ (mech.Faction == Faction.OfPlayer || mech.HostFaction == Faction.OfPlayer))
+ {
+ var pilotComp = mech.TryGetComp();
+ if (pilotComp != null && pilotComp.HasPilots)
+ {
+ var pilots = pilotComp.GetPilots()
+ .Where(p => p.Faction == Faction.OfPlayer || p.HostFaction == Faction.OfPlayer)
+ .ToList();
+
+ if (pilots.Count > 0)
+ {
+ mechToPilots[mech] = pilots;
+ foreach (var pilot in pilots)
+ {
+ pilotToMech[pilot] = mech;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // 如果没有找到有机甲驾驶员的机甲,直接返回
+ if (mechToPilots.Count == 0)
+ return;
+
+ // 创建新的条目列表
+ var newEntries = new List();
+
+ // 第一轮:处理原始条目,隐藏驾驶员
+ foreach (var entry in ___cachedEntries)
+ {
+ var pawn = entry.pawn;
+ if (pawn == null)
+ {
+ // 保留空条目
+ newEntries.Add(entry);
+ continue;
+ }
+
+ // 如果是驾驶员,跳过(隐藏)
+ if (pilotToMech.ContainsKey(pawn))
+ {
+ continue;
+ }
+
+ // 如果是机甲
+ if (mechToPilots.ContainsKey(pawn))
+ {
+ // 确保机甲还没有被添加,并且有驾驶员
+ if (!mechEntries.Contains(pawn) && mechToPilots[pawn].Count > 0)
+ {
+ newEntries.Add(entry);
+ mechEntries.Add(pawn);
+ }
+ }
+ else
+ {
+ // 普通殖民者(不属于任何机甲的驾驶员)
+ newEntries.Add(entry);
+ }
+ }
+
+ // 第二轮:确保有机甲驾驶员的机甲被添加到列表
+ foreach (var mech in mechToPilots.Keys)
+ {
+ // 如果机甲还没有被添加,且有驾驶员
+ if (!mechEntries.Contains(mech) && mechToPilots[mech].Count > 0)
+ {
+ // 需要创建一个新的Entry
+ var map = mech.MapHeld;
+ if (map != null && map.IsPlayerHome)
+ {
+ // 计算group:需要找到地图对应的group
+ int group = GetGroupForMap(map, ___cachedEntries);
+ newEntries.Add(new ColonistBar.Entry(mech, map, group));
+ mechEntries.Add(mech);
+ }
+ else if (mechToPilots[mech].Count > 0)
+ {
+ // 如果机甲不在任何地图上(可能被携带等),但仍有驾驶员
+ // 使用第一个驾驶员的地图和group作为参考
+ var pilot = mechToPilots[mech].First();
+ var pilotMap = pilot.MapHeld;
+ if (pilotMap != null && pilotMap.IsPlayerHome)
+ {
+ int group = GetGroupForMap(pilotMap, ___cachedEntries);
+ newEntries.Add(new ColonistBar.Entry(mech, pilotMap, group));
+ mechEntries.Add(mech);
+ }
+ }
+ }
+ }
+
+ // 替换原列表
+ ___cachedEntries = newEntries;
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"[DD] Error in fixed ColonistBar patch: {ex}");
+ // 出错时不改变原列表
+ }
+ }
+
+ // 辅助方法:获取地图对应的group
+ private static int GetGroupForMap(Map map, List originalEntries)
+ {
+ if (map == null)
+ return 0;
+
+ // 在原始条目中查找第一个属于该地图的条目,返回它的group
+ var entry = originalEntries.FirstOrDefault(e => e.map == map && e.pawn != null);
+ if (entry.pawn != null)
+ {
+ return entry.group;
+ }
+
+ // 如果没有找到有pawn的条目,尝试查找任何属于该地图的条目(包括空条目)
+ entry = originalEntries.FirstOrDefault(e => e.map == map);
+ if (entry.map != null)
+ {
+ return entry.group;
+ }
+
+ // 如果还是找不到,返回0作为默认值
+ return 0;
+ }
+
+ // 备用方案:使用更简单的方法计算group
+ private static int CalculateGroupForMap(Map map)
+ {
+ if (map == null)
+ return 0;
+
+ // 简单的计算:玩家殖民地地图的索引
+ // 注意:这可能不完全准确,但通常工作
+ var playerMaps = Find.Maps.Where(m => m.IsPlayerHome).ToList();
+ int index = playerMaps.IndexOf(map);
+
+ // 如果找不到,返回0
+ if (index < 0)
+ return 0;
+
+ return index;
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_MechSpecificWeapon.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_MechSpecificWeapon.cs
new file mode 100644
index 00000000..a2b4b048
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_MechSpecificWeapon.cs
@@ -0,0 +1,74 @@
+// File: Patches/Patch_Wulamechunit.cs (修改EquipmentUtility_CanEquip_Patch部分)
+using HarmonyLib;
+using RimWorld;
+using System;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ [HarmonyPatch(typeof(EquipmentUtility), nameof(EquipmentUtility.CanEquip), new Type[] { typeof(Thing), typeof(Pawn), typeof(string), typeof(bool) }, new ArgumentType[] { ArgumentType.Normal, ArgumentType.Normal, ArgumentType.Out, ArgumentType.Normal })]
+ public static class EquipmentUtility_CanEquip_Patch
+ {
+ [HarmonyPrefix]
+ public static bool CanEquip_Prefix(Thing thing, Pawn pawn, out string cantReason, ref bool __result)
+ {
+ cantReason = null;
+
+ try
+ {
+ // 检查是否有机甲专用武器组件
+ var mechWeapon = thing?.TryGetComp();
+
+ // 情况1:这是机甲专用武器
+ if (mechWeapon != null)
+ {
+ // 检查是否是机甲
+ if (pawn is Wulamechunit)
+ {
+ // 检查是否允许此机甲使用
+ if (mechWeapon.CanBeEquippedByMech(pawn))
+ {
+ // 机甲可以使用此专用武器
+ return true;
+ }
+ else
+ {
+ // 此机甲不在允许列表中
+ cantReason = "DD_Equipment_For_Other_Mech".Translate();
+ __result = false;
+ return false;
+ }
+ }
+ else
+ {
+ // 非机甲尝试装备专用武器,禁止
+ cantReason = "DD_Human_Cannot_Equip_Mech_Weapon".Translate();
+ __result = false;
+ return false;
+ }
+ }
+ // 情况2:这是普通武器
+ else if (thing?.def?.IsWeapon == true)
+ {
+ // 检查是否是机甲
+ if (pawn is Wulamechunit)
+ {
+ // 机甲不能装备普通武器
+ cantReason = "DD_Equipment_Not_Allow_For_Mech".Translate();
+ __result = false;
+ return false;
+ }
+ // 非机甲可以装备普通武器,继续检查
+ }
+
+ // 情况3:不是武器或不是机甲,按原逻辑处理
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] CanEquip patch error: {ex}");
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_RomanceFix.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_RomanceFix.cs
new file mode 100644
index 00000000..2d42f528
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_RomanceFix.cs
@@ -0,0 +1,130 @@
+// File: Patch_RomanceFix.cs
+using HarmonyLib;
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 修复浪漫关系菜单相关的空引用异常
+ ///
+ public static class RomancePatches
+ {
+ ///
+ /// 补丁:防止对机甲单位显示浪漫菜单
+ ///
+ [HarmonyPatch(typeof(FloatMenuOptionProvider_Romance))]
+ [HarmonyPatch("GetSingleOptionFor")]
+ public static class Patch_FloatMenuOptionProvider_Romance_GetSingleOptionFor
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn clickedPawn, ref FloatMenuOption __result)
+ {
+ // 如果是机甲单位,直接返回null
+ if (clickedPawn is Wulamechunit)
+ {
+ __result = null;
+ return false; // 跳过原始方法
+ }
+
+ // 额外检查:如果clickedPawn没有story组件,也跳过
+ if (clickedPawn?.story == null)
+ {
+ __result = null;
+ return false;
+ }
+
+ return true; // 继续执行原始方法
+ }
+ }
+
+ ///
+ /// 补丁:防止在爱情关系检查中出现空引用
+ ///
+ [HarmonyPatch(typeof(LovePartnerRelationUtility))]
+ [HarmonyPatch("ExistingLovePartners")]
+ public static class Patch_LovePartnerRelationUtility_ExistingLovePartners
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn pawn, bool allowDead, ref List __result)
+ {
+ // 如果pawn是机甲单位,返回空列表
+ if (pawn is Wulamechunit)
+ {
+ __result = new List();
+ return false; // 跳过原始方法
+ }
+
+ // 如果pawn没有story组件,返回空列表
+ if (pawn?.story == null)
+ {
+ __result = new List();
+ return false;
+ }
+
+ return true; // 继续执行原始方法
+ }
+ }
+
+ ///
+ /// 补丁:防止浪漫关系配对检查中的空引用
+ ///
+ [HarmonyPatch(typeof(RelationsUtility))]
+ [HarmonyPatch("RomanceEligiblePair")]
+ public static class Patch_RelationsUtility_RomanceEligiblePair
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn initiator, Pawn target, bool forOpinionExplanation, ref AcceptanceReport __result)
+ {
+ // 如果任一pawn是机甲单位,返回拒绝
+ if (initiator is Wulamechunit || target is Wulamechunit)
+ {
+ __result = new AcceptanceReport("DD_MechCannotRomance".Translate());
+ return false; // 跳过原始方法
+ }
+
+ // 如果任一pawn没有story组件,返回拒绝
+ if (initiator?.story == null || target?.story == null)
+ {
+ __result = new AcceptanceReport("DD_NoStoryComponent".Translate());
+ return false;
+ }
+
+ return true; // 继续执行原始方法
+ }
+ }
+
+ ///
+ /// 补丁:防止浪漫关系检查中的空引用
+ ///
+ [HarmonyPatch(typeof(RelationsUtility))]
+ [HarmonyPatch("RomanceOption")]
+ public static class Patch_RelationsUtility_RomanceOption
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn initiator, Pawn romanceTarget, ref FloatMenuOption option, ref float chance, ref bool __result)
+ {
+ // 如果任一pawn是机甲单位,返回false
+ if (initiator is Wulamechunit || romanceTarget is Wulamechunit)
+ {
+ __result = false;
+ option = null;
+ chance = 0f;
+ return false; // 跳过原始方法
+ }
+
+ // 如果任一pawn没有story组件,返回false
+ if (initiator?.story == null || romanceTarget?.story == null)
+ {
+ __result = false;
+ option = null;
+ chance = 0f;
+ return false;
+ }
+
+ return true; // 继续执行原始方法
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_TakeDamage.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_TakeDamage.cs
new file mode 100644
index 00000000..87d8308a
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_TakeDamage.cs
@@ -0,0 +1,230 @@
+using HarmonyLib;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+
+namespace WulaFallenEmpire.HarmonyPatches
+{
+ ///
+ /// 整合的伤害处理系统
+ /// 处理:1. 机甲装甲系统 2. HediffComp_Invulnerable免疫系统
+ ///
+ [HarmonyPatch(typeof(Thing))]
+ [HarmonyPatch("TakeDamage")]
+ public static class Thing_TakeDamage_Patch
+ {
+ // 缓存装甲值StatDef
+ private static readonly StatDef ArmorStatDef = StatDef.Named("DD_MechArmor");
+
+ // 阻挡效果的MoteDef
+ private static readonly ThingDef BlockMoteDef = DefDatabase.GetNamedSilentFail("Mote_Spark");
+
+ // 阻挡音效
+ private static readonly SoundDef BlockSoundDef = DefDatabase.GetNamedSilentFail("ArmorBlock");
+
+ // 免疫效果的MoteDef
+ private static readonly ThingDef ImmuneMoteDef = DefDatabase.GetNamedSilentFail("Mote_Immunity");
+
+ // 免疫音效
+ private static readonly SoundDef ImmuneSoundDef = DefDatabase.GetNamedSilentFail("ImmuneSound");
+
+ // 调试统计
+ private static readonly Dictionary DebugStats = new Dictionary();
+
+ private class DamageBlockStats
+ {
+ public int mechArmorBlocked = 0;
+ public int invulnerableBlocked = 0;
+ public int totalHits = 0;
+
+ public int TotalBlocked => mechArmorBlocked + invulnerableBlocked;
+
+ public override string ToString()
+ {
+ return $"机甲装甲阻挡: {mechArmorBlocked}, 免疫阻挡: {invulnerableBlocked}, 总计阻挡: {TotalBlocked}, 总命中: {totalHits}";
+ }
+ }
+
+ ///
+ /// 前置补丁:在TakeDamage执行前检查伤害免疫
+ ///
+ [HarmonyPrefix]
+ public static bool Prefix(Thing __instance, ref DamageInfo dinfo, ref DamageWorker.DamageResult __result)
+ {
+ // 更新调试统计
+ if (!DebugStats.ContainsKey(__instance))
+ DebugStats[__instance] = new DamageBlockStats();
+ DebugStats[__instance].totalHits++;
+
+ // 第一步:检查伤害免疫HediffComp
+ if (__instance is Pawn pawn)
+ {
+ bool blockedByInvulnerable = CheckInvulnerableHediff(pawn, dinfo, out HediffComp_Invulnerable invulnerableComp);
+
+ if (blockedByInvulnerable)
+ {
+ // 被HediffComp免疫阻挡
+ DebugStats[__instance].invulnerableBlocked++;
+
+ // 显示免疫效果
+ ShowImmuneEffect(pawn, dinfo);
+
+ // 播放免疫音效
+ PlayImmuneSound(pawn);
+
+ // 调用HediffComp的OnDamageBlocked方法
+ invulnerableComp?.OnDamageBlocked(dinfo);
+
+ // 返回空结果,跳过原方法
+ __result = new DamageWorker.DamageResult();
+ return false;
+ }
+ }
+
+ // 第二步:检查机甲装甲系统
+ float armorValue = __instance.GetStatValue(ArmorStatDef);
+
+ // 如果装甲值 <= 0,不启动装甲系统,继续原方法
+ if (armorValue <= 0)
+ return true;
+
+ // 计算穿甲伤害
+ float armorPenetration = dinfo.ArmorPenetrationInt;
+ float piercingDamage = dinfo.Amount * armorPenetration;
+
+ // 判断是否应该阻挡
+ bool shouldBlock = piercingDamage < armorValue;
+
+ if (shouldBlock)
+ {
+ // 机甲装甲阻挡成功
+ DebugStats[__instance].mechArmorBlocked++;
+
+ // 显示阻挡效果
+ ShowBlockEffect(__instance, dinfo);
+
+ // 播放阻挡音效
+ PlayBlockSound(__instance);
+
+ // 返回空结果,跳过原方法
+ __result = new DamageWorker.DamageResult();
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// 检查伤害免疫HediffComp
+ ///
+ private static bool CheckInvulnerableHediff(Pawn pawn, DamageInfo dinfo, out HediffComp_Invulnerable invulnerableComp)
+ {
+ invulnerableComp = null;
+
+ if (pawn == null || pawn.health == null || pawn.health.hediffSet == null)
+ return false;
+
+ // 检查所有Hediff,寻找HediffComp_Invulnerable
+ foreach (Hediff hediff in pawn.health.hediffSet.hediffs)
+ {
+ if (hediff.TryGetComp() is HediffComp_Invulnerable comp)
+ {
+ invulnerableComp = comp;
+ return comp.ShouldBlockDamage(dinfo);
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 显示免疫效果
+ ///
+ private static void ShowImmuneEffect(Pawn pawn, DamageInfo dinfo)
+ {
+ if (!pawn.Spawned)
+ return;
+
+ // 显示文字效果
+ Vector3 textPos = pawn.DrawPos + new Vector3(0, 0, 1f);
+ MoteMaker.ThrowText(textPos, pawn.Map, "DD_ImmuneToDamage".Translate(), Color.green, 2.5f);
+
+ // 显示粒子效果
+ if (ImmuneMoteDef != null)
+ {
+ MoteMaker.MakeStaticMote(pawn.DrawPos, pawn.Map, ImmuneMoteDef, 1f);
+ }
+ }
+
+ ///
+ /// 播放免疫音效
+ ///
+ private static void PlayImmuneSound(Pawn pawn)
+ {
+ if (!pawn.Spawned)
+ return;
+
+ if (ImmuneSoundDef != null)
+ {
+ ImmuneSoundDef.PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));
+ }
+ }
+
+ ///
+ /// 显示阻挡效果
+ ///
+ private static void ShowBlockEffect(Thing target, DamageInfo dinfo)
+ {
+ if (!target.Spawned)
+ return;
+
+ // 显示文字效果
+ Vector3 textPos = target.DrawPos + new Vector3(0, 0, 1f);
+ MoteMaker.ThrowText(textPos, target.Map, "DD_BlockByMechArmor".Translate(), Color.yellow, 2.5f);
+
+ // 显示粒子效果
+ if (BlockMoteDef != null)
+ {
+ MoteMaker.MakeStaticMote(target.DrawPos, target.Map, BlockMoteDef, 1f);
+ }
+ }
+
+ ///
+ /// 播放阻挡音效
+ ///
+ private static void PlayBlockSound(Thing target)
+ {
+ if (!target.Spawned)
+ return;
+
+ if (BlockSoundDef != null)
+ {
+ BlockSoundDef.PlayOneShot(new TargetInfo(target.Position, target.Map));
+ }
+ else
+ {
+ // 备用音效
+ SoundDefOf.MetalHitImportant.PlayOneShot(new TargetInfo(target.Position, target.Map));
+ }
+ }
+
+ ///
+ /// 获取调试统计信息
+ ///
+ public static string GetDebugStats()
+ {
+ System.Text.StringBuilder sb = new System.Text.StringBuilder();
+ sb.AppendLine("伤害阻挡统计:");
+
+ foreach (var kvp in DebugStats)
+ {
+ sb.AppendLine($"{kvp.Key.LabelCap}: {kvp.Value}");
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_mechunit.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_mechunit.cs
new file mode 100644
index 00000000..238de388
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patch_mechunit.cs
@@ -0,0 +1,285 @@
+using HarmonyLib;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Verse;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ [HarmonyPatch(typeof(Pawn), "get_CanTakeOrder")]
+ public class Patch_CanTakeOrder
+ {
+ [HarmonyPostfix]
+ public static void postfix(ref bool __result, Pawn __instance)
+ {
+ if (__instance is Wulamechunit && __instance.Drafted)
+ {
+ __result = true;
+ }
+ }
+ }
+ [HarmonyPatch(typeof(PawnComponentsUtility), "AddAndRemoveDynamicComponents")]
+ public class Patch_PawnTracer
+ {
+ [HarmonyPostfix]
+ public static void postfix(Pawn pawn)
+ {
+ if (pawn is Wulamechunit)
+ {
+ pawn.drafter = new Pawn_DraftController(pawn);
+ pawn.skills = new Pawn_SkillTracker(pawn);
+ }
+ }
+ }
+ [HarmonyPatch(typeof(FloatMenuOptionProvider), "SelectedPawnValid")]
+ public class Patch_GetSingleOption
+ {
+ [HarmonyPostfix]
+ public static void postfix(ref bool __result, Pawn pawn)
+ {
+ if (pawn is Wulamechunit)
+ {
+ __result = true;
+ }
+ }
+ }
+ [HarmonyPatch(typeof(FloatMenuUtility), nameof(FloatMenuUtility.GetMeleeAttackAction))]
+ public static class Patch_FloatMenuUtility_GetMeleeAttackAction
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn pawn, LocalTargetInfo target, out string failStr, ref Action __result, bool ignoreControlled = false)
+ {
+ failStr = "";
+
+ if (pawn is Wulamechunit)
+ {
+ // ֱӷ true Ƽ
+ ignoreControlled = true;
+
+ // ֱӵĺķ
+ __result = ModifiedGetMeleeAttackAction(pawn, target, out failStr, ignoreControlled);
+ return false; // ԭʼ
+ }
+
+ return true; // ûִԭʼ
+ }
+
+ private static Action ModifiedGetMeleeAttackAction(Pawn pawn, LocalTargetInfo target, out string failStr, bool ignoreControlled = false)
+ {
+ failStr = "";
+
+ try
+ {
+ // ֱʹԭʼ룬Ƽ
+ if (!pawn.Drafted && !ignoreControlled)
+ {
+ failStr = "IsNotDraftedLower".Translate(pawn.LabelShort, pawn);
+ }
+ else if (target.IsValid && !pawn.CanReach(target, PathEndMode.Touch, Danger.Deadly))
+ {
+ failStr = "NoPath".Translate();
+ }
+ else if (pawn.WorkTagIsDisabled(WorkTags.Violent))
+ {
+ failStr = "IsIncapableOfViolenceLower".Translate(pawn.LabelShort, pawn);
+ }
+ else if (pawn.meleeVerbs?.TryGetMeleeVerb(target.Thing) == null)
+ {
+ failStr = "Incapable".Translate();
+ }
+ else if (pawn == target.Thing)
+ {
+ failStr = "CannotAttackSelf".Translate();
+ }
+ else if (target.Thing is Pawn targetPawn && (pawn.InSameExtraFaction(targetPawn, ExtraFactionType.HomeFaction) || pawn.InSameExtraFaction(targetPawn, ExtraFactionType.MiniFaction)))
+ {
+ failStr = "CannotAttackSameFactionMember".Translate();
+ }
+ else
+ {
+ if (!(target.Thing is Pawn pawn2) || !pawn2.RaceProps.Animal || !HistoryEventUtility.IsKillingInnocentAnimal(pawn, pawn2) || new HistoryEvent(HistoryEventDefOf.KilledInnocentAnimal, pawn.Named(HistoryEventArgsNames.Doer)).DoerWillingToDo())
+ {
+ return delegate
+ {
+ Job job = JobMaker.MakeJob(JobDefOf.AttackMelee, target);
+ if (target.Thing is Pawn pawn3)
+ {
+ job.killIncappedTarget = pawn3.Downed;
+ }
+ pawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
+ };
+ }
+ failStr = "IdeoligionForbids".Translate();
+ }
+
+ failStr = failStr.CapitalizeFirst();
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[IRMF] Error in ModifiedGetMeleeAttackAction: {ex}");
+ failStr = "Cannot attack";
+ return null;
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(FloatMenuUtility), nameof(FloatMenuUtility.GetRangedAttackAction))]
+ public static class Patch_FloatMenuUtility_GetRangedAttackAction
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(Pawn pawn, LocalTargetInfo target, out string failStr, ref Action __result)
+ {
+ failStr = "";
+
+ if (pawn is Wulamechunit)
+ {
+ // ֱӵĺķ
+ __result = ModifiedGetRangedAttackAction(pawn, target, out failStr);
+ return false; // ԭʼ
+ }
+
+ return true; // ûִԭʼ
+ }
+
+ private static Action ModifiedGetRangedAttackAction(Pawn pawn, LocalTargetInfo target, out string failStr, bool ignoreControlled = false)
+ {
+ failStr = "";
+
+ try
+ {
+ if (pawn.equipment.Primary == null)
+ {
+ return null;
+ }
+ Verb primaryVerb = pawn.equipment.PrimaryEq.PrimaryVerb;
+ if (primaryVerb.verbProps.IsMeleeAttack)
+ {
+ return null;
+ }
+ if (!pawn.Drafted)
+ {
+ failStr = "IsNotDraftedLower".Translate(pawn.LabelShort, pawn);
+ }
+ else if (pawn.IsColonyMechPlayerControlled && target.IsValid && !MechanitorUtility.InMechanitorCommandRange(pawn, target))
+ {
+ failStr = "OutOfCommandRange".Translate();
+ }
+ else if (target.IsValid && !pawn.equipment.PrimaryEq.PrimaryVerb.CanHitTarget(target))
+ {
+ if (!pawn.Position.InHorDistOf(target.Cell, primaryVerb.EffectiveRange))
+ {
+ failStr = "OutOfRange".Translate();
+ }
+ else
+ {
+ float num = primaryVerb.verbProps.EffectiveMinRange(target, pawn);
+ if ((float)pawn.Position.DistanceToSquared(target.Cell) < num * num)
+ {
+ failStr = "TooClose".Translate();
+ }
+ else
+ {
+ failStr = "CannotHitTarget".Translate();
+ }
+ }
+ }
+ else if (pawn.WorkTagIsDisabled(WorkTags.Violent))
+ {
+ failStr = "IsIncapableOfViolenceLower".Translate(pawn.LabelShort, pawn);
+ }
+ else if (pawn == target.Thing)
+ {
+ failStr = "CannotAttackSelf".Translate();
+ }
+ else if (target.Thing is Pawn target2 && (pawn.InSameExtraFaction(target2, ExtraFactionType.HomeFaction) || pawn.InSameExtraFaction(target2, ExtraFactionType.MiniFaction)))
+ {
+ failStr = "CannotAttackSameFactionMember".Translate();
+ }
+ else if (target.Thing is Pawn victim && HistoryEventUtility.IsKillingInnocentAnimal(pawn, victim) && !new HistoryEvent(HistoryEventDefOf.KilledInnocentAnimal, pawn.Named(HistoryEventArgsNames.Doer)).DoerWillingToDo())
+ {
+ failStr = "IdeoligionForbids".Translate();
+ }
+ else
+ {
+ if (!(target.Thing is Pawn pawn2) || pawn.Ideo == null || !pawn.Ideo.IsVeneratedAnimal(pawn2) || new HistoryEvent(HistoryEventDefOf.HuntedVeneratedAnimal, pawn.Named(HistoryEventArgsNames.Doer)).DoerWillingToDo())
+ {
+ return delegate
+ {
+ Job job = JobMaker.MakeJob(JobDefOf.AttackStatic, target);
+ pawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
+ };
+ }
+ failStr = "IdeoligionForbids".Translate();
+ }
+ failStr = failStr.CapitalizeFirst();
+ return null;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[IRMF] Error in ModifiedGetRangeAttackAction: {ex}");
+ failStr = "Cannot attack";
+ return null;
+ }
+ }
+ [HarmonyPatch(typeof(FloatMenuOptionProvider_Romance), "GetSingleOptionFor")]
+ [HarmonyPrefix]
+ public static bool GetSingleOptionFor_Prefix(Pawn clickedPawn, ref FloatMenuOption __result)
+ {
+ if (clickedPawn is Wulamechunit)
+ {
+ __result = null;
+ return false; // ԭʼ
+ }
+ return true; // ִԭʼ
+ }
+ }
+
+
+ [HarmonyPatch]
+ public static class Patch_Pawn_MeleeVerbs_TryMeleeAttack
+ {
+ // ȡҪķ
+ [HarmonyTargetMethod]
+ public static MethodBase TargetMethod()
+ {
+ // Pawn_MeleeVerbs.TryMeleeAttack
+ return AccessTools.Method(typeof(Pawn_MeleeVerbs), nameof(Pawn_MeleeVerbs.TryMeleeAttack));
+ }
+ // ǰòԭʼִǰ
+ [HarmonyPrefix]
+ public static bool Prefix(ref bool __result, Pawn_MeleeVerbs __instance, Thing target, Verb verbToUse, bool surpriseAttack)
+ {
+ try
+ {
+ // ȡ Pawn
+ var pawnField = AccessTools.Field(typeof(Pawn_MeleeVerbs), "pawn");
+ if (pawnField == null)
+ return true; // ҲֶΣִԭ
+ Pawn pawn = pawnField.GetValue(__instance) as Pawn;
+ if (pawn == null)
+ return true;
+ // ǷΪ
+ if (pawn is Wulamechunit)
+ {
+ // ǷмʻԱ
+ var pilotComp = pawn.TryGetComp();
+ if (pilotComp != null && !pilotComp.HasPilots)
+ {
+ // ûмʻԱֹս
+ __result = false;
+ return false; // ԭʼ
+ }
+ }
+ return true; // ִԭʼ
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] Harmony patch error in TryMeleeAttack: {ex}");
+ return true; // ʱִԭʼ
+ }
+ }
+ }
+ }
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patche_SkillSystem.cs b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patche_SkillSystem.cs
new file mode 100644
index 00000000..7df66de5
--- /dev/null
+++ b/Source/WulaFallenEmpire/HarmonyPatches/WULA_MechUnit/Patche_SkillSystem.cs
@@ -0,0 +1,28 @@
+// File: HarmonyPatches/SkillSystemPatches.cs
+using HarmonyLib;
+using RimWorld;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 针对SkillRecord.Interval()的补丁,防止在机甲上出现空引用
+ ///
+ [HarmonyPatch(typeof(SkillRecord))]
+ [HarmonyPatch("Interval")]
+ public static class Patch_SkillRecord_Interval
+ {
+ [HarmonyPrefix]
+ public static bool Prefix(SkillRecord __instance)
+ {
+ // 额外检查:如果pawn.story为null,也跳过
+ if (__instance?.Pawn?.story == null)
+ {
+ return false; // 跳过原方法
+ }
+
+ return true; // 执行原方法
+ }
+ }
+
+}
diff --git a/Source/WulaFallenEmpire/Job/JobGiver_InspectBuilding.cs b/Source/WulaFallenEmpire/Job/JobGiver_InspectBuilding.cs
index 8d83ddc1..4221f9e3 100644
--- a/Source/WulaFallenEmpire/Job/JobGiver_InspectBuilding.cs
+++ b/Source/WulaFallenEmpire/Job/JobGiver_InspectBuilding.cs
@@ -40,7 +40,7 @@ namespace WulaFallenEmpire
return null;
// 检查是否已经有工作
- if (pawn.CurJob != null && pawn.CurJob.def == JobDefOf_WULA.WULA_InspectBuilding)
+ if (pawn.CurJob != null && pawn.CurJob.def == Wula_JobDefOf.WULA_InspectBuilding)
return null;
// 检查冷却时间
@@ -53,7 +53,7 @@ namespace WulaFallenEmpire
return null;
// 创建考察工作
- Job job = JobMaker.MakeJob(JobDefOf_WULA.WULA_InspectBuilding, inspectionTarget);
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_InspectBuilding, inspectionTarget);
job.expiryInterval = Rand.Range(300, 600); // 5-10秒的随机时间
job.checkOverrideOnExpire = true;
@@ -252,7 +252,7 @@ namespace WulaFallenEmpire
{
if (otherPawn != currentPawn &&
otherPawn.CurJob != null &&
- otherPawn.CurJob.def == JobDefOf_WULA.WULA_InspectBuilding &&
+ otherPawn.CurJob.def == Wula_JobDefOf.WULA_InspectBuilding &&
otherPawn.CurJob.targetA.Thing == thing)
{
return true;
diff --git a/Source/WulaFallenEmpire/Pawn/Mechunit.cs b/Source/WulaFallenEmpire/Pawn/Mechunit.cs
new file mode 100644
index 00000000..963d37c0
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn/Mechunit.cs
@@ -0,0 +1,166 @@
+// File: DDmechunit_Fixed.cs
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+using Verse.AI.Group;
+
+namespace WulaFallenEmpire
+{
+ public class Wulamechunit : Pawn, IThingHolder
+ {
+ public override void DrawExtraSelectionOverlays()
+ {
+ base.DrawExtraSelectionOverlays();
+ pather.curPath?.DrawPath(this);
+ jobs.DrawLinesBetweenTargets();
+ }
+
+ public override IEnumerable GetGizmos()
+ {
+ foreach (Gizmo gizmo in base.GetGizmos())
+ {
+ yield return gizmo;
+ }
+
+ // 添加驾驶员相关的Gizmo
+ var pilotComp = this.TryGetComp();
+ if (pilotComp != null)
+ {
+ foreach (var gizmo in pilotComp.CompGetGizmosExtra())
+ {
+ yield return gizmo;
+ }
+ }
+
+ // 原有的征兆Gizmo
+ if (drafter == null)
+ {
+ yield break;
+ }
+
+ foreach (Gizmo draftGizmo in GetDraftGizmos())
+ {
+ yield return draftGizmo;
+ }
+ }
+
+ public IEnumerable GetDraftGizmos()
+ {
+ AcceptanceReport allowsDrafting = this.GetLord()?.AllowsDrafting(this) ?? ((AcceptanceReport)true);
+
+ if (!drafter.ShowDraftGizmo)
+ {
+ yield break;
+ }
+
+ Command_Toggle command_Toggle = new Command_Toggle
+ {
+ hotKey = KeyBindingDefOf.Command_ColonistDraft,
+ isActive = () => base.Drafted,
+ toggleAction = delegate
+ {
+ // 检查是否有驾驶员
+ var pilotComp = this.TryGetComp();
+ if (pilotComp != null && !pilotComp.HasPilots)
+ {
+ Messages.Message("DD_CannotDraftWithoutPilot".Translate(this.LabelShort),
+ this, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ drafter.Drafted = !drafter.Drafted;
+ PlayerKnowledgeDatabase.KnowledgeDemonstrated(ConceptDefOf.Drafting, KnowledgeAmount.SpecificInteraction);
+ if (base.Drafted)
+ {
+ LessonAutoActivator.TeachOpportunity(ConceptDefOf.QueueOrders, OpportunityType.GoodToKnow);
+ }
+ },
+ defaultDesc = "CommandToggleDraftDesc".Translate(),
+ icon = TexCommand.Draft,
+ turnOnSound = SoundDefOf.DraftOn,
+ turnOffSound = SoundDefOf.DraftOff,
+ groupKeyIgnoreContent = 81729172,
+ defaultLabel = (base.Drafted ? "CommandUndraftLabel" : "CommandDraftLabel").Translate()
+ };
+
+ if (base.Faction != Faction.OfPlayer)
+ {
+ command_Toggle.Disable("CannotOrderNonControlledLower".Translate());
+ }
+
+ if (base.Downed)
+ {
+ command_Toggle.Disable("IsIncapped".Translate(LabelShort, this));
+ }
+ if (base.Deathresting)
+ {
+ command_Toggle.Disable("IsDeathresting".Translate(this.Named("PAWN")));
+ }
+
+ // 没有驾驶员时禁用
+ var pilotComp = this.TryGetComp();
+ if (pilotComp != null && !pilotComp.HasPilots)
+ {
+ command_Toggle.Disable("DD_NoPilot".Translate());
+ }
+
+ command_Toggle.tutorTag = ((!base.Drafted) ? "Draft" : "Undraft");
+ yield return command_Toggle;
+
+ foreach (Gizmo attackGizmo in PawnAttackGizmoUtility.GetAttackGizmos(this))
+ {
+ if (!allowsDrafting && !attackGizmo.Disabled)
+ {
+ attackGizmo.Disabled = true;
+ attackGizmo.disabledReason = allowsDrafting.Reason;
+ }
+ yield return attackGizmo;
+ }
+ }
+
+ // 关键修复:重写死亡相关方法
+ public override void Kill(DamageInfo? dinfo = null, Hediff exactCulprit = null)
+ {
+ // 在死亡前弹出所有驾驶员
+ var pilotComp = this.TryGetComp();
+ if (pilotComp != null && pilotComp.HasPilots)
+ {
+ pilotComp.EjectAllPilotsOnDeath();
+ }
+
+ base.Kill(dinfo, exactCulprit);
+ }
+
+ // 重写销毁方法
+ public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
+ {
+ // 在销毁前弹出所有驾驶员
+ var pilotComp = this.TryGetComp();
+ if (pilotComp != null && pilotComp.HasPilots)
+ {
+ pilotComp.EjectAllPilotsOnDeath();
+ }
+
+ base.Destroy(mode);
+ }
+
+ // IThingHolder 接口实现
+ public new ThingOwner GetDirectlyHeldThings()
+ {
+ var pilotComp = this.TryGetComp();
+ return pilotComp?.GetDirectlyHeldThings();
+ }
+
+ public new void GetChildHolders(List outChildren)
+ {
+ ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
+ }
+
+ public override void ExposeData()
+ {
+ base.ExposeData();
+
+ // 驾驶员容器的数据会在CompMechPilotHolder中自动保存
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs b/Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs
index 523876f9..1aa9158d 100644
--- a/Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs
+++ b/Source/WulaFallenEmpire/Pawn/WULA_Energy/Need_WulaEnergy.cs
@@ -32,7 +32,7 @@ namespace WulaFallenEmpire
if (Ext != null)
{
// 从XML读取每天消耗值,并转换为每tick消耗值,并应用StatDef因子
- return (Ext.fallPerDay / 60000f) * pawn.GetStatValue(WulaStatDefOf.WulaEnergyFallRateFactor);
+ return (Ext.fallPerDay / 60000f) * pawn.GetStatValue(Wula_StatDefOf.WulaEnergyFallRateFactor);
}
// 如果XML中没有定义,则使用一个默认值
return 2.6666667E-05f;
@@ -57,7 +57,7 @@ namespace WulaFallenEmpire
if (Ext != null)
{
// 应用StatDef偏移量
- return Ext.maxLevel + pawn.GetStatValue(WulaStatDefOf.WulaEnergyMaxLevelOffset);
+ return Ext.maxLevel + pawn.GetStatValue(Wula_StatDefOf.WulaEnergyMaxLevelOffset);
}
return 1f;
}
diff --git a/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/CompMaintenancePod.cs b/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/CompMaintenancePod.cs
index af6a2a0b..c89648a3 100644
--- a/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/CompMaintenancePod.cs
+++ b/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/CompMaintenancePod.cs
@@ -400,7 +400,7 @@ namespace WulaFallenEmpire
if (pawn.Downed || !pawn.IsFreeColonist)
{
// 需要搬运
- var haulJob = JobMaker.MakeJob(JobDefOf_WULA.WULA_HaulToMaintenancePod, pawn, parent);
+ var haulJob = JobMaker.MakeJob(Wula_JobDefOf.WULA_HaulToMaintenancePod, pawn, parent);
var hauler = FindBestHauler(pawn);
if (hauler != null)
{
@@ -414,7 +414,7 @@ namespace WulaFallenEmpire
else
{
// 自己进入
- var enterJob = JobMaker.MakeJob(JobDefOf_WULA.WULA_EnterMaintenancePod, parent);
+ var enterJob = JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMaintenancePod, parent);
pawn.jobs.TryTakeOrderedJob(enterJob);
}
});
diff --git a/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/WorkGiver_DoMaintenance.cs b/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/WorkGiver_DoMaintenance.cs
index 457e5648..fa9aa936 100644
--- a/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/WorkGiver_DoMaintenance.cs
+++ b/Source/WulaFallenEmpire/Pawn/WULA_Maintenance/WorkGiver_DoMaintenance.cs
@@ -42,7 +42,7 @@ namespace WulaFallenEmpire
public override Job JobOnThing(Pawn pawn, Thing t, bool forced = false)
{
- return JobMaker.MakeJob(JobDefOf_WULA.WULA_EnterMaintenancePod, t);
+ return JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMaintenancePod, t);
}
// 检查单个Pawn是否需要维护
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompMechDefaultPilot.cs b/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompMechDefaultPilot.cs
new file mode 100644
index 00000000..f45717e3
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompMechDefaultPilot.cs
@@ -0,0 +1,236 @@
+// CompMechDefaultPilot.cs
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompMechDefaultPilot : ThingComp
+ {
+ public CompProperties_MechDefaultPilot Props => (CompProperties_MechDefaultPilot)props;
+ private bool defaultPilotsSpawned = false;
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ }
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ if (respawningAfterLoad)
+ return;
+
+ // 检查是否需要生成默认驾驶员
+ TrySpawnDefaultPilots();
+ }
+
+ // 尝试生成默认驾驶员
+ public void TrySpawnDefaultPilots()
+ {
+ if (defaultPilotsSpawned)
+ return;
+
+ var mech = parent as Pawn;
+ if (mech == null)
+ return;
+
+ var mechFaction = mech.Faction;
+ if (mechFaction == null)
+ return;
+
+ // 检查阵营条件
+ bool isPlayerFaction = mechFaction == Faction.OfPlayer;
+ if (isPlayerFaction && !Props.enableForPlayerFaction)
+ {
+ return;
+ }
+
+ if (!isPlayerFaction && !Props.enableForNonPlayerFaction)
+ {
+ return;
+ }
+
+ // 检查生成几率
+ if (Rand.Value > Props.defaultPilotChance)
+ {
+ return;
+ }
+
+ // 获取驾驶员容器
+ var pilotHolder = mech.TryGetComp();
+ if (pilotHolder == null)
+ {
+ return;
+ }
+
+ // 检查是否需要生成
+ if (Props.spawnOnlyIfNoPilot && pilotHolder.HasPilots)
+ {
+ return;
+ }
+
+ // 如果需要替换现有驾驶员,先移除所有
+ if (Props.replaceExistingPilots && pilotHolder.HasPilots)
+ {
+ pilotHolder.RemoveAllPilots();
+ }
+
+ // 计算要生成的驾驶员数量
+ int maxPilots = Props.maxDefaultPilots > 0 ?
+ Props.maxDefaultPilots : pilotHolder.Props.maxPilots;
+ int pilotsToSpawn = maxPilots - pilotHolder.CurrentPilotCount;
+
+ if (pilotsToSpawn <= 0)
+ {
+ return;
+ }
+
+ // 生成驾驶员
+ int spawnedCount = 0;
+ for (int i = 0; i < pilotsToSpawn; i++)
+ {
+ if (TrySpawnDefaultPilot(mech, pilotHolder))
+ spawnedCount++;
+ }
+
+ if (spawnedCount > 0)
+ {
+ defaultPilotsSpawned = true;
+ }
+ }
+
+ // 尝试生成单个默认驾驶员
+ private bool TrySpawnDefaultPilot(Pawn mech, CompMechPilotHolder pilotHolder)
+ {
+ // 选择驾驶员类型
+ var pilotKind = Props.SelectRandomPilotKind();
+ if (pilotKind == null)
+ {
+ Log.Warning($"[DD] No valid pilot kind found");
+ return false;
+ }
+
+ // 创建驾驶员生成请求
+ PawnGenerationRequest request = new PawnGenerationRequest(
+ kind: pilotKind,
+ faction: mech.Faction,
+ context: PawnGenerationContext.NonPlayer,
+ tile: mech.Map?.Tile ?? -1,
+ forceGenerateNewPawn: true,
+ allowDead: false,
+ allowDowned: false,
+ canGeneratePawnRelations: false,
+ mustBeCapableOfViolence: false,
+ colonistRelationChanceFactor: 0f,
+ forceAddFreeWarmLayerIfNeeded: false,
+ allowGay: true,
+ allowFood: true,
+ allowAddictions: true
+ );
+
+ try
+ {
+ // 生成驾驶员
+ Pawn pilot = PawnGenerator.GeneratePawn(request);
+
+ // 设置驾驶员名字
+ if (pilot.Name == null || pilot.Name is NameSingle)
+ {
+ pilot.Name = PawnBioAndNameGenerator.GeneratePawnName(pilot, NameStyle.Numeric);
+ }
+
+ // 添加到机甲
+ if (pilotHolder.CanAddPilot(pilot))
+ {
+ pilotHolder.AddPilot(pilot);
+
+ return true;
+ }
+ else
+ {
+ Log.Warning($"[DD] Cannot add pilot {pilot.LabelShortCap} to mech");
+ // 清理生成的pawn
+ pilot.Destroy();
+ return false;
+ }
+ }
+ catch (System.Exception ex)
+ {
+ Log.Error($"[DD] Error generating default pilot: {ex}");
+ return false;
+ }
+ }
+
+ // 开发者命令:强制生成默认驾驶员
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ foreach (Gizmo gizmo in base.CompGetGizmosExtra())
+ {
+ yield return gizmo;
+ }
+
+ if (DebugSettings.ShowDevGizmos)
+ {
+ var mech = parent as Pawn;
+ if (mech != null && mech.Faction == Faction.OfPlayer)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "DEV: Spawn Default Pilots",
+ defaultDesc = "Force spawn default pilots for this mech",
+ action = () =>
+ {
+ defaultPilotsSpawned = false;
+ TrySpawnDefaultPilots();
+ Messages.Message("Default pilots spawned", parent, MessageTypeDefOf.NeutralEvent);
+ }
+ };
+
+ yield return new Command_Action
+ {
+ defaultLabel = "DEV: Clear Default Pilots",
+ defaultDesc = "Remove default pilots and allow respawning",
+ action = () =>
+ {
+ defaultPilotsSpawned = false;
+ var pilotHolder = mech.TryGetComp();
+ if (pilotHolder != null)
+ {
+ pilotHolder.RemoveAllPilots();
+ }
+ Messages.Message("Default pilots cleared", parent, MessageTypeDefOf.NeutralEvent);
+ }
+ };
+ }
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref defaultPilotsSpawned, "defaultPilotsSpawned", false);
+ }
+
+ // 当机甲阵营改变时重新检查
+ public void Notify_FactionChanged()
+ {
+ // 重置标记,允许在新阵营下生成驾驶员
+ defaultPilotsSpawned = false;
+ TrySpawnDefaultPilots();
+ }
+
+ // 当驾驶员被移除时,如果全部移除,允许重新生成
+ public void Notify_PilotRemoved()
+ {
+ var pilotHolder = parent.TryGetComp();
+ if (pilotHolder != null && !pilotHolder.HasPilots)
+ {
+ // 如果所有驾驶员都被移除了,重置标记
+ defaultPilotsSpawned = false;
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompProperties_MechDefaultPilot.cs b/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompProperties_MechDefaultPilot.cs
new file mode 100644
index 00000000..14e11533
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/DefaultPilotEntry/CompProperties_MechDefaultPilot.cs
@@ -0,0 +1,127 @@
+// CompProperties_MechDefaultPilot.cs
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class DefaultPilotEntry
+ {
+ public PawnKindDef pawnKind;
+ public float weight = 1f; // 权重,用于随机选择
+ public bool required = false; // 是否必选
+
+ public DefaultPilotEntry() { }
+
+ public DefaultPilotEntry(PawnKindDef pawnKind, float weight = 1f, bool required = false)
+ {
+ this.pawnKind = pawnKind;
+ this.weight = weight;
+ this.required = required;
+ }
+ }
+
+ public class CompProperties_MechDefaultPilot : CompProperties
+ {
+ // 基本配置
+ public bool enableForNonPlayerFaction = true;
+ public bool enableForPlayerFaction = false; // 玩家阵营是否也生成默认驾驶员
+ public float defaultPilotChance = 1.0f; // 默认生成几率
+
+ // 驾驶员配置
+ public List defaultPilots = new List();
+
+ // 高级配置
+ public bool spawnOnlyIfNoPilot = true; // 只在没有驾驶员时生成
+ public bool replaceExistingPilots = false; // 替换现有驾驶员
+ public int maxDefaultPilots = -1; // -1表示使用CompMechPilotHolder的最大容量
+
+ public CompProperties_MechDefaultPilot()
+ {
+ this.compClass = typeof(CompMechDefaultPilot);
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string error in base.ConfigErrors(parentDef))
+ {
+ yield return error;
+ }
+
+ if (defaultPilotChance < 0f || defaultPilotChance > 1f)
+ {
+ yield return $"defaultPilotChance must be between 0 and 1 for {parentDef.defName}";
+ }
+
+ if (defaultPilots.Count == 0)
+ {
+ yield return $"No defaultPilots defined for {parentDef.defName}";
+ }
+ else
+ {
+ foreach (var entry in defaultPilots)
+ {
+ if (entry.pawnKind == null)
+ {
+ yield return $"Null pawnKind in defaultPilots for {parentDef.defName}";
+ }
+ }
+ }
+ }
+
+ // 获取有效的默认驾驶员列表
+ public List GetValidDefaultPilots()
+ {
+ return defaultPilots.FindAll(p => p.pawnKind != null);
+ }
+
+ // 计算总权重
+ public float GetTotalWeight()
+ {
+ float total = 0f;
+ foreach (var entry in defaultPilots)
+ {
+ if (entry.pawnKind != null && !entry.required)
+ {
+ total += entry.weight;
+ }
+ }
+ return total;
+ }
+
+ // 随机选择一个驾驶员类型
+ public PawnKindDef SelectRandomPilotKind()
+ {
+ var validPilots = GetValidDefaultPilots();
+ if (validPilots.Count == 0)
+ return null;
+
+ // 先检查必选项
+ foreach (var entry in validPilots)
+ {
+ if (entry.required)
+ return entry.pawnKind;
+ }
+
+ // 如果没有必选项,按权重随机选择
+ float totalWeight = GetTotalWeight();
+ if (totalWeight <= 0f)
+ return validPilots[0].pawnKind; // 回退到第一个
+
+ float random = Rand.Range(0f, totalWeight);
+ float current = 0f;
+
+ foreach (var entry in validPilots)
+ {
+ if (entry.required)
+ continue;
+
+ current += entry.weight;
+ if (random <= current)
+ return entry.pawnKind;
+ }
+
+ return validPilots[validPilots.Count - 1].pawnKind; // 回退到最后一个
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompHediffGiverByKind.cs b/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompHediffGiverByKind.cs
new file mode 100644
index 00000000..5e3659a0
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompHediffGiverByKind.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using Verse;
+using RimWorld;
+
+namespace WulaFallenEmpire
+{
+ public class CompHediffGiverByKind : ThingComp
+ {
+ private bool hediffsApplied = false;
+ private PawnKindDef appliedPawnKind = null; // 记录应用时的PawnKind
+
+ public CompProperties_HediffGiverByKind Props => (CompProperties_HediffGiverByKind)this.props;
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ if (this.parent is Pawn pawn)
+ {
+ // 检查是否需要重新应用hediff(例如PawnKind发生变化)
+ if (!hediffsApplied || ShouldReapplyHediffs(pawn))
+ {
+ ApplyHediffsToPawn(pawn);
+ hediffsApplied = true;
+ appliedPawnKind = pawn.kindDef;
+ }
+ }
+ }
+
+ private bool ShouldReapplyHediffs(Pawn pawn)
+ {
+ // 如果PawnKind发生了变化,需要重新应用
+ return appliedPawnKind != pawn.kindDef;
+ }
+
+ private void ApplyHediffsToPawn(Pawn pawn)
+ {
+ try
+ {
+ // 获取对应PawnKind的hediff配置
+ float addChance;
+ bool allowDuplicates;
+ var hediffs = Props.GetHediffsForPawnKind(pawn.kindDef, out addChance, out allowDuplicates);
+
+ if (hediffs == null || hediffs.Count == 0)
+ {
+ return;
+ }
+
+ // 检查概率
+ if (addChance < 1.0f && Rand.Value > addChance)
+ {
+ return;
+ }
+
+ // 移除旧的hediff(如果配置了不允许重复)
+ if (!allowDuplicates)
+ {
+ RemoveExistingHediffs(pawn, hediffs);
+ }
+
+ // 添加新的hediff
+ int addedCount = 0;
+ foreach (HediffDef hediffDef in hediffs)
+ {
+ if (!allowDuplicates && pawn.health.hediffSet.HasHediff(hediffDef))
+ {
+ continue;
+ }
+
+ pawn.health.AddHediff(hediffDef);
+ addedCount++;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[WFE] Error applying hediffs to {pawn.LabelCap}: {ex}");
+ }
+ }
+
+ private void RemoveExistingHediffs(Pawn pawn, List hediffDefs)
+ {
+ foreach (var hediffDef in hediffDefs)
+ {
+ var existingHediff = pawn.health.hediffSet.GetFirstHediffOfDef(hediffDef);
+ if (existingHediff != null)
+ {
+ pawn.health.RemoveHediff(existingHediff);
+ }
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref hediffsApplied, "hediffsApplied", false);
+ Scribe_Defs.Look(ref appliedPawnKind, "appliedPawnKind");
+ }
+
+ // 调试方法:手动应用hediff
+ public void DebugApplyHediffs()
+ {
+ if (this.parent is Pawn pawn)
+ {
+ ApplyHediffsToPawn(pawn);
+ hediffsApplied = true;
+ appliedPawnKind = pawn.kindDef;
+ }
+ }
+
+ // 获取当前配置的hediff列表(用于调试)
+ public List GetCurrentHediffs()
+ {
+ if (this.parent is Pawn pawn)
+ {
+ float addChance;
+ bool allowDuplicates;
+ return Props.GetHediffsForPawnKind(pawn.kindDef, out addChance, out allowDuplicates);
+ }
+ return null;
+ }
+
+ // 检查当前配置信息
+ public string GetConfigInfo()
+ {
+ if (this.parent is Pawn pawn)
+ {
+ float addChance;
+ bool allowDuplicates;
+ var hediffs = Props.GetHediffsForPawnKind(pawn.kindDef, out addChance, out allowDuplicates);
+
+ string info = $"Pawn: {pawn.LabelCap}\n";
+ info += $"PawnKind: {pawn.kindDef?.defName ?? "None"}\n";
+ info += $"Add Chance: {addChance * 100}%\n";
+ info += $"Allow Duplicates: {allowDuplicates}\n";
+
+ if (hediffs != null && hediffs.Count > 0)
+ {
+ info += "Hediffs:\n";
+ foreach (var hediff in hediffs)
+ {
+ info += $" - {hediff.defName}\n";
+ }
+ }
+ else
+ {
+ info += "No hediffs configured\n";
+ }
+
+ return info;
+ }
+
+ return "Parent is not a Pawn";
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompProperties_HediffGiverByKind.cs b/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompProperties_HediffGiverByKind.cs
new file mode 100644
index 00000000..53fb7fc8
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/HediffGiverByKind/CompProperties_HediffGiverByKind.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ [Serializable]
+ public class PawnKindHediffEntry
+ {
+ public PawnKindDef pawnKind;
+ public List hediffs;
+ public float addChance = 1.0f; // 特定PawnKind的添加概率
+ public bool allowDuplicates = false; // 是否允许重复添加
+
+ public PawnKindHediffEntry()
+ {
+ }
+
+ public PawnKindHediffEntry(PawnKindDef pawnKind, List hediffs)
+ {
+ this.pawnKind = pawnKind;
+ this.hediffs = hediffs;
+ }
+ }
+
+ public class CompProperties_HediffGiverByKind : CompProperties
+ {
+ // 默认的hediff列表(当没有找到匹配的PawnKind时使用)
+ public List defaultHediffs;
+ public float defaultAddChance = 1.0f;
+ public bool defaultAllowDuplicates = false;
+
+ // PawnKind特定的hediff配置
+ public List pawnKindHediffs;
+
+ // 是否启用调试日志
+ public bool debugMode = false;
+
+ // 获取指定PawnKind的hediff配置
+ public PawnKindHediffEntry GetHediffEntryForPawnKind(PawnKindDef pawnKind)
+ {
+ if (pawnKindHediffs == null || pawnKind == null)
+ return null;
+
+ foreach (var entry in pawnKindHediffs)
+ {
+ if (entry.pawnKind == pawnKind)
+ return entry;
+ }
+
+ return null;
+ }
+
+ // 获取要添加的hediff列表
+ public List GetHediffsForPawnKind(PawnKindDef pawnKind, out float addChance, out bool allowDuplicates)
+ {
+ var entry = GetHediffEntryForPawnKind(pawnKind);
+
+ if (entry != null)
+ {
+ addChance = entry.addChance;
+ allowDuplicates = entry.allowDuplicates;
+ return entry.hediffs;
+ }
+
+ // 使用默认配置
+ addChance = defaultAddChance;
+ allowDuplicates = defaultAllowDuplicates;
+ return defaultHediffs;
+ }
+
+ public CompProperties_HediffGiverByKind()
+ {
+ this.compClass = typeof(CompHediffGiverByKind);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs
new file mode 100644
index 00000000..de554798
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs
@@ -0,0 +1,199 @@
+// File: CompMechArmor.cs
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+using static RimWorld.MechClusterSketch;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 机甲装甲组件:提供基于装甲值的伤害减免系统
+ ///
+ public class CompMechArmor : ThingComp
+ {
+ public CompProperties_MechArmor Props =>
+ (CompProperties_MechArmor)props;
+
+ private static StatDef DD_MechArmorDef = null;
+
+ // 当前装甲值(从Stat获取)
+ public float CurrentArmor
+ {
+ get
+ {
+ if (DD_MechArmorDef == null)
+ {
+ DD_MechArmorDef = StatDef.Named("DD_MechArmor");
+ if (DD_MechArmorDef == null)
+ {
+ Log.Warning("[DD] DD_MechArmor stat definition not found!");
+ return 0f;
+ }
+ }
+ return parent.GetStatValue(DD_MechArmorDef);
+ }
+ }
+
+ // 调试信息
+ private int blockedHits = 0;
+ private int totalHits = 0;
+
+ ///
+ /// 检查伤害是否被装甲抵消
+ ///
+ /// 伤害信息
+ /// true=伤害被抵消,false=伤害有效
+ public bool TryBlockDamage(ref DamageInfo dinfo)
+ {
+ totalHits++;
+
+ // 获取穿甲率(如果没有则为0)
+ float armorPenetration = dinfo.ArmorPenetrationInt;
+
+ // 计算穿甲伤害
+ float armorPiercingDamage = dinfo.Amount * armorPenetration;
+
+ // 获取当前装甲值
+ float currentArmor = CurrentArmor;
+
+ // 检查是否应该被装甲抵消
+ bool shouldBlock = armorPiercingDamage < currentArmor;
+
+ if (shouldBlock)
+ {
+ blockedHits++;
+
+ // 可选:触发视觉效果
+ if (Props.showBlockEffect && parent.Spawned)
+ {
+ ShowBlockEffect(dinfo);
+ }
+
+ // 可选:播放音效
+ if (Props.soundOnBlock != null && parent.Spawned)
+ {
+ Props.soundOnBlock.PlayOneShot(parent);
+ }
+ }
+
+ // 调试日志
+ if (Props.debugLogging && parent.Spawned)
+ {
+ Log.Message($"[DD Armor] {parent.LabelShort}: " +
+ $"Damage={dinfo.Amount}, " +
+ $"Penetration={armorPenetration:P0}, " +
+ $"PierceDamage={armorPiercingDamage:F1}, " +
+ $"Armor={currentArmor:F1}, " +
+ $"Blocked={shouldBlock}");
+ }
+
+ return shouldBlock;
+ }
+
+ ///
+ /// 显示阻挡效果
+ ///
+ private void ShowBlockEffect(DamageInfo dinfo)
+ {
+ MoteMaker.ThrowText(parent.DrawPos, parent.Map, "DD_BlockByMechArmor".Translate(), Color.white, 3.5f);
+ // 创建火花或特效
+ if (Props.blockEffectMote != null)
+ {
+ MoteMaker.MakeStaticMote(
+ parent.DrawPos + new Vector3(0, 0, 0.5f),
+ parent.Map,
+ Props.blockEffectMote,
+ 1.0f
+ );
+ }
+ }
+
+ ///
+ /// 获取装甲信息(用于调试)
+ ///
+ public string GetArmorInfo()
+ {
+ return $"{parent.LabelShort}的装甲系统\n" +
+ $"当前装甲值: {CurrentArmor:F1}\n" +
+ $"阻挡规则: 穿甲伤害 < 装甲值\n" +
+ $"统计: 已阻挡 {blockedHits}/{totalHits} 次攻击\n" +
+ $"阻挡率: {(totalHits > 0 ? (float)blockedHits / totalHits * 100 : 0):F1}%";
+ }
+
+ ///
+ /// 获取调试按钮
+ ///
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ foreach (var gizmo in base.CompGetGizmosExtra())
+ {
+ yield return gizmo;
+ }
+
+ // 在开发模式下显示装甲信息
+ if (DebugSettings.ShowDevGizmos)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "DEBUG: 装甲信息",
+ defaultDesc = GetArmorInfo(),
+ //icon = TexCommand.Shield,
+ action = () =>
+ {
+ Find.WindowStack.Add(new Dialog_MessageBox(
+ GetArmorInfo(),
+ "关闭",
+ null,
+ null,
+ null,
+ "机甲装甲信息"
+ ));
+ }
+ };
+
+ yield return new Command_Action
+ {
+ defaultLabel = "DEBUG: 重置统计",
+ defaultDesc = "重置阻挡统计计数器",
+ //icon = TexCommand.Clear,
+ action = () =>
+ {
+ blockedHits = 0;
+ totalHits = 0;
+ Messages.Message("装甲统计已重置", MessageTypeDefOf.TaskCompletion);
+ }
+ };
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref blockedHits, "blockedHits", 0);
+ Scribe_Values.Look(ref totalHits, "totalHits", 0);
+ }
+ }
+
+ ///
+ /// 机甲装甲组件属性
+ ///
+ public class CompProperties_MechArmor : CompProperties
+ {
+ // 视觉效果
+ public bool showBlockEffect = true;
+ public ThingDef blockEffectMote; // 阻挡时显示的特效
+
+ // 音效
+ public SoundDef soundOnBlock;
+
+ // 调试
+ public bool debugLogging = false;
+
+ public CompProperties_MechArmor()
+ {
+ compClass = typeof(CompMechArmor);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs
new file mode 100644
index 00000000..ed900195
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs
@@ -0,0 +1,446 @@
+// CompMechFuel.cs
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using System.Linq;
+
+namespace WulaFallenEmpire
+{
+ public class CompMechFuel : ThingComp
+ {
+ public CompProperties_MechFuel Props => (CompProperties_MechFuel)props;
+
+ private float fuel;
+ private bool isShutdown = false;
+ private int lastFuelTick = -1;
+
+ public float Fuel => fuel;
+ public float FuelPercent => fuel / Props.fuelCapacity;
+ public bool HasFuel => fuel > 0f;
+ public bool IsFull => FuelPercent >= 0.999f;
+ public bool NeedsRefueling => FuelPercent < Props.autoRefuelThreshold && Props.allowAutoRefuel;
+ public bool IsShutdown => isShutdown;
+
+ public ThingDef FuelType => Props.fuelType;
+
+ // 停机状态 Hediff
+ private HediffDef ShutdownHediffDef => HediffDef.Named("DD_MechShutdown");
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+ lastFuelTick = Find.TickManager.TicksGame;
+
+ // 如果是新生成的机甲,自动加满燃料
+ if (!respawningAfterLoad && fuel <= 0f)
+ {
+ Refuel(Props.fuelCapacity);
+ }
+
+ // 确保停机状态和 Hediff 同步
+ SyncShutdownHediff();
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ // 每60ticks(1秒)消耗一次燃料
+ if (Find.TickManager.TicksGame % 60 == 0)
+ {
+ ConsumeFuelOverTime();
+ }
+
+ // 检查是否需要关机
+ CheckShutdown();
+
+ // 确保停机状态和 Hediff 同步
+ SyncShutdownHediff();
+ }
+
+ private void SyncShutdownHediff()
+ {
+ var mech = parent as Pawn;
+ if (mech == null || mech.health == null || ShutdownHediffDef == null)
+ return;
+
+ bool hasShutdownHediff = mech.health.hediffSet.HasHediff(ShutdownHediffDef);
+
+ // 如果处于停机状态但没有 Hediff,添加 Hediff
+ if (isShutdown && !hasShutdownHediff)
+ {
+ mech.health.AddHediff(ShutdownHediffDef);
+ }
+ // 如果不处于停机状态但有 Hediff,移除 Hediff
+ else if (!isShutdown && hasShutdownHediff)
+ {
+ var hediff = mech.health.hediffSet.GetFirstHediffOfDef(ShutdownHediffDef);
+ if (hediff != null)
+ {
+ mech.health.RemoveHediff(hediff);
+ }
+ }
+ }
+
+ private void ConsumeFuelOverTime()
+ {
+ if (fuel <= 0f || !parent.Spawned)
+ return;
+
+ // 检查是否有驾驶员 - 没有驾驶员时不消耗燃料
+ var pilotComp = parent.TryGetComp();
+ bool hasPilot = pilotComp != null && pilotComp.HasPilots;
+
+ if (!hasPilot)
+ return; // 没有驾驶员,不消耗燃料
+
+ // 获取当前时间
+ int currentTick = Find.TickManager.TicksGame;
+
+ // 计算经过的游戏时间(以天为单位)
+ float daysPassed = (currentTick - lastFuelTick) / 60000f; // 60000 ticks = 1天
+
+ // 计算基础燃料消耗
+ float consumption = Props.dailyFuelConsumption * daysPassed;
+
+ // 如果机甲正在活动(移动、战斗等),消耗更多燃料
+ var mech = parent as Pawn;
+ if (mech != null)
+ {
+ // 增加活动消耗
+ if (mech.pather.Moving)
+ {
+ consumption *= 2f; // 移动时消耗加倍
+ }
+
+ // 战斗状态消耗更多
+ if (mech.CurJob != null && mech.CurJob.def == JobDefOf.AttackStatic)
+ {
+ consumption *= 1.5f;
+ }
+ }
+
+ // 消耗燃料
+ fuel = Mathf.Max(0f, fuel - consumption);
+
+ // 更新最后消耗时间
+ lastFuelTick = currentTick;
+ }
+
+ private void CheckShutdown()
+ {
+ if (!Props.shutdownWhenEmpty || isShutdown)
+ return;
+
+ // 燃料耗尽时关机
+ if (fuel <= 0f && parent.Spawned)
+ {
+ Shutdown();
+ }
+ }
+
+ private void Shutdown()
+ {
+ if (isShutdown)
+ return;
+
+ isShutdown = true;
+
+ var mech = parent as Pawn;
+ if (mech != null)
+ {
+ // 取消所有工作
+ mech.jobs.StopAll();
+ mech.drafter.Drafted = false;
+
+ // 添加停机 Hediff
+ if (ShutdownHediffDef != null)
+ {
+ mech.health.AddHediff(ShutdownHediffDef);
+ }
+
+ // 播放关机效果
+ MoteMaker.ThrowText(mech.DrawPos, mech.Map, "DD_Shutdown".Translate(), Color.gray, 3.5f);
+ }
+ }
+
+ public void Startup()
+ {
+ if (!isShutdown || fuel <= 0f)
+ return;
+
+ isShutdown = false;
+
+ var mech = parent as Pawn;
+ if (mech != null)
+ {
+ // 移除停机 Hediff
+ if (ShutdownHediffDef != null)
+ {
+ var hediff = mech.health.hediffSet.GetFirstHediffOfDef(ShutdownHediffDef);
+ if (hediff != null)
+ {
+ mech.health.RemoveHediff(hediff);
+ }
+ }
+
+ MoteMaker.ThrowText(mech.DrawPos, mech.Map, "DD_Startup".Translate(), Color.green, 3.5f);
+ }
+ }
+
+ public bool TryConsumeFuel(float amount)
+ {
+ if (fuel >= amount)
+ {
+ fuel -= amount;
+ return true;
+ }
+ return false;
+ }
+
+ public bool Refuel(float amount)
+ {
+ float oldFuel = fuel;
+ fuel = Mathf.Min(Props.fuelCapacity, fuel + amount);
+
+ // 如果之前关机了且现在有燃料了,启动
+ if (isShutdown && fuel > 0f)
+ {
+ Startup();
+ }
+
+ return fuel > oldFuel;
+ }
+
+ // 设置燃料到特定值(测试用)
+ public void SetFuel(float amount)
+ {
+ float oldFuel = fuel;
+ fuel = Mathf.Clamp(amount, 0f, Props.fuelCapacity);
+
+ // 检查是否需要关机或启动
+ if (fuel <= 0f)
+ {
+ if (!isShutdown && Props.shutdownWhenEmpty)
+ {
+ Shutdown();
+ }
+ }
+ else if (isShutdown && fuel > 0f)
+ {
+ Startup();
+ }
+
+ // 发送调试消息
+ if (DebugSettings.godMode)
+ {
+ Messages.Message($"DD_Debug_FuelSet".Translate(
+ parent.LabelShort,
+ fuel.ToString("F1"),
+ Props.fuelCapacity.ToString("F1"),
+ (fuel / Props.fuelCapacity * 100f).ToString("F0") + "%"
+ ), parent, MessageTypeDefOf.PositiveEvent);
+ }
+ }
+
+ public float FuelSpaceRemaining => Mathf.Max(0f, Props.fuelCapacity - fuel);
+
+ public int GetFuelCountToFullyRefuel()
+ {
+ if (FuelType == null)
+ return 0;
+
+ float fuelNeeded = FuelSpaceRemaining;
+ return Mathf.CeilToInt(fuelNeeded);
+ }
+
+ // 立刻加注 - 现在触发最近殖民者来加注
+ public void RefuelNow()
+ {
+ if (IsFull || parent.Map == null || FuelType == null)
+ return;
+
+ // 寻找最近的可用殖民者
+ Pawn bestColonist = FindBestColonistForRefuel();
+
+ if (bestColonist == null)
+ {
+ Messages.Message("DD_NoColonistAvailable".Translate(), parent, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 寻找燃料
+ Thing fuel = FindFuelForRefuel(bestColonist);
+ if (fuel == null)
+ {
+ Messages.Message("DD_NoFuelAvailable".Translate(FuelType), parent, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 为殖民者创建强制加注工作
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.DD_RefuelMech, parent, fuel);
+ job.count = GetFuelCountToFullyRefuel();
+ job.playerForced = true;
+
+ bestColonist.jobs.StartJob(job, JobCondition.InterruptForced, null, resumeCurJobAfterwards: true);
+
+ // 显示消息
+ Messages.Message("DD_OrderedRefuel".Translate(bestColonist.LabelShort, parent.LabelShort),
+ parent, MessageTypeDefOf.PositiveEvent);
+ }
+
+ private Pawn FindBestColonistForRefuel()
+ {
+ Map map = parent.Map;
+ if (map == null)
+ return null;
+
+ // 寻找所有可用的殖民者
+ List colonists = map.mapPawns.FreeColonists.ToList();
+
+ // 过滤掉无法工作或无法到达机甲的殖民者
+ colonists = colonists.Where(colonist =>
+ colonist.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation) &&
+ colonist.health.capacities.CapableOf(PawnCapacityDefOf.Moving) &&
+ !colonist.Downed &&
+ !colonist.Dead &&
+ colonist.CanReserveAndReach(parent, PathEndMode.Touch, Danger.Some)
+ ).ToList();
+
+ if (!colonists.Any())
+ return null;
+
+ // 按照距离排序,选择最近的殖民者
+ return colonists.OrderBy(colonist => colonist.Position.DistanceTo(parent.Position)).FirstOrDefault();
+ }
+
+ private Thing FindFuelForRefuel(Pawn colonist)
+ {
+ if (FuelType == null)
+ return null;
+
+ // 先在殖民者库存中寻找
+ if (colonist.inventory != null)
+ {
+ Thing fuelInInventory = colonist.inventory.innerContainer.FirstOrDefault(t => t.def == FuelType);
+ if (fuelInInventory != null)
+ return fuelInInventory;
+ }
+
+ // 在地图上寻找可用的燃料
+ return GenClosest.ClosestThingReachable(
+ colonist.Position,
+ colonist.Map,
+ ThingRequest.ForDef(FuelType),
+ PathEndMode.ClosestTouch,
+ TraverseParms.For(colonist),
+ 9999f,
+ validator: thing => !thing.IsForbidden(colonist) && colonist.CanReserve(thing)
+ );
+ }
+
+ public bool CanRefuelNow()
+ {
+ if (IsFull || parent.Map == null || FuelType == null)
+ return false;
+
+ // 检查是否有可用殖民者
+ if (FindBestColonistForRefuel() == null)
+ return false;
+
+ return true;
+ }
+
+ // 检查机甲是否有驾驶员
+ public bool HasPilot()
+ {
+ var pilotComp = parent.TryGetComp();
+ return pilotComp != null && pilotComp.HasPilots;
+ }
+
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ // 添加燃料状态Gizmo
+ yield return new Gizmo_MechFuelStatus(this);
+
+ // 添加立刻加注按钮
+ if (!IsFull && parent.Faction == Faction.OfPlayer)
+ {
+ Command_Action refuelNow = new Command_Action
+ {
+ defaultLabel = "DD_RefuelNow".Translate(),
+ defaultDesc = "DD_RefuelNowDesc".Translate(),
+ icon = ContentFinder.Get("WulaFallenEmpire/UI/Commands/DD_Refuel_Mech"),
+ action = () => RefuelNow()
+ };
+
+ // 检查是否可以立刻加注
+ if (!CanRefuelNow())
+ {
+ refuelNow.Disable("DD_CannotRefuelNow".Translate());
+ }
+
+ yield return refuelNow;
+ }
+
+ // 在 God Mode 下显示测试按钮
+ if (DebugSettings.godMode && parent.Faction == Faction.OfPlayer)
+ {
+ // 设置燃料为空
+ Command_Action setEmpty = new Command_Action
+ {
+ defaultLabel = "DD_Debug_SetEmpty".Translate(),
+ defaultDesc = "DD_Debug_SetEmptyDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/SetEmpty", false) ?? BaseContent.BadTex,
+ action = () => SetFuel(0f)
+ };
+ yield return setEmpty;
+
+ // 设置燃料为50%
+ Command_Action setHalf = new Command_Action
+ {
+ defaultLabel = "DD_Debug_SetHalf".Translate(),
+ defaultDesc = "DD_Debug_SetHalfDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/SetHalf", false) ?? BaseContent.BadTex,
+ action = () => SetFuel(Props.fuelCapacity * 0.5f)
+ };
+ yield return setHalf;
+
+ // 设置燃料为满
+ Command_Action setFull = new Command_Action
+ {
+ defaultLabel = "DD_Debug_SetFull".Translate(),
+ defaultDesc = "DD_Debug_SetFullDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/SetFull", false) ?? BaseContent.BadTex,
+ action = () => SetFuel(Props.fuelCapacity)
+ };
+ yield return setFull;
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+
+ Scribe_Values.Look(ref fuel, "fuel", Props.fuelCapacity);
+ Scribe_Values.Look(ref isShutdown, "isShutdown", false);
+ Scribe_Values.Look(ref lastFuelTick, "lastFuelTick", -1);
+ }
+
+ public override string CompInspectStringExtra()
+ {
+ string baseString = base.CompInspectStringExtra();
+
+ string fuelString = "DD_Fuel".Translate(FuelType) + ": " +
+ fuel.ToString("F1") + " / " + Props.fuelCapacity.ToString("F1") +
+ " (" + (FuelPercent * 100f).ToString("F0") + "%)";
+
+ if (!baseString.NullOrEmpty())
+ return baseString + "\n" + fuelString;
+ else
+ return fuelString;
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompProperties_MechFuel.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompProperties_MechFuel.cs
new file mode 100644
index 00000000..39f44e9c
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompProperties_MechFuel.cs
@@ -0,0 +1,56 @@
+// CompProperties_MechFuel.cs
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechFuel : CompProperties
+ {
+ public ThingDef fuelType; // 燃料种类
+ public float fuelCapacity = 100f; // 燃料容量
+ public float dailyFuelConsumption = 10f; // 每日燃料消耗量
+ public float refuelSpeedFactor = 1f; // 加注速度因子
+ public int refuelDuration = 240; // 基础加注时间(ticks)
+
+ // Gizmo显示设置
+ public Color fuelBarColor = new Color(0.1f, 0.6f, 0.9f); // 燃料条颜色
+ public Color emptyBarColor = new Color(0.2f, 0.2f, 0.24f); // 空燃料条颜色
+
+ // 自动加注设置
+ public float autoRefuelThreshold = 0.3f; // 自动加注阈值(低于此值自动加注)
+ public bool allowAutoRefuel = true; // 是否允许自动加注
+
+ // 燃料耗尽效果
+ public bool shutdownWhenEmpty = true; // 燃料耗尽时关机
+
+ public CompProperties_MechFuel()
+ {
+ this.compClass = typeof(CompMechFuel);
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string error in base.ConfigErrors(parentDef))
+ {
+ yield return error;
+ }
+
+ if (fuelType == null)
+ {
+ yield return $"fuelType is null for {parentDef.defName}";
+ }
+
+ if (fuelCapacity <= 0f)
+ {
+ yield return $"fuelCapacity must be positive for {parentDef.defName}";
+ }
+
+ if (dailyFuelConsumption < 0f)
+ {
+ yield return $"dailyFuelConsumption cannot be negative for {parentDef.defName}";
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/Gizmo_MechFuelStatus.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/Gizmo_MechFuelStatus.cs
new file mode 100644
index 00000000..2ea975fa
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechFuel/Gizmo_MechFuelStatus.cs
@@ -0,0 +1,127 @@
+// Gizmo_MechFuelStatus.cs
+using RimWorld;
+using UnityEngine;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ [StaticConstructorOnStartup]
+ public class Gizmo_MechFuelStatus : Gizmo
+ {
+ public CompMechFuel fuelComp;
+
+ private static readonly Texture2D FullFuelBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.1f, 0.6f, 0.9f));
+ private static readonly Texture2D EmptyFuelBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.2f, 0.2f, 0.24f));
+ private static readonly Texture2D WarningFuelBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.9f, 0.6f, 0.1f));
+
+ // 测试模式图标
+ private static readonly Texture2D DebugIcon = ContentFinder.Get("UI/Commands/Debug", false);
+
+ public Gizmo_MechFuelStatus(CompMechFuel fuelComp)
+ {
+ this.fuelComp = fuelComp;
+ Order = -90f; // 在护盾Gizmo之后显示
+ }
+
+ public override float GetWidth(float maxWidth)
+ {
+ return 140f;
+ }
+
+ public override GizmoResult GizmoOnGUI(Vector2 topLeft, float maxWidth, GizmoRenderParms parms)
+ {
+ Rect rect = new Rect(topLeft.x, topLeft.y, GetWidth(maxWidth), 75f);
+ Rect rect2 = rect.ContractedBy(6f);
+
+ Widgets.DrawWindowBackground(rect);
+
+ // 在 God Mode 下显示调试图标
+ if (DebugSettings.godMode && DebugIcon != null)
+ {
+ Rect debugIconRect = new Rect(rect.x + 5f, rect.y + 5f, 12f, 12f);
+ GUI.DrawTexture(debugIconRect, DebugIcon);
+ }
+
+ // 标题区域
+ Rect titleRect = rect2;
+ titleRect.height = rect.height / 2f;
+ Text.Font = GameFont.Tiny;
+
+ // 在 God Mode 下显示"调试模式"标题
+ string title = DebugSettings.godMode ?
+ "DD_MechFuel".Translate().Resolve() + " [DEBUG]" :
+ "DD_MechFuel".Translate().Resolve();
+
+ Widgets.Label(titleRect, title);
+
+ // 燃料条区域
+ Rect barRect = rect2;
+ barRect.yMin = rect2.y + rect2.height / 2f;
+
+ // 选择燃料条颜色(低燃料时用警告色)
+ Texture2D barTex;
+ if (fuelComp.FuelPercent < 0.2f)
+ {
+ barTex = WarningFuelBarTex;
+ }
+ else
+ {
+ barTex = FullFuelBarTex;
+ }
+
+ // 绘制燃料条
+ Widgets.FillableBar(barRect, fuelComp.FuelPercent, barTex, EmptyFuelBarTex, doBorder: false);
+
+ // 绘制燃料数值
+ Text.Font = GameFont.Small;
+ Text.Anchor = TextAnchor.MiddleCenter;
+ Widgets.Label(barRect,
+ fuelComp.Fuel.ToString("F0") + " / " +
+ fuelComp.Props.fuelCapacity.ToString("F0") +
+ " (" + (fuelComp.FuelPercent * 100f).ToString("F0") + "%)");
+ Text.Anchor = TextAnchor.UpperLeft;
+
+ // 状态文本
+ if (fuelComp.IsShutdown)
+ {
+ Rect statusRect = new Rect(barRect.x, barRect.y - 15f, barRect.width, 15f);
+ Text.Font = GameFont.Tiny;
+ Text.Anchor = TextAnchor.UpperCenter;
+ GUI.color = Color.red;
+ Widgets.Label(statusRect, "DD_Shutdown".Translate());
+ GUI.color = Color.white;
+ Text.Anchor = TextAnchor.UpperLeft;
+ }
+
+ // 工具提示
+ string tip = "DD_MechFuelTip".Translate(
+ fuelComp.FuelPercent.ToStringPercent(),
+ fuelComp.Props.dailyFuelConsumption,
+ fuelComp.Props.fuelType.label
+ );
+
+ if (fuelComp.IsShutdown)
+ {
+ tip += "\n\n" + "DD_ShutdownTip".Translate();
+ }
+ else if (fuelComp.NeedsRefueling)
+ {
+ tip += "\n\n" + "DD_NeedsRefueling".Translate();
+ }
+
+ // 在 God Mode 下添加调试信息到工具提示
+ if (DebugSettings.godMode)
+ {
+ tip += "\n\n" + "DD_Debug_Tip".Translate().Colorize(Color.gray) +
+ "\n" + "DD_Debug_Status".Translate(
+ fuelComp.IsShutdown ? "DD_Shutdown".Translate() : "DD_Running".Translate(),
+ fuelComp.HasPilot() ? "DD_HasPilot".Translate() : "DD_NoPilot".Translate()
+ );
+ }
+
+ TooltipHandler.TipRegion(rect2, tip);
+
+ return new GizmoResult(GizmoState.Clear);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompMechInherentWeapon.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompMechInherentWeapon.cs
new file mode 100644
index 00000000..679e5b6b
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompMechInherentWeapon.cs
@@ -0,0 +1,96 @@
+// CompMechInherentWeapon.cs
+using RimWorld;
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompMechInherentWeapon : ThingComp
+ {
+ public CompProperties_MechInherentWeapon Props => (CompProperties_MechInherentWeapon)props;
+
+ private Pawn mech => parent as Pawn;
+ private int lastCheckTick = -1;
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ // 机甲生成时立即检查一次
+ CheckAndEquipWeapon();
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (mech == null || mech.Dead || !mech.Spawned || Props.weaponDef == null)
+ return;
+
+ // 每250ticks检查一次
+ int currentTick = Find.TickManager.TicksGame;
+ if (currentTick - lastCheckTick >= 250)
+ {
+ CheckAndEquipWeapon();
+ lastCheckTick = currentTick;
+ }
+ }
+
+ private void CheckAndEquipWeapon()
+ {
+ if (mech == null || mech.Dead || !mech.Spawned || Props.weaponDef == null)
+ return;
+
+ // 检查当前装备
+ ThingWithComps currentWeapon = mech.equipment?.Primary;
+
+ // 如果当前装备的不是指定武器,或者没有装备武器
+ if (currentWeapon == null || currentWeapon.def != Props.weaponDef)
+ {
+ // 丢弃当前武器
+ if (currentWeapon != null)
+ {
+ DropCurrentWeapon(currentWeapon);
+ }
+
+ // 装备固有武器
+ EquipInherentWeapon();
+ }
+ }
+
+ private void DropCurrentWeapon(ThingWithComps weapon)
+ {
+ if (weapon == null || mech.Map == null || mech.equipment == null)
+ return;
+
+ // 从装备中移除
+ if (mech.equipment.Contains(weapon))
+ {
+ mech.equipment.Remove(weapon);
+ }
+
+ // 放置到机甲位置
+ GenPlace.TryPlaceThing(weapon, mech.Position, mech.Map, ThingPlaceMode.Near);
+ }
+
+ private void EquipInherentWeapon()
+ {
+ if (mech == null || mech.equipment == null || Props.weaponDef == null)
+ return;
+
+ // 创建固有武器
+ ThingWithComps inherentWeapon = ThingMaker.MakeThing(Props.weaponDef) as ThingWithComps;
+ if (inherentWeapon == null)
+ return;
+
+ // 装备武器
+ mech.equipment.AddEquipment(inherentWeapon);
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref lastCheckTick, "lastCheckTick", -1);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompProperties_MechInherentWeapon.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompProperties_MechInherentWeapon.cs
new file mode 100644
index 00000000..9c72236f
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechInherentWeapon/CompProperties_MechInherentWeapon.cs
@@ -0,0 +1,16 @@
+// CompProperties_MechInherentWeapon.cs
+using RimWorld;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechInherentWeapon : CompProperties
+ {
+ public ThingDef weaponDef; // 固有武器的定义
+
+ public CompProperties_MechInherentWeapon()
+ {
+ this.compClass = typeof(CompMechInherentWeapon);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompMechMovementSound.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompMechMovementSound.cs
new file mode 100644
index 00000000..adaf0f04
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompMechMovementSound.cs
@@ -0,0 +1,359 @@
+// CompMechMovementSound_Fixed.cs
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+using System.Collections.Generic;
+
+namespace WulaFallenEmpire
+{
+ public class CompMechMovementSound : ThingComp
+ {
+ public CompProperties_MechMovementSound Props => (CompProperties_MechMovementSound)props;
+
+ // 核心状态
+ private Sustainer soundSustainer;
+ private bool isPlaying = false;
+ private Vector3 lastPosition = Vector3.zero;
+ private float currentSpeed = 0f;
+ private int ticksSinceLastMovement = 0;
+ private bool wasMovingLastTick = false;
+
+ // 缓存引用
+ private Pawn mechPawn;
+ private CompPowerTrader powerComp;
+ private CompMechPilotHolder pilotComp;
+
+ // 状态平滑
+ private const int MOVEMENT_CHECK_INTERVAL = 3; // 每10ticks检查一次移动
+ private const int STOP_DELAY_TICKS = 1; // 停止后延迟30ticks再停止音效
+ private const float SPEED_SMOOTHING = 0.2f; // 速度平滑系数
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ mechPawn = parent as Pawn;
+ }
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ powerComp = parent.TryGetComp();
+ pilotComp = parent.TryGetComp();
+
+ if (mechPawn != null && mechPawn.Spawned)
+ {
+ lastPosition = GetCurrentPositionSafe();
+ }
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ // 每帧都更新位置,但减少移动状态检查频率
+ if (mechPawn != null && mechPawn.Spawned)
+ {
+ UpdatePosition();
+ }
+
+ // 每10ticks检查一次移动状态
+ if (Find.TickManager.TicksGame % MOVEMENT_CHECK_INTERVAL == 0)
+ {
+ UpdateMovementState();
+ }
+
+ // 每帧维持音效
+ MaintainSound();
+ }
+
+ // 安全的获取当前位置
+ private Vector3 GetCurrentPositionSafe()
+ {
+ try
+ {
+ if (mechPawn == null || !mechPawn.Spawned)
+ return mechPawn?.Position.ToVector3Shifted() ?? Vector3.zero;
+
+ // 优先使用DrawPos,如果不可用则使用网格位置
+ if (mechPawn.Drawer != null)
+ {
+ return mechPawn.DrawPos;
+ }
+ return mechPawn.Position.ToVector3Shifted();
+ }
+ catch
+ {
+ return mechPawn?.Position.ToVector3Shifted() ?? Vector3.zero;
+ }
+ }
+
+ // 更新位置(每帧)
+ private void UpdatePosition()
+ {
+ Vector3 currentPos = GetCurrentPositionSafe();
+
+ // 计算当前帧的速度(使用真实时间)
+ float deltaTime = Time.deltaTime;
+ if (deltaTime > 0)
+ {
+ float distance = Vector3.Distance(currentPos, lastPosition);
+ float rawSpeed = distance / deltaTime;
+
+ // 应用平滑过滤
+ currentSpeed = Mathf.Lerp(currentSpeed, rawSpeed, SPEED_SMOOTHING);
+ }
+
+ lastPosition = currentPos;
+ }
+
+ // 更新移动状态(低频检查)
+ private void UpdateMovementState()
+ {
+ if (!ShouldProcess())
+ {
+ ticksSinceLastMovement++;
+ if (isPlaying && ticksSinceLastMovement > STOP_DELAY_TICKS)
+ {
+ StopSound();
+ }
+ return;
+ }
+
+ // 检查是否在移动
+ bool isMoving = CheckIfMoving();
+
+ // 更新移动状态
+ if (isMoving)
+ {
+ ticksSinceLastMovement = 0;
+
+ if (!isPlaying)
+ {
+ StartSound();
+ }
+ }
+ else
+ {
+ ticksSinceLastMovement++;
+
+ // 延迟停止,避免频繁启停
+ if (isPlaying && ticksSinceLastMovement > STOP_DELAY_TICKS)
+ {
+ StopSound();
+ }
+ }
+
+ wasMovingLastTick = isMoving;
+ }
+
+ // 检查是否应该处理音效
+ private bool ShouldProcess()
+ {
+ if (mechPawn == null || Props.movementSound == null)
+ return false;
+
+ // 基础状态检查
+ if (!mechPawn.Spawned || mechPawn.Dead || mechPawn.Downed || mechPawn.InMentalState)
+ return false;
+
+ // 条件检查
+ if (Props.requirePower && powerComp != null && !powerComp.PowerOn)
+ return false;
+
+ if (Props.requirePilot && pilotComp != null && !pilotComp.HasPilots)
+ return false;
+
+ return true;
+ }
+
+ // 综合判断是否在移动
+ private bool CheckIfMoving()
+ {
+ // 方法1:速度阈值
+ if (currentSpeed > Props.minMovementSpeed)
+ return true;
+
+ // 方法2:检查寻路器
+ if (mechPawn.pather?.Moving ?? false)
+ return true;
+
+ // 方法3:检查当前任务
+ var job = mechPawn.CurJob;
+ if (job != null)
+ {
+ if (job.def == JobDefOf.Goto ||
+ job.def == JobDefOf.GotoWander ||
+ job.def == JobDefOf.Flee ||
+ job.def == JobDefOf.Follow)
+ return true;
+ }
+
+ return false;
+ }
+
+ // 维持音效
+ private void MaintainSound()
+ {
+ if (soundSustainer != null && isPlaying)
+ {
+ try
+ {
+ // 更新音效位置
+ if (mechPawn != null && mechPawn.Spawned)
+ {
+ var map = mechPawn.Map;
+ if (map != null)
+ {
+ // 创建一个新的SoundInfo来更新位置
+ SoundInfo soundInfo = SoundInfo.InMap(mechPawn, MaintenanceType.PerTick);
+ soundSustainer?.SustainerUpdate();
+ }
+ }
+
+ // 维持音效
+ soundSustainer.Maintain();
+ }
+ catch
+ {
+ // 如果sustainer失效,重置状态
+ soundSustainer = null;
+ isPlaying = false;
+ }
+ }
+ }
+
+ // 开始音效
+ private void StartSound()
+ {
+ if (Props.movementSound == null || soundSustainer != null)
+ return;
+
+ try
+ {
+ // 创建音效信息
+ SoundInfo soundInfo = SoundInfo.InMap(mechPawn, MaintenanceType.PerTick);
+
+ // 使用TrySpawnSustainer
+ soundSustainer = Props.movementSound.TrySpawnSustainer(soundInfo);
+
+ if (soundSustainer != null)
+ {
+ isPlaying = true;
+ }
+ else
+ {
+ Log.Warning($"[DD] Failed to create sustainer for {Props.movementSound.defName}");
+ isPlaying = false;
+ }
+ }
+ catch
+ {
+ soundSustainer = null;
+ isPlaying = false;
+ }
+ }
+
+ // 停止音效
+ private void StopSound()
+ {
+ if (soundSustainer != null)
+ {
+ try
+ {
+ // 先检查sustainer是否有效
+ if (!soundSustainer.Ended)
+ {
+ soundSustainer.End();
+ }
+ }
+ finally
+ {
+ soundSustainer = null;
+ isPlaying = false;
+ }
+ }
+ }
+
+ // 事件处理
+ public override void PostDestroy(DestroyMode mode, Map previousMap)
+ {
+ base.PostDestroy(mode, previousMap);
+ StopSound();
+ }
+
+ public void PostDeSpawn(Map map)
+ {
+ base.PostDeSpawn(map);
+ StopSound();
+ }
+
+ public override void Notify_Downed()
+ {
+ base.Notify_Downed();
+ StopSound();
+ }
+
+ // 序列化
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+
+ Scribe_Values.Look(ref lastPosition, "lastPosition", Vector3.zero);
+ Scribe_Values.Look(ref currentSpeed, "currentSpeed", 0f);
+ Scribe_Values.Look(ref ticksSinceLastMovement, "ticksSinceLastMovement", 0);
+ Scribe_Values.Look(ref wasMovingLastTick, "wasMovingLastTick", false);
+
+ // 加载后重新初始化
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ soundSustainer = null;
+ isPlaying = false;
+ }
+ }
+
+ // 调试信息
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ foreach (Gizmo gizmo in base.CompGetGizmosExtra())
+ {
+ yield return gizmo;
+ }
+
+ if (DebugSettings.ShowDevGizmos && mechPawn != null && mechPawn.Faction == Faction.OfPlayer)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "DEV: Sound Debug",
+ defaultDesc = GetDebugInfo(),
+ action = () =>
+ {
+ // 切换声音状态
+ if (soundSustainer == null)
+ {
+ StartSound();
+ Messages.Message("Sound started", mechPawn, MessageTypeDefOf.NeutralEvent);
+ }
+ else
+ {
+ StopSound();
+ Messages.Message("Sound stopped", mechPawn, MessageTypeDefOf.NeutralEvent);
+ }
+ }
+ };
+ }
+ }
+
+ private string GetDebugInfo()
+ {
+ return $"Movement Sound Debug:\n" +
+ $" Playing: {isPlaying}\n" +
+ $" Speed: {currentSpeed:F2} (min: {Props.minMovementSpeed})\n" +
+ $" Ticks since move: {ticksSinceLastMovement}\n" +
+ $" Sustainer: {soundSustainer != null}\n" +
+ $" Was moving: {wasMovingLastTick}\n" +
+ $" Pawn pathing: {mechPawn.pather?.Moving ?? false}";
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompProperties_MechMovementSound.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompProperties_MechMovementSound.cs
new file mode 100644
index 00000000..be894f41
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechMovementSound/CompProperties_MechMovementSound.cs
@@ -0,0 +1,66 @@
+// CompProperties_MechMovementSound_Enhanced.cs
+using RimWorld;
+using Verse;
+using System.Collections.Generic;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechMovementSound : CompProperties
+ {
+ // 基础音效
+ public SoundDef movementSound;
+
+ // 控制参数
+ public bool requirePilot = false;
+ public bool requirePower = false;
+ public float minMovementSpeed = 0.1f;
+
+ // 新增:平滑控制
+ public int movementCheckInterval = 10; // 移动检查间隔
+ public int stopDelayTicks = 30; // 停止延迟
+ public float speedSmoothing = 0.2f; // 速度平滑系数
+
+ // 新增:声音参数
+ public bool loopSound = true; // 是否循环播放
+ public float volumeMultiplier = 1.0f; // 音量乘数
+
+ public CompProperties_MechMovementSound()
+ {
+ this.compClass = typeof(CompMechMovementSound);
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string error in base.ConfigErrors(parentDef))
+ {
+ yield return error;
+ }
+
+ if (movementSound == null)
+ {
+ yield return $"movementSound is not defined for {parentDef.defName}";
+ }
+
+ if (minMovementSpeed < 0f)
+ {
+ yield return $"minMovementSpeed cannot be negative for {parentDef.defName}";
+ }
+
+ if (movementCheckInterval < 1)
+ {
+ yield return $"movementCheckInterval must be at least 1 for {parentDef.defName}";
+ }
+
+ if (stopDelayTicks < 0)
+ {
+ yield return $"stopDelayTicks cannot be negative for {parentDef.defName}";
+ }
+
+ // 如果需要驾驶员,检查是否配置了驾驶员容器
+ if (requirePilot && parentDef.GetCompProperties() == null)
+ {
+ Log.Warning($"[DD] requirePilot is true but no CompProperties_MechPilotHolder found for {parentDef.defName}");
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechPilotHolder/CompMechPilotHolder.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechPilotHolder/CompMechPilotHolder.cs
new file mode 100644
index 00000000..62ad3c17
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechPilotHolder/CompMechPilotHolder.cs
@@ -0,0 +1,1015 @@
+// File: CompMechPilotHolder.cs (添加残疾殖民者搬运逻辑)
+using WulaFallenEmpire;
+using RimWorld;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechPilotHolder : CompProperties
+ {
+ public int maxPilots = 1;
+ public string pilotWorkTag = "MechPilot";
+
+ // 新增:驾驶员图标配置
+ public string summonPilotIcon = "WulaFallenEmpire/UI/Commands/DD_Enter_Mech";
+ public string ejectPilotIcon = "WulaFallenEmpire/UI/Commands/DD_Exit_Mech";
+
+ public float ejectPilotHealthPercentThreshold = 0.1f; // 默认30%血量
+ public bool allowEntryBelowThreshold = false; // 血量低于阈值时是否允许进入
+
+ // 新增:Hediff同步配置
+ public bool syncPilotHediffs = true; // 是否同步驾驶员的Hediff
+ public List syncedHediffDefs = null; // 需要同步的Hediff列表(null表示全部)
+ public bool autoApplyHediffOnEntry = false; // 进入时自动添加指定的Hediff
+ public HediffDef autoHediffDef = null; // 自动添加的Hediff
+ public float autoHediffSeverity = 0.5f; // 自动添加的Hediff严重性
+
+ public CompProperties_MechPilotHolder()
+ {
+ this.compClass = typeof(CompMechPilotHolder);
+ }
+
+ // 新增:加载图标的方法
+ public Texture2D GetSummonPilotIcon()
+ {
+ if (!string.IsNullOrEmpty(summonPilotIcon) && ContentFinder.Get(summonPilotIcon, false) != null)
+ {
+ return ContentFinder.Get(summonPilotIcon);
+ }
+ return ContentFinder.Get("UI/Commands/SummonPilot", false) ??
+ BaseContent.BadTex;
+ }
+
+ public Texture2D GetEjectPilotIcon()
+ {
+ if (!string.IsNullOrEmpty(ejectPilotIcon) && ContentFinder.Get(ejectPilotIcon, false) != null)
+ {
+ return ContentFinder.Get(ejectPilotIcon);
+ }
+ return ContentFinder.Get("UI/Commands/Eject", false) ??
+ BaseContent.BadTex;
+ }
+ }
+
+ public class CompMechPilotHolder : ThingComp, IThingHolder, ISuspendableThingHolder
+ {
+ public ThingOwner innerContainer;
+
+ // 标记是否正在处理死亡/销毁事件,避免重复处理
+ private bool isProcessingDestruction = false;
+
+ // 新增:记录是否已经因为低血量弹出过驾驶员
+ private bool hasEjectedDueToLowHealth = false;
+
+ // 新增:存储驾驶员同步的Hediff
+ private Dictionary> syncedHediffs = new Dictionary>();
+
+ public CompProperties_MechPilotHolder Props => (CompProperties_MechPilotHolder)props;
+
+ public int CurrentPilotCount => innerContainer.Count;
+ public bool HasPilots => innerContainer.Count > 0;
+ public bool HasRoom => innerContainer.Count < Props.maxPilots;
+ public bool IsFull => innerContainer.Count >= Props.maxPilots;
+
+ public bool IsContentsSuspended => true;
+
+ // 新增:获取精神状态定义
+ private MentalStateDef MechNoPilotStateDef => WULA_MentalStateDefOf.WULA_MechNoPilot;
+
+ // 新增:检查并更新精神状态
+ private void CheckAndUpdateMentalState()
+ {
+ var mech = parent as Pawn;
+ if (mech == null || mech.Dead || MechNoPilotStateDef == null)
+ return;
+
+ // 如果没有驾驶员,尝试进入待机状态
+ if (!HasPilots)
+ {
+ if (mech.MentalStateDef != MechNoPilotStateDef && !mech.InMentalState)
+ {
+ mech.mindState.mentalStateHandler.TryStartMentalState(MechNoPilotStateDef, null, true);
+ }
+ }
+ // 如果有驾驶员,确保退出待机状态
+ else
+ {
+ if (mech.MentalStateDef == MechNoPilotStateDef)
+ {
+ mech.mindState.mentalStateHandler.CurState?.RecoverFromState();
+ }
+ }
+ }
+
+ // 修改:添加驾驶员 - 添加Hediff同步功能
+ public void AddPilot(Pawn pawn)
+ {
+ if (!CanAddPilot(pawn))
+ return;
+
+ // 将pawn添加到容器中
+ if (pawn.Spawned)
+ pawn.DeSpawnOrDeselect();
+
+ innerContainer.TryAdd(pawn, true);
+
+ // 停止pawn的移动
+ pawn.pather?.StopDead();
+ pawn.jobs?.StopAll();
+
+ // 触发事件
+ Notify_PilotAdded(pawn);
+
+ // 更新机甲的精神状态
+ CheckAndUpdateMentalState();
+
+ // 新增:同步驾驶员的Hediff
+ if (Props.syncPilotHediffs)
+ {
+ SyncPilotHediffs(pawn);
+ }
+
+ // 新增:自动添加Hediff
+ if (Props.autoApplyHediffOnEntry && Props.autoHediffDef != null)
+ {
+ AddAutoHediff(pawn);
+ }
+ }
+
+ // 修改:移除驾驶员 - 添加Hediff取消同步功能
+ public void RemovePilot(Pawn pawn, IntVec3? exitPos = null)
+ {
+ // 新增:移除前,清理同步的Hediff
+ if (Props.syncPilotHediffs)
+ {
+ UnsyncPilotHediffs(pawn);
+ }
+
+ if (innerContainer.Contains(pawn))
+ {
+ // 从容器中移除
+ innerContainer.Remove(pawn);
+
+ // 将pawn放回地图
+ TrySpawnPilotAtPosition(pawn, exitPos ?? parent.Position);
+
+ // 触发事件
+ Notify_PilotRemoved(pawn);
+
+ // 停止机甲的工作
+ StopMechJobs();
+
+ // 更新机甲的精神状态
+ CheckAndUpdateMentalState();
+ }
+ }
+
+ // 新增:同步驾驶员的Hediff
+ private void SyncPilotHediffs(Pawn pawn)
+ {
+ // 修复:确保parent是Wulamechunit类型
+ if (pawn == null || !(parent is Wulamechunit mech))
+ return;
+
+ try
+ {
+ var hediffsToSync = new List();
+
+ // 收集需要同步的Hediff
+ foreach (var hediff in pawn.health.hediffSet.hediffs)
+ {
+ if (ShouldSyncHediff(hediff))
+ {
+ hediffsToSync.Add(hediff);
+
+ // 激活Hediff的同步组件
+ var syncComp = hediff.TryGetComp();
+ if (syncComp != null)
+ {
+ syncComp.OnPilotEnteredMech(mech); // 这里现在应该可以了
+ }
+ }
+ }
+
+ // 存储同步的Hediff
+ if (hediffsToSync.Count > 0)
+ {
+ syncedHediffs[pawn] = hediffsToSync;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 同步Hediff时出错: {ex}");
+ }
+ }
+
+ // 新增:取消同步驾驶员的Hediff
+ private void UnsyncPilotHediffs(Pawn pawn)
+ {
+ if (pawn == null || !syncedHediffs.ContainsKey(pawn))
+ return;
+
+ try
+ {
+ // 通知所有同步的Hediff断开连接
+ foreach (var hediff in syncedHediffs[pawn])
+ {
+ var syncComp = hediff.TryGetComp();
+ if (syncComp != null)
+ {
+ syncComp.OnPilotExitedMech();
+ }
+ }
+
+ // 从记录中移除
+ syncedHediffs.Remove(pawn);
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 取消同步Hediff时出错: {ex}");
+ }
+ }
+
+ // 新增:判断Hediff是否需要同步
+ private bool ShouldSyncHediff(Hediff hediff)
+ {
+ if (hediff == null)
+ return false;
+
+ // 检查是否有同步组件
+ var syncComp = hediff.TryGetComp();
+ if (syncComp == null)
+ return false;
+
+ // 检查是否在指定的同步列表中
+ if (Props.syncedHediffDefs != null &&
+ Props.syncedHediffDefs.Count > 0)
+ {
+ return Props.syncedHediffDefs.Contains(hediff.def.defName);
+ }
+
+ // 默认同步所有有同步组件的Hediff
+ return true;
+ }
+
+ // 新增:自动添加Hediff
+ private void AddAutoHediff(Pawn pawn)
+ {
+ try
+ {
+ // 检查是否已经有相同的Hediff
+ var existingHediff = pawn.health.hediffSet.GetFirstHediffOfDef(Props.autoHediffDef);
+ if (existingHediff == null)
+ {
+ var hediff = HediffMaker.MakeHediff(Props.autoHediffDef, pawn);
+ hediff.Severity = Props.autoHediffSeverity;
+ pawn.health.AddHediff(hediff);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 自动添加Hediff时出错: {ex}");
+ }
+ }
+
+ // 修改:在CompTick中添加Hediff同步检查
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ try
+ {
+ // 每60帧检查一次血量和精神状态
+ if (Find.TickManager.TicksGame % 60 == 0)
+ {
+ CheckLowHealth();
+ CheckAndUpdateMentalState();
+ }
+
+ // 每120帧检查一次Hediff同步状态
+ if (Find.TickManager.TicksGame % 120 == 0)
+ {
+ CheckHediffSync();
+ }
+
+ // 检查机甲是否死亡
+ var mech = parent as Pawn;
+ if (mech != null && mech.Dead && HasPilots)
+ {
+ EjectAllPilotsOnDeath();
+ return;
+ }
+
+ // 定期检查驾驶员状态
+ var pilotsToRemove = new List();
+ foreach (var thing in innerContainer)
+ {
+ if (thing is Pawn pawn && (pawn.Dead))
+ {
+ pilotsToRemove.Add(pawn);
+ }
+ }
+
+ foreach (var pawn in pilotsToRemove)
+ {
+ RemovePilot(pawn);
+ }
+
+ // 确保容器内的pawn处于正确状态
+ foreach (var thing in innerContainer)
+ {
+ if (thing is Pawn pawn)
+ {
+ // 确保pawn在容器内不执行任何工作
+ pawn.jobs?.StopAll();
+ pawn.pather?.StopDead();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] CompTick error: {ex}");
+ }
+ }
+
+ // 新增:检查Hediff同步状态
+ private void CheckHediffSync()
+ {
+ // 修复:确保parent是Wulamechunit类型
+ if (!Props.syncPilotHediffs || !(parent is Wulamechunit))
+ return;
+
+ try
+ {
+ // 检查每个驾驶员的同步状态
+ foreach (var pilot in GetPilots())
+ {
+ if (pilot == null || pilot.Dead || pilot.Destroyed)
+ continue;
+
+ // 检查是否有新的需要同步的Hediff
+ SyncPilotHediffs(pilot);
+
+ // 检查是否有需要移除的Hediff
+ if (syncedHediffs.ContainsKey(pilot))
+ {
+ var currentHediffs = pilot.health.hediffSet.hediffs
+ .Where(ShouldSyncHediff)
+ .ToList();
+
+ // 找出不再存在的Hediff
+ var removedHediffs = syncedHediffs[pilot]
+ .Where(h => !currentHediffs.Contains(h))
+ .ToList();
+
+ // 清理不再存在的Hediff
+ foreach (var hediff in removedHediffs)
+ {
+ var syncComp = hediff.TryGetComp();
+ if (syncComp != null)
+ {
+ syncComp.OnPilotExitedMech();
+ }
+ }
+
+ // 更新记录
+ syncedHediffs[pilot] = currentHediffs;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 检查Hediff同步状态时出错: {ex}");
+ }
+ }
+
+ // 修改:在生成后初始化精神状态
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ if (!(parent is Wulamechunit))
+ {
+ Log.Warning($"[DD] CompMechPilotHolder attached to non-mech: {parent}");
+ }
+
+ // 确保加载后恢复状态
+ if (innerContainer == null)
+ {
+ innerContainer = new ThingOwner(this);
+ }
+
+ // 初始化精神状态
+ CheckAndUpdateMentalState();
+
+ // 新增:加载后重新同步Hediff
+ if (Props.syncPilotHediffs)
+ {
+ foreach (var pilot in GetPilots())
+ {
+ SyncPilotHediffs(pilot);
+ }
+ }
+ }
+
+ // 修改:在数据保存和加载时处理Hediff同步
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+
+ Scribe_Deep.Look(ref innerContainer, "innerContainer", this);
+ Scribe_Values.Look(ref isProcessingDestruction, "isProcessingDestruction", false);
+ Scribe_Values.Look(ref hasEjectedDueToLowHealth, "hasEjectedDueToLowHealth", false);
+ Scribe_Collections.Look(ref syncedHediffs, "syncedHediffs",
+ LookMode.Reference, LookMode.Deep);
+
+ // 加载后检查精神状态和Hediff同步
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ CheckAndUpdateMentalState();
+
+ if (Props.syncPilotHediffs)
+ {
+ // 重新同步所有驾驶员的Hediff
+ foreach (var pilot in GetPilots())
+ {
+ SyncPilotHediffs(pilot);
+ }
+ }
+ }
+ }
+
+ // 新增:停止机甲所有工作
+ private void StopMechJobs()
+ {
+ var mech = parent as Pawn;
+ if (mech == null)
+ return;
+
+ // 停止所有工作
+ mech.jobs?.StopAll();
+
+ // 停止移动
+ mech.pather?.StopDead();
+
+ // 取消征召
+ var drafter = mech.drafter;
+ if (drafter != null && mech.Drafted)
+ {
+ mech.drafter.Drafted = false;
+ }
+
+ // 停止当前所有工作队列
+ mech.jobs?.ClearQueuedJobs();
+
+ // 清除敌人目标
+ mech.mindState.enemyTarget = null;
+ }
+
+ // 获取机甲当前血量百分比
+ public float CurrentHealthPercent
+ {
+ get
+ {
+ var mech = parent as Pawn;
+ if (mech == null || mech.health == null)
+ return 1.0f;
+
+ return mech.health.summaryHealth.SummaryHealthPercent;
+ }
+ }
+
+ // 检查机甲是否低于血量阈值
+ public bool IsBelowHealthThreshold
+ {
+ get
+ {
+ return CurrentHealthPercent < Props.ejectPilotHealthPercentThreshold;
+ }
+ }
+
+ // 修改 CanAddPilot 方法,添加血量检查
+ public bool CanAddPilot(Pawn pawn)
+ {
+ if (pawn == null || pawn.Dead)
+ return false;
+
+ // 允许无法行动但还活着的殖民者
+ if (pawn.Downed)
+ return true; // 这是新增的关键修改
+
+ if (!HasRoom)
+ return false;
+ if (innerContainer.Contains(pawn))
+ return false;
+ // 检查工作标签
+ if (!string.IsNullOrEmpty(Props.pilotWorkTag))
+ {
+ WorkTags tag;
+ if (System.Enum.TryParse(Props.pilotWorkTag, out tag))
+ {
+ if (pawn.WorkTagIsDisabled(tag))
+ return false;
+ }
+ }
+
+ // 新增:检查血量阈值
+ if (!Props.allowEntryBelowThreshold && IsBelowHealthThreshold)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ // 修改:检查殖民者是否能够自行移动到机甲
+ private bool CanPawnMoveToMech(Pawn pawn, Wulamechunit mech)
+ {
+ if (pawn == null || mech == null)
+ return false;
+
+ // 如果殖民者无法行动,需要搬运
+ if (pawn.Downed)
+ return false;
+
+ // 检查殖民者是否能到达机甲
+ return pawn.CanReach(mech, PathEndMode.Touch, Danger.Deadly);
+ }
+
+ // 修改 CompMechPilotHolder 的 CheckLowHealth 方法
+ private void CheckLowHealth()
+ {
+ if (IsBelowHealthThreshold && HasPilots)
+ {
+ // 如果低于阈值且有驾驶员,弹出所有驾驶员
+ EjectPilotsDueToLowHealth();
+ }
+ else if (!IsBelowHealthThreshold)
+ {
+ // 如果恢复到阈值以上,重置标记
+ hasEjectedDueToLowHealth = false;
+ }
+ }
+
+ // 新增:因为低血量弹出驾驶员
+ private void EjectPilotsDueToLowHealth()
+ {
+ if (hasEjectedDueToLowHealth)
+ return;
+
+ // 弹出所有驾驶员
+ RemoveAllPilots();
+
+ // 发送消息
+ if (parent.Faction == Faction.OfPlayer)
+ {
+ Messages.Message("DD_PilotsEjectedDueToLowHealth".Translate(parent.LabelShort,
+ (Props.ejectPilotHealthPercentThreshold * 100).ToString("F0")),
+ parent, MessageTypeDefOf.NegativeEvent);
+ }
+
+ hasEjectedDueToLowHealth = true;
+ }
+
+ // 新增:在承受伤害后检查血量
+ public override void PostPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)
+ {
+ base.PostPostApplyDamage(dinfo, totalDamageDealt);
+
+ // 如果机甲死亡,弹出驾驶员
+ var mech = parent as Pawn;
+ if (mech != null && mech.Dead)
+ {
+ EjectAllPilotsOnDeath();
+ }
+ else
+ {
+ // 检查是否因为伤害导致血量过低
+ CheckLowHealth();
+ }
+ }
+
+ // 修改 Gizmo 显示,添加血量信息和Hediff同步状态
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ // 修复:确保parent是Wulamechunit类型
+ if (!(parent is Wulamechunit mech) || mech.Faction != Faction.OfPlayer)
+ yield break;
+
+ // 召唤驾驶员Gizmo
+ if (HasRoom)
+ {
+ Command_Action summonCommand = new Command_Action
+ {
+ defaultLabel = "DD_SummonPilot".Translate(),
+ defaultDesc = "DD_SummonPilotDesc".Translate(),
+ icon = Props.GetSummonPilotIcon(),
+ action = () =>
+ {
+ ShowPilotSelectionMenu();
+ },
+ hotKey = KeyBindingDefOf.Misc2
+ };
+
+ // 如果血量低于阈值且不允许进入,禁用按钮
+ if (!Props.allowEntryBelowThreshold && IsBelowHealthThreshold)
+ {
+ summonCommand.Disable("DD_MechTooDamagedForEntry".Translate());
+ }
+
+ yield return summonCommand;
+ }
+
+ // 弹出所有驾驶员按钮
+ if (innerContainer.Count > 0)
+ {
+ yield return new Command_Action
+ {
+ defaultLabel = "DD_EjectAllPilots".Translate(),
+ defaultDesc = "DD_EjectAllPilotsDesc".Translate(),
+ icon = Props.GetEjectPilotIcon(),
+ action = () =>
+ {
+ RemoveAllPilots();
+ },
+ hotKey = KeyBindingDefOf.Misc1
+ };
+ }
+ }
+
+ public CompMechPilotHolder()
+ {
+ innerContainer = new ThingOwner(this);
+ }
+
+ // 修改:弹出所有驾驶员时取消Hediff同步
+ public void RemoveAllPilots(IntVec3? exitPos = null)
+ {
+ // 记录是否有驾驶员
+ bool hadPilots = HasPilots;
+
+ // 复制列表以避免迭代时修改的问题
+ var pilotsToRemove = innerContainer.ToList();
+
+ // 先取消所有Hediff同步
+ foreach (var thing in pilotsToRemove)
+ {
+ if (thing is Pawn pawn)
+ {
+ UnsyncPilotHediffs(pawn);
+ }
+ }
+
+ // 然后移除所有驾驶员
+ foreach (var thing in pilotsToRemove)
+ {
+ if (thing is Pawn pawn)
+ {
+ RemovePilot(pawn, exitPos);
+ }
+ }
+
+ // 如果有机甲并且原来有驾驶员,现在没有了,停止工作
+ if (hadPilots && parent is Pawn mech)
+ {
+ StopMechJobs();
+ }
+ }
+
+ // 修改:专门用于死亡/销毁时弹出驾驶员的方法,取消Hediff同步
+ public void EjectAllPilotsOnDeath()
+ {
+ if (isProcessingDestruction)
+ return;
+
+ try
+ {
+ isProcessingDestruction = true;
+
+ if (!HasPilots)
+ {
+ return;
+ }
+
+ // 先取消所有Hediff同步
+ var pilots = innerContainer.ToList();
+ foreach (var thing in pilots)
+ {
+ if (thing is Pawn pawn)
+ {
+ UnsyncPilotHediffs(pawn);
+ }
+ }
+
+ // 获取安全位置
+ IntVec3 ejectPos = FindSafeEjectPosition();
+
+ // 弹出所有驾驶员
+ foreach (var thing in pilots)
+ {
+ if (thing is Pawn pawn)
+ {
+ // 从容器中移除
+ innerContainer.Remove(pawn);
+
+ // 尝试生成到地图上
+ if (TrySpawnPilotAtPosition(pawn, ejectPos))
+ {
+ // 驾驶员成功弹出
+ }
+ else
+ {
+ Log.Error($"[DD] 无法弹出驾驶员: {pawn.LabelShort}");
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 弹出驾驶员时发生错误: {ex}");
+ }
+ finally
+ {
+ isProcessingDestruction = false;
+ }
+ }
+
+ private IntVec3 FindSafeEjectPosition()
+ {
+ Map map = parent.Map;
+ if (map == null)
+ return parent.Position;
+
+ // 优先选择机甲周围的安全位置
+ IntVec3 pos = parent.Position;
+
+ // 如果当前位置不安全,查找周围安全位置
+ if (!pos.Walkable(map) || pos.Fogged(map))
+ {
+ for (int i = 1; i <= 5; i++)
+ {
+ foreach (IntVec3 cell in GenRadial.RadialCellsAround(pos, i, true))
+ {
+ if (cell.Walkable(map) && !cell.Fogged(map))
+ {
+ return cell;
+ }
+ }
+ }
+ }
+
+ // 如果周围没有安全位置,使用随机位置
+ if (!pos.Walkable(map) || pos.Fogged(map))
+ {
+ CellFinder.TryFindRandomCellNear(pos, map, 10,
+ cell => cell.Walkable(map) && !cell.Fogged(map),
+ out pos, 100);
+ }
+
+ return pos;
+ }
+
+ private bool TrySpawnPilotAtPosition(Pawn pawn, IntVec3 position)
+ {
+ Map map = parent.Map;
+ if (map == null)
+ {
+ Log.Error($"[DD] 尝试在没有地图的情况下生成驾驶员: {pawn.LabelShort}");
+ return false;
+ }
+
+ // 尝试在指定位置生成
+ try
+ {
+ if (GenGrid.InBounds(position, map) && position.Walkable(map) && !position.Fogged(map))
+ {
+ GenSpawn.Spawn(pawn, position, map, WipeMode.Vanish);
+ return true;
+ }
+
+ // 如果指定位置不行,找附近的位置
+ IntVec3 spawnPos;
+ if (RCellFinder.TryFindRandomCellNearWith(position,
+ cell => cell.Walkable(map) && !cell.Fogged(map),
+ map, out spawnPos, 1, 10))
+ {
+ GenSpawn.Spawn(pawn, spawnPos, map, WipeMode.Vanish);
+ return true;
+ }
+
+ // 实在找不到位置,就在任意位置生成
+ CellFinder.TryFindRandomCellNear(position, map, 20,
+ cell => cell.Walkable(map) && !cell.Fogged(map),
+ out spawnPos);
+ GenSpawn.Spawn(pawn, spawnPos, map, WipeMode.Vanish);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] 生成驾驶员时发生错误: {ex}");
+ return false;
+ }
+ }
+
+ public Pawn GetPrimaryPilot()
+ {
+ if (innerContainer.Count > 0)
+ {
+ foreach (var thing in innerContainer)
+ {
+ if (thing is Pawn pawn)
+ return pawn;
+ }
+ }
+ return null;
+ }
+
+ public IEnumerable GetPilots()
+ {
+ foreach (var thing in innerContainer)
+ {
+ if (thing is Pawn pawn)
+ yield return pawn;
+ }
+ }
+
+ public void Notify_PilotAdded(Pawn pilot)
+ {
+ if (pilot.Faction == Faction.OfPlayer)
+ {
+ Messages.Message("DD_PilotEnteredMech".Translate(pilot.LabelShort, parent.LabelShort),
+ parent, MessageTypeDefOf.PositiveEvent);
+ }
+ }
+
+ public void Notify_PilotRemoved(Pawn pilot)
+ {
+ if (pilot.Faction == Faction.OfPlayer)
+ {
+ Messages.Message("DD_PilotExitedMech".Translate(pilot.LabelShort, parent.LabelShort),
+ parent, MessageTypeDefOf.NeutralEvent);
+ }
+ }
+
+ // 关键修复:重写销毁相关方法
+ public override void PostDestroy(DestroyMode mode, Map previousMap)
+ {
+ // 先弹出所有驾驶员并取消Hediff同步
+ if (HasPilots)
+ {
+ EjectAllPilotsOnDeath();
+ }
+
+ base.PostDestroy(mode, previousMap);
+ }
+
+ // IThingHolder 接口实现
+ public ThingOwner GetDirectlyHeldThings()
+ {
+ return innerContainer;
+ }
+
+ public void GetChildHolders(List outChildren)
+ {
+ ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
+ }
+
+ // 修改:显示驾驶员选择菜单,包含无法行动的殖民者
+ private void ShowPilotSelectionMenu()
+ {
+ // 修复:确保parent是Wulamechunit类型
+ if (!(parent is Wulamechunit mech))
+ return;
+
+ List options = new List();
+
+ // 获取所有可用的殖民者(包括无法行动的)
+ var allColonists = mech.Map.mapPawns.FreeColonists
+ .Where(p => CanAddPilot(p))
+ .ToList();
+
+ // 分类:能够行动和无法行动的
+ var ableColonists = allColonists.Where(p => CanPawnMoveToMech(p, mech)).ToList();
+ var disabledColonists = allColonists.Where(p => !CanPawnMoveToMech(p, mech)).ToList();
+
+ // 为能够行动的殖民者创建选项
+ if (ableColonists.Count == 0 && disabledColonists.Count == 0)
+ {
+ options.Add(new FloatMenuOption("DD_NoAvailablePilots".Translate(), null));
+ }
+ else
+ {
+ // 能够行动的殖民者:直接进入
+ foreach (var colonist in ableColonists)
+ {
+ string colonistLabel = colonist.LabelShortCap;
+ Action action = () => OrderColonistToEnterMech(colonist);
+
+ FloatMenuOption option = new FloatMenuOption(
+ colonistLabel,
+ action,
+ colonist,
+ Color.white,
+ MenuOptionPriority.Default,
+ null,
+ null,
+ 0f,
+ null,
+ null,
+ true,
+ 0
+ );
+
+ options.Add(option);
+ }
+
+ // 无法行动的殖民者:需要搬运
+ foreach (var colonist in disabledColonists)
+ {
+ string colonistLabel = colonist.LabelShortCap + " " + "DD_DisabledColonistRequiresCarry".Translate();
+ Action action = () => OrderCarryDisabledColonistToMech(colonist);
+
+ FloatMenuOption option = new FloatMenuOption(
+ colonistLabel,
+ action,
+ colonist,
+ Color.yellow,
+ MenuOptionPriority.Default,
+ null,
+ null,
+ 0f,
+ null,
+ null,
+ true,
+ 0
+ );
+
+ options.Add(option);
+ }
+ }
+
+ Find.WindowStack.Add(new FloatMenu(options));
+ }
+
+ private void OrderColonistToEnterMech(Pawn colonist)
+ {
+ // 修复:确保parent是Wulamechunit类型
+ if (!(parent is Wulamechunit mech) || colonist == null)
+ return;
+
+ // 为殖民者安排进入机甲的工作
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_EnterMech, mech);
+ colonist.jobs.TryTakeOrderedJob(job, JobTag.Misc);
+ }
+
+ // 新增:为残疾殖民者安排搬运工作
+ private void OrderCarryDisabledColonistToMech(Pawn disabledColonist)
+ {
+ if (!(parent is Wulamechunit mech) || disabledColonist == null)
+ return;
+
+ // 寻找最近的、能够搬运的殖民者
+ Pawn carrier = FindClosestAvailableCarrier(disabledColonist, mech);
+
+ if (carrier == null)
+ {
+ Messages.Message("DD_NoAvailableCarrier".Translate(disabledColonist.LabelShortCap),
+ parent, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 为搬运者安排搬运工作
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.WULA_CarryToMech, disabledColonist, mech);
+ carrier.jobs.TryTakeOrderedJob(job, JobTag.Misc);
+
+ Messages.Message("DD_CarrierAssigned".Translate(carrier.LabelShortCap, disabledColonist.LabelShortCap),
+ parent, MessageTypeDefOf.PositiveEvent);
+ }
+
+ // 新增:寻找最近的可用搬运者
+ private Pawn FindClosestAvailableCarrier(Pawn disabledColonist, Wulamechunit mech)
+ {
+ if (disabledColonist.Map == null)
+ return null;
+
+ // 寻找能够行动的殖民者,并且能够搬运
+ var potentialCarriers = disabledColonist.Map.mapPawns.FreeColonists
+ .Where(p => p != disabledColonist && !p.Downed &&
+ p.CanReserveAndReach(disabledColonist, PathEndMode.OnCell, Danger.Deadly, 1, -1, null, false) &&
+ p.CanReserveAndReach(mech, PathEndMode.Touch, Danger.Deadly, 1, -1, null, false))
+ .ToList();
+
+ if (potentialCarriers.Count == 0)
+ return null;
+
+ // 选择最近的殖民者
+ return potentialCarriers
+ .OrderBy(p => p.Position.DistanceTo(disabledColonist.Position))
+ .FirstOrDefault();
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechRepairable/CompMechRepairable.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechRepairable/CompMechRepairable.cs
new file mode 100644
index 00000000..0495b4ca
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechRepairable/CompMechRepairable.cs
@@ -0,0 +1,289 @@
+// CompMechRepairable.cs
+using RimWorld;
+using System.Collections.Generic;
+using UnityEngine;
+using Verse;
+using Verse.AI;
+using System.Linq;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechRepairable : CompProperties
+ {
+ public float healthPercentThreshold = 1f; // 低于此值需要维修
+ public bool allowAutoRepair = true; // 是否允许自动维修
+ public SoundDef repairSound = null; // 维修音效
+ public EffecterDef repairEffect = null; // 维修特效
+ public float repairAmountPerCycle = 1f; // 每个修复周期修复的HP量
+ public int ticksPerRepairCycle = 120; // 每个修复周期的ticks数
+
+ public CompProperties_MechRepairable()
+ {
+ this.compClass = typeof(CompMechRepairable);
+ }
+ }
+
+ public class CompMechRepairable : ThingComp
+ {
+ public CompProperties_MechRepairable Props => (CompProperties_MechRepairable)props;
+
+ private Pawn mech => parent as Pawn;
+ private float totalRepairedHP = 0f; // 累计修复的HP量
+
+ // 是否需要维修
+ public bool NeedsRepair
+ {
+ get
+ {
+ if (mech == null || mech.health == null || mech.Dead)
+ return false;
+
+ return mech.health.summaryHealth.SummaryHealthPercent < Props.healthPercentThreshold;
+ }
+ }
+
+ // 是否可以自动维修(无驾驶员时)
+ public bool CanAutoRepair
+ {
+ get
+ {
+ if (!Props.allowAutoRepair || !NeedsRepair || mech.Faction != Faction.OfPlayer)
+ return false;
+
+ // 检查是否有驾驶员
+ var pilotComp = parent.TryGetComp();
+ return pilotComp == null || !pilotComp.HasPilots;
+ }
+ }
+
+ // 检查是否可以手动强制维修(有驾驶员时)
+ public bool CanForceRepair
+ {
+ get
+ {
+ if (!NeedsRepair || mech.Faction != Faction.OfPlayer)
+ return false;
+ else
+ return true;
+ }
+ }
+
+ // 记录修复量
+ public void RecordRepair(float amount)
+ {
+ totalRepairedHP += amount;
+ }
+
+ public override IEnumerable CompGetGizmosExtra()
+ {
+ if (parent.Faction != Faction.OfPlayer)
+ yield break;
+
+ // 强制维修按钮(仅在机甲有驾驶员且需要维修时显示)
+ if (CanForceRepair)
+ {
+ Command_Action repairCommand = new Command_Action
+ {
+ defaultLabel = "DD_ForceRepair".Translate(),
+ defaultDesc = "DD_ForceRepairDesc".Translate(),
+ icon = ContentFinder.Get("WulaFallenEmpire/UI/Commands/DD_Repair_Mech"),
+ action = () => ForceRepairNow()
+ };
+
+ // 检查是否可以立即维修
+ if (!CanRepairNow())
+ {
+ repairCommand.Disable("DD_CannotRepairNow".Translate());
+ }
+
+ yield return repairCommand;
+ }
+
+ // 在 God Mode 下显示维修测试按钮
+ if (DebugSettings.godMode && parent.Faction == Faction.OfPlayer)
+ {
+ // 模拟受伤按钮
+ Command_Action damageCommand = new Command_Action
+ {
+ defaultLabel = "DD_Debug_Damage".Translate(),
+ defaultDesc = "DD_Debug_DamageDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/Damage", false) ?? BaseContent.BadTex,
+ action = () => DebugDamage()
+ };
+ yield return damageCommand;
+
+ // 完全修复按钮
+ Command_Action fullRepairCommand = new Command_Action
+ {
+ defaultLabel = "DD_Debug_FullRepair".Translate(),
+ defaultDesc = "DD_Debug_FullRepairDesc".Translate(),
+ icon = ContentFinder.Get("UI/Commands/Repair", false) ?? BaseContent.BadTex,
+ action = () => DebugFullRepair()
+ };
+ yield return fullRepairCommand;
+
+ // 显示维修统计
+ Command_Action statsCommand = new Command_Action
+ {
+ defaultLabel = "DD_Debug_RepairStats".Translate(),
+ defaultDesc = "DD_Debug_RepairStatsDesc".Translate(totalRepairedHP.ToString("F1")),
+ icon = ContentFinder.Get("UI/Commands/Stats", false) ?? BaseContent.BadTex,
+ action = () => DebugShowStats()
+ };
+ yield return statsCommand;
+ }
+ }
+
+ // 强制立即维修 - 征召最近的殖民者
+ public void ForceRepairNow()
+ {
+ if (!CanForceRepair || parent.Map == null)
+ return;
+
+ // 寻找最近的可用殖民者
+ Pawn bestColonist = FindBestColonistForRepair();
+
+ if (bestColonist == null)
+ {
+ Messages.Message("DD_NoColonistAvailable".Translate(), parent, MessageTypeDefOf.RejectInput);
+ return;
+ }
+
+ // 创建强制维修工作
+ Job job = JobMaker.MakeJob(Wula_JobDefOf.DD_RepairMech, parent);
+ job.playerForced = true;
+
+ bestColonist.jobs.StartJob(job, JobCondition.InterruptForced, null, resumeCurJobAfterwards: true);
+
+ // 显示消息
+ Messages.Message("DD_OrderedRepair".Translate(bestColonist.LabelShort, parent.LabelShort),
+ parent, MessageTypeDefOf.PositiveEvent);
+ }
+
+ private Pawn FindBestColonistForRepair()
+ {
+ Map map = parent.Map;
+ if (map == null)
+ return null;
+
+ // 寻找所有可用的殖民者
+ List colonists = map.mapPawns.FreeColonists.ToList();
+
+ // 过滤掉无法工作或无法到达机甲的殖民者
+ colonists = colonists.Where(colonist =>
+ colonist.workSettings != null &&
+ colonist.workSettings.WorkIsActive(WorkTypeDefOf.Crafting) &&
+ colonist.health.capacities.CapableOf(PawnCapacityDefOf.Manipulation) &&
+ colonist.health.capacities.CapableOf(PawnCapacityDefOf.Moving) &&
+ !colonist.Downed &&
+ !colonist.Dead &&
+ colonist.CanReserveAndReach(parent, PathEndMode.Touch, Danger.Some)
+ ).ToList();
+
+ if (!colonists.Any())
+ return null;
+
+ // 按照技能排序(机械维修技能优先)
+ return colonists
+ .OrderByDescending(colonist => colonist.skills?.GetSkill(SkillDefOf.Crafting)?.Level ?? 0)
+ .ThenBy(colonist => colonist.Position.DistanceTo(parent.Position))
+ .FirstOrDefault();
+ }
+
+ public bool CanRepairNow()
+ {
+ if (parent.Map == null)
+ return false;
+
+ // 检查是否有可用殖民者
+ if (FindBestColonistForRepair() == null)
+ return false;
+
+ return true;
+ }
+
+ // 调试功能:模拟受伤
+ private void DebugDamage()
+ {
+ var mech = parent as Pawn;
+ if (mech == null || mech.health == null || mech.Dead)
+ return;
+
+ // 随机选择一个身体部位造成伤害
+ var bodyParts = mech.RaceProps.body.AllParts.Where(p => p.depth == BodyPartDepth.Outside).ToList();
+ if (!bodyParts.Any())
+ return;
+
+ BodyPartRecord part = bodyParts.RandomElement();
+
+ // 造成随机伤害
+ float damage = Rand.Range(10f, 50f);
+ DamageInfo dinfo = new DamageInfo(DamageDefOf.Cut, damage, 1f, -1f, null, part);
+ mech.TakeDamage(dinfo);
+
+ Messages.Message($"DD_Debug_Damaged".Translate(parent.LabelShort, damage.ToString("F1")),
+ parent, MessageTypeDefOf.NeutralEvent);
+ }
+
+ // 调试功能:完全修复
+ private void DebugFullRepair()
+ {
+ var mech = parent as Pawn;
+ if (mech == null || mech.health == null || mech.Dead)
+ return;
+
+ // 修复所有伤口
+ foreach (var hediff in mech.health.hediffSet.hediffs.ToList())
+ {
+ if (hediff is Hediff_Injury injury)
+ {
+ mech.health.RemoveHediff(injury);
+ }
+ else if (hediff is Hediff_MissingPart missingPart)
+ {
+ // 对于缺失部位,创建新的健康部分
+ mech.health.RestorePart(missingPart.Part);
+ }
+ }
+
+ Messages.Message($"DD_Debug_FullyRepaired".Translate(parent.LabelShort),
+ parent, MessageTypeDefOf.PositiveEvent);
+ }
+
+ // 调试功能:显示维修统计
+ private void DebugShowStats()
+ {
+ Messages.Message($"DD_Debug_RepairStatsInfo".Translate(
+ parent.LabelShort,
+ totalRepairedHP.ToString("F1"),
+ Props.repairAmountPerCycle.ToString("F1"),
+ Props.ticksPerRepairCycle
+ ), parent, MessageTypeDefOf.NeutralEvent);
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref totalRepairedHP, "totalRepairedHP", 0f);
+ }
+
+ public override string CompInspectStringExtra()
+ {
+ if (mech == null || mech.health == null)
+ return base.CompInspectStringExtra();
+
+ string baseString = base.CompInspectStringExtra();
+
+ string repairString = "";
+ if (NeedsRepair)
+ {
+ repairString = "DD_NeedsRepair".Translate().Colorize(Color.yellow);
+ }
+
+ if (!baseString.NullOrEmpty())
+ return baseString + "\n" + repairString;
+ else
+ return repairString;
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompMechSelfDestruct.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompMechSelfDestruct.cs
new file mode 100644
index 00000000..8de0983a
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompMechSelfDestruct.cs
@@ -0,0 +1,325 @@
+// CompMechSelfDestruct.cs
+using System.Collections.Generic;
+using RimWorld;
+using UnityEngine;
+using Verse;
+using Verse.Sound;
+
+namespace WulaFallenEmpire
+{
+ public class CompMechSelfDestruct : ThingComp
+ {
+ public bool wickStarted;
+ public int wickTicksLeft;
+ private Thing instigator;
+ private List thingsIgnoredByExplosion;
+ private Sustainer wickSoundSustainer;
+ private OverlayHandle? overlayBurningWick;
+
+ public CompProperties_MechSelfDestruct Props => (CompProperties_MechSelfDestruct)props;
+
+ protected Pawn MechPawn => parent as Pawn;
+
+ // 获取当前健康百分比
+ private float CurrentHealthPercent
+ {
+ get
+ {
+ if (MechPawn == null || MechPawn.Dead || MechPawn.health == null)
+ return 0f;
+
+ return MechPawn.health.summaryHealth.SummaryHealthPercent;
+ }
+ }
+
+ // 获取健康阈值(基于百分比)
+ private float HealthThreshold => Props.healthPercentThreshold;
+
+ protected virtual bool CanEverExplode
+ {
+ get
+ {
+ if (Props.chanceNeverExplode >= 1f)
+ return false;
+
+ if (Props.chanceNeverExplode <= 0f)
+ return true;
+
+ Rand.PushState();
+ Rand.Seed = parent.thingIDNumber.GetHashCode();
+ bool result = Rand.Value > Props.chanceNeverExplode;
+ Rand.PopState();
+ return result;
+ }
+ }
+
+ public void AddThingsIgnoredByExplosion(List things)
+ {
+ if (thingsIgnoredByExplosion == null)
+ {
+ thingsIgnoredByExplosion = new List();
+ }
+ thingsIgnoredByExplosion.AddRange(things);
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_References.Look(ref instigator, "instigator");
+ Scribe_Collections.Look(ref thingsIgnoredByExplosion, "thingsIgnoredByExplosion", LookMode.Reference);
+ Scribe_Values.Look(ref wickStarted, "wickStarted", defaultValue: false);
+ Scribe_Values.Look(ref wickTicksLeft, "wickTicksLeft", 0);
+ }
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ UpdateOverlays();
+ }
+
+ public override void CompTick()
+ {
+ if (MechPawn == null || MechPawn.Dead)
+ return;
+
+ // 检查健康阈值(每60ticks检查一次以提高性能)
+ if (Find.TickManager.TicksGame % 60 == 0)
+ {
+ CheckHealthThreshold();
+ }
+
+ if (!wickStarted)
+ return;
+
+ // 处理引信
+ if (wickSoundSustainer == null)
+ {
+ StartWickSustainer();
+ }
+ else
+ {
+ wickSoundSustainer.Maintain();
+ }
+
+ wickTicksLeft--;
+ if (wickTicksLeft <= 0)
+ {
+ Detonate(parent.MapHeld);
+ }
+ }
+
+ private void CheckHealthThreshold()
+ {
+ if (wickStarted || !CanEverExplode || !Props.triggerOnHealthThreshold)
+ return;
+
+ float currentHealthPercent = CurrentHealthPercent;
+
+ if (currentHealthPercent <= HealthThreshold && currentHealthPercent > 0f)
+ {
+ StartWick();
+ }
+ }
+
+ private void StartWickSustainer()
+ {
+ SoundDefOf.MetalHitImportant.PlayOneShot(new TargetInfo(parent.PositionHeld, parent.MapHeld));
+ SoundInfo info = SoundInfo.InMap(parent, MaintenanceType.PerTick);
+ wickSoundSustainer = SoundDefOf.HissSmall.TrySpawnSustainer(info);
+ }
+
+ private void EndWickSustainer()
+ {
+ if (wickSoundSustainer != null)
+ {
+ wickSoundSustainer.End();
+ wickSoundSustainer = null;
+ }
+ }
+
+ private void UpdateOverlays()
+ {
+ if (parent.Spawned && Props.drawWick)
+ {
+ parent.Map.overlayDrawer.Disable(parent, ref overlayBurningWick);
+ if (wickStarted)
+ {
+ overlayBurningWick = parent.Map.overlayDrawer.Enable(parent, OverlayTypes.BurningWick);
+ }
+ }
+ }
+
+ public override void PostDestroy(DestroyMode mode, Map previousMap)
+ {
+ if (Props.triggerOnDeath && mode == DestroyMode.KillFinalize)
+ {
+ Detonate(previousMap, ignoreUnspawned: true);
+ }
+ }
+
+ public override void PostDeSpawn(Map map, DestroyMode mode = DestroyMode.Vanish)
+ {
+ base.PostDeSpawn(map, mode);
+ EndWickSustainer();
+ StopWick();
+ }
+
+ public override void PostPreApplyDamage(ref DamageInfo dinfo, out bool absorbed)
+ {
+ absorbed = false;
+
+ if (!CanEverExplode || MechPawn == null || MechPawn.Dead)
+ return;
+
+ // 特定伤害类型触发自毁
+ if (!wickStarted && Props.startWickOnDamageTaken != null &&
+ Props.startWickOnDamageTaken.Contains(dinfo.Def))
+ {
+ StartWick(dinfo.Instigator);
+ }
+ }
+
+ public override void PostPostApplyDamage(DamageInfo dinfo, float totalDamageDealt)
+ {
+ if (!CanEverExplode || MechPawn == null || MechPawn.Dead || wickStarted)
+ return;
+
+ // 内部伤害触发自毁
+ if (Props.startWickOnInternalDamageTaken != null &&
+ Props.startWickOnInternalDamageTaken.Contains(dinfo.Def))
+ {
+ StartWick(dinfo.Instigator);
+ }
+
+ // 特定伤害类型触发自毁
+ if (!wickStarted && Props.startWickOnDamageTaken != null &&
+ Props.startWickOnDamageTaken.Contains(dinfo.Def))
+ {
+ StartWick(dinfo.Instigator);
+ }
+
+ // 检查是否需要停止引信(如眩晕)
+ if (wickStarted && dinfo.Def == DamageDefOf.Stun)
+ {
+ StopWick();
+ }
+ }
+
+ public void StartWick(Thing instigator = null)
+ {
+ if (!wickStarted && ExplosiveRadius() > 0f && CanEverExplode)
+ {
+ this.instigator = instigator;
+ wickStarted = true;
+ wickTicksLeft = Props.wickTicks.RandomInRange;
+ StartWickSustainer();
+ GenExplosion.NotifyNearbyPawnsOfDangerousExplosive(parent, Props.explosiveDamageType, null, instigator);
+ UpdateOverlays();
+ }
+ }
+
+ public void StopWick()
+ {
+ wickStarted = false;
+ instigator = null;
+ UpdateOverlays();
+ EndWickSustainer();
+ }
+
+ public float ExplosiveRadius()
+ {
+ float radius = Props.explosiveRadius;
+
+ // 根据堆叠数量扩展
+ if (parent.stackCount > 1 && Props.explosiveExpandPerStackcount > 0f)
+ {
+ radius += Mathf.Sqrt((parent.stackCount - 1) * Props.explosiveExpandPerStackcount);
+ }
+
+ // 根据燃料扩展
+ if (Props.explosiveExpandPerFuel > 0f && parent.GetComp() != null)
+ {
+ radius += Mathf.Sqrt(parent.GetComp().Fuel * Props.explosiveExpandPerFuel);
+ }
+
+ return radius;
+ }
+
+ protected void Detonate(Map map, bool ignoreUnspawned = false)
+ {
+ if (!ignoreUnspawned && !parent.SpawnedOrAnyParentSpawned)
+ return;
+
+ float radius = ExplosiveRadius();
+ if (radius <= 0f)
+ return;
+
+ Thing responsible = (instigator == null || (instigator.HostileTo(parent.Faction) && parent.Faction != Faction.OfPlayer))
+ ? parent
+ : instigator;
+
+ // 消耗燃料
+ if (Props.explosiveExpandPerFuel > 0f && parent.GetComp() != null)
+ {
+ parent.GetComp().ConsumeFuel(parent.GetComp().Fuel);
+ }
+
+ EndWickSustainer();
+ wickStarted = false;
+ UpdateOverlays();
+
+ if (map == null)
+ {
+ Log.Warning("Tried to detonate CompMechSelfDestruct in a null map.");
+ return;
+ }
+
+ // 播放爆炸效果
+ if (Props.explosionEffect != null)
+ {
+ Effecter effecter = Props.explosionEffect.Spawn();
+ effecter.Trigger(new TargetInfo(parent.PositionHeld, map), new TargetInfo(parent.PositionHeld, map));
+ effecter.Cleanup();
+ }
+
+ // 执行爆炸
+ GenExplosion.DoExplosion(
+ parent.PositionHeld,
+ map,
+ radius,
+ Props.explosiveDamageType,
+ responsible,
+ Props.damageAmountBase,
+ Props.armorPenetrationBase,
+ Props.explosionSound,
+ null, null, null,
+ Props.postExplosionSpawnThingDef,
+ Props.postExplosionSpawnChance,
+ Props.postExplosionSpawnThingCount,
+ Props.postExplosionGasType,
+ Props.postExplosionGasRadiusOverride,
+ Props.postExplosionGasAmount,
+ Props.applyDamageToExplosionCellsNeighbors,
+ Props.preExplosionSpawnThingDef,
+ Props.preExplosionSpawnChance,
+ Props.preExplosionSpawnThingCount,
+ Props.chanceToStartFire,
+ Props.damageFalloff,
+ null,
+ thingsIgnoredByExplosion,
+ null,
+ Props.doVisualEffects,
+ Props.propagationSpeed,
+ 0f,
+ Props.doSoundEffects,
+ null, 1f, null, null,
+ Props.postExplosionSpawnSingleThingDef,
+ Props.preExplosionSpawnSingleThingDef
+ );
+
+ if (!MechPawn.Dead)
+ {
+ MechPawn.Kill(null, null);
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompProperties_MechSelfDestruct.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompProperties_MechSelfDestruct.cs
new file mode 100644
index 00000000..77f65b31
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechSelfDestruct/CompProperties_MechSelfDestruct.cs
@@ -0,0 +1,88 @@
+// CompProperties_MechSelfDestruct.cs
+using System.Collections.Generic;
+using RimWorld;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MechSelfDestruct : CompProperties
+ {
+ // 爆炸相关属性
+ public float explosiveRadius = 3.9f;
+ public DamageDef explosiveDamageType;
+ public int damageAmountBase = 60;
+ public float armorPenetrationBase = -1f;
+ public ThingDef postExplosionSpawnThingDef;
+ public float postExplosionSpawnChance;
+ public int postExplosionSpawnThingCount = 1;
+ public bool applyDamageToExplosionCellsNeighbors;
+ public ThingDef preExplosionSpawnThingDef;
+ public float preExplosionSpawnChance;
+ public int preExplosionSpawnThingCount = 1;
+ public float chanceToStartFire;
+ public bool damageFalloff;
+ public GasType? postExplosionGasType;
+ public float? postExplosionGasRadiusOverride;
+ public int postExplosionGasAmount = 255;
+ public bool doVisualEffects = true;
+ public bool doSoundEffects = true;
+ public float propagationSpeed = 1f;
+ public ThingDef postExplosionSpawnSingleThingDef;
+ public ThingDef preExplosionSpawnSingleThingDef;
+ public float explosiveExpandPerStackcount;
+ public float explosiveExpandPerFuel;
+ public EffecterDef explosionEffect;
+ public SoundDef explosionSound;
+
+ // 自毁相关属性
+ public float healthPercentThreshold = 0.2f; // 健康百分比阈值,低于此值启动自毁
+ public IntRange wickTicks = new IntRange(140, 150); // 自毁延迟ticks范围
+ public bool drawWick = true; // 是否显示引信
+ public string extraInspectStringKey; // 额外检查字符串键
+ public List wickMessages; // 引信消息
+
+ // 自毁触发条件
+ public bool triggerOnDeath = true; // 死亡时触发
+ public bool triggerOnHealthThreshold = true; // 健康阈值时触发
+ public List startWickOnDamageTaken; // 特定伤害类型触发自毁
+ public List startWickOnInternalDamageTaken; // 内部伤害触发自毁
+ public float chanceNeverExplode = 0f; // 永不爆炸的几率
+ public float destroyThingOnExplosionSize = 9999f; // 爆炸时摧毁物体的尺寸阈值
+ public DamageDef requiredDamageTypeToExplode; // 触发爆炸所需的伤害类型
+
+ public CompProperties_MechSelfDestruct()
+ {
+ compClass = typeof(CompMechSelfDestruct);
+ }
+
+ public override void ResolveReferences(ThingDef parentDef)
+ {
+ base.ResolveReferences(parentDef);
+ if (explosiveDamageType == null)
+ {
+ explosiveDamageType = DamageDefOf.Bomb;
+ }
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string item in base.ConfigErrors(parentDef))
+ {
+ yield return item;
+ }
+
+ if (parentDef.tickerType != TickerType.Normal)
+ {
+ yield return "CompMechSelfDestruct requires Normal ticker type";
+ }
+ }
+ }
+
+ // 引信消息类
+ public class WickMessage
+ {
+ public int ticksLeft;
+ public string wickMessagekey;
+ public MessageTypeDef messageType;
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MechSkillInheritance/CompMechSkillInheritance.cs b/Source/WulaFallenEmpire/Pawn_Comps/MechSkillInheritance/CompMechSkillInheritance.cs
new file mode 100644
index 00000000..30922f21
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MechSkillInheritance/CompMechSkillInheritance.cs
@@ -0,0 +1,124 @@
+// File: CompMechSkillInheritance_Simple.cs
+using RimWorld;
+using System.Collections.Generic;
+using System.Linq;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 简化安全版:机甲技能继承自驾驶员
+ ///
+ public class CompMechSkillInheritance : ThingComp
+ {
+ private int ticksUntilUpdate = 0;
+ private CompMechPilotHolder pilotHolder;
+ private Pawn mechPawn;
+
+ public CompProperties_MechSkillInheritance Props =>
+ (CompProperties_MechSkillInheritance)props;
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ mechPawn = parent as Pawn;
+ pilotHolder = parent.TryGetComp();
+
+ // 初始更新
+ ticksUntilUpdate = Props.updateIntervalTicks;
+ UpdateMechSkills();
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ if (mechPawn == null || mechPawn.skills == null)
+ return;
+
+ ticksUntilUpdate--;
+
+ if (ticksUntilUpdate <= 0)
+ {
+ UpdateMechSkills();
+ ticksUntilUpdate = Props.updateIntervalTicks;
+ }
+ }
+
+ ///
+ /// 更新机甲技能
+ ///
+ private void UpdateMechSkills()
+ {
+ if (mechPawn == null || mechPawn.skills == null)
+ return;
+
+ // 获取驾驶员组件
+ var pilots = pilotHolder?.GetPilots()?.ToList() ?? new List();
+
+ // 遍历机甲的所有技能
+ foreach (var mechSkill in mechPawn.skills.skills)
+ {
+ if (mechSkill == null || mechSkill.TotallyDisabled)
+ continue;
+
+ int maxLevel = Props.baseSkillLevelWhenNoPilot;
+
+ // 如果有驾驶员,取最高等级
+ if (pilots.Count > 0)
+ {
+ foreach (var pilot in pilots)
+ {
+ if (pilot == null || pilot.skills == null)
+ continue;
+
+ var pilotSkill = pilot.skills.GetSkill(mechSkill.def);
+ if (pilotSkill != null && !pilotSkill.TotallyDisabled)
+ {
+ int pilotLevel = pilotSkill.Level;
+
+ // 应用倍率
+ int adjustedLevel = (int)(pilotLevel * Props.skillMultiplierForPilots);
+
+ if (adjustedLevel > maxLevel)
+ maxLevel = adjustedLevel;
+ }
+ }
+ }
+
+ // 设置技能等级(使用Level属性,不要直接设置levelInt)
+ mechSkill.Level = maxLevel;
+
+ // 可选:重置经验值,防止自然变化
+ if (Props.preventNaturalDecay)
+ {
+ mechSkill.xpSinceLastLevel = 0f;
+ mechSkill.xpSinceMidnight = 0f;
+ }
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref ticksUntilUpdate, "ticksUntilUpdate", 0);
+ }
+ }
+
+ ///
+ /// 简化版组件属性
+ ///
+ public class CompProperties_MechSkillInheritance : CompProperties
+ {
+ public int updateIntervalTicks = 250;
+ public int baseSkillLevelWhenNoPilot = 0;
+ public float skillMultiplierForPilots = 1.0f;
+ public bool preventNaturalDecay = true; // 阻止技能自然遗忘
+
+ public CompProperties_MechSkillInheritance()
+ {
+ compClass = typeof(CompMechSkillInheritance);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MoteEmitterNorthward/CompMoteEmitterNorthward.cs b/Source/WulaFallenEmpire/Pawn_Comps/MoteEmitterNorthward/CompMoteEmitterNorthward.cs
new file mode 100644
index 00000000..1c9cc887
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MoteEmitterNorthward/CompMoteEmitterNorthward.cs
@@ -0,0 +1,476 @@
+// File: CompMoteEmitterNorthward.cs
+using RimWorld;
+using System;
+using UnityEngine;
+using System.Collections.Generic;
+using Verse;
+using Verse.Sound;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 组件:持续产生向上(北向)移动的Mote
+ ///
+ public class CompMoteEmitterNorthward : ThingComp
+ {
+ private CompProperties_MoteEmitterNorthward Props =>
+ (CompProperties_MoteEmitterNorthward)props;
+
+ private int ticksUntilNextEmit;
+
+ // 移动状态跟踪
+ private Vector3 lastPosition;
+ private bool isMoving;
+ private int positionUpdateCooldown = 0;
+
+ // 缓存引用
+ private CompMechPilotHolder pilotHolder;
+
+ // Pawn引用
+ private Pawn pawnParent;
+
+ // 是否已销毁标记
+ private bool isDestroyed = false;
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ // 随机化初始计时器,避免所有发射器同时发射
+ ticksUntilNextEmit = Rand.Range(0, Props.emitIntervalTicks);
+
+ // 获取Pawn引用
+ pawnParent = parent as Pawn;
+ }
+
+ public override void PostSpawnSetup(bool respawningAfterLoad)
+ {
+ base.PostSpawnSetup(respawningAfterLoad);
+
+ // 重置销毁标记
+ isDestroyed = false;
+
+ // 获取驾驶员容器组件
+ pilotHolder = parent.TryGetComp();
+
+ // 如果需要驾驶员但组件不存在,发出警告
+ if (Props.requirePilot && pilotHolder == null)
+ {
+ Log.Warning($"[DD] CompMoteEmitterNorthward on {parent} requires pilot but no CompMechPilotHolder found");
+ }
+
+ // 初始化位置
+ if (parent.Spawned)
+ {
+ lastPosition = GetSafePosition();
+ }
+ }
+
+ public override void CompTick()
+ {
+ base.CompTick();
+
+ // 如果已标记销毁,跳过所有处理
+ if (isDestroyed || parent == null)
+ return;
+
+ if (!parent.Spawned || parent.Map == null)
+ return;
+
+ // 更新移动状态
+ if (positionUpdateCooldown <= 0)
+ {
+ UpdateMovementState();
+ positionUpdateCooldown = 10; // 每10ticks更新一次位置,减少开销
+ }
+ else
+ {
+ positionUpdateCooldown--;
+ }
+
+ // 检查是否满足发射条件
+ if (!CanEmit())
+ return;
+
+ ticksUntilNextEmit--;
+
+ if (ticksUntilNextEmit <= 0)
+ {
+ EmitMote();
+
+ // 根据移动状态设置下次发射间隔
+ ticksUntilNextEmit = isMoving ?
+ Props.emitIntervalMovingTicks :
+ Props.emitIntervalTicks;
+ }
+ }
+
+ ///
+ /// 安全获取当前位置
+ ///
+ private Vector3 GetSafePosition()
+ {
+ try
+ {
+ if (parent == null || !parent.Spawned)
+ return parent?.Position.ToVector3Shifted() ?? Vector3.zero;
+
+ // 如果是Pawn且绘制器可用,使用DrawPos
+ if (pawnParent != null && pawnParent.Drawer != null)
+ {
+ return pawnParent.DrawPos;
+ }
+
+ // 否则使用网格位置
+ return parent.Position.ToVector3Shifted();
+ }
+ catch (NullReferenceException)
+ {
+ // 发生异常时返回网格位置
+ return parent?.Position.ToVector3Shifted() ?? Vector3.zero;
+ }
+ }
+
+ ///
+ /// 更新移动状态
+ ///
+ private void UpdateMovementState()
+ {
+ if (!parent.Spawned || parent.Destroyed)
+ {
+ isMoving = false;
+ return;
+ }
+
+ try
+ {
+ Vector3 currentPos = GetSafePosition();
+ float distanceMoved = Vector3.Distance(currentPos, lastPosition);
+
+ // 简单移动检测:如果位置有变化就算移动
+ isMoving = distanceMoved > 0.01f;
+
+ lastPosition = currentPos;
+ }
+ catch (NullReferenceException ex)
+ {
+ // 发生异常时重置状态
+ Log.Warning($"[DD] Error updating movement state for {parent}: {ex.Message}");
+ isMoving = false;
+ }
+ }
+
+ ///
+ /// 检查是否可以发射Mote
+ ///
+ private bool CanEmit()
+ {
+ // 基础检查
+ if (parent == null || !parent.Spawned || parent.Map == null || Props.moteDef == null)
+ return false;
+
+ // 如果Pawn状态异常,不发射
+ if (pawnParent != null)
+ {
+ if (pawnParent.Dead || pawnParent.Downed || pawnParent.InMentalState)
+ return false;
+
+ // 检查绘制器是否可用
+ if (pawnParent.Drawer == null)
+ return false;
+ }
+
+ // 新增:检查驾驶员条件
+ if (Props.requirePilot)
+ {
+ // 需要至少一个驾驶员
+ if (pilotHolder == null || !pilotHolder.HasPilots)
+ return false;
+
+ // 可选:检查驾驶员是否存活
+ if (Props.requirePilotAlive)
+ {
+ foreach (var pilot in pilotHolder.GetPilots())
+ {
+ if (pilot.Dead || pilot.Downed)
+ return false;
+ }
+ }
+ }
+
+ // 检查电源条件
+ if (Props.onlyWhenPowered)
+ {
+ var powerComp = parent.TryGetComp();
+ if (powerComp != null && !powerComp.PowerOn)
+ return false;
+ }
+
+ // 检查天气条件
+ if (!string.IsNullOrEmpty(Props.onlyInWeather))
+ {
+ var currentWeather = parent.Map.weatherManager.curWeather;
+ if (currentWeather == null || currentWeather.defName != Props.onlyInWeather)
+ return false;
+ }
+
+ // 检查地形条件
+ if (Props.onlyOnTerrain != null)
+ {
+ var terrain = parent.Position.GetTerrain(parent.Map);
+ if (terrain != Props.onlyOnTerrain)
+ return false;
+ }
+
+ return true;
+ }
+
+ private void EmitMote()
+ {
+ try
+ {
+ // 计算发射位置(根据朝向调整偏移)
+ Vector3 emitPos = GetSafePosition() + GetOffsetForFacing();
+
+ // 如果父物体是Pawn,可以添加一些随机偏移
+ if (pawnParent != null && Props.randomOffsetRadius > 0f)
+ {
+ emitPos += new Vector3(
+ Rand.Range(-Props.randomOffsetRadius, Props.randomOffsetRadius),
+ 0f,
+ Rand.Range(-Props.randomOffsetRadius, Props.randomOffsetRadius)
+ );
+ }
+
+ // 创建Mote
+ Mote mote = (Mote)ThingMaker.MakeThing(Props.moteDef);
+
+ if (mote is MoteThrown moteThrown)
+ {
+ // 设置初始位置
+ moteThrown.exactPosition = emitPos;
+
+ // 设置向北移动的速度
+ moteThrown.SetVelocity(
+ angle: 0f, // 0度 = 北向
+ speed: Props.moveSpeed
+ );
+
+ // 设置旋转
+ moteThrown.exactRotation = Props.rotation;
+ moteThrown.rotationRate = Props.rotationRate;
+
+ // 设置缩放
+ moteThrown.Scale = Props.scale;
+
+ // 设置存活时间
+ moteThrown.airTimeLeft = Props.lifetimeTicks;
+
+ // 添加到地图
+ GenSpawn.Spawn(mote, parent.Position, parent.Map);
+ }
+ else
+ {
+ // 不是MoteThrown类型,使用基础设置
+ mote.exactPosition = emitPos;
+ mote.Scale = Props.scale;
+ GenSpawn.Spawn(mote, parent.Position, parent.Map);
+ }
+
+ // 播放发射音效
+ if (Props.soundOnEmit != null)
+ {
+ Props.soundOnEmit.PlayOneShot(parent);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"[DD] Error emitting mote: {ex}");
+ }
+ }
+
+ ///
+ /// 根据朝向获取偏移位置
+ ///
+ private Vector3 GetOffsetForFacing()
+ {
+ Vector3 offset = Props.offset;
+
+ // 如果不是Pawn,返回基础偏移
+ if (pawnParent == null)
+ return offset;
+
+ // 检查Pawn是否可用
+ if (pawnParent.Destroyed || !pawnParent.Spawned)
+ return offset;
+
+ try
+ {
+ // 根据朝向调整偏移
+ switch (pawnParent.Rotation.AsInt)
+ {
+ case 0: // 北
+ return offset;
+ case 1: // 东
+ return new Vector3(-offset.z, offset.y, offset.x);
+ case 2: // 南
+ return new Vector3(-offset.x, offset.y, offset.z);
+ case 3: // 西
+ return new Vector3(offset.z, offset.y, -offset.x);
+ default:
+ return offset;
+ }
+ }
+ catch (NullReferenceException)
+ {
+ // 如果访问Rotation失败,返回基础偏移
+ return offset;
+ }
+ }
+
+ public override void PostExposeData()
+ {
+ base.PostExposeData();
+ Scribe_Values.Look(ref ticksUntilNextEmit, "ticksUntilNextEmit", 0);
+ Scribe_Values.Look(ref lastPosition, "lastPosition", Vector3.zero);
+ Scribe_Values.Look(ref isMoving, "isMoving", false);
+ }
+
+ // 添加销毁相关的清理
+ public override void PostDestroy(DestroyMode mode, Map previousMap)
+ {
+ base.PostDestroy(mode, previousMap);
+ isDestroyed = true;
+ }
+
+ public void PostDeSpawn(Map map)
+ {
+ base.PostDeSpawn(map);
+ isDestroyed = true;
+ }
+
+ ///
+ /// 获取组件状态信息(用于调试)
+ ///
+ public string GetStatusInfo()
+ {
+ if (parent == null || isDestroyed)
+ return "Component destroyed";
+
+ string pilotStatus = "N/A";
+ if (pilotHolder != null)
+ {
+ pilotStatus = pilotHolder.HasPilots ?
+ $"Has {pilotHolder.CurrentPilotCount} pilot(s)" :
+ "No pilots";
+ }
+
+ return $"Mote Emitter Status:\n" +
+ $" Active: {!isDestroyed}\n" +
+ $" Can Emit: {CanEmit()}\n" +
+ $" Moving: {isMoving}\n" +
+ $" Pilot Status: {pilotStatus}\n" +
+ $" Next Emit: {ticksUntilNextEmit} ticks\n" +
+ $" Powered: {(Props.onlyWhenPowered ? CheckPowerStatus() : "N/A")}";
+ }
+
+ private string CheckPowerStatus()
+ {
+ var powerComp = parent.TryGetComp();
+ if (powerComp == null)
+ return "No power comp";
+ return powerComp.PowerOn ? "Powered" : "No power";
+ }
+ }
+
+ ///
+ /// 组件属性(更新版)
+ ///
+ public class CompProperties_MoteEmitterNorthward : CompProperties
+ {
+ /// Mote定义
+ public ThingDef moteDef;
+
+ /// 发射间隔(ticks)- 静止时
+ public int emitIntervalTicks = 60; // 默认1秒
+
+ /// 发射间隔(ticks)- 移动时
+ public int emitIntervalMovingTicks = 30; // 移动时默认0.5秒
+
+ /// 移动速度
+ public float moveSpeed = 1f;
+
+ /// Mote生命周期(ticks)
+ public float lifetimeTicks = 120f; // 默认2秒
+
+ /// 初始旋转角度
+ public float rotation = 0f;
+
+ /// 旋转速度(度/秒)
+ public float rotationRate = 0f;
+
+ /// 缩放大小
+ public float scale = 1f;
+
+ /// 偏移位置(相对于父物体)- 默认朝北时的偏移
+ public Vector3 offset = Vector3.zero;
+
+ /// 随机偏移半径
+ public float randomOffsetRadius = 0f;
+
+ /// 发射时的音效
+ public SoundDef soundOnEmit;
+
+ /// 是否只在启用的状态发射
+ public bool onlyWhenPowered = false;
+
+ /// 是否只在至少有一个驾驶员时发射
+ public bool requirePilot = true; // 新增:驾驶员条件
+
+ /// 天气条件:只在指定天气发射(用逗号分隔)
+ public string onlyInWeather;
+
+ /// 地形条件:只在指定地形发射
+ public TerrainDef onlyOnTerrain;
+
+ /// 驾驶员条件:只在驾驶员存活时发射
+ public bool requirePilotAlive = true; // 新增:要求驾驶员存活
+
+ public CompProperties_MoteEmitterNorthward()
+ {
+ compClass = typeof(CompMoteEmitterNorthward);
+ }
+
+ public override IEnumerable ConfigErrors(ThingDef parentDef)
+ {
+ foreach (string error in base.ConfigErrors(parentDef))
+ {
+ yield return error;
+ }
+
+ if (moteDef == null)
+ {
+ yield return $"moteDef is not defined for {parentDef.defName}";
+ }
+
+ if (emitIntervalTicks <= 0)
+ {
+ yield return $"emitIntervalTicks must be greater than 0 for {parentDef.defName}";
+ }
+
+ if (emitIntervalMovingTicks <= 0)
+ {
+ yield return $"emitIntervalMovingTicks must be greater than 0 for {parentDef.defName}";
+ }
+
+ if (lifetimeTicks <= 0)
+ {
+ yield return $"lifetimeTicks must be greater than 0 for {parentDef.defName}";
+ }
+
+ if (requirePilot && parentDef.GetCompProperties() == null)
+ {
+ yield return $"requirePilot is true but no CompProperties_MechPilotHolder found for {parentDef.defName}";
+ }
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/Pawn_Comps/MultiTurretGun/CompMultiTurretGun.cs b/Source/WulaFallenEmpire/Pawn_Comps/MultiTurretGun/CompMultiTurretGun.cs
new file mode 100644
index 00000000..8237f609
--- /dev/null
+++ b/Source/WulaFallenEmpire/Pawn_Comps/MultiTurretGun/CompMultiTurretGun.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using Verse;
+using Verse.AI;
+using RimWorld;
+using UnityEngine;
+
+namespace WulaFallenEmpire
+{
+ public class CompProperties_MultiTurretGun : CompProperties_TurretGun
+ {
+ public int ID;
+ public CompProperties_MultiTurretGun()
+ {
+ compClass = typeof(Comp_MultiTurretGun);
+ }
+ }
+ public class Comp_MultiTurretGun : CompTurretGun
+ {
+ private bool fireAtWill = true;
+ public new CompProperties_MultiTurretGun Props => (CompProperties_MultiTurretGun)props;
+ public override void CompTick()
+ {
+ base.CompTick();
+ if (!currentTarget.IsValid && burstCooldownTicksLeft <= 0)
+ {
+ // 在其他情况下没有目标且冷却结束时也回正
+ curRotation = parent.Rotation.AsAngle + Props.angleOffset;
+ }
+ }
+ private void MakeGun()
+ {
+ gun = ThingMaker.MakeThing(Props.turretDef);
+ UpdateGunVerbs();
+ }
+ private void UpdateGunVerbs()
+ {
+ List allVerbs = gun.TryGetComp().AllVerbs;
+ for (int i = 0; i < allVerbs.Count; i++)
+ {
+ Verb verb = allVerbs[i];
+ verb.caster = parent;
+ verb.castCompleteCallback = delegate
+ {
+ burstCooldownTicksLeft = AttackVerb.verbProps.defaultCooldownTime.SecondsToTicks();
+ };
+ }
+ }
+ public override void PostExposeData()
+ {
+ Scribe_Values.Look(ref burstCooldownTicksLeft, "burstCooldownTicksLeft", 0);
+ Scribe_Values.Look(ref burstWarmupTicksLeft, "burstWarmupTicksLeft", 0);
+ Scribe_TargetInfo.Look(ref currentTarget, "currentTarget_" + Props.ID);
+ Scribe_Deep.Look(ref gun, "gun_" + Props.ID);
+ Scribe_Values.Look(ref fireAtWill, "fireAtWill", defaultValue: true);
+ if (Scribe.mode == LoadSaveMode.PostLoadInit)
+ {
+ if (gun == null)
+ {
+ MakeGun();
+ }
+ else
+ {
+ UpdateGunVerbs();
+ }
+ }
+ }
+ }
+
+}
diff --git a/Source/WulaFallenEmpire/ThingComp/CompAndPatch_GiveHediffOnShot.cs b/Source/WulaFallenEmpire/ThingComp/WULA_GiveHediffOnShot/CompAndPatch_GiveHediffOnShot.cs
similarity index 51%
rename from Source/WulaFallenEmpire/ThingComp/CompAndPatch_GiveHediffOnShot.cs
rename to Source/WulaFallenEmpire/ThingComp/WULA_GiveHediffOnShot/CompAndPatch_GiveHediffOnShot.cs
index 23368a90..20de6d9d 100644
--- a/Source/WulaFallenEmpire/ThingComp/CompAndPatch_GiveHediffOnShot.cs
+++ b/Source/WulaFallenEmpire/ThingComp/WULA_GiveHediffOnShot/CompAndPatch_GiveHediffOnShot.cs
@@ -41,10 +41,10 @@ namespace WulaFallenEmpire
}
// Patch 2: Specifically for Verb_ShootWithOffset.
- [HarmonyPatch(typeof(Verb_ShootWithOffset), "TryCastShot")]
- public static class Patch_Verb_ShootWithOffset_TryCastShot
+ [HarmonyPatch(typeof(Verb_Shoot), "TryCastShot")]
+ public static class Patch_Verb_Shoot_TryCastShot
{
- public static void Postfix(Verb_ShootWithOffset __instance, bool __result)
+ public static void Postfix(Verb_Shoot __instance, bool __result)
{
if (!__result) return;
if (__instance.CasterPawn == null || __instance.EquipmentSource == null) return;
@@ -58,45 +58,26 @@ namespace WulaFallenEmpire
var disappearsComp = hediff.TryGetComp();
disappearsComp?.ResetElapsedTicks();
}
- }
- // Patch 3: Specifically for our new Verb_ShootShotgunWithOffset.
- [HarmonyPatch(typeof(Verb_ShootShotgunWithOffset), "TryCastShot")]
- public static class Patch_Verb_ShootShotgunWithOffset_TryCastShot
- {
- public static void Postfix(Verb_ShootShotgunWithOffset __instance, bool __result)
+
+ // Patch 2: Specifically for Verb_ShootWithOffset.
+ [HarmonyPatch(typeof(Verb_ShootWithOffset), "TryCastShot")]
+ public static class Patch_ShootWithOffset_TryCastShot
{
- if (!__result) return;
- if (__instance.CasterPawn == null || __instance.EquipmentSource == null) return;
+ public static void Postfix(Verb_ShootWithOffset __instance, bool __result)
+ {
+ if (!__result) return;
+ if (__instance.CasterPawn == null || __instance.EquipmentSource == null) return;
- CompGiveHediffOnShot comp = __instance.EquipmentSource.GetComp();
- if (comp == null || comp.Props.hediffDef == null) return;
+ CompGiveHediffOnShot comp = __instance.EquipmentSource.GetComp();
+ if (comp == null || comp.Props.hediffDef == null) return;
- Hediff hediff = __instance.CasterPawn.health.GetOrAddHediff(comp.Props.hediffDef);
- hediff.Severity += comp.Props.severityToAdd;
+ Hediff hediff = __instance.CasterPawn.health.GetOrAddHediff(comp.Props.hediffDef);
+ hediff.Severity += comp.Props.severityToAdd;
- var disappearsComp = hediff.TryGetComp();
- disappearsComp?.ResetElapsedTicks();
+ var disappearsComp = hediff.TryGetComp();
+ disappearsComp?.ResetElapsedTicks();
+ }
}
}
-
- // 新增补丁:为 Verb_ShootBeamExplosive 添加支持
- [HarmonyPatch(typeof(Verb_ShootBeamExplosive), "TryCastShot")]
- public static class Patch_Verb_ShootBeamExplosive_TryCastShot
- {
- public static void Postfix(Verb_ShootBeamExplosive __instance, bool __result)
- {
- if (!__result) return;
- if (__instance.CasterPawn == null || __instance.EquipmentSource == null) return;
-
- CompGiveHediffOnShot comp = __instance.EquipmentSource.GetComp();
- if (comp == null || comp.Props.hediffDef == null) return;
-
- Hediff hediff = __instance.CasterPawn.health.GetOrAddHediff(comp.Props.hediffDef);
- hediff.Severity += comp.Props.severityToAdd;
-
- var disappearsComp = hediff.TryGetComp();
- disappearsComp?.ResetElapsedTicks();
- }
- }
-}
+}
\ No newline at end of file
diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_MechSpecificWeapon/CompMechSpecificWeapon.cs b/Source/WulaFallenEmpire/ThingComp/WULA_MechSpecificWeapon/CompMechSpecificWeapon.cs
new file mode 100644
index 00000000..d1b898e1
--- /dev/null
+++ b/Source/WulaFallenEmpire/ThingComp/WULA_MechSpecificWeapon/CompMechSpecificWeapon.cs
@@ -0,0 +1,39 @@
+// File: CompMechOnlyWeapon.cs
+using System.Collections.Generic;
+using Verse;
+
+namespace WulaFallenEmpire
+{
+ ///
+ /// 简单的机甲专用武器组件
+ ///
+ public class CompMechOnlyWeapon : ThingComp
+ {
+ public List allowedMechRaces;
+
+ public override void Initialize(CompProperties props)
+ {
+ base.Initialize(props);
+ allowedMechRaces = ((CompProperties_MechOnlyWeapon)props).allowedMechRaces;
+ }
+
+ ///
+ /// 检查机甲是否可以装备此武器
+ ///
+ public bool CanBeEquippedByMech(Pawn mech)
+ {
+ if (mech == null || mech.def == null) return false;
+ return allowedMechRaces != null && allowedMechRaces.Contains(mech.def);
+ }
+ }
+
+ public class CompProperties_MechOnlyWeapon : CompProperties
+ {
+ public List allowedMechRaces = new List();
+
+ public CompProperties_MechOnlyWeapon()
+ {
+ compClass = typeof(CompMechOnlyWeapon);
+ }
+ }
+}
diff --git a/Source/WulaFallenEmpire/ThingComp/WULA_PlaySoundOnSpawn/CompPlaySoundOnSpawn.cs b/Source/WulaFallenEmpire/ThingComp/WULA_PlaySoundOnSpawn/CompPlaySoundOnSpawn.cs
index 92e31362..1d19278a 100644
--- a/Source/WulaFallenEmpire/ThingComp/WULA_PlaySoundOnSpawn/CompPlaySoundOnSpawn.cs
+++ b/Source/WulaFallenEmpire/ThingComp/WULA_PlaySoundOnSpawn/CompPlaySoundOnSpawn.cs
@@ -127,16 +127,10 @@ namespace WulaFallenEmpire
// 播放声音
Props.sound.PlayOneShot(soundInfo);
soundPlayed = true;
-
- // 调试日志
- if (Prefs.DevMode)
- {
- WulaLog.Debug($"Played spawn sound: {Props.sound.defName} for {parent.Label} at {parent.Position}");
- }
}
catch (System.Exception ex)
{
- WulaLog.Debug($"Error playing spawn sound for {parent.Label}: {ex}");
+ Log.Error($"Error playing spawn sound for {parent.Label}: {ex}");
}
}
@@ -162,10 +156,6 @@ namespace WulaFallenEmpire
{
PlaySound();
}
- else
- {
- WulaLog.Debug("No sound defined for CompPlaySoundOnSpawn");
- }
}
};
diff --git a/Source/WulaFallenEmpire/WulaDefOf.cs b/Source/WulaFallenEmpire/WulaDefOf.cs
index 954f262e..e1245472 100644
--- a/Source/WulaFallenEmpire/WulaDefOf.cs
+++ b/Source/WulaFallenEmpire/WulaDefOf.cs
@@ -5,79 +5,97 @@ namespace WulaFallenEmpire
{
[DefOf]
- public static class ThingDefOf_WULA
+ public static class Wula_ThingDefOf
{
public static ThingDef WULA_MaintenancePod;
public static ThingDef WULA_Charging_Station_Synth;
public static ThingDef WULA_PocketMapExit;
public static ThingDef Hyperweave;
- static ThingDefOf_WULA()
+ static Wula_ThingDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(ThingDefOf_WULA));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_ThingDefOf));
}
}
[DefOf]
- public static class JobDefOf_WULA
+ public static class Wula_JobDefOf
{
public static JobDef WULA_EnterMaintenancePod;
public static JobDef WULA_HaulToMaintenancePod;
public static JobDef WULA_InspectBuilding;
-
- static JobDefOf_WULA()
+ public static JobDef WULA_Launch_Proj;
+ public static JobDef WULA_EnterMech;
+ public static JobDef WULA_RefuelMech;
+ public static JobDef WULA_Refuel;
+ public static JobDef WULA_RepairMech;
+ public static JobDef WULA_ForceEjectPilot;
+ public static JobDef WULA_CarryToMech;
+
+ static Wula_JobDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(JobDefOf_WULA));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_JobDefOf));
}
}
[DefOf]
- public static class WulaStatDefOf
+ public static class Wula_StatDefOf
{
public static StatDef WulaEnergyMaxLevelOffset;
public static StatDef WulaEnergyFallRateFactor;
- static WulaStatDefOf()
+ static Wula_StatDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(WulaStatDefOf));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_StatDefOf));
}
}
[DefOf]
- public static class WulaNeedDefOf
+ public static class Wula_NeedDefOf
{
public static NeedDef WULA_Energy;
- static WulaNeedDefOf()
+ static Wula_NeedDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(WulaNeedDefOf));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_NeedDefOf));
}
}
[DefOf]
- public static class WulaStatCategoryDefOf
+ public static class Wula_StatCategoryDefOf
{
public static StatCategoryDef WULA_Synth;
- static WulaStatCategoryDefOf()
+ static Wula_StatCategoryDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(WulaStatCategoryDefOf));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_StatCategoryDefOf));
}
}
[DefOf]
- public static class WulaDamageDefOf
+ public static class Wula_DamageDefOf
{
public static DamageDef Wula_Dark_Matter_Flame;
- static WulaDamageDefOf()
+ static Wula_DamageDefOf()
{
- DefOfHelper.EnsureInitializedInCtor(typeof(WulaDamageDefOf));
+ DefOfHelper.EnsureInitializedInCtor(typeof(Wula_DamageDefOf));
}
}
[DefOf]
+ public static class WULA_MentalStateDefOf
+ {
+ public static MentalStateDef WULA_MechNoPilot;
+
+ static WULA_MentalStateDefOf()
+ {
+ DefOfHelper.EnsureInitializedInCtor(typeof(WULA_MentalStateDefOf));
+ }
+ }
+
+ [DefOf]
public static class WulaDefOf
{
// public static PawnTableDef WULA_AutonomousMechs;
diff --git a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
index c331bfdd..e5ddbadc 100644
--- a/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
+++ b/Source/WulaFallenEmpire/WulaFallenEmpire.csproj
@@ -34,6 +34,41 @@
4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
..\..\..\..\..\..\workshop\content\294100\2009463077\1.5\Assemblies\0Harmony.dll
False
@@ -82,7 +117,398 @@
..\..\..\..\..\..\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.ScreenCaptureModule.dll
False
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+