This commit is contained in:
2026-02-24 12:02:38 +08:00
parent 1af5f0c1d8
commit 96bc1d4c5a
57 changed files with 6595 additions and 1170 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

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

View File

@@ -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; // 发射间隔(可选)
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
// 创建带有指定材质的物品

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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; // 继续执行原始方法
}
}
}
}

View File

@@ -0,0 +1,230 @@
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++;
// 第一步检查伤害免疫HediffComp
if (__instance is Pawn pawn)
{
bool blockedByInvulnerable = CheckInvulnerableHediff(pawn, dinfo, out HediffComp_Invulnerable invulnerableComp);
if (blockedByInvulnerable)
{
// 被HediffComp免疫阻挡
DebugStats[__instance].invulnerableBlocked++;
// 显示免疫效果
ShowImmuneEffect(pawn, dinfo);
// 播放免疫音效
PlayImmuneSound(pawn);
// 调用HediffComp的OnDamageBlocked方法
invulnerableComp?.OnDamageBlocked(dinfo);
// 返回空结果,跳过原方法
__result = new DamageWorker.DamageResult();
return false;
}
}
// 第二步:检查机甲装甲系统
float armorValue = __instance.GetStatValue(ArmorStatDef);
// 如果装甲值 <= 0不启动装甲系统继续原方法
if (armorValue <= 0)
return true;
// 计算穿甲伤害
float armorPenetration = dinfo.ArmorPenetrationInt;
float piercingDamage = dinfo.Amount * armorPenetration;
// 判断是否应该阻挡
bool shouldBlock = piercingDamage < armorValue;
if (shouldBlock)
{
// 机甲装甲阻挡成功
DebugStats[__instance].mechArmorBlocked++;
// 显示阻挡效果
ShowBlockEffect(__instance, dinfo);
// 播放阻挡音效
PlayBlockSound(__instance);
// 返回空结果,跳过原方法
__result = new DamageWorker.DamageResult();
return false;
}
return true;
}
/// <summary>
/// 检查伤害免疫HediffComp
/// </summary>
private static bool CheckInvulnerableHediff(Pawn pawn, DamageInfo dinfo, out HediffComp_Invulnerable invulnerableComp)
{
invulnerableComp = null;
if (pawn == null || pawn.health == null || pawn.health.hediffSet == null)
return false;
// 检查所有Hediff寻找HediffComp_Invulnerable
foreach (Hediff hediff in pawn.health.hediffSet.hediffs)
{
if (hediff.TryGetComp<HediffComp_Invulnerable>() is HediffComp_Invulnerable comp)
{
invulnerableComp = comp;
return comp.ShouldBlockDamage(dinfo);
}
}
return false;
}
/// <summary>
/// 显示免疫效果
/// </summary>
private static void ShowImmuneEffect(Pawn pawn, DamageInfo dinfo)
{
if (!pawn.Spawned)
return;
// 显示文字效果
Vector3 textPos = pawn.DrawPos + new Vector3(0, 0, 1f);
MoteMaker.ThrowText(textPos, pawn.Map, "DD_ImmuneToDamage".Translate(), Color.green, 2.5f);
// 显示粒子效果
if (ImmuneMoteDef != null)
{
MoteMaker.MakeStaticMote(pawn.DrawPos, pawn.Map, ImmuneMoteDef, 1f);
}
}
/// <summary>
/// 播放免疫音效
/// </summary>
private static void PlayImmuneSound(Pawn pawn)
{
if (!pawn.Spawned)
return;
if (ImmuneSoundDef != null)
{
ImmuneSoundDef.PlayOneShot(new TargetInfo(pawn.Position, pawn.Map));
}
}
/// <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();
}
}
}

View File

@@ -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; // 出错时继续执行原始方法
}
}
}
}

View File

@@ -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; // 执行原方法
}
}
}

View File

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

View File

@@ -0,0 +1,166 @@
// File: DDmechunit_Fixed.cs
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI.Group;
namespace WulaFallenEmpire
{
public class Wulamechunit : Pawn, IThingHolder
{
public override void DrawExtraSelectionOverlays()
{
base.DrawExtraSelectionOverlays();
pather.curPath?.DrawPath(this);
jobs.DrawLinesBetweenTargets();
}
public override IEnumerable<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中自动保存
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
});

View File

@@ -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是否需要维护

View File

@@ -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;
}
}
}
}

View File

@@ -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; // 回退到最后一个
}
}
}

View File

@@ -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";
}
}
}

View File

@@ -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);
}
}
}

View 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);
}
}
}

View 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();
// 每60ticks1秒消耗一次燃料
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.DD_RefuelMech, parent, fuel);
job.count = GetFuelCountToFullyRefuel();
job.playerForced = true;
bestColonist.jobs.StartJob(job, JobCondition.InterruptForced, null, resumeCurJobAfterwards: true);
// 显示消息
Messages.Message("DD_OrderedRefuel".Translate(bestColonist.LabelShort, parent.LabelShort),
parent, MessageTypeDefOf.PositiveEvent);
}
private Pawn FindBestColonistForRefuel()
{
Map map = parent.Map;
if (map == null)
return null;
// 寻找所有可用的殖民者
List<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;
}
}
}

View File

@@ -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}";
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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}";
}
}
}

View File

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

View File

@@ -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.DD_RepairMech, parent);
job.playerForced = true;
bestColonist.jobs.StartJob(job, JobCondition.InterruptForced, null, resumeCurJobAfterwards: true);
// 显示消息
Messages.Message("DD_OrderedRepair".Translate(bestColonist.LabelShort, parent.LabelShort),
parent, MessageTypeDefOf.PositiveEvent);
}
private Pawn FindBestColonistForRepair()
{
Map map = parent.Map;
if (map == null)
return null;
// 寻找所有可用的殖民者
List<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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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}";
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using Verse;
using Verse.AI;
using RimWorld;
using UnityEngine;
namespace WulaFallenEmpire
{
public class CompProperties_MultiTurretGun : CompProperties_TurretGun
{
public int ID;
public CompProperties_MultiTurretGun()
{
compClass = typeof(Comp_MultiTurretGun);
}
}
public class Comp_MultiTurretGun : CompTurretGun
{
private bool fireAtWill = true;
public new CompProperties_MultiTurretGun Props => (CompProperties_MultiTurretGun)props;
public override void CompTick()
{
base.CompTick();
if (!currentTarget.IsValid && burstCooldownTicksLeft <= 0)
{
// 在其他情况下没有目标且冷却结束时也回正
curRotation = parent.Rotation.AsAngle + Props.angleOffset;
}
}
private void MakeGun()
{
gun = ThingMaker.MakeThing(Props.turretDef);
UpdateGunVerbs();
}
private void UpdateGunVerbs()
{
List<Verb> allVerbs = gun.TryGetComp<CompEquippable>().AllVerbs;
for (int i = 0; i < allVerbs.Count; i++)
{
Verb verb = allVerbs[i];
verb.caster = parent;
verb.castCompleteCallback = delegate
{
burstCooldownTicksLeft = AttackVerb.verbProps.defaultCooldownTime.SecondsToTicks();
};
}
}
public override void PostExposeData()
{
Scribe_Values.Look(ref burstCooldownTicksLeft, "burstCooldownTicksLeft", 0);
Scribe_Values.Look(ref burstWarmupTicksLeft, "burstWarmupTicksLeft", 0);
Scribe_TargetInfo.Look(ref currentTarget, "currentTarget_" + Props.ID);
Scribe_Deep.Look(ref gun, "gun_" + Props.ID);
Scribe_Values.Look(ref fireAtWill, "fireAtWill", defaultValue: true);
if (Scribe.mode == LoadSaveMode.PostLoadInit)
{
if (gun == null)
{
MakeGun();
}
else
{
UpdateGunVerbs();
}
}
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}
};

View File

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

View File

@@ -34,6 +34,41 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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_RequiresNonHostility\CompAbilityEffect_RequiresNonHostility.cs" />
<Compile Include="Ability\WULA_ResearchPrereq\CompAbilityEffect_ResearchPrereq.cs" />
<Compile Include="BuildingComp\WULA_PathCostUpdater\CompPathCostUpdater.cs" />
<Compile Include="Building\Building_ExtraGraphics.cs" />
<Compile Include="Building\Building_MapObserver.cs" />
<Compile Include="Building\Building_TurretGunHasSpeed.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="Pawn\Mechunit.cs" />
<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" />
<Reference Include="0Harmony">
<HintPath>..\..\..\..\..\..\workshop\content\294100\2009463077\1.5\Assemblies\0Harmony.dll</HintPath>
<Private>False</Private>
@@ -82,7 +117,398 @@
<HintPath>..\..\..\..\..\..\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.ScreenCaptureModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Compile Include="**\*.cs" Exclude="bin\**;obj\**" />
<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_PullTarget\CompAbilityEffect_PullTarget.cs" />
<Compile Include="Ability\WULA_PullTarget\CompProperties_AbilityPullTarget.cs" />
<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_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" />
<Compile Include="Damage\DamageDefExtension_TerrainCover.cs" />
<Compile Include="Damage\DamageDef_ExtraDamageExtension.cs" />
<Compile Include="Damage\DamageWorker_ExplosionWithTerrain.cs" />
<Compile Include="Damage\DamageWorker_ExtraDamage.cs" />
<Compile Include="Designator\Designator_CallSkyfallerInArea.cs" />
<Compile Include="Designator\Designator_TeleportArrival.cs" />
<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" />
<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="Flyover\WULA_SpawnFlyOver\CompAbilityEffect_SpawnFlyOver.cs" />
<Compile Include="Flyover\WULA_SpawnFlyOver\CompProperties_AbilitySpawnFlyOver.cs" />
<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" />
<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" />
<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_DamageResponse.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" />
<Compile Include="Job\JobDriver_InspectBuilding.cs" />
<Compile Include="Job\JobGiver_InspectBuilding.cs" />
<Compile Include="PawnsArrivalMode\PawnsArrivalModeWorker_EdgeTeleport.cs" />
<Compile Include="Pawn\Comp_MultiTurretGun.cs" />
<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" />
<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" />
<Compile Include="Placeworker\CompProperties_CustomRadius.cs" />
<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" />
<Compile Include="Properties\AssemblyInfo.cs" />
<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" />
<Compile Include="SectionLayer_WulaHull.cs" />
<Compile Include="Stat\StatWorker_Energy.cs" />
<Compile Include="Stat\StatWorker_Maintenance.cs" />
<Compile Include="Stat\StatWorker_NanoRepair.cs" />
<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" />
<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" />
<Compile Include="Utils\BezierUtil.cs" />
<Compile Include="Utils\DefInjectedExportUtility.cs" />
<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" />
<Compile Include="WorkGiver\WorkGiver_DeepDrill_WulaConstructor.cs" />
<Compile Include="WulaDefOf.cs" />
<Compile Include="WulaFallenEmpireMod.cs" />
<Compile Include="WulaFallenEmpireSettings.cs" />
<Compile Include="WulaLog.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 自定义清理任务删除obj文件夹中的临时文件 -->