This commit is contained in:
2026-02-25 17:30:59 +08:00
parent 0509f26c3c
commit fff40b0edb
70 changed files with 3951 additions and 1219 deletions

View File

@@ -63,7 +63,7 @@ namespace WulaFallenEmpire
var pilotComp = this.TryGetComp<CompMechPilotHolder>();
if (pilotComp != null && !pilotComp.HasPilots)
{
Messages.Message("DD_CannotDraftWithoutPilot".Translate(this.LabelShort),
Messages.Message("WULA_CannotDraftWithoutPilot".Translate(this.LabelShort),
this, MessageTypeDefOf.RejectInput);
return;
}
@@ -101,7 +101,7 @@ namespace WulaFallenEmpire
var pilotComp = this.TryGetComp<CompMechPilotHolder>();
if (pilotComp != null && !pilotComp.HasPilots)
{
command_Toggle.Disable("DD_NoPilot".Translate());
command_Toggle.Disable("WULA_NoPilot".Translate());
}
command_Toggle.tutorTag = ((!base.Drafted) ? "Draft" : "Undraft");

View File

@@ -1,195 +0,0 @@
using RimWorld;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Verse;
using Verse.AI.Group;
namespace WulaFallenEmpire
{
public class CompAutoMechCarrier : CompMechCarrier
{
#region Reflected Fields
private static FieldInfo spawnedPawnsField;
private static FieldInfo cooldownTicksRemainingField;
private static FieldInfo innerContainerField;
private List<Pawn> SpawnedPawns
{
get
{
if (spawnedPawnsField == null)
spawnedPawnsField = typeof(CompMechCarrier).GetField("spawnedPawns", BindingFlags.NonPublic | BindingFlags.Instance);
return (List<Pawn>)spawnedPawnsField.GetValue(this);
}
}
private int CooldownTicksRemaining
{
get
{
if (cooldownTicksRemainingField == null)
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
return (int)cooldownTicksRemainingField.GetValue(this);
}
set
{
if (cooldownTicksRemainingField == null)
cooldownTicksRemainingField = typeof(CompMechCarrier).GetField("cooldownTicksRemaining", BindingFlags.NonPublic | BindingFlags.Instance);
cooldownTicksRemainingField.SetValue(this, value);
}
}
private ThingOwner InnerContainer
{
get
{
if (innerContainerField == null)
innerContainerField = typeof(CompMechCarrier).GetField("innerContainer", BindingFlags.NonPublic | BindingFlags.Instance);
return (ThingOwner)innerContainerField.GetValue(this);
}
}
#endregion
public CompProperties_AutoMechCarrier AutoProps => (CompProperties_AutoMechCarrier)props;
private int TotalPawnCapacity => AutoProps.productionQueue.Sum(e => e.count);
private int LiveSpawnedPawnsCount(PawnKindDef kind)
{
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed);
return SpawnedPawns.Count(p => p.kindDef == kind);
}
private AcceptanceReport CanSpawnNow(PawnKindDef kind)
{
if (parent is Pawn pawn && (pawn.IsSelfShutdown() || !pawn.Awake() || pawn.Downed || pawn.Dead || !pawn.Spawned))
return false;
if (CooldownTicksRemaining > 0)
return "CooldownTime".Translate() + " " + CooldownTicksRemaining.ToStringSecondsFromTicks();
PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
int cost = entry.cost ?? Props.costPerPawn;
if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < cost)
return "MechCarrierNotEnoughResources".Translate();
return true;
}
private void TrySpawnPawn(PawnKindDef kind)
{
PawnGenerationRequest request = new PawnGenerationRequest(kind, parent.Faction, PawnGenerationContext.NonPlayer, -1, forceGenerateNewPawn: true);
Pawn pawn = PawnGenerator.GeneratePawn(request);
GenSpawn.Spawn(pawn, parent.Position, parent.Map);
SpawnedPawns.Add(pawn);
if (parent is Pawn p && p.GetLord() != null)
p.GetLord().AddPawn(pawn);
if (!AutoProps.freeProduction)
{
PawnProductionEntry entry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
int costLeft = entry.cost ?? Props.costPerPawn;
List<Thing> things = new List<Thing>(InnerContainer);
for (int j = 0; j < things.Count; j++)
{
Thing thing = InnerContainer.Take(things[j], Mathf.Min(things[j].stackCount, costLeft));
costLeft -= thing.stackCount;
thing.Destroy();
if (costLeft <= 0) break;
}
}
PawnProductionEntry spawnEntry = AutoProps.productionQueue.First(e => e.pawnKind == kind);
CooldownTicksRemaining = spawnEntry.cooldownTicks ?? Props.cooldownTicks;
if (Props.spawnedMechEffecter != null)
EffecterTrigger(Props.spawnedMechEffecter, Props.attachSpawnedMechEffecter, pawn);
if (Props.spawnEffecter != null)
EffecterTrigger(Props.spawnEffecter, Props.attachSpawnedEffecter, parent);
}
private void EffecterTrigger(EffecterDef effecterDef, bool attach, Thing target)
{
Effecter effecter = new Effecter(effecterDef);
effecter.Trigger(attach ? ((TargetInfo)target) : new TargetInfo(target.Position, target.Map), TargetInfo.Invalid);
effecter.Cleanup();
}
public override void CompTick()
{
base.CompTick();
if (parent.IsHashIntervalTick(60)) // 每秒检查一次
{
// 检查是否有抑制生产的Hediff
if (AutoProps.disableHediff != null && (parent as Pawn)?.health.hediffSet.HasHediff(AutoProps.disableHediff) == true)
{
return; // 有Hediff停止生产
}
// 1. 先检查是否满员
bool isFull = true;
foreach (var entry in AutoProps.productionQueue)
{
if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count)
{
isFull = false;
break;
}
}
if (isFull)
{
return; // 如果已满员,则不进行任何操作,包括冷却计时
}
// 2. 如果未满员,才检查冷却时间
if (CooldownTicksRemaining > 0) return;
// 3. 寻找空位并生产
foreach (var entry in AutoProps.productionQueue)
{
if (LiveSpawnedPawnsCount(entry.pawnKind) < entry.count)
{
if (CanSpawnNow(entry.pawnKind).Accepted)
{
TrySpawnPawn(entry.pawnKind);
break; // 每次只生产一个
}
}
}
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
// 移除所有Gizmo逻辑
return Enumerable.Empty<Gizmo>();
}
public override string CompInspectStringExtra()
{
SpawnedPawns.RemoveAll(p => p == null || p.Destroyed);
string text = "Pawns: " + SpawnedPawns.Count + " / " + TotalPawnCapacity;
foreach (var entry in AutoProps.productionQueue)
{
text += $"\n- {entry.pawnKind.LabelCap}: {LiveSpawnedPawnsCount(entry.pawnKind)} / {entry.count}";
}
if (CooldownTicksRemaining > 0)
{
text += "\n" + "CooldownTime".Translate() + ": " + CooldownTicksRemaining.ToStringSecondsFromTicks();
}
if (!AutoProps.freeProduction)
{
text += "\n" + base.CompInspectStringExtra();
}
return text;
}
}
}

View File

@@ -1,54 +0,0 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public class CompProperties_AutoMechCarrier : CompProperties_MechCarrier
{
// XML中定义生产是否消耗资源
public bool freeProduction = false;
// 如果单位拥有这个Hediff则停止生产
public HediffDef disableHediff;
// 定义生产队列
public List<PawnProductionEntry> productionQueue = new List<PawnProductionEntry>();
public CompProperties_AutoMechCarrier()
{
// 确保这个属性类指向我们新的功能实现类
compClass = typeof(CompAutoMechCarrier);
}
public override IEnumerable<string> ConfigErrors(ThingDef parentDef)
{
foreach (string error in base.ConfigErrors(parentDef))
{
yield return error;
}
if (productionQueue.NullOrEmpty())
{
yield return "CompProperties_AutoMechCarrier must have at least one entry in productionQueue.";
}
}
public override void ResolveReferences(ThingDef parentDef)
{
base.ResolveReferences(parentDef);
// Prevent division by zero if costPerPawn is not set, which the base game AI might try to access.
if (costPerPawn <= 0)
{
costPerPawn = 1;
}
// 如果spawnPawnKind为空因为我们用了新的队列系统
// 就从队列里取第一个作为“假”值以防止基类方法在生成Gizmo标签时出错。
if (spawnPawnKind == null && !productionQueue.NullOrEmpty())
{
spawnPawnKind = productionQueue[0].pawnKind;
}
}
}
}

View File

@@ -1,23 +0,0 @@
using Verse;
namespace WulaFallenEmpire
{
/// <summary>
/// A data class to hold information about a pawn to be produced in a queue.
/// Used in XML definitions.
/// </summary>
public class PawnProductionEntry
{
// The PawnKindDef of the unit to spawn.
public PawnKindDef pawnKind;
// The maximum number of this kind of unit to maintain.
public int count = 1;
// Optional: specific cooldown for this entry. If not set, the parent comp's cooldown is used.
public int? cooldownTicks;
// Optional: specific cost for this entry. If not set, the parent comp's costPerPawn is used.
public int? cost;
}
}

View File

@@ -1,392 +0,0 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
// 自定义条件节点:检查是否处于自主工作模式
public class ThinkNode_ConditionalAutonomousMech : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Downed)
return false;
// 检查是否被征召
if (pawn.Drafted)
return false;
// 检查是否有机械师控制
if (pawn.GetOverseer() != null)
return false;
// 检查是否有自主能力
var comp = pawn.GetComp<CompAutonomousMech>();
if (comp == null || !comp.CanWorkAutonomously)
return false;
return true;
}
}
public class CompProperties_AutonomousMech : CompProperties
{
public bool enableAutonomousDrafting = true;
public bool enableAutonomousWork = true;
public bool requirePowerForAutonomy = true;
public bool suppressUncontrolledWarning = true;
// 保留能量管理设置供 ThinkNode 使用
public float lowEnergyThreshold = 0.3f; // 低能量阈值
public float criticalEnergyThreshold = 0.1f; // 临界能量阈值
public float rechargeCompleteThreshold = 0.9f; // 充电完成阈值
public MechWorkModeDef initialWorkMode;
public CompProperties_AutonomousMech()
{
compClass = typeof(CompAutonomousMech);
}
}
public class CompAutonomousMech : ThingComp
{
public CompProperties_AutonomousMech Props => (CompProperties_AutonomousMech)props;
public Pawn MechPawn => parent as Pawn;
private MechWorkModeDef currentWorkMode;
public bool CanBeAutonomous
{
get
{
if (MechPawn == null || MechPawn.Dead )
return false;
if (!Props.enableAutonomousDrafting)
return false;
if (MechPawn.GetOverseer() != null)
return false;
return true;
}
}
public bool CanWorkAutonomously
{
get
{
if (!Props.enableAutonomousWork)
return false;
if (!CanBeAutonomous)
return false;
if (MechPawn.Drafted)
return false;
return true;
}
}
public bool ShouldSuppressUncontrolledWarning
{
get
{
if (!Props.suppressUncontrolledWarning)
return false;
return CanBeAutonomous;
}
}
public bool IsInCombatMode
{
get
{
if (MechPawn == null || MechPawn.Dead || MechPawn.Downed)
return false;
// 被征召或处于自主战斗模式
return MechPawn.Drafted || (CanFightAutonomously && MechPawn.mindState?.duty?.def == DutyDefOf.AssaultColony);
}
}
// 在 CompAutonomousMech 类中添加这个新属性
public bool CanFightAutonomously
{
get
{
if (MechPawn == null || MechPawn.Dead || MechPawn.Downed)
return false;
if (!Props.enableAutonomousDrafting)
return false;
if (MechPawn.GetOverseer() != null)
return false;
if (!MechPawn.drafter?.Drafted == true)
return false;
if (Props.requirePowerForAutonomy)
{
if (GetEnergyLevel() < Props.criticalEnergyThreshold)
return false;
}
return true;
}
}
public MechWorkModeDef CurrentWorkMode => currentWorkMode;
// 新增:能量状态检查方法
public float GetEnergyLevel()
{
var energyNeed = MechPawn.needs?.TryGetNeed<Need_MechEnergy>();
return energyNeed?.CurLevelPercentage ?? 0f;
}
public bool IsLowEnergy => GetEnergyLevel() < Props.lowEnergyThreshold;
public bool IsCriticalEnergy => GetEnergyLevel() < Props.criticalEnergyThreshold;
public bool IsFullyCharged => GetEnergyLevel() >= Props.rechargeCompleteThreshold;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (currentWorkMode == null)
{
currentWorkMode = Props.initialWorkMode ?? MechWorkModeDefOf.Work;
}
// 确保使用独立战斗系统
InitializeAutonomousCombat();
}
private void InitializeAutonomousCombat()
{
// 确保有 draftController
if (MechPawn.drafter == null)
{
MechPawn.drafter = new Pawn_DraftController(MechPawn);
}
// 强制启用 FireAtWill
if (MechPawn.drafter != null)
{
MechPawn.drafter.FireAtWill = true;
}
}
public override void CompTick()
{
base.CompTick();
// 每60 tick检查一次能量状态
if (MechPawn != null && MechPawn.IsColonyMech && Find.TickManager.TicksGame % 60 == 0)
{
// 删除了自动切换模式的 CheckEnergyStatus 调用
EnsureWorkSettings();
}
}
// 删除了整个 CheckEnergyStatus 方法,因为充电逻辑在 ThinkNode 中处理
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
if (MechPawn == null || !CanBeAutonomous)
yield break;
// 工作模式切换按钮
if (CanWorkAutonomously)
{
DroneGizmo droneGizmo = new DroneGizmo(this);
if (droneGizmo != null)
{
yield return droneGizmo;
}
}
// 更换武器按钮 - 确保不返回null
Gizmo weaponSwitchGizmo = CreateWeaponSwitchGizmo();
if (weaponSwitchGizmo != null)
{
yield return weaponSwitchGizmo;
}
}
/// <summary>
/// 创建更换武器的Gizmo
/// </summary>
private Gizmo CreateWeaponSwitchGizmo()
{
try
{
// 检查Pawn是否属于玩家派系
if (MechPawn?.Faction != Faction.OfPlayer)
{
return null; // 非玩家派系时不显示
}
// 检查Pawn是否有效
if (MechPawn == null || MechPawn.Dead || MechPawn.Destroyed)
{
return null;
}
// 检查equipment是否有效
if (MechPawn.equipment == null)
{
return null;
}
Command_Action switchWeaponCommand = new Command_Action
{
defaultLabel = "WULA_SwitchWeapon".Translate(),
defaultDesc = "WULA_SwitchWeapon_Desc".Translate(),
icon = ContentFinder<Texture2D>.Get("Wula/UI/Abilities/WULA_WeaponSwitchAbility", false) ?? BaseContent.BadTex,
action = SwitchWeapon,
};
// 确保Command不为null
if (switchWeaponCommand == null)
{
WulaLog.Debug($"Failed to create weapon switch gizmo for {MechPawn?.LabelCap}");
return null;
}
return switchWeaponCommand;
}
catch (System.Exception ex)
{
WulaLog.Debug($"Error creating weapon switch gizmo: {ex}");
return null;
}
}
/// <summary>
/// 更换武器逻辑
/// </summary>
private void SwitchWeapon()
{
if (MechPawn == null || MechPawn.Destroyed || !MechPawn.Spawned)
return;
try
{
// 1. 扔掉当前武器
ThingWithComps currentWeapon = MechPawn.equipment?.Primary;
if (currentWeapon != null)
{
// 将武器扔在地上
MechPawn.equipment.TryDropEquipment(currentWeapon, out ThingWithComps droppedWeapon, MechPawn.Position, true);
if (Prefs.DevMode)
{
WulaLog.Debug($"[CompAutonomousMech] {MechPawn.LabelCap} dropped weapon: {currentWeapon.LabelCap}");
}
}
// 2. 从PawnKind允许的武器中生成新武器
ThingDef newWeaponDef = GetRandomWeaponFromPawnKind();
if (newWeaponDef != null)
{
// 生成新武器
Thing newWeapon = ThingMaker.MakeThing(newWeaponDef);
if (newWeapon is ThingWithComps newWeaponWithComps)
{
// 使用 AddEquipment 方法装备新武器
MechPawn.equipment.AddEquipment(newWeaponWithComps);
Messages.Message("WULA_WeaponSwitched".Translate(MechPawn.LabelCap, newWeaponDef.LabelCap),
MechPawn, MessageTypeDefOf.PositiveEvent);
if (Prefs.DevMode)
{
WulaLog.Debug($"[CompAutonomousMech] {MechPawn.LabelCap} equipped new weapon: {newWeaponDef.LabelCap}");
}
}
}
else
{
Messages.Message("WULA_NoWeaponAvailable".Translate(MechPawn.LabelCap),
MechPawn, MessageTypeDefOf.NegativeEvent);
}
}
catch (System.Exception ex)
{
WulaLog.Debug($"[CompAutonomousMech] Error switching weapon for {MechPawn?.LabelCap}: {ex}");
}
}
/// <summary>
/// 从PawnKind允许的武器中随机获取一个武器定义
/// </summary>
private ThingDef GetRandomWeaponFromPawnKind()
{
if (MechPawn.kindDef?.weaponTags == null || MechPawn.kindDef.weaponTags.Count == 0)
return null;
// 收集所有匹配的武器
List<ThingDef> availableWeapons = new List<ThingDef>();
foreach (string weaponTag in MechPawn.kindDef.weaponTags)
{
foreach (ThingDef thingDef in DefDatabase<ThingDef>.AllDefs)
{
if (thingDef.IsWeapon && thingDef.weaponTags != null && thingDef.weaponTags.Contains(weaponTag))
{
availableWeapons.Add(thingDef);
}
}
}
if (availableWeapons.Count == 0)
return null;
// 随机选择一个武器
return availableWeapons.RandomElement();
}
public void SetWorkMode(MechWorkModeDef mode)
{
currentWorkMode = mode;
// 清除当前工作,让机械族重新选择符合新模式的工作
if (MechPawn.CurJob != null && MechPawn.CurJob.def != JobDefOf.Wait_Combat)
{
MechPawn.jobs.StopAll();
}
Messages.Message("WULA_SwitchedToMode".Translate(MechPawn.LabelCap, mode.label),
MechPawn, MessageTypeDefOf.NeutralEvent);
}
private void EnsureWorkSettings()
{
if (MechPawn.workSettings == null)
{
MechPawn.workSettings = new Pawn_WorkSettings(MechPawn);
}
}
public string GetAutonomousStatusString()
{
if (!CanBeAutonomous)
return null;
string energyInfo = "WULA_EnergyInfoShort".Translate(GetEnergyLevel().ToStringPercent());
if (MechPawn.Drafted)
return "WULA_Autonomous_Drafted".Translate() + energyInfo;
else
return "WULA_Autonomous_Mode".Translate(currentWorkMode?.label ?? "Unknown") + energyInfo;
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Defs.Look(ref currentWorkMode, "currentWorkMode");
// 删除了 wasLowEnergy 的序列化
}
}
}

View File

@@ -1,115 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public class DroneGizmo : Gizmo
{
private CompAutonomousMech comp;
private HashSet<CompAutonomousMech> groupedComps;
private static readonly Texture2D BarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.34f, 0.42f, 0.43f));
private static readonly Texture2D BarHighlightTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.43f, 0.54f, 0.55f));
private static readonly Texture2D EmptyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.03f, 0.035f, 0.05f));
// private static readonly Texture2D DragBarTex = SolidColorMaterials.NewSolidColorTexture(new Color(0.74f, 0.97f, 0.8f));
// private static bool draggingBar;
public DroneGizmo(CompAutonomousMech comp)
{
this.comp = comp;
}
public override float GetWidth(float maxWidth)
{
return 160f;
}
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(10f);
Widgets.DrawWindowBackground(rect);
string text = "WULA_AutonomousMech".Translate();
Rect rect3 = new Rect(rect2.x, rect2.y, rect2.width, Text.CalcHeight(text, rect2.width) + 8f);
Text.Font = GameFont.Small;
Widgets.Label(rect3, text);
Rect rect4 = new Rect(rect2.x, rect3.yMax, rect2.width, rect2.height - rect3.height);
DraggableBarForGroup(rect4);
Text.Anchor = TextAnchor.MiddleCenter;
string energyText = comp.GetEnergyLevel().ToStringPercent();
Widgets.Label(rect4, energyText);
Text.Anchor = TextAnchor.UpperLeft;
TooltipHandler.TipRegion(rect4, () => "WULA_EnergyInfo".Translate(energyText), Gen.HashCombineInt(comp.GetHashCode(), 34242419));
// Work Mode Button
Rect rect6 = new Rect(rect2.x + rect2.width - 24f, rect2.y, 24f, 24f);
if (Widgets.ButtonImageFitted(rect6, comp.CurrentWorkMode?.uiIcon ?? BaseContent.BadTex))
{
Find.WindowStack.Add(new FloatMenu(GetWorkModeOptions(comp, groupedComps).ToList()));
}
TooltipHandler.TipRegion(rect6, "WULA_Switch_Mech_WorkMode".Translate());
Widgets.DrawHighlightIfMouseover(rect6);
return new GizmoResult(GizmoState.Clear);
}
private void DraggableBarForGroup(Rect rect)
{
// We are not actually dragging the energy level, but maybe a threshold?
// For now, just display the energy level.
// If we want to set recharge threshold, we need a property in CompAutonomousMech for that.
// Assuming we want to visualize energy level:
Widgets.FillableBar(rect, comp.GetEnergyLevel(), BarTex, EmptyBarTex, false);
}
public static IEnumerable<FloatMenuOption> GetWorkModeOptions(CompAutonomousMech comp, HashSet<CompAutonomousMech> groupedComps = null)
{
foreach (MechWorkModeDef mode in DefDatabase<MechWorkModeDef>.AllDefs.OrderBy(d => d.uiOrder))
{
yield return new FloatMenuOption(mode.LabelCap, delegate
{
comp.SetWorkMode(mode);
if (groupedComps != null)
{
foreach (CompAutonomousMech groupedComp in groupedComps)
{
groupedComp.SetWorkMode(mode);
}
}
}, mode.uiIcon, Color.white);
}
}
public override bool GroupsWith(Gizmo other)
{
return other is DroneGizmo;
}
public override void MergeWith(Gizmo other)
{
base.MergeWith(other);
if (other is DroneGizmo droneGizmo)
{
if (groupedComps == null)
{
groupedComps = new HashSet<CompAutonomousMech>();
}
groupedComps.Add(droneGizmo.comp);
if (droneGizmo.groupedComps != null)
{
groupedComps.AddRange(droneGizmo.groupedComps);
}
}
}
}
}

View File

@@ -1,20 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class JobGiver_DroneSelfShutdown : ThinkNode_JobGiver
{
protected override Job TryGiveJob(Pawn pawn)
{
if (!RCellFinder.TryFindNearbyMechSelfShutdownSpot(pawn.Position, pawn, pawn.Map, out var result, allowForbidden: true))
{
result = pawn.Position;
}
Job job = JobMaker.MakeJob(JobDefOf.SelfShutdown, result);
job.forceSleep = true;
return job;
}
}
}

View File

@@ -1,42 +0,0 @@
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public class PawnColumnWorker_DroneEnergy : PawnColumnWorker
{
private const int Width = 120;
private const int BarPadding = 4;
public static readonly Texture2D EnergyBarTex = SolidColorMaterials.NewSolidColorTexture(new Color32(252, byte.MaxValue, byte.MaxValue, 65));
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
CompAutonomousMech comp = pawn.TryGetComp<CompAutonomousMech>();
if (comp == null || !comp.CanBeAutonomous)
{
return;
}
Widgets.FillableBar(rect.ContractedBy(4f), comp.GetEnergyLevel(), EnergyBarTex, BaseContent.ClearTex, doBorder: false);
Text.Font = GameFont.Small;
Text.Anchor = TextAnchor.MiddleCenter;
Widgets.Label(rect, comp.GetEnergyLevel().ToStringPercent());
Text.Anchor = TextAnchor.UpperLeft;
Text.Font = GameFont.Small;
}
public override int GetMinWidth(PawnTable table)
{
return Mathf.Max(base.GetMinWidth(table), 120);
}
public override int GetMaxWidth(PawnTable table)
{
return Mathf.Min(base.GetMaxWidth(table), GetMinWidth(table));
}
}
}

View File

@@ -1,42 +0,0 @@
using System.Linq;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class PawnColumnWorker_DroneWorkMode : PawnColumnWorker_Icon
{
protected override int Padding => 0;
public override void DoCell(Rect rect, Pawn pawn, PawnTable table)
{
CompAutonomousMech comp = pawn.TryGetComp<CompAutonomousMech>();
if (comp == null || !comp.CanBeAutonomous)
{
return;
}
if (Widgets.ButtonInvisible(rect))
{
Find.WindowStack.Add(new FloatMenu(DroneGizmo.GetWorkModeOptions(comp).ToList()));
}
base.DoCell(rect, pawn, table);
}
protected override Texture2D GetIconFor(Pawn pawn)
{
return pawn?.TryGetComp<CompAutonomousMech>()?.CurrentWorkMode?.uiIcon;
}
protected override string GetIconTip(Pawn pawn)
{
string text = pawn.TryGetComp<CompAutonomousMech>()?.CurrentWorkMode?.description;
if (!text.NullOrEmpty())
{
return text;
}
return null;
}
}
}

View File

@@ -1,36 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalAutonomousWorkMode : ThinkNode_Conditional
{
public MechWorkModeDef requiredMode;
protected override bool Satisfied(Pawn pawn)
{
try
{
if (pawn == null || pawn.Dead || pawn.Downed)
return false;
// 检查是否有自主机械组件
var comp = pawn.GetComp<CompAutonomousMech>();
if (comp == null)
return false;
// 检查当前工作模式是否匹配要求
if (comp.CurrentWorkMode != requiredMode)
return false;
return true;
}
catch (System.Exception ex)
{
WulaLog.Debug($"[WULA] Exception in ThinkNode_ConditionalAutonomousWorkMode.Satisfied for pawn {pawn?.LabelShort}: {ex}");
return false;
}
}
}
}

View File

@@ -1,18 +0,0 @@
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalLowEnergy_Drone : ThinkNode_Conditional
{
protected override bool Satisfied(Pawn pawn)
{
CompAutonomousMech compDrone = pawn.TryGetComp<CompAutonomousMech>();
if (compDrone != null && compDrone.IsLowEnergy)
{
return true;
}
return false;
}
}
}

View File

@@ -1,60 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
// 检查机械族是否需要充电
public class ThinkNode_ConditionalNeedRecharge : ThinkNode_Conditional
{
public float energyThreshold = 0.3f;
protected override bool Satisfied(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Downed)
return false;
var energyNeed = pawn.needs?.TryGetNeed<Need_MechEnergy>();
if (energyNeed == null)
return false;
return energyNeed.CurLevelPercentage < energyThreshold;
}
}
// 检查机械族是否紧急需要充电
public class ThinkNode_ConditionalEmergencyRecharge : ThinkNode_Conditional
{
public float emergencyThreshold = 0.1f;
protected override bool Satisfied(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Downed)
return false;
var energyNeed = pawn.needs?.TryGetNeed<Need_MechEnergy>();
if (energyNeed == null)
return false;
return energyNeed.CurLevelPercentage < emergencyThreshold;
}
}
// 检查机械族是否充满电
public class ThinkNode_ConditionalFullyCharged : ThinkNode_Conditional
{
public float fullChargeThreshold = 0.9f;
protected override bool Satisfied(Pawn pawn)
{
if (pawn == null || pawn.Dead || pawn.Downed)
return false;
var energyNeed = pawn.needs?.TryGetNeed<Need_MechEnergy>();
if (energyNeed == null)
return false;
return energyNeed.CurLevelPercentage >= fullChargeThreshold;
}
}
}

View File

@@ -1,32 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class ThinkNode_ConditionalWorkMode_Drone : ThinkNode_Conditional
{
public MechWorkModeDef workMode;
public override ThinkNode DeepCopy(bool resolve = true)
{
ThinkNode_ConditionalWorkMode_Drone thinkNode_ConditionalWorkMode_Drone = (ThinkNode_ConditionalWorkMode_Drone)base.DeepCopy(resolve);
thinkNode_ConditionalWorkMode_Drone.workMode = workMode;
return thinkNode_ConditionalWorkMode_Drone;
}
protected override bool Satisfied(Pawn pawn)
{
if (!pawn.RaceProps.IsMechanoid || pawn.Faction != Faction.OfPlayer)
{
return false;
}
CompAutonomousMech compDrone = pawn.TryGetComp<CompAutonomousMech>();
if (compDrone == null)
{
return false;
}
return compDrone.CurrentWorkMode == workMode;
}
}
}

View File

@@ -1,26 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
namespace WulaFallenEmpire
{
public class MentalBreakWorker_BrokenPersonality : MentalBreakWorker
{
public override bool TryStart(Pawn pawn, string reason, bool causedByMood)
{
// 先尝试启动精神状态
if (base.TryStart(pawn, reason, causedByMood))
{
// 成功启动后,执行附加逻辑
var extension = def.mentalState.GetModExtension<MentalStateDefExtension_BrokenPersonality>();
if (extension != null && extension.traitToAdd != null && !pawn.story.traits.HasTrait(extension.traitToAdd))
{
pawn.story.traits.GainTrait(new Trait(extension.traitToAdd));
}
return true;
}
return false;
}
}
}

View File

@@ -1,12 +0,0 @@
using RimWorld;
using Verse;
namespace WulaFallenEmpire
{
public class MentalStateDefExtension_BrokenPersonality : DefModExtension
{
public TraitDef traitToAdd;
public FactionDef factionToJoin;
public float skillLevelFactor = 1f;
}
}

View File

@@ -1,93 +0,0 @@
using RimWorld;
using Verse;
using Verse.AI;
using Verse.AI.Group;
namespace WulaFallenEmpire
{
public class MentalState_BrokenPersonality : MentalState
{
public override void PostStart(string reason)
{
base.PostStart(reason);
// 发送信件
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
// 手动实现备用逻辑:如果信件标题(beginLetterLabel)为空,则使用精神状态的通用标签(label)
string labelText = def.beginLetterLabel;
if (string.IsNullOrEmpty(labelText))
{
labelText = def.label;
}
TaggedString letterLabel = labelText.Formatted(pawn.LabelShort, pawn.Named("PAWN")).CapitalizeFirst();
TaggedString letterText = def.beginLetter.Formatted(pawn.LabelShort, pawn.Named("PAWN")).CapitalizeFirst();
if (reason != null)
{
letterText += "\n\n" + reason;
}
Find.LetterStack.ReceiveLetter(letterLabel, letterText, LetterDefOf.ThreatBig, pawn);
}
var extension = def.GetModExtension<MentalStateDefExtension_BrokenPersonality>();
if (extension != null)
{
bool alreadyBroken = pawn.story.traits.HasTrait(extension.traitToAdd);
if (!alreadyBroken)
{
// 移除所有技能热情
foreach (SkillRecord skill in pawn.skills.skills)
{
skill.passion = Passion.None;
}
// 所有技能等级减半
foreach (SkillRecord skill in pawn.skills.skills)
{
int currentLevel = skill.Level;
skill.Level = (int)(currentLevel * extension.skillLevelFactor);
}
}
// 改变派系
Faction newFaction = Find.FactionManager.FirstFactionOfDef(extension.factionToJoin);
if (newFaction == null)
{
newFaction = Find.FactionManager.FirstFactionOfDef(FactionDefOf.AncientsHostile);
}
if (newFaction != null)
{
pawn.SetFaction(newFaction, null);
}
}
// 离开地图
Lord lord = pawn.GetLord();
if (lord == null)
{
LordJob_ExitMapBest lordJob = new LordJob_ExitMapBest(LocomotionUrgency.Jog, canDig: true, canDefendSelf: true);
lord = LordMaker.MakeNewLord(pawn.Faction, lordJob, pawn.Map, Gen.YieldSingle(pawn));
}
else
{
lord.ReceiveMemo("PawnBroken");
}
// 强制恢复以避免状态无限持续
this.forceRecoverAfterTicks = 150;
}
public override void MentalStateTick(int delta)
{
base.MentalStateTick(delta);
// 确保在下一帧就恢复,因为所有效果都已经应用
if (age > 0)
{
RecoverFromState();
}
}
}
}

View File

@@ -1,281 +0,0 @@
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
using Verse.AI.Group;
using Verse.Sound;
namespace WulaFallenEmpire
{
public class CompFighterInvisible : ThingComp
{
public CompProperties_FighterInvisible Props => (CompProperties_FighterInvisible)props;
[Unsaved(false)]
private HediffComp_Invisibility invisibility;
private int lastDetectedTick = -99999;
private int lastRevealedTick = -99999;
private Pawn Sightstealer => (Pawn)parent;
// 新增:记录最后一次检查敌人的时间
private int lastEnemyCheckTick = -99999;
// 新增:记录最后一次发信的时间
private int lastLetterTick = -99999;
public HediffDef GetTargetInvisibilityDef()
{
return Props.InvisibilityDef;
}
// 添加一个属性来检查是否有效
private bool IsValid => Sightstealer?.health?.hediffSet != null &&
GetTargetInvisibilityDef() != null &&
!Sightstealer.IsShambler &&
Sightstealer.Spawned &&
Sightstealer.Map != null;
private HediffComp_Invisibility Invisibility
{
get
{
if (!IsValid) return null;
return invisibility ?? (invisibility = Sightstealer.health.hediffSet
.GetFirstHediffOfDef(GetTargetInvisibilityDef())
?.TryGetComp<HediffComp_Invisibility>());
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref lastDetectedTick, "lastDetectedTick", 0);
Scribe_Values.Look(ref lastRevealedTick, "lastRevealedTick", 0);
Scribe_Values.Look(ref lastEnemyCheckTick, "lastEnemyCheckTick", 0);
Scribe_Values.Look(ref lastLetterTick, "lastLetterTick", 0);
}
public override void CompTick()
{
// 使用统一的有效性检查
if (!IsValid || Invisibility == null) return;
// 检查敌人
CheckForEnemiesInSight();
// 隐身恢复逻辑
if (Sightstealer.IsHashIntervalTick(Props.CheckDetectedIntervalTicks))
{
if (Find.TickManager.TicksGame > lastDetectedTick + Props.stealthCooldownTicks)
{
Invisibility.BecomeInvisible();
}
}
}
public override void Notify_UsedVerb(Pawn pawn, Verb verb)
{
base.Notify_UsedVerb(pawn, verb);
// 统一的 null 检查
if (Invisibility == null) return;
Invisibility.BecomeVisible();
lastDetectedTick = Find.TickManager.TicksGame;
// 触发显现事件
TrySendLetter("attack");
}
/// <summary>
/// 检查视线内是否有敌人
/// </summary>
private void CheckForEnemiesInSight()
{
// 检查频率每30 tick检查一次约0.5秒)
if (!Sightstealer.IsHashIntervalTick(30) ||
Find.TickManager.TicksGame <= lastEnemyCheckTick + 30)
{
return;
}
lastEnemyCheckTick = Find.TickManager.TicksGame;
// 检查视线内是否有敌人
bool enemyInSight = false;
List<Pawn> enemiesInSight = new List<Pawn>();
// 获取地图上所有Pawn
IReadOnlyList<Pawn> allPawns = Sightstealer.Map.mapPawns.AllPawnsSpawned;
foreach (Pawn otherPawn in allPawns)
{
// 跳过自身
if (otherPawn == Sightstealer) continue;
// 跳过自律机械
if (otherPawn.GetComp<CompAutonomousMech>() != null) continue;
// 跳过死亡的
if (otherPawn.Dead) continue;
// 跳过倒地的(如果配置为忽略)
if (Props.ignoreDownedEnemies && otherPawn.Downed) continue;
// 跳过睡着的(如果配置为忽略)
if (Props.ignoreSleepingEnemies && otherPawn.CurJobDef == JobDefOf.LayDown) continue;
// 检查是否为敌对关系
if (!otherPawn.HostileTo(Sightstealer)) continue;
// 检查敌人类型过滤器(如果有)
if (Props.enemyTypeFilter != null && Props.enemyTypeFilter.Count > 0)
{
if (!Props.enemyTypeFilter.Contains(otherPawn.def)) continue;
}
// 关键修改:直接检查直线可见性,不使用距离限制
if (GenSight.LineOfSight(Sightstealer.Position, otherPawn.Position, Sightstealer.Map))
{
enemiesInSight.Add(otherPawn);
enemyInSight = true;
// 如果只需要知道是否有敌人,且已经找到一个,可以提前退出循环
if (Props.minEnemiesToReveal <= 1)
{
break;
}
}
}
// 如果启用敌人检测后解除隐身,并且发现了足够数量的敌人
if (enemyInSight && Props.revealOnEnemyInSight && enemiesInSight.Count >= Props.minEnemiesToReveal)
{
// 立即解除隐身
Invisibility.BecomeVisible();
lastDetectedTick = Find.TickManager.TicksGame;
lastRevealedTick = Find.TickManager.TicksGame;
// 触发显现事件
TrySendLetter("detected", enemiesInSight);
}
}
/// <summary>
/// 尝试发送信件
/// </summary>
private void TrySendLetter(string cause, List<Pawn> enemies = null)
{
// 检查是否应该发送信件
if (!ShouldSendLetter())
return;
// 发送信件
SendLetter(cause, enemies);
}
/// <summary>
/// 检查是否应该发送信件
/// </summary>
private bool ShouldSendLetter()
{
// 如果配置为不发信直接返回false
if (!Props.sendLetterOnReveal)
return false;
// 检查发送间隔
int currentTick = Find.TickManager.TicksGame;
if (currentTick < lastLetterTick + Props.letterIntervalTicks)
{
// 还没到发送间隔
return false;
}
// 检查Pawn是否非玩家控制
if (Sightstealer.Faction == Faction.OfPlayer)
{
// 玩家控制的Pawn不发送信件
return false;
}
return true;
}
/// <summary>
/// 发送信件
/// </summary>
private void SendLetter(string cause, List<Pawn> enemies = null)
{
try
{
int currentTick = Find.TickManager.TicksGame;
// 获取信件标题和内容
string title = Props.letterTitle;
string text = Props.letterText;
// 如果标题或内容为空,使用默认值
if (string.IsNullOrEmpty(title))
title = "隐身单位现身";
if (string.IsNullOrEmpty(text))
{
string enemyInfo = "";
if (enemies != null && enemies.Count > 0)
{
if (enemies.Count == 1)
{
enemyInfo = $"被 {enemies[0].LabelCap} 发现";
}
else
{
enemyInfo = $"被 {enemies.Count} 个敌人发现";
}
}
else if (cause == "attack")
{
enemyInfo = "发动了攻击";
}
text = $"{Sightstealer.LabelCap}{Sightstealer.Faction?.Name ?? ""})在 {Sightstealer.Map?.Parent?.LabelCap ?? ""} 现身了。\n\n{enemyInfo}\n位置{Sightstealer.Position}";
}
// 发送信件
Letter letter = LetterMaker.MakeLetter(
title,
text,
LetterDefOf.NeutralEvent,
new LookTargets(Sightstealer)
);
Find.LetterStack.ReceiveLetter(letter);
// 更新最后发信时间
lastLetterTick = currentTick;
}
catch (System.Exception ex)
{
WulaLog.Debug($"CompFighterInvisible: Error sending letter for {Sightstealer?.LabelCap}: {ex}");
}
}
// ... 其他方法保持不变 ...
/// <summary>
/// 手动触发解除隐身(供外部调用)
/// </summary>
public void ForceReveal()
{
if (Invisibility == null) return;
Invisibility.BecomeVisible();
lastDetectedTick = Find.TickManager.TicksGame;
lastRevealedTick = Find.TickManager.TicksGame;
// 尝试发送信件
TrySendLetter("manual");
}
// ... 其他方法保持不变 ...
}
}

View File

@@ -1,80 +0,0 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_FighterInvisible : CompProperties
{
public float BaseVisibleRadius = 14f;
public int UndetectedTimeout = 120;
public int CheckDetectedIntervalTicks = 7;
public float FirstDetectedRadius = 30f;
public int RevealedLetterDelayTicks = 6;
public int AmbushCallMTBTicks = 600;
// 修改一个可定义的提供隐身的hediff
public HediffDef InvisibilityDef;
// 隐身冷却
public int stealthCooldownTicks = 1200;
// 新增:是否在视线内出现敌人时解除隐身
public bool revealOnEnemyInSight = false;
// 新增解除隐身的检测半径默认为FirstDetectedRadius
public float revealDetectionRadius = 500f;
// 新增:是否显示解除隐身效果
public bool showRevealEffect = true;
// 新增:解除隐身时的声音
public SoundDef revealSound;
// 新增:是否发送解除隐身消息
public bool sendRevealMessage = false;
// 新增解除隐身的最小敌人数量默认为1
public int minEnemiesToReveal = 1;
// 新增:敌人类型过滤器(空表示所有敌人)
public List<ThingDef> enemyTypeFilter;
// 新增:是否只在战斗状态时检查敌人
public bool onlyCheckInCombat = false;
// 新增:是否忽略某些状态的敌人(如倒地、死亡等)
public bool ignoreDownedEnemies = true;
public bool ignoreSleepingEnemies = false;
// 新增:简化发信配置
public bool sendLetterOnReveal = false;
public string letterTitle = "";
public string letterText = "";
public int letterIntervalTicks = 1200;
public CompProperties_FighterInvisible()
{
compClass = typeof(CompFighterInvisible);
}
public override IEnumerable<string> ConfigErrors(ThingDef parentDef)
{
foreach (string error in base.ConfigErrors(parentDef))
{
yield return error;
}
if (InvisibilityDef == null)
{
yield return "InvisibilityDef is not defined for CompProperties_FighterInvisible";
}
if (revealDetectionRadius <= 0)
{
revealDetectionRadius = FirstDetectedRadius;
}
}
}
}

View File

@@ -1,70 +0,0 @@
using System;
using System.Collections.Generic;
using Verse;
using RimWorld;
namespace WulaFallenEmpire
{
public class CompHediffGiver : ThingComp
{
private bool hediffsApplied = false; // 新增标记是否已经应用过hediff
public CompProperties_HediffGiver Props => (CompProperties_HediffGiver)this.props;
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
// 只有当thing是pawn时才添加hediff
if (this.parent is Pawn pawn)
{
// 新增检查是否已经应用过hediff或者是否是读档
if (!hediffsApplied && !respawningAfterLoad)
{
AddHediffsToPawn(pawn);
hediffsApplied = true; // 标记为已应用
}
}
}
private void AddHediffsToPawn(Pawn pawn)
{
// 检查是否有hediff列表
if (Props.hediffs == null || Props.hediffs.Count == 0)
return;
// 检查概率
if (Props.addChance < 1.0f && Rand.Value > Props.addChance)
return;
// 为每个hediff添加到pawn
foreach (HediffDef hediffDef in Props.hediffs)
{
// 检查是否允许重复添加
if (!Props.allowDuplicates && pawn.health.hediffSet.HasHediff(hediffDef))
continue;
// 添加hediff
pawn.health.AddHediff(hediffDef);
}
}
// 新增序列化hediffsApplied标记
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref hediffsApplied, "hediffsApplied", false);
}
// 新增调试方法用于手动触发hediff添加仅开发模式
public void DebugApplyHediffs()
{
if (this.parent is Pawn pawn && !hediffsApplied)
{
AddHediffsToPawn(pawn);
hediffsApplied = true;
WulaLog.Debug($"Debug: Applied hediffs to {pawn.Label}");
}
}
}
}

View File

@@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_HediffGiver : CompProperties
{
// 要添加的hediff列表
public List<HediffDef> hediffs;
// 添加hediff的概率0-1之间
public float addChance = 1.0f;
// 是否允许重复添加相同的hediff
public bool allowDuplicates = false;
public CompProperties_HediffGiver()
{
this.compClass = typeof(CompHediffGiver);
}
}
}

View File

@@ -1,14 +0,0 @@
using Verse;
namespace WulaFallenEmpire
{
/// <summary>
/// A marker component that holds custom flight properties.
/// The actual flight logic is handled by Harmony patches that check for this component
/// and use its properties to override or trigger vanilla flight behavior.
/// </summary>
public class CompPawnFlight : ThingComp
{
public CompProperties_PawnFlight Props => (CompProperties_PawnFlight)props;
}
}

View File

@@ -1,55 +0,0 @@
using Verse;
using RimWorld;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public enum FlightCondition
{
Drafted,
MechAlwaysExceptSpecialJobs // 新增:机械族在非特殊工作状态下始终飞行
}
public class CompProperties_PawnFlight : CompProperties
{
// --- Custom Flight Logic ---
public FlightCondition flightCondition = FlightCondition.Drafted;
// --- 新增:机械族特殊工作检查 ---
public List<JobDef> mechForbiddenJobs = new List<JobDef>
{
JobDefOf.MechCharge, // 充电工作
JobDefOf.SelfShutdown // 关机工作
};
// --- Vanilla PawnKindDef Flight Parameters ---
[NoTranslate]
public string flyingAnimationFramePathPrefix;
[NoTranslate]
public string flyingAnimationFramePathPrefixFemale;
public int flyingAnimationFrameCount;
public int flyingAnimationTicksPerFrame = -1;
public float flyingAnimationDrawSize = 1f;
public bool flyingAnimationDrawSizeIsMultiplier;
public bool flyingAnimationInheritColors;
// --- Vanilla PawnKindLifeStage Flight Parameters ---
public AnimationDef flyingAnimationEast;
public AnimationDef flyingAnimationNorth;
public AnimationDef flyingAnimationSouth;
public AnimationDef flyingAnimationEastFemale;
public AnimationDef flyingAnimationNorthFemale;
public AnimationDef flyingAnimationSouthFemale;
public CompProperties_PawnFlight()
{
compClass = typeof(CompPawnFlight);
}
}
}

View File

@@ -1,22 +0,0 @@
using Verse;
using UnityEngine;
namespace WulaFallenEmpire
{
public class PawnRenderNodeWorker_AttachmentBody_NoFlight : PawnRenderNodeWorker_AttachmentBody
{
public override bool CanDrawNow(PawnRenderNode node, PawnDrawParms parms)
{
if (parms.pawn.Flying)
{
return false;
}
return base.CanDrawNow(node, parms);
}
public override Vector3 ScaleFor(PawnRenderNode node, PawnDrawParms parms)
{
return base.ScaleFor(node, parms);
}
}
}

View File

@@ -1,81 +0,0 @@
using HarmonyLib;
using Verse;
using RimWorld;
using Verse.AI;
namespace WulaFallenEmpire
{
[HarmonyPatch]
public static class FlightHarmonyPatches
{
// Corrected Patch 1: The method signature now correctly matches the static target method.
[HarmonyPrefix]
[HarmonyPatch(typeof(Pawn_FlightTracker), "GetBestFlyAnimation")]
public static bool GetBestFlyAnimation_Prefix(Pawn pawn, ref AnimationDef __result) // Correct parameters: Pawn pawn, not __instance and ___pawn
{
var flightComp = pawn?.TryGetComp<CompPawnFlight>();
if (flightComp == null) // No props check needed, as the crash was due to wrong signature
{
return true;
}
var compProps = flightComp.Props;
AnimationDef selectedAnim = null;
if (pawn.gender == Gender.Female && compProps.flyingAnimationNorthFemale != null)
{
switch (pawn.Rotation.AsInt)
{
case 0: selectedAnim = compProps.flyingAnimationNorthFemale; break;
case 1: selectedAnim = compProps.flyingAnimationEastFemale; break;
case 2: selectedAnim = compProps.flyingAnimationSouthFemale; break;
case 3: selectedAnim = compProps.flyingAnimationEastFemale ?? compProps.flyingAnimationEast; break;
}
}
else
{
switch (pawn.Rotation.AsInt)
{
case 0: selectedAnim = compProps.flyingAnimationNorth; break;
case 1: selectedAnim = compProps.flyingAnimationEast; break;
case 2: selectedAnim = compProps.flyingAnimationSouth; break;
case 3: selectedAnim = compProps.flyingAnimationEast; break;
}
}
if (selectedAnim != null)
{
__result = selectedAnim;
return false;
}
return true;
}
// Patch 2 remains correct as Notify_JobStarted is a non-static method.
[HarmonyPrefix]
[HarmonyPatch(typeof(Pawn_FlightTracker), "Notify_JobStarted")]
public static bool Notify_JobStarted_Prefix(Job job, Pawn_FlightTracker __instance, Pawn ___pawn)
{
var flightComp = ___pawn?.TryGetComp<CompPawnFlight>();
if (flightComp == null || __instance == null || !__instance.CanEverFly || ___pawn == null || ___pawn.Dead)
{
return true;
}
var compProps = flightComp.Props;
bool shouldBeFlying = (compProps.flightCondition == FlightCondition.Drafted && ___pawn.Drafted);
if (shouldBeFlying)
{
if (!__instance.Flying) __instance.StartFlying();
job.flying = true;
}
else
{
if (__instance.Flying) __instance.ForceLand();
job.flying = false;
}
return false;
}
}
}

View File

@@ -1,128 +0,0 @@
using System;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using RimWorld;
using UnityEngine;
using Verse;
namespace WulaFallenEmpire
{
public class CompProperties_PawnRenderExtra : CompProperties
{
public CompProperties_PawnRenderExtra()
{
this.compClass = typeof(Comp_PawnRenderExtra);
}
public string path;
public Vector3 size;
public Color colorAlly;
public Color colorEnemy;
public ShaderTypeDef shader;
public DrawData drawData;
}
[StaticConstructorOnStartup]
public class Comp_PawnRenderExtra : ThingComp
{
public CompProperties_PawnRenderExtra Props
{
get
{
return this.props as CompProperties_PawnRenderExtra;
}
}
private Pawn ParentPawn
{
get
{
return this.parent as Pawn;
}
}
public override void PostDraw()
{
base.PostDraw();
if (!this.ParentPawn.Dead && !this.ParentPawn.Downed && this.ParentPawn.CurJobDef != JobDefOf.MechCharge && this.ParentPawn.CurJobDef != JobDefOf.SelfShutdown)
{
this.DrawPawnRenderExtra();
}
}
public void DrawPawnRenderExtra()
{
Vector3 pos = this.ParentPawn.DrawPos;
if (this.ParentPawn.Faction == Faction.OfPlayer || !this.ParentPawn.Faction.HostileTo(Faction.OfPlayer))
{
this.color = this.Props.colorAlly;
}
else
{
this.color = this.Props.colorEnemy;
}
string graphic = this.GetPawnRenderExtra();
Vector3 offset = GetOffsetByRot();
float layer = GetLayerByRot();
pos.y = AltitudeLayer.Pawn.AltitudeFor(layer);
Matrix4x4 matrix = default(Matrix4x4);
matrix.SetTRS(pos + offset, Quaternion.AngleAxis(0f, Vector3.up), this.Props.size);
Material material = MaterialPool.MatFrom(graphic, this.Props.shader.Shader, this.color);
Graphics.DrawMesh(MeshPool.plane10, matrix, material, (int)layer);
}
public Vector3 GetOffsetByRot()
{
Vector3 result;
if (this.Props.drawData != null)
{
result = this.Props.drawData.OffsetForRot(this.ParentPawn.Rotation);
}
else
{
result = Vector3.zero;
}
return result;
}
public float GetLayerByRot()
{
float result;
if (this.Props.drawData != null)
{
result = this.Props.drawData.LayerForRot(this.ParentPawn.Rotation, 0);
}
else
{
result = 0;
}
return result;
}
public string GetPawnRenderExtra()
{
if (this.ParentPawn.Rotation.AsInt == 0)
{
return this.Props.path + "_north";
}
if (this.ParentPawn.Rotation.AsInt == 1)
{
return this.Props.path + "_east";
}
if (this.ParentPawn.Rotation.AsInt == 2)
{
return this.Props.path + "_south";
}
if (this.ParentPawn.Rotation.AsInt == 3)
{
return this.Props.path + "_west";
}
return null;
}
public Color color;
}
}