精华提取茧,蓝图卵和附带的所有修改

This commit is contained in:
Tourswen
2025-10-15 02:41:20 +08:00
parent a9c95142af
commit 417b87498f
41 changed files with 1262 additions and 235 deletions

View File

@@ -1,18 +1,14 @@
{
"Version": 1,
"WorkspaceRootPath": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"WorkspaceRootPath": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|d:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compextraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compinteractiveproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:thing_comps\\ara_compextraincubationinfo\\compproperties_extraincubationinfo.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_buildingterrainspawn\\compdelayedterrainspawn.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
"AbsoluteMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|e:\\steamlibrary\\steamapps\\common\\rimworld\\mods\\arachnaeswarm\\source\\arachnaeswarm\\building_comps\\ara_compinteractiveproducer\\compresearchproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:building_comps\\ara_compinteractiveproducer\\compresearchproducer.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}
],
"DocumentGroupContainers": [
@@ -22,48 +18,37 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 2,
"SelectedChildIndex": 1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "CompProperties_ExtraIncubationInfo.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompProperties_ExtraIncubationInfo.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAABMAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T03:27:00.382Z"
},
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "CompExtraIncubationInfo.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"RelativeDocumentMoniker": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"RelativeToolTip": "Thing_Comps\\ARA_CompExtraIncubationInfo\\CompExtraIncubationInfo.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAA==",
"Title": "CompInteractiveProducer.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompInteractiveProducer.cs",
"ViewState": "AgIAAEMBAAAAAAAAAAAswIQBAABUAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-13T03:26:37.426Z",
"WhenOpened": "2025-10-14T17:41:44.207Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "CompDelayedTerrainSpawn.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"RelativeToolTip": "Building_Comps\\ARA_BuildingTerrainSpawn\\CompDelayedTerrainSpawn.cs",
"ViewState": "AgIAAA4AAAAAAAAAAAAAwD0AAAAhAAAAAAAAAA==",
"DocumentIndex": 1,
"Title": "CompResearchProducer.cs",
"DocumentMoniker": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"RelativeDocumentMoniker": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"ToolTip": "E:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"RelativeToolTip": "Building_Comps\\ARA_CompInteractiveProducer\\CompResearchProducer.cs",
"ViewState": "AgIAACkBAAAAAAAAAAAQwFYBAAApAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2025-10-12T15:30:31.391Z"
"WhenOpened": "2025-10-14T17:02:58.556Z",
"EditorCaption": ""
}
]
}

View File

@@ -100,6 +100,11 @@
<Compile Include="Abilities\ARA_ShowSpawnablePawnsList\CompAbilityEffect_AbilityShowSpawnablePawns.cs" />
<Compile Include="Abilities\ARA_ShowSpawnablePawnsList\CompProperties_AbilityShowSpawnablePawns.cs" />
<Compile Include="Buildings\Building_TurretGunHasSpeed.cs" />
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\Building_RefuelingVat.cs" />
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\CompProperties_RefuelingVat.cs" />
<Compile Include="Building_Comps\ARA_Building_RefuelingVat\CompRefuelingVat.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\CompResearchProducer.cs" />
<Compile Include="Building_Comps\ARA_CompInteractiveProducer\JobDriver_StartResearchProduction.cs" />
<Compile Include="EventSystem\CompOpenCustomUI.cs" />
<Compile Include="EventSystem\Condition.cs" />
<Compile Include="EventSystem\DebugActions.cs" />
@@ -113,6 +118,7 @@
<Compile Include="EventSystem\EventVariableManager.cs" />
<Compile Include="EventSystem\Letter_EventChoice.cs" />
<Compile Include="EventSystem\QuestNode_Root_EventLetter.cs" />
<Compile Include="Jobs\JobDriver_CarryPrisonerToRefuelingVat.cs" />
<Compile Include="Verbs\Verb_ShootWithOffset.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompAbilityEffect_AbilityShowTemperatureRange.cs" />
<Compile Include="Abilities\ARA_ShowTemperatureRange\CompProperties_AbilityShowTemperatureRange.cs" />

View File

@@ -10,9 +10,11 @@ using Verse.AI;
namespace ArachnaeSwarm
{
[StaticConstructorOnStartup]
public class Building_SacrificialFuelTank : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn
public class Building_RefuelingVat : Building_Enterable, IThingHolder, IThingHolderWithDrawnPawn
{
private CompRefuelable refuelableComp;
private CompRefuelable cachedRefuelableComp;
private CompRefuelingVat cachedRefuelingVatComp;
private CompAutoEjector cachedAutoEjectorComp;
private Graphic cachedTopGraphic;
// IThingHolderWithDrawnPawn implementation
@@ -20,10 +22,9 @@ namespace ArachnaeSwarm
public float HeldPawnBodyAngle => base.Rotation.AsAngle;
public PawnPosture HeldPawnPosture => PawnPosture.LayingOnGroundFaceUp;
// 燃料转换配置
private const float DAMAGE_TO_FUEL_RATIO = 0.1f; // 每点伤害转换为0.1燃料
private const float DAMAGE_INTERVAL_TICKS = 250; // 每250ticks造成一次伤害
private const float DAMAGE_PER_INTERVAL = 5f; // 每次间隔造成5点伤害
// 从 Comp 中获取属性
public float FuelProductionPerDay => RefuelingVatComp?.FuelProductionPerDay ?? 10f;
public float FuelProductionEfficiency => RefuelingVatComp?.FuelProductionEfficiency ?? 1f;
private Graphic TopGraphic
{
@@ -31,7 +32,7 @@ namespace ArachnaeSwarm
{
if (cachedTopGraphic == null)
{
var modExtension = def.GetModExtension<DefModExtension_SacrificialFuelTank>();
var modExtension = def.GetModExtension<DefModExtension_NutrientVat>();
if (modExtension != null && !modExtension.topGraphicPath.NullOrEmpty())
{
cachedTopGraphic = GraphicDatabase.Get(modExtension.graphicClass, modExtension.topGraphicPath, ShaderDatabase.Transparent, def.graphicData.drawSize, Color.white, Color.white);
@@ -41,77 +42,110 @@ namespace ArachnaeSwarm
}
}
public override Vector3 PawnDrawOffset => Vector3.zero;
public CompRefuelable RefuelableComp
{
get
{
if (refuelableComp == null)
if (cachedRefuelableComp == null)
{
refuelableComp = this.TryGetComp<CompRefuelable>();
cachedRefuelableComp = this.TryGetComp<CompRefuelable>();
}
return refuelableComp;
return cachedRefuelableComp;
}
}
public CompRefuelingVat RefuelingVatComp
{
get
{
if (cachedRefuelingVatComp == null)
{
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
}
return cachedRefuelingVatComp;
}
}
public override Vector3 PawnDrawOffset => Vector3.zero;
public CompAutoEjector AutoEjectorComp
{
get
{
if (cachedAutoEjectorComp == null)
{
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
}
return cachedAutoEjectorComp;
}
}
public bool HasFuelCapacity => RefuelableComp != null && RefuelableComp.Fuel < RefuelableComp.Props.fuelCapacity;
public bool IsFuelFull => RefuelableComp != null && RefuelableComp.Fuel >= RefuelableComp.Props.fuelCapacity;
// 燃料生产速率每Tick
public float FuelProductionPerTick
{
get
{
if (selectedPawn != null && base.Working)
{
return (FuelProductionPerDay * FuelProductionEfficiency) / 60000f;
}
return 0f;
}
}
// 酸蚀伤害间隔每6000 tick约1/10天
private const int AcidDamageInterval = 6000;
private Dictionary<Pawn, int> pawnTickCounters = new Dictionary<Pawn, int>();
public override void SpawnSetup(Map map, bool respawningAfterLoad)
{
base.SpawnSetup(map, respawningAfterLoad);
refuelableComp = this.TryGetComp<CompRefuelable>();
cachedRefuelableComp = this.TryGetComp<CompRefuelable>();
cachedRefuelingVatComp = this.TryGetComp<CompRefuelingVat>();
cachedAutoEjectorComp = this.TryGetComp<CompAutoEjector>();
}
public override void DeSpawn(DestroyMode mode = DestroyMode.Vanish)
{
if (mode != DestroyMode.WillReplace && selectedPawn != null && innerContainer.Contains(selectedPawn))
if (mode != DestroyMode.WillReplace)
{
Notify_PawnRemoved();
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
{
Notify_PawnRemoved();
}
}
base.DeSpawn(mode);
}
// 造成伤害并转换为燃料
private void ApplySacrificialDamage(Pawn pawn)
private void ApplyAcidDamage(Pawn pawn)
{
try
{
if (pawn.Dead || pawn.Downed) return;
// 选择随机的身体部位
BodyPartRecord targetPart = GetRandomVulnerablePart(pawn);
if (targetPart == null) return;
// 造成伤害
DamageInfo damage = new DamageInfo(
DamageDefOf.Cut, // 使用切割伤害
DAMAGE_PER_INTERVAL,
0.5f, // 中等护甲穿透
-1f,
DamageDef acidDamageDef = DefDatabase<DamageDef>.GetNamed("AcidBurn") ?? DamageDefOf.Burn;
DamageInfo acidDamage = new DamageInfo(
acidDamageDef,
0.1f, // 每次0.1点伤害
2f, // 护甲穿透
-1f, // 随机角度
instigator: null,
hitPart: targetPart
);
float damageDealt = pawn.TakeDamage(damage).TotalDamageDealt;
// 将伤害转换为燃料
if (damageDealt > 0 && RefuelableComp != null)
{
float fuelToAdd = damageDealt * DAMAGE_TO_FUEL_RATIO;
RefuelableComp.Refuel(fuelToAdd);
}
// 如果pawn死亡停止处理
if (pawn.Dead)
{
OnPawnDied();
}
// 应用伤害
pawn.TakeDamage(acidDamage);
}
catch (Exception ex)
{
Log.Error($"Error applying sacrificial damage to {pawn}: {ex.Message}");
Log.Error($"Error applying acid damage to {pawn}: {ex.Message}");
}
}
private BodyPartRecord GetRandomVulnerablePart(Pawn pawn)
{
// 优先选择外部身体部位
@@ -121,11 +155,11 @@ namespace ArachnaeSwarm
part.def.hitPoints > 0)
.ToList();
return vulnerableParts.Count > 0 ? vulnerableParts.RandomElement() : pawn.RaceProps.body.corePart;
return vulnerableParts.Count > 0 ?
vulnerableParts.RandomElement() :
pawn.RaceProps.body.corePart;
}
private int damageTickCounter = 0;
protected override void Tick()
{
base.Tick();
@@ -136,39 +170,41 @@ namespace ArachnaeSwarm
return;
}
if (base.Working && selectedPawn != null && !selectedPawn.Dead)
if (base.Working && selectedPawn != null)
{
damageTickCounter++;
// 定期造成伤害
if (damageTickCounter >= DAMAGE_INTERVAL_TICKS)
// 酸蚀伤害逻辑
if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
{
ApplySacrificialDamage(selectedPawn);
damageTickCounter = 0;
tickCount++;
pawnTickCounters[selectedPawn] = tickCount;
if (tickCount >= AcidDamageInterval)
{
ApplyAcidDamage(selectedPawn);
pawnTickCounters[selectedPawn] = 0;
}
}
else
{
pawnTickCounters[selectedPawn] = 0;
}
// 检查pawn是否死亡
// 燃料生产逻辑
if (HasFuelCapacity)
{
float fuelToAdd = FuelProductionPerTick;
RefuelableComp.Refuel(fuelToAdd);
}
// 检查俘虏是否死亡
if (selectedPawn.Dead)
{
OnPawnDied();
Finish();
return;
}
}
}
private void OnPawnDied()
{
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
{
// 弹出死亡的pawn
Notify_PawnRemoved();
innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
OnStop();
// 可以选择在这里添加一些效果,比如声音或信息
Messages.Message("SacrificialFuelTank_PawnDied".Translate(selectedPawn.LabelShort), this, MessageTypeDefOf.NeutralEvent);
}
}
public override AcceptanceReport CanAcceptPawn(Pawn pawn)
{
if (base.Working)
@@ -179,12 +215,7 @@ namespace ArachnaeSwarm
{
return "WaitingForPawn".Translate(selectedPawn.Named("PAWN"));
}
if (pawn.Dead || pawn.Downed)
{
return "PawnMustBeAlive".Translate();
}
// 禁止虫群成员
// 禁止置入虫群成员
if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster) ||
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) ||
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker))
@@ -192,11 +223,12 @@ namespace ArachnaeSwarm
return "PawnIsHiveMember".Translate(pawn.Named("PAWN"));
}
// 允许殖民者、囚犯和奴隶,但不能是任务寄宿者
// 允许殖民者、囚犯和奴隶
bool isColonist = pawn.IsColonist;
bool isPrisoner = pawn.IsPrisonerOfColony;
bool isSlave = pawn.IsSlaveOfColony;
// 允许殖民者、囚犯或奴隶,但不能是任务寄宿者
return (isColonist || isPrisoner || isSlave) && !pawn.IsQuestLodger();
}
@@ -212,7 +244,7 @@ namespace ArachnaeSwarm
if (innerContainer.TryAddOrTransfer(pawn))
{
startTick = Find.TickManager.TicksGame;
damageTickCounter = 0; // 重置伤害计数器
pawnTickCounters[pawn] = 0; // 初始化伤害计数器
}
if (deselected)
{
@@ -220,33 +252,50 @@ namespace ArachnaeSwarm
}
}
private void Finish()
{
if (selectedPawn != null && innerContainer.Contains(selectedPawn))
{
Notify_PawnRemoved();
innerContainer.TryDrop(selectedPawn, InteractionCell, base.Map, ThingPlaceMode.Near, 1, out var _);
OnStop();
}
}
private void OnStop()
{
if (selectedPawn != null)
{
pawnTickCounters.Remove(selectedPawn);
}
selectedPawn = null;
startTick = -1;
damageTickCounter = 0;
}
private void Notify_PawnRemoved()
{
// 可以在这里添加音效
// 可以在这里添加音效
}
public override IEnumerable<Gizmo> GetGizmos()
{
// 原有的基础Gizmos
foreach (Gizmo gizmo in base.GetGizmos())
{
yield return gizmo;
}
if (!base.Working)
if (base.Working)
{
// 插入人员的操作
var insertCommand = new Command_Action
}
else
{
// 选择殖民者的操作
var command_Action = new Command_Action
{
defaultLabel = "InsertSacrificialPawn".Translate() + "...",
defaultDesc = "InsertSacrificialPawnDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/Sacrifice"),
defaultLabel = "InsertPerson".Translate() + "...",
defaultDesc = "InsertPersonRefuelingVatDesc".Translate(),
icon = Building_GrowthVat.InsertPawnIcon.Texture,
action = () =>
{
List<FloatMenuOption> list = new List<FloatMenuOption>();
@@ -266,19 +315,21 @@ namespace ArachnaeSwarm
};
if (!base.AnyAcceptablePawns)
{
insertCommand.Disable("NoViablePawns".Translate());
command_Action.Disable("NoViablePawns".Translate());
}
yield return insertCommand;
yield return command_Action;
// 指派搬运囚犯/奴隶的操作
var command_CarryPrisoner = new Command_Action
{
defaultLabel = "AssignCarryPrisoner".Translate() + "...",
defaultDesc = "AssignCarryPrisonerSacrificialDesc".Translate(),
defaultDesc = "AssignCarryPrisonerDesc".Translate(),
icon = ContentFinder<Texture2D>.Get("UI/Commands/Attack"),
action = () =>
{
List<FloatMenuOption> list = new List<FloatMenuOption>();
// 获取所有可接受的囚犯和奴隶
foreach (Pawn p in base.Map.mapPawns.AllPawnsSpawned)
{
if (CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony))
@@ -291,6 +342,7 @@ namespace ArachnaeSwarm
));
}
}
if (!list.Any())
{
list.Add(new FloatMenuOption("NoPrisonersOrSlaves".Translate(), null));
@@ -299,6 +351,7 @@ namespace ArachnaeSwarm
}
};
// 检查是否有可用的囚犯/奴隶
bool hasPrisonersOrSlaves = base.Map.mapPawns.AllPawnsSpawned
.Any(p => CanAcceptPawn(p).Accepted && (p.IsPrisonerOfColony || p.IsSlaveOfColony));
@@ -320,23 +373,43 @@ namespace ArachnaeSwarm
{
stringBuilder.AppendLineIfNotEmpty().Append("CasketContains".Translate().ToString() + ": " + selectedPawn.NameShortColored.Resolve());
// 显示燃料转换效率
if (RefuelableComp != null)
// 显示燃料生产信息
if (HasFuelCapacity)
{
stringBuilder.AppendLineIfNotEmpty().Append("FuelConversionRate".Translate() + ": " + (DAMAGE_TO_FUEL_RATIO * 100).ToString("F1") + "%");
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity.ToString("F1"));
stringBuilder.AppendLineIfNotEmpty().Append("FuelProduction".Translate() + ": " + FuelProductionPerDay.ToString("F1") + " per day");
stringBuilder.AppendLineIfNotEmpty().Append("ProductionEfficiency".Translate() + ": " + FuelProductionEfficiency.ToStringPercent());
// 显示当前燃料状态
if (RefuelableComp != null)
{
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity);
}
}
else
{
stringBuilder.AppendLineIfNotEmpty().Append("FuelTankFull".Translate());
}
// 显示pawn的健康状态
if (selectedPawn.health.summaryHealth.SummaryHealthPercent < 1.0f)
// 显示酸蚀伤害信息
if (pawnTickCounters.TryGetValue(selectedPawn, out int tickCount))
{
stringBuilder.AppendLineIfNotEmpty().Append("PawnHealth".Translate() + ": " + selectedPawn.health.summaryHealth.SummaryHealthPercent.ToStringPercent());
float damageProgress = (float)tickCount / AcidDamageInterval;
stringBuilder.AppendLineIfNotEmpty().Append("AcidDamageProgress".Translate() + ": " + damageProgress.ToStringPercent());
}
}
else if (selectedPawn != null)
{
stringBuilder.AppendLineIfNotEmpty().Append("WaitingForPawn".Translate(selectedPawn.Named("PAWN")).Resolve());
}
else
{
// 显示空闲状态信息
stringBuilder.AppendLineIfNotEmpty().Append("RefuelingVatIdle".Translate());
if (RefuelableComp != null)
{
stringBuilder.AppendLineIfNotEmpty().Append("CurrentFuel".Translate() + ": " + RefuelableComp.Fuel.ToString("F1") + " / " + RefuelableComp.Props.fuelCapacity);
}
}
return stringBuilder.ToString();
}
@@ -355,7 +428,7 @@ namespace ArachnaeSwarm
AcceptanceReport acceptanceReport = CanAcceptPawn(selPawn);
if (acceptanceReport.Accepted)
{
yield return FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("EnterSacrificialTank".Translate(this), () => SelectPawn(selPawn)), selPawn, this);
yield return FloatMenuUtility.DecoratePrioritizedTask(new FloatMenuOption("EnterBuilding".Translate(this), () => SelectPawn(selPawn)), selPawn, this);
}
else if (!acceptanceReport.Reason.NullOrEmpty())
{
@@ -375,6 +448,7 @@ namespace ArachnaeSwarm
protected override void DrawAt(Vector3 drawLoc, bool flip = false)
{
base.DrawAt(drawLoc, flip);
// 绘制顶部图形
if (TopGraphic != null)
{
TopGraphic.Draw(DrawPos + Altitudes.AltIncVect * 2f, base.Rotation, this);
@@ -387,11 +461,13 @@ namespace ArachnaeSwarm
return null;
if (!CanAcceptPawn(prisoner).Accepted)
return null;
JobDef carryJobDef = DefDatabase<JobDef>.GetNamed("ARA_CarryPrisonerToSacrificialTank");
// 创建搬运工作定义 - 使用新的 JobDef
JobDef carryJobDef = DefDatabase<JobDef>.GetNamed("ARA_CarryPrisonerToRefuelingVat");
if (carryJobDef == null)
{
Log.Error("ARA_CarryPrisonerToRefuelingVat JobDef not found!");
return null;
}
Job job = JobMaker.MakeJob(carryJobDef, prisoner, this);
job.count = 1;
return job;
@@ -401,10 +477,12 @@ namespace ArachnaeSwarm
{
foreach (Pawn pawn in base.Map.mapPawns.AllPawnsSpawned)
{
// 检查是否是虫群成员
if (pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindDrone) ||
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindWorker) ||
pawn.health.hediffSet.HasHediff(ARA_HediffDefOf.ARA_HiveMindMaster))
{
// 检查是否能够工作且不是囚犯
if (pawn.workSettings != null && pawn.workSettings.WorkIsActive(WorkTypeDefOf.Hauling) &&
!pawn.Downed && !pawn.IsPrisoner && pawn.CanReach(this, PathEndMode.InteractionCell, Danger.Some))
{
@@ -418,15 +496,17 @@ namespace ArachnaeSwarm
{
if (prisoner == null)
return;
// 获取可用的搬运者
var availableCarriers = GetAvailableCarriers().ToList();
if (!availableCarriers.Any())
{
Messages.Message("NoAvailableHiveCarriers".Translate(), MessageTypeDefOf.RejectInput);
return;
}
// 创建浮动菜单选择搬运者
List<FloatMenuOption> options = new List<FloatMenuOption>();
foreach (Pawn carrier in availableCarriers)
{
options.Add(new FloatMenuOption(
@@ -444,7 +524,6 @@ namespace ArachnaeSwarm
Color.white
));
}
if (options.Any())
{
Find.WindowStack.Add(new FloatMenu(options));
@@ -454,5 +533,12 @@ namespace ArachnaeSwarm
Messages.Message("NoAvailableCarriers".Translate(), MessageTypeDefOf.RejectInput);
}
}
// 保存和加载数据
public override void ExposeData()
{
base.ExposeData();
Scribe_Collections.Look(ref pawnTickCounters, "pawnTickCounters", LookMode.Reference, LookMode.Value);
}
}
}

View File

@@ -0,0 +1,16 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_RefuelingVat : CompProperties
{
public float fuelProductionPerDay = 10f;
public float fuelProductionEfficiency = 1f;
public CompProperties_RefuelingVat()
{
this.compClass = typeof(CompRefuelingVat);
}
}
}

View File

@@ -0,0 +1,20 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompRefuelingVat : ThingComp
{
public CompProperties_RefuelingVat Props => (CompProperties_RefuelingVat)props;
public float FuelProductionPerDay => Props.fuelProductionPerDay;
public float FuelProductionEfficiency => Props.fuelProductionEfficiency;
// 允许运行时修改效率(如果需要)
public void SetEfficiency(float newEfficiency)
{
// 如果需要运行时修改,可以在这里添加逻辑
// 注意:这会修改实例,不会影响 XML 定义
}
}
}

View File

@@ -319,7 +319,7 @@ namespace ArachnaeSwarm
yield return new FloatMenuOption(label, () =>
{
this._selectedProcess = process;
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartInteractiveProduction"), parent);
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartResearchProduction"), parent);
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
});
}

View File

@@ -0,0 +1,482 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using UnityEngine;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class CompProperties_ResearchProducer : CompProperties
{
public List<PawnKindDef> whitelist;
public bool destroyOnSpawn;
public float damagePerTickWhenUnfueled = 0.2f;
public float minNutritionToStart = 0.1f;
// 营养转换系数:研究点数 -> 营养
public float nutritionPerResearchPoint = 0.1f;
// 生产时间系数:研究点数 -> 生产时间ticks
public float productionTicksPerResearchPoint = 10f;
public CompProperties_ResearchProducer()
{
compClass = typeof(CompResearchProducer);
}
}
[StaticConstructorOnStartup]
public class CompResearchProducer : ThingComp
{
private ResearchProcessDef _selectedProcess;
private int productionUntilTick = -1;
private List<ResearchProcessDef> _cachedProcesses;
private int spawnTick = -1;
private const int COOLDOWN_TICKS = 120;
private CompRefuelableNutrition _fuelComp;
private static readonly Texture2D CancelIcon = ContentFinder<Texture2D>.Get("UI/Designators/Cancel");
public bool InProduction => _selectedProcess != null;
public bool InCooldown => spawnTick > 0 && Find.TickManager.TicksGame < spawnTick + COOLDOWN_TICKS;
public CompProperties_ResearchProducer Props => (CompProperties_ResearchProducer)props;
// 生产进度0-1
public float ProductionProgress
{
get
{
if (!InProduction || productionUntilTick <= 0) return 0f;
int totalTicks = _selectedProcess.productionTicks;
int elapsedTicks = totalTicks - (productionUntilTick - Find.TickManager.TicksGame);
return Mathf.Clamp01((float)elapsedTicks / totalTicks);
}
}
// 自动生成的 ResearchProcessDef 列表
public List<ResearchProcessDef> Processes
{
get
{
if (_cachedProcesses == null)
{
BuildProcessList();
}
return _cachedProcesses;
}
}
private CompRefuelableNutrition FuelComp
{
get
{
if (_fuelComp == null) _fuelComp = parent.GetComp<CompRefuelableNutrition>();
return _fuelComp;
}
}
public override void PostSpawnSetup(bool respawningAfterLoad)
{
base.PostSpawnSetup(respawningAfterLoad);
_fuelComp = parent.GetComp<CompRefuelableNutrition>();
BuildProcessList();
if (!respawningAfterLoad)
{
spawnTick = Find.TickManager.TicksGame;
}
}
public override void PostExposeData()
{
base.PostExposeData();
// 序列化 selectedProcess 的 researchDef
ResearchProjectDef selectedProcessResearchDef = _selectedProcess?.researchDef;
Scribe_Defs.Look(ref selectedProcessResearchDef, "selectedProcessResearchDef");
Scribe_Values.Look(ref productionUntilTick, "productionUntilTick", -1);
Scribe_Values.Look(ref spawnTick, "spawnTick", -1);
// 加载时重建 selectedProcess
if (Scribe.mode == LoadSaveMode.LoadingVars && selectedProcessResearchDef != null)
{
if (_cachedProcesses == null)
{
BuildProcessList();
}
_selectedProcess = Processes.FirstOrDefault(p => p.researchDef == selectedProcessResearchDef);
if (_selectedProcess == null)
{
Log.Warning($"Could not find ResearchProcessDef for {selectedProcessResearchDef.defName} after loading. Resetting production.");
ResetProduction();
}
else if (productionUntilTick > 0)
{
if (Find.TickManager.TicksGame >= productionUntilTick)
{
Log.Warning($"Production time already passed for {selectedProcessResearchDef.defName}. Finishing immediately.");
FinishProduction();
}
else if (productionUntilTick - Find.TickManager.TicksGame > _selectedProcess.productionTicks * 10)
{
Log.Warning($"Abnormal production time detected for {selectedProcessResearchDef.defName}. Recalculating.");
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
}
}
}
}
// 扫描 ARA_ResearchTab 下所有需要 techprint 的研究项目
private void BuildProcessList()
{
_cachedProcesses = new List<ResearchProcessDef>();
// 获取 ARA_ResearchTab 的 ResearchTabDef
ResearchTabDef araResearchTab = DefDatabase<ResearchTabDef>.GetNamedSilentFail("ARA_ResearchTab");
if (araResearchTab == null)
{
Log.Warning("ARA_ResearchTab not found. No research processes will be available.");
return;
}
int totalScanned = 0;
int araTabCount = 0;
int techprintCount = 0;
int prerequisitesMetCount = 0;
// 扫描所有研究项目
foreach (ResearchProjectDef researchDef in DefDatabase<ResearchProjectDef>.AllDefs)
{
totalScanned++;
// 检查是否在 ARA_ResearchTab 下 - 使用 ResearchTabDef 比较
if (researchDef.tab == araResearchTab)
{
araTabCount++;
// 检查是否需要 techprint
if (researchDef.TechprintCount > 0)
{
techprintCount++;
// 检查前置条件是否满足
if (!ArePrerequisitesMet(researchDef))
{
continue; // 前置条件不满足,跳过这个研究项目
}
prerequisitesMetCount++;
// 根据命名规则构建 techprint 的 defName
string techprintDefName = "Techprint_" + researchDef.defName;
ThingDef techprintDef = DefDatabase<ThingDef>.GetNamedSilentFail(techprintDefName);
if (techprintDef != null)
{
// 计算营养消耗和生产时间
float nutritionCost = researchDef.baseCost * Props.nutritionPerResearchPoint;
int productionTicks = Mathf.RoundToInt(researchDef.baseCost * Props.productionTicksPerResearchPoint);
ResearchProcessDef process = new ResearchProcessDef
{
researchDef = researchDef,
techprintDef = techprintDef,
productionTicks = productionTicks,
totalNutritionNeeded = nutritionCost,
techprintCount = researchDef.TechprintCount
};
_cachedProcesses.Add(process);
}
else
{
Log.Warning($"Techprint ThingDef not found for research project {researchDef.defName}. Expected defName: {techprintDefName}");
}
}
}
}
// 按研究项目名称排序
_cachedProcesses.SortBy(p => p.researchDef.label);
// 记录详细的扫描结果
Log.Message($"Research production scanner: Scanned {totalScanned} research projects, " +
$"{araTabCount} in ARA_ResearchTab, " +
$"{techprintCount} require techprints, " +
$"{prerequisitesMetCount} have prerequisites met, " +
$"{_cachedProcesses.Count} available for production");
}
// 检查研究项目的前置条件是否满足
private bool ArePrerequisitesMet(ResearchProjectDef researchDef)
{
// 检查 prerequisites
if (researchDef.prerequisites != null)
{
foreach (var prerequisite in researchDef.prerequisites)
{
if (!prerequisite.IsFinished)
{
return false; // 有一个前置研究未完成
}
}
}
// 检查 hiddenPrerequisites
if (researchDef.hiddenPrerequisites != null)
{
foreach (var hiddenPrerequisite in researchDef.hiddenPrerequisites)
{
if (!hiddenPrerequisite.IsFinished)
{
return false; // 有一个隐藏前置研究未完成
}
}
}
return true; // 所有前置条件都满足
}
public override void CompTick()
{
base.CompTick();
if (InProduction && productionUntilTick > 0)
{
if (productionUntilTick <= 0)
{
Log.Error($"Invalid productionUntilTick: {productionUntilTick}. Resetting production.");
ResetProduction();
return;
}
if (FuelComp == null) return;
bool hasFuel = FuelComp.HasFuel;
// 营养归零时持续伤害建筑
if (!hasFuel)
{
parent.TakeDamage(new DamageInfo(DamageDefOf.Rotting, Props.damagePerTickWhenUnfueled));
return;
}
// 检查生产是否完成
if (productionUntilTick > 0 && Find.TickManager.TicksGame >= productionUntilTick)
{
FinishProduction();
}
}
}
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
{
if (InCooldown)
{
int remainingTicks = (spawnTick + COOLDOWN_TICKS) - Find.TickManager.TicksGame;
yield return new FloatMenuOption($"ARA_CocoonCooldown".Translate(remainingTicks.ToStringTicksToPeriod()), null);
yield break;
}
if (InProduction || !selPawn.CanReach(parent, PathEndMode.InteractionCell, Danger.Deadly)) yield break;
if (Props.whitelist != null && !Props.whitelist.Contains(selPawn.kindDef)) yield break;
if (FuelComp == null) yield break;
if (!FuelComp.HasFuel || FuelComp.NutritionStored < Props.minNutritionToStart)
{
yield return new FloatMenuOption("CannotStartProduction".Translate() + ": " + "NoFuel".Translate(), null);
yield break;
}
foreach (var process in Processes)
{
float nutritionCost = process.totalNutritionNeeded;
string label = "StartResearchProduction".Translate(process.researchDef.label) + " " +
"ARA_ProductionCost".Translate(nutritionCost.ToString("F0")) + " ->" +
"ARA_TechprintCount".Translate(process.techprintCount);
// 检查研究是否已经完成
if (process.researchDef.IsFinished)
{
yield return new FloatMenuOption(label + " (" + "ResearchAlreadyCompleted".Translate() + ")", null);
}
else
{
yield return new FloatMenuOption(label, () =>
{
this._selectedProcess = process;
Job job = JobMaker.MakeJob(DefDatabase<JobDef>.GetNamed("ARA_StartResearchProduction"), parent);
selPawn.jobs.TryTakeOrderedJob(job, JobTag.Misc);
});
}
}
}
public void StartProduction()
{
if (InCooldown)
{
Log.Warning("Attempted to start production during cooldown period.");
return;
}
if (_selectedProcess == null) return;
productionUntilTick = Find.TickManager.TicksGame + _selectedProcess.productionTicks;
float nutritionPerDay = (_selectedProcess.totalNutritionNeeded / _selectedProcess.productionTicks) * 60000f;
FuelComp.currentConsumptionRate = nutritionPerDay;
}
private void FinishProduction()
{
if (_selectedProcess == null)
{
Log.Warning("FinishProduction called but _selectedProcess is null. Resetting.");
ResetProduction();
return;
}
try
{
// 生成科技蓝图
Thing techprint = ThingMaker.MakeThing(_selectedProcess.techprintDef);
techprint.stackCount = _selectedProcess.techprintCount;
GenPlace.TryPlaceThing(techprint, parent.Position, parent.Map, ThingPlaceMode.Near);
// 添加消息提示
string messageText = "ARA_TechprintProductionComplete".Translate(
_selectedProcess.techprintCount,
_selectedProcess.techprintDef.label,
_selectedProcess.researchDef.label
);
Messages.Message(messageText, parent, MessageTypeDefOf.PositiveEvent);
if (Props.destroyOnSpawn)
{
parent.Destroy(DestroyMode.Vanish);
}
}
catch (System.Exception ex)
{
Log.Error($"Error in FinishProduction: {ex.Message}");
}
finally
{
ResetProduction();
}
}
private void ResetProduction()
{
if (FuelComp != null) FuelComp.currentConsumptionRate = 0f;
_selectedProcess = null;
productionUntilTick = -1;
}
// 进度条显示
private string GetProgressBar(float progress, int barLength = 20)
{
int filledLength = Mathf.RoundToInt(progress * barLength);
int emptyLength = barLength - filledLength;
StringBuilder bar = new StringBuilder();
bar.Append("[");
for (int i = 0; i < filledLength; i++) bar.Append("█");
for (int i = 0; i < emptyLength; i++) bar.Append("-");
bar.Append("]");
return bar.ToString();
}
public override string CompInspectStringExtra()
{
if (InCooldown)
{
int remainingTicks = (spawnTick + COOLDOWN_TICKS) - Find.TickManager.TicksGame;
return "ARA_CocoonCooldown".Translate(remainingTicks.ToStringTicksToPeriod());
}
if (InProduction)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("ProducingTechprint".Translate(this._selectedProcess.researchDef.label));
float progress = ProductionProgress;
int remainingTicks = productionUntilTick - Find.TickManager.TicksGame;
sb.AppendLine("Progress".Translate() + ": " + GetProgressBar(progress) + " " + progress.ToStringPercent("F0"));
sb.AppendLine("TimeLeft".Translate() + ": " + remainingTicks.ToStringTicksToPeriod());
sb.AppendLine("TechprintsToProduce".Translate() + ": " + _selectedProcess.techprintCount);
return sb.ToString().TrimEnd();
}
if (!InProduction)
{
int availableProcesses = Processes.Count(p => !p.researchDef.IsFinished);
string allowedRaces = "";
if (Props.whitelist != null && Props.whitelist.Count > 0)
{
var raceNames = Props.whitelist.Select(pkd => pkd.LabelCap).Distinct();
allowedRaces = string.Join(", ", raceNames);
}
else
{
allowedRaces = "ARA_AnyArachnaeRace".Translate();
}
return "ARA_NeedSpecificArachnaeToStartResearchProduction".Translate(allowedRaces) +
$" ({availableProcesses} " + "researchProjectsAvailable".Translate() + ")";
}
return null;
}
public override void PostDraw()
{
base.PostDraw();
if (InProduction)
{
Vector3 drawPos = parent.DrawPos;
drawPos.y += 0.15f;
float progress = ProductionProgress;
GenDraw.DrawFillableBar(new GenDraw.FillableBarRequest
{
center = drawPos,
size = new Vector2(1f, 0.15f),
fillPercent = progress,
filledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.green),
unfilledMat = SolidColorMaterials.SimpleSolidColorMaterial(Color.gray),
margin = 0.1f,
rotation = Rot4.North
});
}
}
public override IEnumerable<Gizmo> CompGetGizmosExtra()
{
foreach (var g in base.CompGetGizmosExtra()) yield return g;
if (InCooldown) yield break;
if (InProduction)
{
yield return new Command_Action
{
defaultLabel = "CommandCancelProduction".Translate(),
icon = CancelIcon,
action = () => ResetProduction()
};
if (Prefs.DevMode)
{
yield return new Command_Action
{
defaultLabel = "Debug: Force Finish",
action = () => FinishProduction()
};
}
}
}
}
// 研究进程定义类
public class ResearchProcessDef
{
public ResearchProjectDef researchDef;
public ThingDef techprintDef;
public int productionTicks;
public float totalNutritionNeeded;
public int techprintCount;
}
}

View File

@@ -0,0 +1,46 @@
using System.Collections.Generic;
using RimWorld;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_StartResearchProduction : JobDriver
{
private const TargetIndex BuildingInd = TargetIndex.A;
protected Building Building => (Building)job.GetTarget(BuildingInd).Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Building, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 检查目标建筑是否有效
this.FailOnDespawnedNullOrForbidden(BuildingInd);
this.FailOnBurningImmobile(BuildingInd);
// 第一步:走到建筑
yield return Toils_Goto.GotoThing(BuildingInd, PathEndMode.InteractionCell);
// 第二步:启动生产
Toil work = ToilMaker.MakeToil("MakeNewToils");
work.initAction = delegate
{
var comp = Building.GetComp<CompResearchProducer>();
if (comp != null)
{
comp.StartProduction();
}
else
{
Log.Error($"CompResearchProducer not found on {Building.Label}");
}
};
work.defaultCompleteMode = ToilCompleteMode.Instant;
yield return work;
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using Verse;
namespace ArachnaeSwarm
{
public class DefModExtension_SacrificialFuelTank : DefModExtension
{
public string topGraphicPath;
public Type graphicClass = typeof(Graphic_Multi);
// 可配置的伤害和燃料转换参数
public float damageToFuelRatio = 0.1f;
public int damageIntervalTicks = 250;
public float damagePerInterval = 5f;
}
}

View File

@@ -2,15 +2,16 @@ using System;
using System.Linq;
using RimWorld;
using Verse;
using UnityEngine;
namespace ArachnaeSwarm
{
// Properties for the new component
public class CompProperties_AutoEjector : CompProperties
{
public int checkInterval = 250; // Check roughly every 4 seconds
public float ejectAtPercent = 1.0f; // Eject when fuel reaches this percentage of max capacity (1.0 = 100%)
public Type targetComp = typeof(CompRefuelable); // The specific CompRefuelable class to target, can be overridden in XML
public int checkInterval = 250;
public float ejectAtPercent = 1.0f;
public Type targetComp = typeof(CompRefuelable);
public bool allowEjectedFuel = true; // 新增:控制弹出的燃料是否允许操作
public CompProperties_AutoEjector()
{
@@ -18,7 +19,6 @@ namespace ArachnaeSwarm
}
}
// The component logic
public class CompAutoEjector : ThingComp
{
private CompProperties_AutoEjector Props => (CompProperties_AutoEjector)this.props;
@@ -28,7 +28,6 @@ namespace ArachnaeSwarm
{
base.PostSpawnSetup(respawningAfterLoad);
// Find the specific refuelable component specified in XML
this.refuelableComp = this.parent.GetComps<CompRefuelable>()
.FirstOrDefault(comp => comp.GetType() == this.Props.targetComp);
@@ -42,17 +41,47 @@ namespace ArachnaeSwarm
{
base.CompTick();
// Check if we have a valid refuelable comp and it's time to check
if (this.refuelableComp != null &&
this.parent.IsHashIntervalTick(this.Props.checkInterval))
{
// Check if fuel has reached or exceeded the configured percentage of the MAX capacity
if (this.refuelableComp.FuelPercentOfMax >= this.Props.ejectAtPercent)
{
// Call the public EjectFuel() method.
this.refuelableComp.EjectFuel();
// 使用自定义的弹出方法
EjectFuelWithoutForbid();
}
}
}
/// <summary>
/// 自定义弹出燃料方法,弹出的物品允许操作
/// </summary>
private void EjectFuelWithoutForbid()
{
var props = refuelableComp.Props;
ThingDef thingDef = props.fuelFilter.AllowedThingDefs.First();
int num = Mathf.FloorToInt(refuelableComp.Fuel);
while (num > 0)
{
Thing thing = ThingMaker.MakeThing(thingDef);
thing.stackCount = Mathf.Min(num, thingDef.stackLimit);
num -= thing.stackCount;
// 放置物品
GenPlace.TryPlaceThing(thing, parent.Position, parent.Map, ThingPlaceMode.Near);
// 根据配置决定是否允许操作
if (Props.allowEjectedFuel)
{
thing.SetForbidden(false); // 允许操作
}
}
// 重置燃料并发送信号
typeof(CompRefuelable).GetField("fuel", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
?.SetValue(refuelableComp, 0f);
parent.BroadcastCompSignal("RanOutOfFuel");
}
}
}
}

View File

@@ -0,0 +1,64 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
using Verse.AI;
namespace ArachnaeSwarm
{
public class JobDriver_CarryPrisonerToRefuelingVat : JobDriver
{
private const TargetIndex PrisonerInd = TargetIndex.A;
private const TargetIndex VatInd = TargetIndex.B;
protected Pawn Prisoner => (Pawn)job.GetTarget(PrisonerInd).Thing;
protected Building_RefuelingVat Vat => (Building_RefuelingVat)job.GetTarget(VatInd).Thing;
public override bool TryMakePreToilReservations(bool errorOnFailed)
{
return pawn.Reserve(Prisoner, job, 1, -1, null, errorOnFailed)
&& pawn.Reserve(Vat, job, 1, -1, null, errorOnFailed);
}
protected override IEnumerable<Toil> MakeNewToils()
{
// 验证目标
this.FailOnDestroyedOrNull(PrisonerInd);
this.FailOnDestroyedOrNull(VatInd);
this.FailOn(() => !Vat.CanAcceptPawn(Prisoner).Accepted);
// 1. 前往囚犯位置
yield return Toils_Goto.GotoThing(PrisonerInd, PathEndMode.OnCell)
.FailOn(() => Prisoner.IsInRefuelingVat())
.FailOnSomeonePhysicallyInteracting(PrisonerInd);
// 2. 开始搬运囚犯
yield return Toils_Haul.StartCarryThing(PrisonerInd);
// 3. 前往燃料转化罐
yield return Toils_Goto.GotoThing(VatInd, PathEndMode.InteractionCell);
// 4. 将囚犯放入燃料转化罐
yield return new Toil
{
initAction = () =>
{
if (Vat.CanAcceptPawn(Prisoner).Accepted)
{
Vat.TryAcceptPawn(Prisoner);
Messages.Message("PrisonerPlacedInRefuelingVat".Translate(Prisoner.LabelShort), MessageTypeDefOf.NeutralEvent);
}
},
defaultCompleteMode = ToilCompleteMode.Instant
};
}
}
// 扩展方法检查pawn是否在燃料转化罐中
public static class RefuelingVatPawnExtensions
{
public static bool IsInRefuelingVat(this Pawn pawn)
{
return pawn?.Spawned == false && pawn.ParentHolder is Building_RefuelingVat;
}
}
}

View File

@@ -11,4 +11,4 @@ namespace ArachnaeSwarm
// 公开属性,供其他组件读取
public List<ThingDef> CocoonDefs => Props.cocoonDefs;
}
}
}