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