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

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