精华提取茧,蓝图卵和附带的所有修改
This commit is contained in:
Binary file not shown.
@@ -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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 定义
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,4 @@ namespace ArachnaeSwarm
|
||||
// 公开属性,供其他组件读取
|
||||
public List<ThingDef> CocoonDefs => Props.cocoonDefs;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user