Merge branch 'main' of https://git.ra3battle.cn/Kalospacer/WulaFallenEmpireRW
This commit is contained in:
Binary file not shown.
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<!-- <JobDef>
|
||||
<defName>WULA_DroneSelfShutdown</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_DroneSelfShutdown</driverClass>
|
||||
<reportString>关机自休眠.</reportString>
|
||||
<casualInterruptible>false</casualInterruptible>
|
||||
<allowOpportunisticPrefix>true</allowOpportunisticPrefix>
|
||||
</JobDef> -->
|
||||
|
||||
</Defs>
|
||||
@@ -55,4 +55,80 @@
|
||||
<tryStartFlying>true</tryStartFlying>
|
||||
<overrideFlyChance>1</overrideFlyChance>
|
||||
</JobDef>
|
||||
|
||||
|
||||
<JobDef>
|
||||
<defName>WULA_Launch_Proj</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_CastAbilityMaintainMultiProjectile</driverClass>
|
||||
<reportString>发射中。</reportString>
|
||||
<abilityCasting>true</abilityCasting>
|
||||
<alwaysShowWeapon>true</alwaysShowWeapon>
|
||||
<playerInterruptible>true</playerInterruptible>
|
||||
<casualInterruptible>false</casualInterruptible>
|
||||
<checkOverrideOnDamage>Always</checkOverrideOnDamage>
|
||||
<suspendable>false</suspendable>
|
||||
</JobDef>
|
||||
|
||||
|
||||
<JobDef>
|
||||
<defName>WULA_EnterMech</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_EnterMech</driverClass>
|
||||
<reportString>Entering mech.</reportString>
|
||||
<!-- <checkOverrideOnDamage>true</checkOverrideOnDamage> -->
|
||||
<suspendable>false</suspendable>
|
||||
</JobDef>
|
||||
<JobDef>
|
||||
<defName>WULA_RefuelMech</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_RefuelMech</driverClass>
|
||||
<reportString>Refuleing TargetA.</reportString>
|
||||
<suspendable>false</suspendable>
|
||||
<!-- <canBeForcedByDuty>true</canBeForcedByDuty>
|
||||
<makeTargetPrisoner>false</makeTargetPrisoner> -->
|
||||
</JobDef>
|
||||
<WorkGiverDef>
|
||||
<defName>WULA_Refuel</defName>
|
||||
<label>refuel Mechs</label>
|
||||
<giverClass>WulaFallenEmpire.WorkGiver_RefuelMech</giverClass>
|
||||
<workType>Hauling</workType>
|
||||
<verb>refuel</verb>
|
||||
<gerund>refueling</gerund>
|
||||
<priorityInType>140</priorityInType>
|
||||
<requiredCapacities>
|
||||
<li>Manipulation</li>
|
||||
</requiredCapacities>
|
||||
<prioritizeSustains>true</prioritizeSustains>
|
||||
</WorkGiverDef>
|
||||
<JobDef>
|
||||
<defName>WULA_RepairMech</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_RepairMech</driverClass>
|
||||
<reportString>Repairing TargetA.</reportString>
|
||||
<alwaysShowWeapon>false</alwaysShowWeapon>
|
||||
<suspendable>false</suspendable>
|
||||
</JobDef>
|
||||
<WorkGiverDef>
|
||||
<defName>WULA_RepairMech</defName>
|
||||
<workType>Smithing</workType>
|
||||
<giverClass>WulaFallenEmpire.WorkGiver_RepairMech</giverClass>
|
||||
<priorityInType>50</priorityInType>
|
||||
<requiredCapacities>
|
||||
<li>Manipulation</li>
|
||||
</requiredCapacities>
|
||||
<verb>Repair</verb>
|
||||
<gerund>Repair Mech</gerund>
|
||||
<label>Repair Mech</label>
|
||||
</WorkGiverDef>
|
||||
<JobDef>
|
||||
<defName>WULA_ForceEjectPilot</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_ForceEjectPilot</driverClass>
|
||||
<reportString>Prise TargetA.</reportString>
|
||||
<suspendable>false</suspendable>
|
||||
<!-- <canBeForcedByDuty>true</canBeForcedByDuty>
|
||||
<makeTargetPrisoner>false</makeTargetPrisoner> -->
|
||||
</JobDef>
|
||||
<JobDef>
|
||||
<defName>WULA_CarryToMech</defName>
|
||||
<driverClass>WulaFallenEmpire.JobDriver_CarryToMech</driverClass>
|
||||
<reportString>carrying TargetA to TargetB.</reportString>
|
||||
<suspendable>false</suspendable>
|
||||
</JobDef>
|
||||
</Defs>
|
||||
@@ -43,4 +43,26 @@
|
||||
<inCaravanCanDo>true</inCaravanCanDo>
|
||||
<blocksDefendAndExpandHive>true</blocksDefendAndExpandHive>
|
||||
</MentalStateDef>
|
||||
|
||||
<MentalStateDef>
|
||||
<defName>WULA_MechNoPilot</defName>
|
||||
<label>无驾驶员</label>
|
||||
<stateClass>WulaFallenEmpire.MentalState_MechNoPilot</stateClass>
|
||||
<category>Misc</category>
|
||||
<nameColor>(0.65, 0.9, 0.93)</nameColor>
|
||||
<baseInspectLine>No driver</baseInspectLine>
|
||||
<!-- <beginLetter>{PAWN_nameDef} has no pilot and cannot operate autonomously. It will stand idle until a pilot takes control.</beginLetter> -->
|
||||
<blockNormalThoughts>true</blockNormalThoughts>
|
||||
<downedCanDo>true</downedCanDo>
|
||||
<recoverFromDowned>false</recoverFromDowned>
|
||||
<recoverFromCollapsingExhausted>false</recoverFromCollapsingExhausted>
|
||||
<inCaravanCanDo>true</inCaravanCanDo>
|
||||
<unspawnedNotInCaravanCanDo>true</unspawnedNotInCaravanCanDo>
|
||||
<blockInteractionInitiationExcept>
|
||||
</blockInteractionInitiationExcept>
|
||||
<blockInteractionRecipientExcept>
|
||||
</blockInteractionRecipientExcept>
|
||||
<minTicksBeforeRecovery>10000</minTicksBeforeRecovery>
|
||||
<recoveryMtbDays>999999</recoveryMtbDays>
|
||||
</MentalStateDef>
|
||||
</Defs>
|
||||
|
||||
@@ -481,13 +481,6 @@
|
||||
|
||||
<!-- <ownershipFaction>Player</ownershipFaction> -->
|
||||
</li>
|
||||
<!-- <li Class="WulaFallenEmpire.CompProperties_TransformAtFullCapacity">
|
||||
<targetPawnKind>WULA_Mobile_Bunker</targetPawnKind>
|
||||
<requiredCapacity>6</requiredCapacity>
|
||||
<gizmoLabel>转换为BUk-1"地堡猫猫"</gizmoLabel>
|
||||
<gizmoDesc>让地堡中的两只乌拉猫猫继续操纵地堡中的机枪,剩下的乌拉猫猫抬着地堡移动——虽然很难相信但是事实就是这样的。</gizmoDesc>
|
||||
<gizmoIconPath>Wula/UI/Commands/WULA_BunkerCat</gizmoIconPath>
|
||||
</li> -->
|
||||
<li Class="WulaFallenEmpire.CompProperties_PathCostUpdater">
|
||||
<adaptiveExpansion>true</adaptiveExpansion>
|
||||
</li>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<MapComponent_LightningBombardment>();
|
||||
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<BombardmentCoroutine> activeCoroutines = new List<BombardmentCoroutine>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化当前参数
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动持续发射Job
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查pawn是否还在维持发射工作
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止多射弹发射
|
||||
/// </summary>
|
||||
public void StopMultiProjectile()
|
||||
{
|
||||
isActive = false;
|
||||
startTick = 0;
|
||||
nextProjectileTick = 0;
|
||||
projectilesFired = 0;
|
||||
targetCell = null;
|
||||
parametersInitialized = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取偏移后的目标点
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制效果预览(显示偏移范围)
|
||||
/// </summary>
|
||||
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 状态获取方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态的射弹数量
|
||||
/// </summary>
|
||||
private int GetCurrentNumProjectiles()
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
return state?.numProjectiles ?? Props.numProjectiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态的射弹类型
|
||||
/// </summary>
|
||||
private ThingDef GetCurrentProjectileDef()
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
return state?.projectileDef ?? Props.projectileDef;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态的偏移半径
|
||||
/// </summary>
|
||||
private float GetCurrentOffsetRadius()
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
return state?.offsetRadius ?? Props.offsetRadius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态的发射间隔
|
||||
/// </summary>
|
||||
private int GetCurrentShotIntervalTicks()
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
return state?.shotIntervalTicks ?? Props.shotIntervalTicks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态(基于施法者的Hediff)
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<ProjectileState> 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; // 发射间隔(可选)
|
||||
}
|
||||
}
|
||||
@@ -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<Toil> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,10 +66,6 @@ namespace WulaFallenEmpire
|
||||
|
||||
Messages.Message("WULA_MechRecycled".Translate(mech.LabelCap, storedMechanoidCount, Props.maxStorageCapacity),
|
||||
MessageTypeDefOf.PositiveEvent);
|
||||
|
||||
// 通知转换组件存储更新
|
||||
var transformComp = this.TryGetComp<CompTransformAtFullCapacity>();
|
||||
transformComp?.NotifyStorageUpdated();
|
||||
}
|
||||
|
||||
// 消耗机械族计数
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Gizmo> 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<Texture2D>.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<CompTransformIntoBuilding>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Gizmo> 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<CompProperties_MechanoidRecycler>();
|
||||
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<Texture2D>.Get(Props.gizmoIconPath);
|
||||
}
|
||||
return TexCommand.Install;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以转换(带详细失败原因)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找最近的可用位置
|
||||
/// </summary>
|
||||
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<CompTransformAtFullCapacity>();
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查位置是否可以放置目标建筑(通用版本)
|
||||
/// </summary>
|
||||
public static bool CanPlaceBuildingAt(ThingDef buildingDef, IntVec3 center, Map map, Faction faction, out string failReason)
|
||||
{
|
||||
return CanPlaceBuildingAt(buildingDef, center, map, faction, null, out failReason);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查位置是否可以放置目标建筑(带忽略物体版本)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查建筑尺寸是否为奇数
|
||||
/// </summary>
|
||||
public static bool IsOddSizedBuilding(ThingDef buildingDef)
|
||||
{
|
||||
return buildingDef.Size.x % 2 == 1 && buildingDef.Size.z % 2 == 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查地图边界
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查地形是否可建造 - 重新设计的逻辑
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查affordance兼容性 - 重新设计的逻辑
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查区域是否被其他建筑阻挡(排除指定物体)
|
||||
/// </summary>
|
||||
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<Thing> blockingThings = new List<Thing>();
|
||||
|
||||
foreach (IntVec3 cell in occupiedRect)
|
||||
{
|
||||
if (!cell.InBounds(map))
|
||||
continue;
|
||||
|
||||
// 检查该单元格上的所有建筑
|
||||
List<Thing> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查特殊放置条件
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取建筑占用的所有单元格
|
||||
/// </summary>
|
||||
public static List<IntVec3> GetOccupiedCells(ThingDef buildingDef, IntVec3 center)
|
||||
{
|
||||
return GenAdj.OccupiedRect(center, Rot4.North, buildingDef.Size).Cells.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -390,7 +390,6 @@ For each function call, return a JSON object within <tool_call></tool_call> tags
|
||||
_tools.Add(new Tool_ModifyGoodwill());
|
||||
_tools.Add(new Tool_SendReinforcement());
|
||||
_tools.Add(new Tool_GetPawnStatus());
|
||||
_tools.Add(new Tool_GetPawnGear());
|
||||
_tools.Add(new Tool_GetMapResources());
|
||||
_tools.Add(new Tool_GetAvailablePrefabs());
|
||||
_tools.Add(new Tool_GetMapPawns());
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace WulaFallenEmpire
|
||||
private static readonly Dictionary<StuffCategoryDef, ThingDef> StuffCategoryMapping = new Dictionary<StuffCategoryDef, ThingDef>
|
||||
{
|
||||
{ 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;
|
||||
}
|
||||
|
||||
// 创建带有指定材质的物品
|
||||
|
||||
@@ -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<ColonistBar.Entry> ___cachedEntries)
|
||||
{
|
||||
// 安全检查:只在玩家派系存在时运行
|
||||
if (Faction.OfPlayer == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
// 建立机甲和驾驶员的映射关系
|
||||
var mechToPilots = new Dictionary<Pawn, List<Pawn>>();
|
||||
var pilotToMech = new Dictionary<Pawn, Pawn>();
|
||||
var mechEntries = new HashSet<Pawn>();
|
||||
|
||||
// 只扫描玩家殖民地的地图
|
||||
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<CompMechPilotHolder>();
|
||||
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<ColonistBar.Entry>();
|
||||
|
||||
// 第一轮:处理原始条目,隐藏驾驶员
|
||||
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<ColonistBar.Entry> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CompMechOnlyWeapon>();
|
||||
|
||||
// 情况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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// File: Patch_RomanceFix.cs
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 修复浪漫关系菜单相关的空引用异常
|
||||
/// </summary>
|
||||
public static class RomancePatches
|
||||
{
|
||||
/// <summary>
|
||||
/// 补丁:防止对机甲单位显示浪漫菜单
|
||||
/// </summary>
|
||||
[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; // 继续执行原始方法
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补丁:防止在爱情关系检查中出现空引用
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(LovePartnerRelationUtility))]
|
||||
[HarmonyPatch("ExistingLovePartners")]
|
||||
public static class Patch_LovePartnerRelationUtility_ExistingLovePartners
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static bool Prefix(Pawn pawn, bool allowDead, ref List<DirectPawnRelation> __result)
|
||||
{
|
||||
// 如果pawn是机甲单位,返回空列表
|
||||
if (pawn is Wulamechunit)
|
||||
{
|
||||
__result = new List<DirectPawnRelation>();
|
||||
return false; // 跳过原始方法
|
||||
}
|
||||
|
||||
// 如果pawn没有story组件,返回空列表
|
||||
if (pawn?.story == null)
|
||||
{
|
||||
__result = new List<DirectPawnRelation>();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // 继续执行原始方法
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补丁:防止浪漫关系配对检查中的空引用
|
||||
/// </summary>
|
||||
[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; // 继续执行原始方法
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补丁:防止浪漫关系检查中的空引用
|
||||
/// </summary>
|
||||
[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; // 继续执行原始方法
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace WulaFallenEmpire.HarmonyPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// 整合的伤害处理系统
|
||||
/// 处理:1. 机甲装甲系统 2. HediffComp_Invulnerable免疫系统
|
||||
/// </summary>
|
||||
[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<ThingDef>.GetNamedSilentFail("Mote_Spark");
|
||||
|
||||
// 阻挡音效
|
||||
private static readonly SoundDef BlockSoundDef = DefDatabase<SoundDef>.GetNamedSilentFail("ArmorBlock");
|
||||
|
||||
// 免疫效果的MoteDef
|
||||
private static readonly ThingDef ImmuneMoteDef = DefDatabase<ThingDef>.GetNamedSilentFail("Mote_Immunity");
|
||||
|
||||
// 免疫音效
|
||||
private static readonly SoundDef ImmuneSoundDef = DefDatabase<SoundDef>.GetNamedSilentFail("ImmuneSound");
|
||||
|
||||
// 调试统计
|
||||
private static readonly Dictionary<Thing, DamageBlockStats> DebugStats = new Dictionary<Thing, DamageBlockStats>();
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前置补丁:在TakeDamage执行前检查伤害免疫
|
||||
/// </summary>
|
||||
[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++;
|
||||
|
||||
// 第二步:检查机甲装甲系统
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示阻挡效果
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放阻挡音效
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取调试统计信息
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CompMechPilotHolder>();
|
||||
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; // 出错时继续执行原始方法
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// File: HarmonyPatches/SkillSystemPatches.cs
|
||||
using HarmonyLib;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 针对SkillRecord.Interval()的补丁,防止在机甲上出现空引用
|
||||
/// </summary>
|
||||
[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; // 执行原方法
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// File: HediffComp_SyncedWithMech_Fixed.cs
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using System.Linq;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class HediffCompProperties_SyncedWithMech : HediffCompProperties
|
||||
{
|
||||
public float severityOnPawn = 0.5f; // 在Pawn身上的严重性
|
||||
public float severityOnMech = 1.5f; // 在Mech身上的严重性
|
||||
public bool transferToMech = true; // 是否转移到机甲
|
||||
public bool removeWhenLeaving = true; // 离开时是否从机甲移除
|
||||
public string syncEffectDef = null; // 同步时的效果
|
||||
|
||||
public HediffCompProperties_SyncedWithMech()
|
||||
{
|
||||
this.compClass = typeof(HediffComp_SyncedWithMech);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffComp_SyncedWithMech : HediffComp
|
||||
{
|
||||
// 同步状态数据
|
||||
public Pawn linkedMech = null;
|
||||
public bool isOnMech = false;
|
||||
private bool initialized = false;
|
||||
|
||||
// 缓存属性
|
||||
private HediffCompProperties_SyncedWithMech Props =>
|
||||
(HediffCompProperties_SyncedWithMech)this.props;
|
||||
|
||||
// 新增:供CompMechPilotHolder调用的公共方法
|
||||
public void OnPilotEnteredMech(Pawn mech)
|
||||
{
|
||||
if (mech is Wulamechunit dmech)
|
||||
{
|
||||
LinkToMech(dmech);
|
||||
}
|
||||
else if (Prefs.DevMode)
|
||||
{
|
||||
Log.Warning($"[DD] OnPilotEnteredMech: 参数不是Wulamechunit类型: {mech?.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPilotExitedMech()
|
||||
{
|
||||
UnlinkFromMech();
|
||||
}
|
||||
|
||||
public override void CompPostTick(ref float severityAdjustment)
|
||||
{
|
||||
base.CompPostTick(ref severityAdjustment);
|
||||
|
||||
// 首次初始化
|
||||
if (!initialized)
|
||||
{
|
||||
Initialize();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// 检查是否需要同步
|
||||
CheckSyncStatus();
|
||||
|
||||
// 如果不在机甲上,保持Pawn的严重性
|
||||
if (!isOnMech && !(parent.pawn is Wulamechunit))
|
||||
{
|
||||
parent.Severity = Props.severityOnPawn;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
// 如果Pawn是机甲,不应用这个效果
|
||||
if (parent.pawn is Wulamechunit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置初始严重性
|
||||
parent.Severity = Props.severityOnPawn;
|
||||
|
||||
// 检查Pawn是否已经在机甲中
|
||||
CheckIfInMech();
|
||||
}
|
||||
|
||||
// 检查Pawn是否在机甲中
|
||||
private void CheckIfInMech()
|
||||
{
|
||||
// 如果Pawn是机甲,跳过
|
||||
if (parent.pawn is Wulamechunit)
|
||||
return;
|
||||
|
||||
// 查找Pawn所在的机甲
|
||||
var mech = FindMechContainingPawn(parent.pawn);
|
||||
if (mech != null && mech != linkedMech)
|
||||
{
|
||||
// 连接到新的机甲
|
||||
LinkToMech(mech);
|
||||
}
|
||||
else if (mech == null && linkedMech != null)
|
||||
{
|
||||
// 从机甲断开
|
||||
UnlinkFromMech();
|
||||
}
|
||||
}
|
||||
|
||||
// 查找包含Pawn的机甲
|
||||
private Wulamechunit FindMechContainingPawn(Pawn pawn)
|
||||
{
|
||||
if (pawn == null || pawn.Map == null)
|
||||
return null;
|
||||
|
||||
// 检查所有机甲
|
||||
foreach (var thing in pawn.Map.listerThings.ThingsInGroup(ThingRequestGroup.Pawn))
|
||||
{
|
||||
if (thing is Wulamechunit mech)
|
||||
{
|
||||
var pilotComp = mech.TryGetComp<CompMechPilotHolder>();
|
||||
if (pilotComp != null && pilotComp.GetPilots().Contains(pawn))
|
||||
{
|
||||
return mech;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 连接到机甲
|
||||
private void LinkToMech(Wulamechunit mech)
|
||||
{
|
||||
if (mech == null || !Props.transferToMech)
|
||||
return;
|
||||
|
||||
// 记录连接的机甲
|
||||
linkedMech = mech;
|
||||
isOnMech = true;
|
||||
|
||||
// 设置Pawn的严重性(在机甲内)
|
||||
parent.Severity = Props.severityOnPawn;
|
||||
|
||||
// 在机甲上添加同样的Hediff
|
||||
AddHediffToMech(mech);
|
||||
|
||||
// 触发同步效果
|
||||
TriggerSyncEffect();
|
||||
|
||||
}
|
||||
|
||||
// 从机甲断开
|
||||
private void UnlinkFromMech()
|
||||
{
|
||||
if (linkedMech == null)
|
||||
return;
|
||||
|
||||
// 从机甲移除Hediff
|
||||
if (Props.removeWhenLeaving)
|
||||
{
|
||||
RemoveHediffFromMech(linkedMech);
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
var oldMech = linkedMech;
|
||||
linkedMech = null;
|
||||
isOnMech = false;
|
||||
|
||||
// 恢复Pawn的严重性
|
||||
parent.Severity = Props.severityOnPawn;
|
||||
}
|
||||
|
||||
// 在机甲上添加Hediff
|
||||
private void AddHediffToMech(Wulamechunit mech)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否已经有相同的Hediff
|
||||
var existingHediff = mech.health.hediffSet.GetFirstHediffOfDef(parent.def);
|
||||
if (existingHediff != null)
|
||||
{
|
||||
// 更新现有Hediff的严重性
|
||||
existingHediff.Severity = Props.severityOnMech;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 添加新的Hediff
|
||||
var hediff = HediffMaker.MakeHediff(parent.def, mech);
|
||||
hediff.Severity = Props.severityOnMech;
|
||||
|
||||
// 确保Hediff有同样的comp
|
||||
var syncComp = hediff.TryGetComp<HediffComp_SyncedWithMech>();
|
||||
if (syncComp != null)
|
||||
{
|
||||
syncComp.linkedMech = null; // 机甲上的Hediff不链接其他机甲
|
||||
syncComp.isOnMech = true; // 标记为在机甲上
|
||||
}
|
||||
|
||||
mech.health.AddHediff(hediff);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DD] 在机甲{mech.LabelShort}上添加Hediff时出错: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// 从机甲移除Hediff
|
||||
private void RemoveHediffFromMech(Pawn mech)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hediff = mech.health.hediffSet.GetFirstHediffOfDef(parent.def);
|
||||
if (hediff != null)
|
||||
{
|
||||
mech.health.RemoveHediff(hediff);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DD] 从机甲{mech.LabelShort}移除Hediff时出错: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// 触发同步效果
|
||||
private void TriggerSyncEffect()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Props.syncEffectDef))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var effectDef = DefDatabase<EffecterDef>.GetNamed(Props.syncEffectDef, false);
|
||||
if (effectDef != null && parent.pawn.Spawned)
|
||||
{
|
||||
Effecter effecter = effectDef.Spawn();
|
||||
effecter.Trigger(parent.pawn, parent.pawn);
|
||||
effecter.Cleanup();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[DD] 触发同步效果时出错: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// 定期检查同步状态
|
||||
private void CheckSyncStatus()
|
||||
{
|
||||
// 每60帧检查一次
|
||||
if (Find.TickManager.TicksGame % 60 != 0)
|
||||
return;
|
||||
|
||||
CheckIfInMech();
|
||||
|
||||
// 如果Pawn死亡或消失,从机甲移除
|
||||
if (parent.pawn == null || parent.pawn.Dead || parent.pawn.Destroyed)
|
||||
{
|
||||
UnlinkFromMech();
|
||||
}
|
||||
}
|
||||
|
||||
// 当Hediff被移除时
|
||||
public override void CompPostPostRemoved()
|
||||
{
|
||||
base.CompPostPostRemoved();
|
||||
|
||||
// 从机甲移除对应的Hediff
|
||||
UnlinkFromMech();
|
||||
}
|
||||
|
||||
// 保存和加载状态
|
||||
public override void CompExposeData()
|
||||
{
|
||||
base.CompExposeData();
|
||||
|
||||
Scribe_References.Look(ref linkedMech, "linkedMech");
|
||||
Scribe_Values.Look(ref isOnMech, "isOnMech", false);
|
||||
Scribe_Values.Look(ref initialized, "initialized", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
166
Source/WulaFallenEmpire/Pawn/Mechunit.cs
Normal file
166
Source/WulaFallenEmpire/Pawn/Mechunit.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
// File: Wulamechunit_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<Gizmo> GetGizmos()
|
||||
{
|
||||
foreach (Gizmo gizmo in base.GetGizmos())
|
||||
{
|
||||
yield return gizmo;
|
||||
}
|
||||
|
||||
// 添加驾驶员相关的Gizmo
|
||||
var pilotComp = this.TryGetComp<CompMechPilotHolder>();
|
||||
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<Gizmo> 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<CompMechPilotHolder>();
|
||||
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<CompMechPilotHolder>();
|
||||
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<CompMechPilotHolder>();
|
||||
if (pilotComp != null && pilotComp.HasPilots)
|
||||
{
|
||||
pilotComp.EjectAllPilotsOnDeath();
|
||||
}
|
||||
|
||||
base.Kill(dinfo, exactCulprit);
|
||||
}
|
||||
|
||||
// 重写销毁方法
|
||||
public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
|
||||
{
|
||||
// 在销毁前弹出所有驾驶员
|
||||
var pilotComp = this.TryGetComp<CompMechPilotHolder>();
|
||||
if (pilotComp != null && pilotComp.HasPilots)
|
||||
{
|
||||
pilotComp.EjectAllPilotsOnDeath();
|
||||
}
|
||||
|
||||
base.Destroy(mode);
|
||||
}
|
||||
|
||||
// IThingHolder 接口实现
|
||||
public new ThingOwner GetDirectlyHeldThings()
|
||||
{
|
||||
var pilotComp = this.TryGetComp<CompMechPilotHolder>();
|
||||
return pilotComp?.GetDirectlyHeldThings();
|
||||
}
|
||||
|
||||
public new void GetChildHolders(List<IThingHolder> outChildren)
|
||||
{
|
||||
ThingOwnerUtility.AppendThingHoldersFromThings(outChildren, GetDirectlyHeldThings());
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
// 驾驶员容器的数据会在CompMechPilotHolder中自动保存
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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是否需要维护
|
||||
|
||||
@@ -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<CompMechPilotHolder>();
|
||||
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<Gizmo> 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<CompMechPilotHolder>();
|
||||
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<CompMechPilotHolder>();
|
||||
if (pilotHolder != null && !pilotHolder.HasPilots)
|
||||
{
|
||||
// 如果所有驾驶员都被移除了,重置标记
|
||||
defaultPilotsSpawned = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<DefaultPilotEntry> defaultPilots = new List<DefaultPilotEntry>();
|
||||
|
||||
// 高级配置
|
||||
public bool spawnOnlyIfNoPilot = true; // 只在没有驾驶员时生成
|
||||
public bool replaceExistingPilots = false; // 替换现有驾驶员
|
||||
public int maxDefaultPilots = -1; // -1表示使用CompMechPilotHolder的最大容量
|
||||
|
||||
public CompProperties_MechDefaultPilot()
|
||||
{
|
||||
this.compClass = typeof(CompMechDefaultPilot);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> 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<DefaultPilotEntry> 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; // 回退到最后一个
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<HediffDef> 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<HediffDef> 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
[Serializable]
|
||||
public class PawnKindHediffEntry
|
||||
{
|
||||
public PawnKindDef pawnKind;
|
||||
public List<HediffDef> hediffs;
|
||||
public float addChance = 1.0f; // 特定PawnKind的添加概率
|
||||
public bool allowDuplicates = false; // 是否允许重复添加
|
||||
|
||||
public PawnKindHediffEntry()
|
||||
{
|
||||
}
|
||||
|
||||
public PawnKindHediffEntry(PawnKindDef pawnKind, List<HediffDef> hediffs)
|
||||
{
|
||||
this.pawnKind = pawnKind;
|
||||
this.hediffs = hediffs;
|
||||
}
|
||||
}
|
||||
|
||||
public class CompProperties_HediffGiverByKind : CompProperties
|
||||
{
|
||||
// 默认的hediff列表(当没有找到匹配的PawnKind时使用)
|
||||
public List<HediffDef> defaultHediffs;
|
||||
public float defaultAddChance = 1.0f;
|
||||
public bool defaultAllowDuplicates = false;
|
||||
|
||||
// PawnKind特定的hediff配置
|
||||
public List<PawnKindHediffEntry> 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<HediffDef> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
199
Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs
Normal file
199
Source/WulaFallenEmpire/Pawn_Comps/MechArmor/CompMechArmor.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 机甲装甲组件:提供基于装甲值的伤害减免系统
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 检查伤害是否被装甲抵消
|
||||
/// </summary>
|
||||
/// <param name="dinfo">伤害信息</param>
|
||||
/// <returns>true=伤害被抵消,false=伤害有效</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示阻挡效果
|
||||
/// </summary>
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取装甲信息(用于调试)
|
||||
/// </summary>
|
||||
public string GetArmorInfo()
|
||||
{
|
||||
return $"<b>{parent.LabelShort}的装甲系统</b>\n" +
|
||||
$"当前装甲值: {CurrentArmor:F1}\n" +
|
||||
$"阻挡规则: 穿甲伤害 < 装甲值\n" +
|
||||
$"统计: 已阻挡 {blockedHits}/{totalHits} 次攻击\n" +
|
||||
$"阻挡率: {(totalHits > 0 ? (float)blockedHits / totalHits * 100 : 0):F1}%";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取调试按钮
|
||||
/// </summary>
|
||||
public override IEnumerable<Gizmo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 机甲装甲组件属性
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
446
Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs
Normal file
446
Source/WulaFallenEmpire/Pawn_Comps/MechFuel/CompMechFuel.cs
Normal file
@@ -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<CompMechPilotHolder>();
|
||||
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.WULA_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<Pawn> 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<CompMechPilotHolder>();
|
||||
return pilotComp != null && pilotComp.HasPilots;
|
||||
}
|
||||
|
||||
public override IEnumerable<Gizmo> 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<Texture2D>.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<Texture2D>.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<Texture2D>.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<Texture2D>.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string> 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Texture2D>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<CompPowerTrader>();
|
||||
pilotComp = parent.TryGetComp<CompMechPilotHolder>();
|
||||
|
||||
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<Gizmo> 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string> 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<CompProperties_MechPilotHolder>() == null)
|
||||
{
|
||||
Log.Warning($"[DD] requirePilot is true but no CompProperties_MechPilotHolder found for {parentDef.defName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<CompMechPilotHolder>();
|
||||
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<Gizmo> 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<Texture2D>.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<Texture2D>.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<Texture2D>.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<Texture2D>.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.WULA_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<Pawn> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Thing> 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<Thing> things)
|
||||
{
|
||||
if (thingsIgnoredByExplosion == null)
|
||||
{
|
||||
thingsIgnoredByExplosion = new List<Thing>();
|
||||
}
|
||||
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<CompRefuelable>() != null)
|
||||
{
|
||||
radius += Mathf.Sqrt(parent.GetComp<CompRefuelable>().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<CompRefuelable>() != null)
|
||||
{
|
||||
parent.GetComp<CompRefuelable>().ConsumeFuel(parent.GetComp<CompRefuelable>().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WickMessage> wickMessages; // 引信消息
|
||||
|
||||
// 自毁触发条件
|
||||
public bool triggerOnDeath = true; // 死亡时触发
|
||||
public bool triggerOnHealthThreshold = true; // 健康阈值时触发
|
||||
public List<DamageDef> startWickOnDamageTaken; // 特定伤害类型触发自毁
|
||||
public List<DamageDef> 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<string> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// File: CompMechSkillInheritance_Simple.cs
|
||||
using RimWorld;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 简化安全版:机甲技能继承自驾驶员
|
||||
/// </summary>
|
||||
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<CompMechPilotHolder>();
|
||||
|
||||
// 初始更新
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新机甲技能
|
||||
/// </summary>
|
||||
private void UpdateMechSkills()
|
||||
{
|
||||
if (mechPawn == null || mechPawn.skills == null)
|
||||
return;
|
||||
|
||||
// 获取驾驶员组件
|
||||
var pilots = pilotHolder?.GetPilots()?.ToList() ?? new List<Pawn>();
|
||||
|
||||
// 遍历机甲的所有技能
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简化版组件属性
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
// File: CompMoteEmitterNorthward.cs
|
||||
using RimWorld;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using Verse.Sound;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 组件:持续产生向上(北向)移动的Mote
|
||||
/// </summary>
|
||||
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<CompMechPilotHolder>();
|
||||
|
||||
// 如果需要驾驶员但组件不存在,发出警告
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全获取当前位置
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新移动状态
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以发射Mote
|
||||
/// </summary>
|
||||
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<CompPowerTrader>();
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据朝向获取偏移位置
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取组件状态信息(用于调试)
|
||||
/// </summary>
|
||||
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<CompPowerTrader>();
|
||||
if (powerComp == null)
|
||||
return "No power comp";
|
||||
return powerComp.PowerOn ? "Powered" : "No power";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组件属性(更新版)
|
||||
/// </summary>
|
||||
public class CompProperties_MoteEmitterNorthward : CompProperties
|
||||
{
|
||||
/// <summary>Mote定义</summary>
|
||||
public ThingDef moteDef;
|
||||
|
||||
/// <summary>发射间隔(ticks)- 静止时</summary>
|
||||
public int emitIntervalTicks = 60; // 默认1秒
|
||||
|
||||
/// <summary>发射间隔(ticks)- 移动时</summary>
|
||||
public int emitIntervalMovingTicks = 30; // 移动时默认0.5秒
|
||||
|
||||
/// <summary>移动速度</summary>
|
||||
public float moveSpeed = 1f;
|
||||
|
||||
/// <summary>Mote生命周期(ticks)</summary>
|
||||
public float lifetimeTicks = 120f; // 默认2秒
|
||||
|
||||
/// <summary>初始旋转角度</summary>
|
||||
public float rotation = 0f;
|
||||
|
||||
/// <summary>旋转速度(度/秒)</summary>
|
||||
public float rotationRate = 0f;
|
||||
|
||||
/// <summary>缩放大小</summary>
|
||||
public float scale = 1f;
|
||||
|
||||
/// <summary>偏移位置(相对于父物体)- 默认朝北时的偏移</summary>
|
||||
public Vector3 offset = Vector3.zero;
|
||||
|
||||
/// <summary>随机偏移半径</summary>
|
||||
public float randomOffsetRadius = 0f;
|
||||
|
||||
/// <summary>发射时的音效</summary>
|
||||
public SoundDef soundOnEmit;
|
||||
|
||||
/// <summary>是否只在启用的状态发射</summary>
|
||||
public bool onlyWhenPowered = false;
|
||||
|
||||
/// <summary>是否只在至少有一个驾驶员时发射</summary>
|
||||
public bool requirePilot = true; // 新增:驾驶员条件
|
||||
|
||||
/// <summary>天气条件:只在指定天气发射(用逗号分隔)</summary>
|
||||
public string onlyInWeather;
|
||||
|
||||
/// <summary>地形条件:只在指定地形发射</summary>
|
||||
public TerrainDef onlyOnTerrain;
|
||||
|
||||
/// <summary>驾驶员条件:只在驾驶员存活时发射</summary>
|
||||
public bool requirePilotAlive = true; // 新增:要求驾驶员存活
|
||||
|
||||
public CompProperties_MoteEmitterNorthward()
|
||||
{
|
||||
compClass = typeof(CompMoteEmitterNorthward);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> 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<CompProperties_MechPilotHolder>() == null)
|
||||
{
|
||||
yield return $"requirePilot is true but no CompProperties_MechPilotHolder found for {parentDef.defName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,6 @@ namespace WulaFallenEmpire
|
||||
{
|
||||
if (gun == null)
|
||||
{
|
||||
WulaLog.Debug("CompTurrentGun had null gun after loading. Recreating.");
|
||||
MakeGun();
|
||||
}
|
||||
else
|
||||
@@ -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<HediffComp_Disappears>();
|
||||
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<CompGiveHediffOnShot>();
|
||||
if (comp == null || comp.Props.hediffDef == null) return;
|
||||
CompGiveHediffOnShot comp = __instance.EquipmentSource.GetComp<CompGiveHediffOnShot>();
|
||||
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<HediffComp_Disappears>();
|
||||
disappearsComp?.ResetElapsedTicks();
|
||||
var disappearsComp = hediff.TryGetComp<HediffComp_Disappears>();
|
||||
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<CompGiveHediffOnShot>();
|
||||
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<HediffComp_Disappears>();
|
||||
disappearsComp?.ResetElapsedTicks();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// File: CompMechOnlyWeapon.cs
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 简单的机甲专用武器组件
|
||||
/// </summary>
|
||||
public class CompMechOnlyWeapon : ThingComp
|
||||
{
|
||||
public List<ThingDef> allowedMechRaces;
|
||||
|
||||
public override void Initialize(CompProperties props)
|
||||
{
|
||||
base.Initialize(props);
|
||||
allowedMechRaces = ((CompProperties_MechOnlyWeapon)props).allowedMechRaces;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查机甲是否可以装备此武器
|
||||
/// </summary>
|
||||
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<ThingDef> allowedMechRaces = new List<ThingDef>();
|
||||
|
||||
public CompProperties_MechOnlyWeapon()
|
||||
{
|
||||
compClass = typeof(CompMechOnlyWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -82,7 +82,434 @@
|
||||
<HintPath>..\..\..\..\..\..\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.ScreenCaptureModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Compile Include="**\*.cs" Exclude="bin\**;obj\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- 第一部分:基础类和组件 -->
|
||||
<Compile Include="HediffComp\WULA_DamageResponse\HediffComp_DamageResponse.cs" />
|
||||
<Compile Include="HediffComp\WULA_SyncedWithMech\HediffCompProperties_SyncedWithMech.cs" />
|
||||
<Compile Include="WulaFallenEmpireMod.cs" />
|
||||
<Compile Include="WulaFallenEmpireSettings.cs" />
|
||||
<Compile Include="WulaLog.cs" />
|
||||
<Compile Include="WulaDefOf.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<!-- Ability 相关 -->
|
||||
<Compile Include="Ability\WULA_AbilityAreaDestruction\CompAbilityEffect_AreaDestruction.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityAreaDestruction\CompProperties_AbilityAreaDestruction.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityBombardment\CompAbilityEffect_Bombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityBombardment\CompProperties_AbilityBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCallSkyfaller\CompAbilityEffect_CallSkyfaller.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCallSkyfaller\CompProperties_AbilityCallSkyfaller.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCallSkyfaller\MapComponent_SkyfallerDelayed.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCircularBombardment\CompAbilityEffect_CircularBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityCircularBombardment\CompProperties_AbilityCircularBombardment.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityDeleteTarget\CompAbilityEffect_DeleteTarget.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityDeleteTarget\CompProperties_AbilityDeleteTarget.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\AbilityWeaponDefExtension.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\CompAbilityEffect_EnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\CompProperties_AbilityEnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLance.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityEnergyLance\EnergyLanceExtension.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySpawnAligned\CompAbilityEffect_SpawnAligned.cs" />
|
||||
<Compile Include="Ability\WULA_AbilitySpawnAligned\CompProperties_AbilitySpawnAligned.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityStunKnockback\CompAbilityEffect_StunKnockback.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityStunKnockback\CompProperties_StunKnockback.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityTeleportSelf\CompAbilityEffect_TeleportSelf.cs" />
|
||||
<Compile Include="Ability\WULA_AbilityTeleportSelf\CompProperties_AbilityTeleportSelf.cs" />
|
||||
<Compile Include="Ability\WULA_EquippableAbilities\CompEquippableAbilities.cs" />
|
||||
<Compile Include="Ability\WULA_EquippableAbilities\CompProperties_EquippableAbilities.cs" />
|
||||
<Compile Include="Ability\WULA_LaunchMultiProjectile\CompAbilityEffect_LaunchMultiProjectile.cs" />
|
||||
<Compile Include="Ability\WULA_LaunchMultiProjectile\CompProperties_AbilityLaunchMultiProjectile.cs" />
|
||||
<Compile Include="Ability\WULA_LaunchMultiProjectile\JobDriver_CastAbilityMaintainMultiProjectile.cs" />
|
||||
<Compile Include="Ability\WULA_PullTarget\CompAbilityEffect_PullTarget.cs" />
|
||||
<Compile Include="Ability\WULA_PullTarget\CompProperties_AbilityPullTarget.cs" />
|
||||
<Compile Include="Ability\WULA_RequiresNonHostility\CompAbilityEffect_RequiresNonHostility.cs" />
|
||||
<Compile Include="Ability\WULA_ResearchPrereq\CompAbilityEffect_ResearchPrereq.cs" />
|
||||
<!-- Building 相关 -->
|
||||
<Compile Include="Building\Building_ExtraGraphics.cs" />
|
||||
<Compile Include="Building\Building_MapObserver.cs" />
|
||||
<Compile Include="Building\Building_TurretGunHasSpeed.cs" />
|
||||
<!-- BuildingComp 相关 -->
|
||||
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompBuildingBombardment.cs" />
|
||||
<Compile Include="BuildingComp\WULA_BuildingBombardment\CompProperties_BuildingBombardment.cs" />
|
||||
<Compile Include="BuildingComp\WULA_BuildingSpawner\CompBuildingSpawner.cs" />
|
||||
<Compile Include="BuildingComp\WULA_BuildingSpawner\CompProperties_BuildingSpawner.cs" />
|
||||
<Compile Include="BuildingComp\WULA_EnergyLanceTurret\CompEnergyLanceTurret.cs" />
|
||||
<Compile Include="BuildingComp\WULA_EnergyLanceTurret\CompProperties_EnergyLanceTurret.cs" />
|
||||
<Compile Include="BuildingComp\WULA_InitialFaction\CompProperties_InitialFaction.cs" />
|
||||
<Compile Include="BuildingComp\WULA_MechanoidRecycler\Building_MechanoidRecycler.cs" />
|
||||
<Compile Include="BuildingComp\WULA_MechanoidRecycler\CompProperties_MechanoidRecycler.cs" />
|
||||
<Compile Include="BuildingComp\WULA_MechanoidRecycler\JobDriver_RecycleMechanoid.cs" />
|
||||
<Compile Include="BuildingComp\WULA_PathCostUpdater\CompPathCostUpdater.cs" />
|
||||
<Compile Include="BuildingComp\WULA_PhaseCombatTower\CompPhaseCombatTower.cs" />
|
||||
<Compile Include="BuildingComp\WULA_PhaseCombatTower\CompProperties_PhaseCombatTower.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\ArmedShuttleIncoming.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\Building_ArmedShuttle.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\Building_ArmedShuttleWithPocket.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\Building_PocketMapExit.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\Dialog_ArmedShuttleTransfer.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\GenStep_WulaPocketSpaceSmall.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Shuttle\PocketSpaceThingHolder.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompPrefabSkyfallerCaller.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompProperties_PrefabSkyfallerCaller.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompProperties_SkyfallerCaller.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\CompSkyfallerCaller.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\DebugActions_PrefabSkyfallerCaller.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\Skyfaller_PrefabSpawner.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\WulaSkyfallerWorldComponent.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\WULA_SkyfallerFactioncs\CompProperties_SkyfallerFaction.cs" />
|
||||
<Compile Include="BuildingComp\WULA_SkyfallerCaller\WULA_SkyfallerFactioncs\CompSkyfallerFaction.cs" />
|
||||
<Compile Include="BuildingComp\WULA_StorageTurret\CompProperties_StorageTurret.cs" />
|
||||
<Compile Include="BuildingComp\WULA_StorageTurret\CompStorageTurret.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Teleporter\CompMapTeleporter.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Teleporter\CompProperties_MapTeleporter.cs" />
|
||||
<Compile Include="BuildingComp\WULA_Teleporter\WULA_TeleportLandingMarker.cs" />
|
||||
<Compile Include="BuildingComp\WULA_TrapLauncher\CompProperties_TrapLauncher.cs" />
|
||||
<Compile Include="BuildingComp\WULA_TrapLauncher\CompTrapLauncher.cs" />
|
||||
<!-- Damage 相关 -->
|
||||
<Compile Include="Damage\DamageDefExtension_TerrainCover.cs" />
|
||||
<Compile Include="Damage\DamageDef_ExtraDamageExtension.cs" />
|
||||
<Compile Include="Damage\DamageWorker_ExplosionWithTerrain.cs" />
|
||||
<Compile Include="Damage\DamageWorker_ExtraDamage.cs" />
|
||||
<!-- Designator 相关 -->
|
||||
<Compile Include="Designator\Designator_CallSkyfallerInArea.cs" />
|
||||
<Compile Include="Designator\Designator_TeleportArrival.cs" />
|
||||
<!-- EventSystem 相关 -->
|
||||
<Compile Include="EventSystem\AI\AIAutoCommentary.cs" />
|
||||
<Compile Include="EventSystem\AI\AIHistoryManager.cs" />
|
||||
<Compile Include="EventSystem\AI\AIIntelligenceCore.cs" />
|
||||
<Compile Include="EventSystem\AI\AIMemoryEntry.cs" />
|
||||
<Compile Include="EventSystem\AI\AIMemoryManager.cs" />
|
||||
<Compile Include="EventSystem\AI\Alerts\Alert_AIOverwatchActive.cs" />
|
||||
<Compile Include="EventSystem\AI\CompAbilityEffect_EnableOverwatch.cs" />
|
||||
<Compile Include="EventSystem\AI\DebugActions_WulaLink.cs" />
|
||||
<Compile Include="EventSystem\AI\LetterInterceptor\Patch_LetterStack.cs" />
|
||||
<Compile Include="EventSystem\AI\MapComponent_AIOverwatch.cs" />
|
||||
<Compile Include="EventSystem\AI\MemoryPrompts.cs" />
|
||||
<Compile Include="EventSystem\AI\ScreenCaptureUtility.cs" />
|
||||
<Compile Include="EventSystem\AI\SimpleAIClient.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\AITool.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\BombardmentUtility.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_AnalyzeScreen.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_CallBombardment.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_CallPrefabAirdrop.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_ChangeExpression.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetAvailableBombardments.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetAvailablePrefabs.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetMapPawns.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetMapResources.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetPawnStatus.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_GetRecentNotifications.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_ModifyGoodwill.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_RecallMemories.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_RememberFact.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_SearchPawnKind.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_SearchThingDef.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_SendReinforcement.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_SetOverwatchMode.cs" />
|
||||
<Compile Include="EventSystem\AI\Tools\Tool_SpawnResources.cs" />
|
||||
<Compile Include="EventSystem\AI\UI\Dialog_AIConversation.cs" />
|
||||
<Compile Include="EventSystem\AI\UI\Dialog_ExtraPersonalityPrompt.cs" />
|
||||
<Compile Include="EventSystem\AI\UI\Overlay_WulaLink.cs" />
|
||||
<Compile Include="EventSystem\AI\UI\Overlay_WulaLink_Notification.cs" />
|
||||
<Compile Include="EventSystem\AI\UI\WulaLinkStyles.cs" />
|
||||
<Compile Include="EventSystem\AI\Utils\JsonToolCallParser.cs" />
|
||||
<Compile Include="EventSystem\AI\Utils\PawnKindDefSearcher.cs" />
|
||||
<Compile Include="EventSystem\AI\Utils\ThingDefSearcher.cs" />
|
||||
<Compile Include="EventSystem\AI\Utils\ToolCallValidator.cs" />
|
||||
<Compile Include="EventSystem\AI\Utils\ToolSchemaSanitizer.cs" />
|
||||
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
|
||||
<Compile Include="EventSystem\Condition\ConditionBase.cs" />
|
||||
<Compile Include="EventSystem\Condition\Condition_FlagExists.cs" />
|
||||
<Compile Include="EventSystem\DebugActions.cs" />
|
||||
<Compile Include="EventSystem\DelayedActionManager.cs" />
|
||||
<Compile Include="EventSystem\Dialog_CustomDisplay.cs" />
|
||||
<Compile Include="EventSystem\Dialog_ManageEventVariables.cs" />
|
||||
<Compile Include="EventSystem\Effect\EffectBase.cs" />
|
||||
<Compile Include="EventSystem\Effect\Effect_CallSkyfaller.cs" />
|
||||
<Compile Include="EventSystem\Effect\Effect_OpenAIConversation.cs" />
|
||||
<Compile Include="EventSystem\Effect\Effect_SetTimedFlag.cs" />
|
||||
<Compile Include="EventSystem\EventDef.cs" />
|
||||
<Compile Include="EventSystem\EventUIButtonConfigDef.cs" />
|
||||
<Compile Include="EventSystem\EventUIConfigDef.cs" />
|
||||
<Compile Include="EventSystem\EventVariableManager.cs" />
|
||||
<Compile Include="EventSystem\QuestNode\QuestNode_EventLetter.cs" />
|
||||
<Compile Include="EventSystem\QuestNode\QuestNode_Root_EventLetter.cs" />
|
||||
<Compile Include="EventSystem\QuestNode\QuestNode_WriteToEventVariablesWithAdd.cs" />
|
||||
<!-- Flyover 相关 -->
|
||||
<Compile Include="Flyover\ThingclassFlyOver.cs" />
|
||||
<Compile Include="Flyover\WULA_AircraftHangar\CompAbilityEffect_AircraftStrike.cs" />
|
||||
<Compile Include="Flyover\WULA_AircraftHangar\CompAircraftHangar.cs" />
|
||||
<Compile Include="Flyover\WULA_AircraftHangar\WorldComponent_AircraftManager.cs" />
|
||||
<Compile Include="Flyover\WULA_BlockedByFlyOverFacility\CompAbilityEffect_BlockedByFlyOverFacility.cs" />
|
||||
<Compile Include="Flyover\WULA_DestroyFlyOverByFacilities\CompProperties_DestroyFlyOverByFacilities.cs" />
|
||||
<Compile Include="Flyover\WULA_FlyOverDropPod\CompProperties_FlyOverDropPod.cs" />
|
||||
<Compile Include="Flyover\WULA_FlyOverEscort\CompFlyOverEscort.cs" />
|
||||
<Compile Include="Flyover\WULA_FlyOverEscort\CompProperties_FlyOverEscort.cs" />
|
||||
<Compile Include="Flyover\WULA_FlyOverFacilities\CompAbilityEffect_RequireFlyOverFacility.cs" />
|
||||
<Compile Include="Flyover\WULA_FlyOverFacilities\CompFlyOverFacilities.cs" />
|
||||
<Compile Include="Flyover\WULA_GlobalFlyOverCooldown\CompAbilityEffect_GlobalFlyOverCooldown.cs" />
|
||||
<Compile Include="Flyover\WULA_GlobalFlyOverCooldown\CompFlyOverCooldown.cs" />
|
||||
<Compile Include="Flyover\WULA_GroundStrafing\CompGroundStrafing.cs" />
|
||||
<Compile Include="Flyover\WULA_SectorSurveillance\CompSectorSurveillance.cs" />
|
||||
<Compile Include="Flyover\WULA_SendLetterAfterTicks\CompProperties_SendLetterAfterTicks.cs" />
|
||||
<Compile Include="Flyover\WULA_SendLetterAfterTicks\CompSendLetterAfterTicks.cs" />
|
||||
<Compile Include="Flyover\WULA_ShipArtillery\CompProperties_ShipArtillery.cs" />
|
||||
<Compile Include="Flyover\WULA_ShipArtillery\CompShipArtillery.cs" />
|
||||
<Compile Include="Ability\WULA_SpawnFlyOver\CompAbilityEffect_SpawnFlyOver.cs" />
|
||||
<Compile Include="Ability\WULA_SpawnFlyOver\CompProperties_AbilitySpawnFlyOver.cs" />
|
||||
<!-- GlobalWorkTable 相关 -->
|
||||
<Compile Include="GlobalWorkTable\Building_GlobalWorkTable.cs" />
|
||||
<Compile Include="GlobalWorkTable\CompProperties_ProductionCategory.cs" />
|
||||
<Compile Include="GlobalWorkTable\Dialog_GlobalStorageTransfer.cs" />
|
||||
<Compile Include="GlobalWorkTable\GlobalProductionOrder.cs" />
|
||||
<Compile Include="GlobalWorkTable\GlobalProductionOrderStack.cs" />
|
||||
<Compile Include="GlobalWorkTable\GlobalStorageWorldComponent.cs" />
|
||||
<Compile Include="GlobalWorkTable\GlobalWorkTableAirdropExtension.cs" />
|
||||
<Compile Include="GlobalWorkTable\ITab_GlobalBills.cs" />
|
||||
<Compile Include="GlobalWorkTable\JobDriver_GlobalWorkTable.cs" />
|
||||
<Compile Include="GlobalWorkTable\WorkGiver_GlobalWorkTable.cs" />
|
||||
<Compile Include="GlobalWorkTable\WULA_Launchable_ToGlobalStorage\CompLaunchable_ToGlobalStorage.cs" />
|
||||
<Compile Include="GlobalWorkTable\WULA_Launchable_ToGlobalStorage\CompProperties_GarbageShield.cs" />
|
||||
<Compile Include="GlobalWorkTable\WULA_Launchable_ToGlobalStorage\CompProperties_Launchable_ToGlobalStorage.cs" />
|
||||
<Compile Include="GlobalWorkTable\WULA_ValueConverter\CompProperties_ValueConverter.cs" />
|
||||
<Compile Include="GlobalWorkTable\WULA_ValueConverter\CompValueConverter.cs" />
|
||||
<!-- HarmonyPatches 相关 -->
|
||||
<Compile Include="HarmonyPatches\Caravan_NeedsTracker_TrySatisfyPawnNeeds_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\DamageInfo_Constructor_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\Faction_ShouldHaveLeader_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\FloatMenuOptionProvider_Ingest_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\Hediff_Mechlink_PostAdd_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\IngestPatch.cs" />
|
||||
<Compile Include="HarmonyPatches\MapParent_ShouldRemoveMapNow_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\MechanitorPatch.cs" />
|
||||
<Compile Include="HarmonyPatches\MechanitorUtility_InMechanitorCommandRange_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\MechWeapon\CompMechWeapon.cs" />
|
||||
<Compile Include="HarmonyPatches\MechWeapon\FloatMenuProvider_Mech.cs" />
|
||||
<Compile Include="HarmonyPatches\MechWeapon\Patch_MissingWeapon.cs" />
|
||||
<Compile Include="HarmonyPatches\MechWeapon\Patch_WeaponDrop.cs" />
|
||||
<Compile Include="HarmonyPatches\NoBloodForWulaPatch.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_CaravanInventoryUtility_FindShuttle.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_CaravanUIUtility_AddPawnsSections_Postfix.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_DropCellFinder_SkyfallerCanLandAt.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_JobGiver_GatherOfferingsForPsychicRitual.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_Pawn_JobTracker_StartJob.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_Pawn_PreApplyDamage.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_ThingDefGenerator_Techprints_ImpliedTechprintDefs.cs" />
|
||||
<Compile Include="HarmonyPatches\Projectile_Launch_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\ResurrectionCrashFix.cs" />
|
||||
<Compile Include="HarmonyPatches\ScenPart_PlayerPawnsArriveMethod_Patch.cs" />
|
||||
<Compile Include="HarmonyPatches\WulaSpeciesCorpsePatch.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_Alert_MechLacksOverseer.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_CaravanFormingUtility_AllSendablePawns.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_FloatMenuOptionProvider_SelectedPawnValid.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_IsColonyMechPlayerControlled.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MainTabWindow_Mechs_Pawns.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MechanitorUtility_CanDraftMech.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_MechanitorUtility_EverControllable.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_Pawn_ThreatDisabled.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_AutonomousMech\Patch_UncontrolledMechDrawPulse.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patche_SkillSystem.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patch_ColonistBarMech.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patch_MechSpecificWeapon.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patch_mechunit.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patch_RomanceFix.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_MechUnit\Patch_TakeDamage.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_TurretForceTargetable\CompForceTargetable.cs" />
|
||||
<Compile Include="HarmonyPatches\WULA_TurretForceTargetable\Patch_ForceTargetable.cs" />
|
||||
<!-- HediffComp 相关 -->
|
||||
<Compile Include="HediffComp\HediffCompProperties_DisappearWithEffect.cs" />
|
||||
<Compile Include="HediffComp\HediffCompProperties_GiveHediffsInRangeToRace.cs" />
|
||||
<Compile Include="HediffComp\HediffCompProperties_NanoRepair.cs" />
|
||||
<Compile Include="HediffComp\HediffCompProperties_SwitchableHediff.cs" />
|
||||
<Compile Include="HediffComp\HediffComp_GiveHediffsInRangeToRace.cs" />
|
||||
<Compile Include="HediffComp\HediffComp_RegenerateBackstory.cs" />
|
||||
<Compile Include="HediffComp\HediffComp_TimedExplosion.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffComp_TopTurret\HediffComp_TopTurret.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\DRMDamageShield.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffSpawner\HediffCompProperties_Spawner.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffSpawner\HediffComp_Spawner.cs" />
|
||||
<Compile Include="HediffComp\WULA_HediffSpawner\Tools.cs" />
|
||||
<!-- Job 相关 -->
|
||||
<Compile Include="Job\JobDriver_InspectBuilding.cs" />
|
||||
<Compile Include="Job\JobGiver_InspectBuilding.cs" />
|
||||
<!-- Pawn 相关 -->
|
||||
<Compile Include="Pawn\Comp_PawnRenderExtra.cs" />
|
||||
<Compile Include="Pawn\Mechunit.cs" />
|
||||
<Compile Include="Pawn\WULA_AutoMechCarrier\CompAutoMechCarrier.cs" />
|
||||
<Compile Include="Pawn\WULA_AutoMechCarrier\CompProperties_AutoMechCarrier.cs" />
|
||||
<Compile Include="Pawn\WULA_AutoMechCarrier\PawnProductionEntry.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\CompAutonomousMech.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\DroneGizmo.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\JobGiver_DroneSelfShutdown.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\PawnColumnWorker_DroneEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\PawnColumnWorker_DroneWorkMode.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalAutonomousWorkMode.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalLowEnergy_Drone.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalNeedRecharge.cs" />
|
||||
<Compile Include="Pawn\WULA_AutonomousMech\ThinkNode_ConditionalWorkMode_Drone.cs" />
|
||||
<Compile Include="Pawn\WULA_BrokenPersonality\MentalBreakWorker_BrokenPersonality.cs" />
|
||||
<Compile Include="Pawn\WULA_BrokenPersonality\MentalStateDefExtension_BrokenPersonality.cs" />
|
||||
<Compile Include="Pawn\WULA_BrokenPersonality\MentalState_BrokenPersonality.cs" />
|
||||
<Compile Include="Pawn\WULA_Cat_Invisible\CompFighterInvisible.cs" />
|
||||
<Compile Include="Pawn\WULA_Cat_Invisible\CompProperties_FighterInvisible.cs" />
|
||||
<Compile Include="Pawn\WULA_CompHediffGiver\CompHediffGiver.cs" />
|
||||
<Compile Include="Pawn\WULA_CompHediffGiver\CompProperties_HediffGiver.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\CompChargingBed.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\HediffComp_WulaCharging.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\JobDriver_FeedWulaPatient.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\JobDriver_IngestWulaEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\JobGiverDefExtension_WulaPackEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\JobGiver_WulaGetEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\JobGiver_WulaPackEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\NeedDefExtension_Energy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\Need_WulaEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\ThingDefExtension_EnergySource.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\WorkGiverDefExtension_FeedWula.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\WorkGiver_FeedWulaPatient.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\WorkGiver_Warden_DeliverEnergy.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\WorkGiver_Warden_FeedWula.cs" />
|
||||
<Compile Include="Pawn\WULA_Energy\WulaCaravanEnergyDef.cs" />
|
||||
<Compile Include="Pawn\WULA_Flight\CompPawnFlight.cs" />
|
||||
<Compile Include="Pawn\WULA_Flight\CompProperties_PawnFlight.cs" />
|
||||
<Compile Include="Pawn\WULA_Flight\PawnRenderNodeWorker_AttachmentBody_NoFlight.cs" />
|
||||
<Compile Include="Pawn\WULA_Flight\Pawn_FlightTrackerPatches.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\Building_MaintenancePod.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\CompMaintenancePod.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\HediffCompProperties_MaintenanceDamage.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\JobDriver_EnterMaintenancePod.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\JobDriver_HaulToMaintenancePod.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\MaintenanceNeedExtension.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\Need_Maintenance.cs" />
|
||||
<Compile Include="Pawn\WULA_Maintenance\WorkGiver_DoMaintenance.cs" />
|
||||
<!-- Pawn_Comps 相关 -->
|
||||
<Compile Include="Pawn_Comps\DefaultPilotEntry\CompMechDefaultPilot.cs" />
|
||||
<Compile Include="Pawn_Comps\DefaultPilotEntry\CompProperties_MechDefaultPilot.cs" />
|
||||
<Compile Include="Pawn_Comps\HediffGiverByKind\CompHediffGiverByKind.cs" />
|
||||
<Compile Include="Pawn_Comps\HediffGiverByKind\CompProperties_HediffGiverByKind.cs" />
|
||||
<Compile Include="Pawn_Comps\MechArmor\CompMechArmor.cs" />
|
||||
<Compile Include="Pawn_Comps\MechFuel\CompMechFuel.cs" />
|
||||
<Compile Include="Pawn_Comps\MechFuel\CompProperties_MechFuel.cs" />
|
||||
<Compile Include="Pawn_Comps\MechFuel\Gizmo_MechFuelStatus.cs" />
|
||||
<Compile Include="Pawn_Comps\MechInherentWeapon\CompMechInherentWeapon.cs" />
|
||||
<Compile Include="Pawn_Comps\MechInherentWeapon\CompProperties_MechInherentWeapon.cs" />
|
||||
<Compile Include="Pawn_Comps\MechMovementSound\CompMechMovementSound.cs" />
|
||||
<Compile Include="Pawn_Comps\MechMovementSound\CompProperties_MechMovementSound.cs" />
|
||||
<Compile Include="Pawn_Comps\MechPilotHolder\CompMechPilotHolder.cs" />
|
||||
<Compile Include="Pawn_Comps\MechRepairable\CompMechRepairable.cs" />
|
||||
<Compile Include="Pawn_Comps\MechSelfDestruct\CompMechSelfDestruct.cs" />
|
||||
<Compile Include="Pawn_Comps\MechSelfDestruct\CompProperties_MechSelfDestruct.cs" />
|
||||
<Compile Include="Pawn_Comps\MechSkillInheritance\CompMechSkillInheritance.cs" />
|
||||
<Compile Include="Pawn_Comps\MoteEmitterNorthward\CompMoteEmitterNorthward.cs" />
|
||||
<Compile Include="Pawn_Comps\MultiTurretGun\CompMultiTurretGun.cs" />
|
||||
<!-- PawnsArrivalMode 相关 -->
|
||||
<Compile Include="PawnsArrivalMode\PawnsArrivalModeWorker_EdgeTeleport.cs" />
|
||||
<!-- Placeworker 相关 -->
|
||||
<Compile Include="Placeworker\CompProperties_CustomRadius.cs" />
|
||||
<!-- Projectiles 相关 -->
|
||||
<Compile Include="Projectiles\BulletWithTrail.cs" />
|
||||
<Compile Include="Projectiles\ExplosiveTrackingBulletDef.cs" />
|
||||
<Compile Include="Projectiles\NorthArcModExtension.cs" />
|
||||
<Compile Include="Projectiles\Projectile_ConfigurableHellsphereCannon.cs" />
|
||||
<Compile Include="Projectiles\Projectile_CruiseMissile.cs" />
|
||||
<Compile Include="Projectiles\Projectile_ExplosiveTrackingBullet.cs" />
|
||||
<Compile Include="Projectiles\Projectile_ExplosiveWithTrail.cs" />
|
||||
<Compile Include="Projectiles\Projectile_NorthArcTrail.cs" />
|
||||
<Compile Include="Projectiles\Projectile_TrackingBullet.cs" />
|
||||
<Compile Include="Projectiles\Projectile_WulaPenetratingBeam.cs" />
|
||||
<Compile Include="Projectiles\Projectile_WulaPenetratingBullet.cs" />
|
||||
<Compile Include="Projectiles\TrackingBulletDef.cs" />
|
||||
<!-- QuestNodes 相关 -->
|
||||
<Compile Include="QuestNodes\QuestNode_AddInspectionJob.cs" />
|
||||
<Compile Include="QuestNodes\QuestNode_CheckGlobalResource.cs" />
|
||||
<Compile Include="QuestNodes\QuestNode_GeneratePawnWithCustomization.cs" />
|
||||
<Compile Include="QuestNodes\QuestNode_Hyperlinks.cs" />
|
||||
<Compile Include="QuestNodes\QuestNode_SpawnPrefabSkyfallerCaller.cs" />
|
||||
<Compile Include="QuestNodes\QuestPart_GlobalResourceCheck.cs" />
|
||||
<!-- SectionLayer 相关 -->
|
||||
<Compile Include="SectionLayer_WulaHull.cs" />
|
||||
<!-- Stat 相关 -->
|
||||
<Compile Include="Stat\StatWorker_Energy.cs" />
|
||||
<Compile Include="Stat\StatWorker_Maintenance.cs" />
|
||||
<Compile Include="Stat\StatWorker_NanoRepair.cs" />
|
||||
<!-- Storyteller 相关 -->
|
||||
<Compile Include="Storyteller\WULA_ImportantQuestWithFactionFilter\StorytellerCompProperties_ImportantQuestWithFactionFilter.cs" />
|
||||
<Compile Include="Storyteller\WULA_ImportantQuestWithFactionFilter\StorytellerComp_ImportantQuestWithFactionFilter.cs" />
|
||||
<Compile Include="Storyteller\WULA_SimpleTechnologyTrigger\StorytellerCompProperties_SimpleTechnologyTrigger.cs" />
|
||||
<Compile Include="Storyteller\WULA_SimpleTechnologyTrigger\StorytellerComp_SimpleTechnologyTrigger.cs" />
|
||||
<!-- ThingComp 相关 -->
|
||||
<Compile Include="ThingComp\CompApparelInterceptor.cs" />
|
||||
<Compile Include="ThingComp\CompProperties_DelayedDamageIfNotPlayer.cs" />
|
||||
<Compile Include="ThingComp\CompPsychicScaling.cs" />
|
||||
<Compile Include="ThingComp\CompUseEffect_AddDamageShieldCharges.cs" />
|
||||
<Compile Include="ThingComp\CompUseEffect_OpenCustomUI.cs" />
|
||||
<Compile Include="ThingComp\CompUseEffect_PassionTrainer.cs" />
|
||||
<Compile Include="ThingComp\CompUseEffect_WulaSkillTrainer.cs" />
|
||||
<Compile Include="ThingComp\Comp_WeaponRenderDynamic.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaDamage\CompAreaDamage.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaDamage\CompProperties_AreaDamage.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaShield\AreaShieldManager.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaShield\CompProperties_AreaShield.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaShield\Gizmo_AreaShieldStatus.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaShield\Harmony_AreaShieldInterceptor.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaShield\ThingComp_AreaShield.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaTeleporter\CompProperties_AreaTeleporter.cs" />
|
||||
<Compile Include="ThingComp\WULA_AreaTeleporter\ThingComp_AreaTeleporter.cs" />
|
||||
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompCustomUniqueWeapon.cs" />
|
||||
<Compile Include="ThingComp\WULA_CustomUniqueWeapon\CompProperties_CustomUniqueWeapon.cs" />
|
||||
<Compile Include="ThingComp\WULA_DamageTransaction\CompDamageInterceptor.cs" />
|
||||
<Compile Include="ThingComp\WULA_DamageTransaction\CompDamageRelay.cs" />
|
||||
<Compile Include="ThingComp\WULA_DamageTransaction\CompProperties_DamageInterceptor.cs" />
|
||||
<Compile Include="ThingComp\WULA_DamageTransaction\CompProperties_DamageRelay.cs" />
|
||||
<Compile Include="ThingComp\WULA_GiveHediffOnShot\CompAndPatch_GiveHediffOnShot.cs" />
|
||||
<Compile Include="ThingComp\WULA_GiveHediffsInRange\CompGiveHediffsInRange.cs" />
|
||||
<Compile Include="ThingComp\WULA_GiveHediffsInRange\CompProperties_GiveHediffsInRange.cs" />
|
||||
<Compile Include="ThingComp\WULA_MechRepairKit\CompUseEffect_FixAllHealthConditions.cs" />
|
||||
<Compile Include="ThingComp\WULA_MechRepairKit\Recipe_AdministerWulaMechRepairKit.cs" />
|
||||
<Compile Include="ThingComp\WULA_MechSpecificWeapon\CompMechSpecificWeapon.cs" />
|
||||
<Compile Include="ThingComp\WULA_PeriodicGameCondition\CompPeriodicGameCondition.cs" />
|
||||
<Compile Include="ThingComp\WULA_PeriodicGameCondition\CompProperties_PeriodicGameCondition.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceCore.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompExperienceDataPack.cs" />
|
||||
<Compile Include="ThingComp\WULA_PersonaCore\CompProperties_ExperienceCore.cs" />
|
||||
<Compile Include="ThingComp\WULA_PlaySoundOnSpawn\CompPlaySoundOnSpawn.cs" />
|
||||
<Compile Include="ThingComp\WULA_PlaySoundOnSpawn\CompProperties_PlaySoundOnSpawn.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\CompWulaRitualSpot.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualDef_AddHediff.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualDef_Wula.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualDef_WulaBase.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualToil_AddHediff.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitualToil_GatherForInvocation_Wula.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\PsychicRitual_TechOffering.cs" />
|
||||
<Compile Include="ThingComp\WULA_PsychicRitual\RitualTagExtension.cs" />
|
||||
<Compile Include="ThingComp\WULA_SkyfallerPawnSpawner\CompProperties_SkyfallerPawnSpawner.cs" />
|
||||
<Compile Include="ThingComp\WULA_SkyfallerPawnSpawner\Skyfaller_PawnSpawner.cs" />
|
||||
<Compile Include="ThingComp\WULA_WeaponSwitch\CompAbilityEffect_GiveSwitchHediff.cs" />
|
||||
<Compile Include="ThingComp\WULA_WeaponSwitch\CompAbilityEffect_RemoveSwitchHediff.cs" />
|
||||
<Compile Include="ThingComp\WULA_WeaponSwitch\WeaponSwitch.cs" />
|
||||
<!-- Utils 相关 -->
|
||||
<Compile Include="Utils\BezierUtil.cs" />
|
||||
<Compile Include="Utils\DefInjectedExportUtility.cs" />
|
||||
<!-- Verb 相关 -->
|
||||
<Compile Include="Verb\MeleeAttack_Cleave\CompCleave.cs" />
|
||||
<Compile Include="Verb\MeleeAttack_Cleave\Verb_MeleeAttack_Cleave.cs" />
|
||||
<Compile Include="Verb\MeleeAttack_MultiStrike\CompMultiStrike.cs" />
|
||||
<Compile Include="Verb\MeleeAttack_MultiStrike\Verb_MeleeAttack_MultiStrike.cs" />
|
||||
<Compile Include="Verb\Verb_Excalibur\Thing_ExcaliburBeam.cs" />
|
||||
<Compile Include="Verb\Verb_Excalibur\VerbProperties_Excalibur.cs" />
|
||||
<Compile Include="Verb\Verb_Excalibur\Verb_Excalibur.cs" />
|
||||
<Compile Include="Verb\Verb_ShootArc.cs" />
|
||||
<Compile Include="Verb\Verb_ShootBeamExplosive\VerbPropertiesExplosiveBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootBeamExplosive\Verb_ShootBeamExplosive.cs" />
|
||||
<Compile Include="Verb\Verb_ShootBeamSplitAndChain.cs" />
|
||||
<Compile Include="Verb\Verb_ShootMeltBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootShotgun.cs" />
|
||||
<Compile Include="Verb\Verb_ShootShotgunWithOffset.cs" />
|
||||
<Compile Include="Verb\Verb_ShootWeaponStealBeam\VerbProperties_WeaponStealBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootWeaponStealBeam\Verb_ShootWeaponStealBeam.cs" />
|
||||
<Compile Include="Verb\Verb_ShootWithOffset.cs" />
|
||||
<!-- WorkGiver 相关 -->
|
||||
<Compile Include="WorkGiver\WorkGiver_DeepDrill_WulaConstructor.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- 自定义清理任务,删除obj文件夹中的临时文件 -->
|
||||
|
||||
Reference in New Issue
Block a user