This commit is contained in:
2025-08-19 16:17:16 +08:00
parent 4205273197
commit 0b2efcd559
9 changed files with 491 additions and 14 deletions

View File

@@ -0,0 +1,211 @@
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
{
private bool isAutoSpawning;
#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);
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
if (!respawningAfterLoad)
{
isAutoSpawning = AutoProps.startsAsAutoSpawn;
}
}
public override void PostExposeData()
{
base.PostExposeData();
Scribe_Values.Look(ref isAutoSpawning, "isAutoSpawning", AutoProps.startsAsAutoSpawn);
}
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();
if (!AutoProps.freeProduction && InnerContainer.TotalStackCountOfDef(Props.fixedIngredient) < Props.costPerPawn)
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)
{
int costLeft = 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;
}
}
CooldownTicksRemaining = 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 (isAutoSpawning && parent.IsHashIntervalTick(60)) // 每秒检查一次
{
if (CooldownTicksRemaining > 0) return;
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
foreach (var gizmo in base.CompGetGizmosExtra())
{
// 通过图标来稳定地识别目标按钮
if (gizmo is Command_ActionWithCooldown command && command.icon == ContentFinder<Texture2D>.Get("UI/Gizmos/ReleaseWarUrchins"))
{
// 我们只改造这个按钮,其他按钮原样返回
var modifiedCommand = new Command_ActionWithCooldown
{
// 保留冷却进度条的逻辑
cooldownPercentGetter = command.cooldownPercentGetter,
// 保留原版图标
icon = command.icon,
// 修改功能为切换自动生产
action = () => { isAutoSpawning = !isAutoSpawning; },
// 修改标签和描述
defaultLabel = "WULA_AutoSpawn_Label".Translate(),
defaultDesc = "WULA_AutoSpawn_Desc".Translate()
};
// 如果自动生产开启,则禁用按钮并显示红叉
if (isAutoSpawning)
{
modifiedCommand.Disable("WULA_AutoSpawn_On_Reason".Translate());
}
yield return modifiedCommand;
}
else
{
// 其他按钮(如开发者按钮)原样返回
yield return 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

@@ -0,0 +1,54 @@
using RimWorld;
using Verse;
using System.Collections.Generic;
namespace WulaFallenEmpire
{
public class CompProperties_AutoMechCarrier : CompProperties_MechCarrier
{
// XML中定义初始是否为自动生产模式
public bool startsAsAutoSpawn = false;
// XML中定义生产是否消耗资源
public bool freeProduction = false;
// 定义生产队列
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

@@ -0,0 +1,17 @@
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;
}
}

View File

@@ -1,13 +0,0 @@
using Verse;
namespace WulaFallenEmpire
{
[StaticConstructorOnStartup]
public static class StartupLogger
{
static StartupLogger()
{
Log.Message("WulaFallenEmpire Mod DLL, version 1.0.2, has been loaded.");
}
}
}

View File

@@ -70,6 +70,9 @@
<ItemGroup>
<Compile Include="CompChargingBed.cs" />
<Compile Include="CompCleave.cs" />
<Compile Include="CompAutoMechCarrier.cs" />
<Compile Include="CompProperties_AutoMechCarrier.cs" />
<Compile Include="PawnProductionEntry.cs" />
<Compile Include="Building_Wula_DarkEnergy_Engine.cs" />
<Compile Include="CompApparelInterceptor.cs" />
<Compile Include="CompCustomUniqueWeapon.cs" />
@@ -154,7 +157,6 @@
<Compile Include="HediffComp_DamageResponse.cs" />
<Compile Include="JobDefOf_WULA.cs" />
<Compile Include="ThingDefOf_WULA.cs" />
<Compile Include="StartupLogger.cs" />
<Compile Include="WeaponSwitch.cs" />
<Compile Include="Verb\CompMultiStrike.cs" />
<Compile Include="Verb\Verb_MeleeAttack_MultiStrike.cs" />

View File

@@ -19,4 +19,13 @@ namespace WulaFallenEmpire
}
}
[StaticConstructorOnStartup]
public static class StartupLogger
{
static StartupLogger()
{
Log.Message("WulaFallenEmpire Mod DLL, version 1.0.2, has been loaded.");
}
}
}