This commit is contained in:
2026-02-14 15:59:42 +08:00
parent 17f843ad3a
commit da99833af8
31 changed files with 1515 additions and 1103 deletions

View File

@@ -2,6 +2,26 @@
"Version": 1,
"WorkspaceRootPath": "D:\\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\\abilities\\compabilityeffect_researchprereq.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:abilities\\compabilityeffect_researchprereq.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\\hediffs\\ara_spawner\\hediffcompproperties_spawner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_spawner\\hediffcompproperties_spawner.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\\hediffs\\ara_productionqueue\\hediffcompproperties_productionqueue.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_productionqueue\\hediffcompproperties_productionqueue.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\\hediffs\\ara_spawner\\hediffcomp_spawner.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_spawner\\hediffcomp_spawner.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\\hediffs\\ara_productionqueue\\hediffcomp_productionqueue.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_productionqueue\\hediffcomp_productionqueue.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\\hediffs\\ara_gestaltnode\\hediffcomp_gestaltnode.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{EAE0DB6B-E282-C812-7F5A-6D13E9D24581}|ArachnaeSwarm.csproj|solutionrelative:hediffs\\ara_gestaltnode\\hediffcomp_gestaltnode.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
@@ -66,15 +86,80 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 2,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "CompAbilityEffect_ResearchPrereq.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_ResearchPrereq.cs",
"RelativeDocumentMoniker": "Abilities\\CompAbilityEffect_ResearchPrereq.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\CompAbilityEffect_ResearchPrereq.cs",
"RelativeToolTip": "Abilities\\CompAbilityEffect_ResearchPrereq.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAABBAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T07:44:51.729Z",
"EditorCaption": ""
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}"
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "HediffCompProperties_ProductionQueue.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ProductionQueue\\HediffCompProperties_ProductionQueue.cs",
"RelativeDocumentMoniker": "Hediffs\\ARA_ProductionQueue\\HediffCompProperties_ProductionQueue.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ProductionQueue\\HediffCompProperties_ProductionQueue.cs",
"RelativeToolTip": "Hediffs\\ARA_ProductionQueue\\HediffCompProperties_ProductionQueue.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T07:21:19.792Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "HediffComp_ProductionQueue.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ProductionQueue\\HediffComp_ProductionQueue.cs",
"RelativeDocumentMoniker": "Hediffs\\ARA_ProductionQueue\\HediffComp_ProductionQueue.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_ProductionQueue\\HediffComp_ProductionQueue.cs",
"RelativeToolTip": "Hediffs\\ARA_ProductionQueue\\HediffComp_ProductionQueue.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAoAAAArAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T07:19:42.058Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "HediffCompProperties_Spawner.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_Spawner\\HediffCompProperties_Spawner.cs",
"RelativeDocumentMoniker": "Hediffs\\ARA_Spawner\\HediffCompProperties_Spawner.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_Spawner\\HediffCompProperties_Spawner.cs",
"RelativeToolTip": "Hediffs\\ARA_Spawner\\HediffCompProperties_Spawner.cs",
"ViewState": "AgIAAFcAAAAAAAAAAADgv3gAAAApAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T07:09:01.203Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "HediffComp_Spawner.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_Spawner\\HediffComp_Spawner.cs",
"RelativeDocumentMoniker": "Hediffs\\ARA_Spawner\\HediffComp_Spawner.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_Spawner\\HediffComp_Spawner.cs",
"RelativeToolTip": "Hediffs\\ARA_Spawner\\HediffComp_Spawner.cs",
"ViewState": "AgIAAFwAAAAAAAAAAAAvwHEAAAAoAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T07:08:13.475Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 6,
"Title": "CompNodeSwarmLifetime.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs",
"RelativeDocumentMoniker": "Pawn_Comps\\ARA_NodeSwarmLifetime\\CompNodeSwarmLifetime.cs",
@@ -86,20 +171,20 @@
},
{
"$type": "Document",
"DocumentIndex": 0,
"DocumentIndex": 5,
"Title": "HediffComp_GestaltNode.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_GestaltNode\\HediffComp_GestaltNode.cs",
"RelativeDocumentMoniker": "Hediffs\\ARA_GestaltNode\\HediffComp_GestaltNode.cs",
"ToolTip": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Hediffs\\ARA_GestaltNode\\HediffComp_GestaltNode.cs",
"RelativeToolTip": "Hediffs\\ARA_GestaltNode\\HediffComp_GestaltNode.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAA4AAAAnAAAAAAAAAA==",
"ViewState": "AgIAABMAAAAAAAAAAAAAwBUAAAAVAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-02-14T02:36:39.249Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"DocumentIndex": 9,
"Title": "ARA_DefOf.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\ARA_DefOf.cs",
"RelativeDocumentMoniker": "ARA_DefOf.cs",
@@ -111,7 +196,7 @@
},
{
"$type": "Document",
"DocumentIndex": 5,
"DocumentIndex": 10,
"Title": "JobDriver_CastAbilityMaintainMultiProjectile.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_LaunchMultiProjectile\\JobDriver_CastAbilityMaintainMultiProjectile.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_LaunchMultiProjectile\\JobDriver_CastAbilityMaintainMultiProjectile.cs",
@@ -123,7 +208,7 @@
},
{
"$type": "Document",
"DocumentIndex": 2,
"DocumentIndex": 7,
"Title": "CompProperties_AbilityLaunchMultiProjectile.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_LaunchMultiProjectile\\CompProperties_AbilityLaunchMultiProjectile.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_LaunchMultiProjectile\\CompProperties_AbilityLaunchMultiProjectile.cs",
@@ -135,7 +220,7 @@
},
{
"$type": "Document",
"DocumentIndex": 6,
"DocumentIndex": 11,
"Title": "CompAbilityEffect_Transform.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_Morphable\\CompAbilityEffect_Transform.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_Morphable\\CompAbilityEffect_Transform.cs",
@@ -147,7 +232,7 @@
},
{
"$type": "Document",
"DocumentIndex": 3,
"DocumentIndex": 8,
"Title": "CompAbilityEffect_LaunchMultiProjectile.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_LaunchMultiProjectile\\CompAbilityEffect_LaunchMultiProjectile.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_LaunchMultiProjectile\\CompAbilityEffect_LaunchMultiProjectile.cs",
@@ -159,7 +244,7 @@
},
{
"$type": "Document",
"DocumentIndex": 8,
"DocumentIndex": 13,
"Title": "CompProperties_AbilityPsychicLoadCost.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadCost\\CompProperties_AbilityPsychicLoadCost.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadCost\\CompProperties_AbilityPsychicLoadCost.cs",
@@ -171,7 +256,7 @@
},
{
"$type": "Document",
"DocumentIndex": 9,
"DocumentIndex": 14,
"Title": "Gizmo_PawnResearchProgress.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs",
"RelativeDocumentMoniker": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Gizmo_PawnResearchProgress.cs",
@@ -183,7 +268,7 @@
},
{
"$type": "Document",
"DocumentIndex": 7,
"DocumentIndex": 12,
"Title": "CompAbilityEffect_PsychicLoadCost.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\ARA_PsychicLoadCost\\CompAbilityEffect_PsychicLoadCost.cs",
"RelativeDocumentMoniker": "Abilities\\ARA_PsychicLoadCost\\CompAbilityEffect_PsychicLoadCost.cs",
@@ -195,7 +280,7 @@
},
{
"$type": "Document",
"DocumentIndex": 12,
"DocumentIndex": 17,
"Title": "CompAbilityEffect_PsychicBrainburn.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Abilities\\PsychicBrainburn\\CompAbilityEffect_PsychicBrainburn.cs",
"RelativeDocumentMoniker": "Abilities\\PsychicBrainburn\\CompAbilityEffect_PsychicBrainburn.cs",
@@ -207,7 +292,7 @@
},
{
"$type": "Document",
"DocumentIndex": 11,
"DocumentIndex": 16,
"Title": "Gizmo_ResearchProgress.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_ResearchBlueprintReader\\Gizmo_ResearchProgress.cs",
"RelativeDocumentMoniker": "Buildings\\Building_ResearchBlueprintReader\\Gizmo_ResearchProgress.cs",
@@ -219,7 +304,7 @@
},
{
"$type": "Document",
"DocumentIndex": 13,
"DocumentIndex": 18,
"Title": "ResearchBlueprintData.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Buildings\\Building_ResearchBlueprintReader\\ResearchBlueprintData.cs",
"RelativeDocumentMoniker": "Buildings\\Building_ResearchBlueprintReader\\ResearchBlueprintData.cs",
@@ -231,7 +316,7 @@
},
{
"$type": "Document",
"DocumentIndex": 10,
"DocumentIndex": 15,
"Title": "Comp_PawnResearchBlueprintReader.cs",
"DocumentMoniker": "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\Mods\\ArachnaeSwarm\\Source\\ArachnaeSwarm\\Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs",
"RelativeDocumentMoniker": "Pawn_Comps\\ARA_PawnResearchBlueprintReader\\Comp_PawnResearchBlueprintReader.cs",

View File

@@ -0,0 +1,62 @@
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompAbilityEffect_HediffBlacklist : CompAbilityEffect
{
public new CompProperties_AbilityHediffBlacklist Props => (CompProperties_AbilityHediffBlacklist)props;
public override bool Valid(LocalTargetInfo target, bool throwMessages = false)
{
Pawn targetPawn = target.Pawn;
// 如果不是Pawn返回父类验证结果
if (targetPawn == null)
return base.Valid(target, throwMessages);
// 检查目标是否拥有黑名单中的任意一个Hediff
if (HasBlacklistedHediff(targetPawn))
{
if (throwMessages)
{
Messages.Message(Props.blockedMessage.Translate(targetPawn.LabelShort),
targetPawn, MessageTypeDefOf.RejectInput, false);
}
return false;
}
return base.Valid(target, throwMessages);
}
/// <summary>
/// 检查目标Pawn是否拥有黑名单中的任意一个Hediff
/// </summary>
private bool HasBlacklistedHediff(Pawn pawn)
{
if (pawn?.health?.hediffSet == null || Props.blacklistedHediffs.NullOrEmpty())
return false;
foreach (HediffDef hediffDef in Props.blacklistedHediffs)
{
if (hediffDef != null && pawn.health.hediffSet.HasHediff(hediffDef))
return true;
}
return false;
}
/// <summary>
/// 在能力描述中显示黑名单信息
/// </summary>
public override string ExtraLabelMouseAttachment(LocalTargetInfo target)
{
Pawn targetPawn = target.Pawn;
if (targetPawn != null && HasBlacklistedHediff(targetPawn))
{
return "CannotTargetPawnWithBlacklistedHediff".Translate();
}
return null;
}
}
}

View File

@@ -0,0 +1,24 @@
using RimWorld;
using System.Collections.Generic;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_AbilityHediffBlacklist : CompProperties_AbilityEffect
{
/// <summary>
/// 黑名单Hediff列表拥有这些Hediff的Pawn无法成为目标
/// </summary>
public List<HediffDef> blacklistedHediffs;
/// <summary>
/// 目标被阻止时显示的消息
/// </summary>
public string blockedMessage = "ARA_BlacklistedHediff_Blocked";
public CompProperties_AbilityHediffBlacklist()
{
this.compClass = typeof(CompAbilityEffect_HediffBlacklist);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace ArachnaeSwarm
public class CompProperties_AbilityResearchPrereq : CompProperties_AbilityEffect
{
public ResearchProjectDef requiredResearch;
public string failMessage = "Research not completed.";
public string failMessage;
public CompProperties_AbilityResearchPrereq()
{
@@ -22,7 +22,7 @@ namespace ArachnaeSwarm
{
if (Props.requiredResearch != null && !Props.requiredResearch.IsFinished)
{
reason = Props.failMessage;
reason = Props.failMessage ?? "ARA_AbilityRequiresTech".Translate(Props.requiredResearch);
return true;
}

View File

@@ -118,11 +118,11 @@
<Compile Include="Abilities\ARA_Morphable\CompMorphable.cs" />
<Compile Include="Abilities\ARA_Morphable\CompProperties_AbilityTransform.cs" />
<Compile Include="Abilities\ARA_Morphable\CompProperties_Morphable.cs" />
<Compile Include="Abilities\ARA_QueenAbility\CompAbilityEffect_BodyPartCheck.cs" />
<Compile Include="Abilities\ARA_QueenAbility\CompAbilityEffect_NeedCost.cs" />
<Compile Include="Abilities\ARA_QueenAbility\CompAbilityEffect_ResearchPrereq.cs" />
<Compile Include="Abilities\ARA_QueenAbility\CompAbilityEffect_SprayLiquidMulti.cs" />
<Compile Include="Abilities\ARA_QueenAbility\CompProperties_AbilitySprayLiquidMulti.cs" />
<Compile Include="Abilities\CompAbilityEffect_BodyPartCheck.cs" />
<Compile Include="Abilities\CompAbilityEffect_NeedCost.cs" />
<Compile Include="Abilities\CompAbilityEffect_ResearchPrereq.cs" />
<Compile Include="Abilities\ARA_SprayLiquidMulti\CompAbilityEffect_SprayLiquidMulti.cs" />
<Compile Include="Abilities\ARA_SprayLiquidMulti\CompProperties_AbilitySprayLiquidMulti.cs" />
<Compile Include="Abilities\ARA_ShowInteractiveThing\CompAbilityEffect_ShowInteractiveThing.cs" />
<Compile Include="Abilities\ARA_ShowInteractiveThing\CompProperties_AbilityShowInteractiveThing.cs" />
<Compile Include="Abilities\ARA_ShowSpawnablePawnsList\CompAbilityEffect_AbilityShowSpawnablePawns.cs" />
@@ -134,12 +134,12 @@
<Compile Include="Abilities\CompAbilityEffect_DRM_Deaddustpop.cs" />
<Compile Include="Abilities\CompAbilityEffect_RandomHediff.cs" />
<Compile Include="Abilities\CompAbilityEffect_TransformCorpse.cs" />
<Compile Include="Abilities\PsychicBrainburn\CompAbilityEffect_PsychicBrainburn.cs" />
<Compile Include="Abilities\PsychicBrainburn\CompProperties_PsychicBrainburn.cs" />
<Compile Include="Abilities\TrackingCharge\CompAbilityEffect_TrackingCharge.cs" />
<Compile Include="Abilities\TrackingCharge\CompProperties_TrackingCharge.cs" />
<Compile Include="Abilities\TrackingCharge\PawnFlyer_TrackingCharge.cs" />
<Compile Include="Abilities\TrackingCharge\Verb_CastAbilityTrackingCharge.cs" />
<Compile Include="Abilities\ARA_PsychicBrainburn\CompAbilityEffect_PsychicBrainburn.cs" />
<Compile Include="Abilities\ARA_PsychicBrainburn\CompProperties_PsychicBrainburn.cs" />
<Compile Include="Abilities\ARA_TrackingCharge\CompAbilityEffect_TrackingCharge.cs" />
<Compile Include="Abilities\ARA_TrackingCharge\CompProperties_TrackingCharge.cs" />
<Compile Include="Abilities\ARA_TrackingCharge\PawnFlyer_TrackingCharge.cs" />
<Compile Include="Abilities\ARA_TrackingCharge\Verb_CastAbilityTrackingCharge.cs" />
<Compile Include="ArachnaeLog.cs" />
<Compile Include="ArachnaeSwarmMod.cs" />
<Compile Include="ArachnaeSwarmSettings.cs" />
@@ -309,9 +309,8 @@
<Compile Include="Hediffs\HediffComp_TerrainBasedSeverity\HediffCompProperties_TerrainBasedSeverity.cs" />
<Compile Include="Hediffs\HediffComp_TerrainBasedSeverity\HediffComp_TerrainBasedSeverity.cs" />
<Compile Include="Hediffs\HediffGiver_RandomWithSeverity.cs" />
<Compile Include="Hediffs\MoharHediffs\HediffCompProperties_Spawner.cs" />
<Compile Include="Hediffs\MoharHediffs\HediffComp_Spawner.cs" />
<Compile Include="Hediffs\MoharHediffs\Tools.cs" />
<Compile Include="Hediffs\ARA_Spawner\HediffCompProperties_Spawner.cs" />
<Compile Include="Hediffs\ARA_Spawner\HediffComp_Spawner.cs" />
<Compile Include="Hediffs\ProphecyGearEffect.cs" />
<Compile Include="Hediffs\WULA_HediffDamgeShield\DRMDamageShield.cs" />
<Compile Include="Hediffs\WULA_HediffDamgeShield\Hediff_DamageShield.cs" />

View File

@@ -0,0 +1,123 @@
using System;
using Verse;
namespace ArachnaeSwarm
{
public class HediffCompProperties_Spawner : HediffCompProperties
{
public HediffCompProperties_Spawner()
{
this.compClass = typeof(HediffComp_Spawner);
}
/// <summary>
/// 要生成的物品的ThingDef。如果animalThing为false则使用此项。
/// </summary>
public ThingDef thingToSpawn;
/// <summary>
/// 每次生成的基础物品数量。
/// </summary>
public int spawnCount = 1;
/// <summary>
/// 如果为true则生成一个Pawn动物。如果为false则生成一个Thing。
/// </summary>
public bool animalThing;
/// <summary>
/// 要生成的动物的PawnKindDef。如果animalThing为true则使用此项。
/// </summary>
public PawnKindDef animalToSpawn;
/// <summary>
/// 如果为true生成的动物将属于玩家派系。
/// </summary>
public bool factionOfPlayerAnimal;
/// <summary>
/// 下一次生成事件发生前的最少天数。
/// </summary>
public float minDaysB4Next = 1f;
/// <summary>
/// 下一次生成事件发生前的最大天数。
/// </summary>
public float maxDaysB4Next = 2f;
/// <summary>
/// 生成后进入宽限期延迟下一次生成的几率0.0到1.0)。
/// </summary>
public float randomGrace;
/// <summary>
/// 如果触发,宽限期的持续时间(天)。
/// </summary>
public float graceDays = 0.5f;
/// <summary>
/// 附近允许的相同Pawn的最大数量。如果超过该数量则暂停生成。-1为禁用。
/// </summary>
public int spawnMaxAdjacent = -1;
/// <summary>
/// 如果为true生成的物品将被禁用。
/// </summary>
public bool spawnForbidden;
/// <summary>
/// 如果为true当宿主Pawn饥饿时生成将暂停。
/// </summary>
public bool hungerRelative;
/// <summary>
/// 如果为true当宿主Pawn受伤时生成将暂停。
/// </summary>
public bool healthRelative;
/// <summary>
/// 如果为true生成数量将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedQuantity;
/// <summary>
/// 如果为true生成周期两次生成之间的时间将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedPeriod;
/// <summary>
/// 如果为true且ageWeightedPeriod为true则随着宿主年龄增长生成周期变短。如果为false则变长。
/// </summary>
public bool olderSmallerPeriod;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随着宿主年龄增长生成数量变多。如果为false则变少。
/// </summary>
public bool olderBiggerQuantity;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随年龄增长的数量缩放将是指数性的而非线性的。
/// </summary>
public bool exponentialQuantity;
/// <summary>
/// 指数级数量缩放的最大乘数,以防止出现荒谬的数字。
/// </summary>
public int exponentialRatioLimit = 15;
/// <summary>
/// 生成时显示的消息的翻译键(例如,"下了一个蛋")。
/// </summary>
public string spawnVerb = "delivery";
/// <summary>
/// 如果为true则为此组件启用详细的调试日志记录。
/// </summary>
public bool debug;
/// <summary>
/// 新增:生成物品时是否销毁随机身体部位
/// </summary>
public bool destroyRandomBodyPart = false;
}
}

View File

@@ -0,0 +1,883 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using Verse;
namespace ArachnaeSwarm
{
public class HediffComp_Spawner : HediffComp
{
public HediffCompProperties_Spawner Props
{
get
{
return (HediffCompProperties_Spawner)this.props;
}
}
public override void CompExposeData()
{
Scribe_Values.Look<int>(ref this.ticksUntilSpawn, "ticksUntilSpawn", 0, false);
Scribe_Values.Look<int>(ref this.initialTicksUntilSpawn, "initialTicksUntilSpawn", 0, false);
Scribe_Values.Look<float>(ref this.calculatedMinDaysB4Next, "calculatedMinDaysB4Next", 0f, false);
Scribe_Values.Look<float>(ref this.calculatedMaxDaysB4Next, "calculatedMaxDaysB4Next", 0f, false);
Scribe_Values.Look<int>(ref this.calculatedQuantity, "calculatedQuantity", 0, false);
Scribe_Values.Look<int>(ref this.graceTicks, "graceTicks", 0, false);
}
public override void CompPostMake()
{
this.myDebug = this.Props.debug;
Warn(string.Concat(new string[]
{
">>> ",
this.parent.pawn.Label,
" - ",
this.parent.def.defName,
" - CompPostMake start"
}), this.myDebug);
TraceProps();
CheckProps();
CalculateValues();
CheckCalculatedValues();
TraceCalculatedValues();
if (this.initialTicksUntilSpawn == 0)
{
Warn("Reseting countdown bc initialTicksUntilSpawn == 0 (comppostmake)", this.myDebug);
ResetCountdown();
}
}
public override void CompPostTick(ref float severityAdjustment)
{
this.pawn = this.parent.pawn;
if (!OkPawn(this.pawn))
{
return;
}
if (this.blockSpawn)
{
return;
}
if (this.graceTicks > 0)
{
this.graceTicks--;
return;
}
if (this.Props.hungerRelative && this.pawn.Starving())
{
int num = (int)(this.RandomGraceDays() * 60000f);
this.hungerReset++;
this.graceTicks = num;
return;
}
if (this.Props.healthRelative && this.pawn.Starving())
{
int num2 = (int)(this.RandomGraceDays() * 60000f);
this.healthReset++;
this.graceTicks = num2;
return;
}
this.hungerReset = (this.healthReset = 0);
if (this.CheckShouldSpawn())
{
Warn("Reseting countdown bc spawned thing", this.myDebug);
CalculateValues();
CheckCalculatedValues();
ResetCountdown();
if (Rand.Chance(this.Props.randomGrace))
{
int num3 = (int)(this.RandomGraceDays() * 60000f);
this.graceTicks = num3;
}
}
}
private void TraceProps()
{
Warn(string.Concat(new string[]
{
"Props => minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
"; maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
"; randomGrace: ",
this.Props.randomGrace.ToString(),
"; graceDays: ",
this.Props.graceDays.ToString(),
"; hungerRelative: ",
this.Props.hungerRelative.ToString(),
"; healthRelative: ",
this.Props.healthRelative.ToString(),
"; destroyRandomBodyPart: ",
this.Props.destroyRandomBodyPart.ToString(),
"; "
}), this.myDebug);
if (this.Props.animalThing)
{
Warn(string.Concat(new string[]
{
"animalThing: ",
this.Props.animalThing.ToString(),
"; animalName: ",
this.Props.animalToSpawn.defName,
"; factionOfPlayerAnimal: ",
this.Props.factionOfPlayerAnimal.ToString(),
"; "
}), this.myDebug);
}
if (this.Props.ageWeightedQuantity)
{
Warn(string.Concat(new string[]
{
"ageWeightedQuantity:",
this.Props.ageWeightedQuantity.ToString(),
"; olderBiggerQuantity:",
this.Props.olderBiggerQuantity.ToString(),
"; ",
this.myDebug.ToString()
}), false);
if (this.Props.exponentialQuantity)
{
Warn(string.Concat(new string[]
{
"exponentialQuantity:",
this.Props.exponentialQuantity.ToString(),
"; exponentialRatioLimit:",
this.Props.exponentialRatioLimit.ToString(),
"; "
}), this.myDebug);
}
}
Warn(string.Concat(new string[]
{
"ageWeightedPeriod:",
this.Props.ageWeightedPeriod.ToString(),
"; olderSmallerPeriod:",
this.Props.olderSmallerPeriod.ToString(),
"; ",
this.myDebug.ToString()
}), false);
}
private void CalculateValues()
{
float num = GetPawnAgeOverlifeExpectancyRatio(this.parent.pawn, this.myDebug);
num = ((num > 1f) ? 1f : num);
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next;
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next;
if (this.Props.ageWeightedPeriod)
{
float num2 = this.Props.olderSmallerPeriod ? (-num) : num;
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next * (1f + num2);
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next * (1f + num2);
Warn(string.Concat(new string[]
{
" ageWeightedPeriod: ",
this.Props.ageWeightedPeriod.ToString(),
" ageRatio: ",
num.ToString(),
" minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
" maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
" daysAgeRatio: ",
num2.ToString(),
" calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"; calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"; "
}), this.myDebug);
}
this.calculatedQuantity = this.Props.spawnCount;
if (this.Props.ageWeightedQuantity)
{
float num3 = this.Props.olderBiggerQuantity ? num : (-num);
Warn("quantityAgeRatio: " + num3.ToString(), this.myDebug);
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)(1f + num3));
if (this.Props.exponentialQuantity)
{
num3 = 1f - num;
if (num3 == 0f)
{
Warn(">ERROR< quantityAgeRatio is f* up : " + num3.ToString(), this.myDebug);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
float num4 = this.Props.olderBiggerQuantity ? (1f / num3) : (num3 * num3);
bool flag = false;
bool flag2 = false;
if (num4 > (float)this.Props.exponentialRatioLimit)
{
num4 = (float)this.Props.exponentialRatioLimit;
flag = true;
}
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)num4);
if (this.calculatedQuantity < 1)
{
this.calculatedQuantity = 1;
flag2 = true;
}
Warn(string.Concat(new string[]
{
" exponentialQuantity: ",
this.Props.exponentialQuantity.ToString(),
"; expoFactor: ",
num4.ToString(),
"; gotLimited: ",
flag.ToString(),
"; gotAugmented: ",
flag2.ToString()
}), this.myDebug);
}
Warn("; Props.spawnCount: " + this.Props.spawnCount.ToString() + "; calculatedQuantity: " + this.calculatedQuantity.ToString(), this.myDebug);
}
}
private void CheckCalculatedValues()
{
if (this.calculatedQuantity > this.errorSpawnCount)
{
Warn(string.Concat(new string[]
{
">ERROR< calculatedQuantity is too high: ",
this.calculatedQuantity.ToString(),
"(>",
this.errorSpawnCount.ToString(),
"), check and adjust your hediff props"
}), this.myDebug);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.calculatedMinDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMinDaysB4Next = this.errorMinDaysB4Next;
}
if (this.calculatedMaxDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMaxDaysB4Next = this.errorMinDaysB4Next;
}
}
private void TraceCalculatedValues()
{
Warn("calculatedMinDaysB4Next:" + this.calculatedMinDaysB4Next.ToString(), this.myDebug);
Warn("calculatedMaxDaysB4Next:" + this.calculatedMaxDaysB4Next.ToString(), this.myDebug);
Warn("calculatedQuantity:" + this.calculatedQuantity.ToString(), this.myDebug);
}
private void CheckProps()
{
if (this.Props.animalThing && this.Props.animalToSpawn == null)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with animalflag but without animalToSpawn", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.minDaysB4Next <= 0f)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative minDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next <= 0f)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative maxDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next < this.Props.minDaysB4Next)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with maxDaysB4Next < minDaysB4Next", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.spawnCount <= 0)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative spawnCount", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (!this.Props.animalThing && this.Props.thingToSpawn == null)
{
Warn(this.parent.pawn.Label + " has a hediffcomp_spawner without thingToSpawn", true);
this.blockSpawn = true;
DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.ageWeightedQuantity && this.Props.exponentialQuantity && this.Props.exponentialRatioLimit > this.errorExponentialLimit)
{
Warn(string.Concat(new string[]
{
this.parent.pawn.Label,
" has a hediffcomp_spawner with exponentialRatioLimit>",
this.errorExponentialLimit.ToString(),
" this is not allowed. It will be set to ",
this.errorExponentialLimit.ToString()
}), true);
this.Props.exponentialRatioLimit = this.errorExponentialLimit;
}
}
private bool CheckShouldSpawn()
{
this.ticksUntilSpawn--;
if (this.ticksUntilSpawn <= 0)
{
if (this.TryDoSpawn())
{
return true;
}
Warn("Did not spawn, reseting countdown", this.myDebug);
ResetCountdown();
}
return false;
}
public bool TryDoSpawn()
{
Pawn pawn = this.parent.pawn;
if (this.Props.animalThing)
{
// 动物生成逻辑保持不变
if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP)
{
ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn;
if (defToCompare?.race == null)
{
return false;
}
return mP.def == defToCompare && mP.Position.InHorDistOf(pawn.Position, (float)this.Props.spawnMaxAdjacent);
}).Count<Pawn>() >= this.Props.spawnMaxAdjacent)
{
return false;
}
if (this.Props.animalToSpawn == null)
{
return false;
}
Faction faction = this.Props.factionOfPlayerAnimal ? Faction.OfPlayer : null;
int i = 0;
while (i < this.calculatedQuantity)
{
IntVec3 intVec;
if (!this.TryFindSpawnCell(out intVec))
{
return false;
}
Pawn pawn2 = PawnGenerator.GeneratePawn(this.Props.animalToSpawn, faction);
if (pawn2 == null)
{
return false;
}
GenSpawn.Spawn(pawn2, intVec, pawn.Map, WipeMode.Vanish);
pawn2.SetFaction(faction, null);
FilthMaker.TryMakeFilth(intVec, pawn.Map, ThingDefOf.Filth_AmnioticFluid, pawn.LabelIndefinite(), 5, FilthSourceFlags.None);
if (!this.Props.spawnForbidden)
{
pawn2.playerSettings.Master = pawn;
pawn2.training.Train(TrainableDefOf.Obedience, pawn, true);
}
i++;
continue;
}
if (PawnUtility.ShouldSendNotificationAbout(pawn) || PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.PositiveEvent, true);
}
return true;
}
else
{
// 重新设计物品生成逻辑:按优先级顺序尝试
bool success = TrySpawnItemWithPriority(pawn);
// === 新增:如果配置了销毁随机部位,在成功生成物品后执行 ===
if (success && this.Props.destroyRandomBodyPart)
{
DestroyRandomBodyPart(pawn);
}
return success;
}
}
// 新增:销毁随机身体部位(参考 CompAbilityEffect_DestroyOwnBodyPart
private void DestroyRandomBodyPart(Pawn pawn)
{
try
{
if (pawn == null || pawn.Dead)
{
Warn($"Cannot destroy body part for null or dead pawn", this.myDebug);
return;
}
// 获取所有可以销毁的身体部位
List<BodyPartRecord> possibleParts = GetDestroyableBodyParts(pawn);
if (possibleParts.Count == 0)
{
Warn($"No destroyable body parts found for {pawn.LabelShort}", this.myDebug);
return;
}
// 随机选择一个部位
BodyPartRecord partToDestroy = possibleParts.RandomElement();
if (partToDestroy == null)
{
Warn($"Selected null body part for {pawn.LabelShort}", this.myDebug);
return;
}
// 记录部位名称
string partName = partToDestroy.def?.label ?? "未知部位";
// 添加缺失部位hediff
pawn.health.AddHediff(HediffDefOf.MissingBodyPart, partToDestroy);
Warn($"Destroyed {partName} on {pawn.LabelShort}", this.myDebug);
}
catch (Exception ex)
{
Log.Error($"Error destroying random body part for {pawn?.LabelShort}: {ex.Message}");
}
}
// 新增:获取可以销毁的身体部位列表
private List<BodyPartRecord> GetDestroyableBodyParts(Pawn pawn)
{
List<BodyPartRecord> destroyableParts = new List<BodyPartRecord>();
if (pawn?.health?.hediffSet == null)
return destroyableParts;
// 获取所有身体部位
List<BodyPartRecord> allParts = pawn.RaceProps.body.AllParts;
foreach (BodyPartRecord part in allParts)
{
// 排除核心部位(避免死亡)
if (IsCriticalBodyPart(part))
continue;
// 排除已经缺失的部位
if (pawn.health.hediffSet.PartIsMissing(part))
continue;
// 排除已经有严重伤害的部位(可选)
if (pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(part))
continue;
destroyableParts.Add(part);
}
return destroyableParts;
}
// 新增:检查是否是关键身体部位
private bool IsCriticalBodyPart(BodyPartRecord part)
{
if (part == null)
return false;
// 根据标签判断是否为关键部位
if (part.def.tags != null)
{
// 这些标签通常表示关键部位
if (part.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 意识源(大脑)
part.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 血液泵源(心脏)
part.def.tags.Contains(BodyPartTagDefOf.BreathingSource) || // 呼吸源(肺)
part.def.tags.Contains(BodyPartTagDefOf.MetabolismSource) // 代谢源(肝脏)
)
{
return true;
}
}
// 根据深度判断(深度较深的通常是内部器官)
if (part.depth == BodyPartDepth.Inside && part.parent == null)
return true;
return false;
}
// 新增:按优先级顺序尝试生成物品
private bool TrySpawnItemWithPriority(Pawn pawn)
{
Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null);
if (thing == null)
{
Warn("Failed to create thing: " + this.Props.thingToSpawn?.defName, this.myDebug);
return false;
}
thing.stackCount = this.calculatedQuantity;
// 记录原始物品用于校验
ThingDef originalThingDef = thing.def;
int originalStackCount = thing.stackCount;
// 按优先级顺序尝试生成
bool success = false;
string spawnMethod = "";
// 优先级1: 尝试在pawn附近空地生成
if (!success)
{
success = TrySpawnAtNearbyEmptyCell(pawn, thing, ref spawnMethod);
}
// 优先级2: 尝试在pawn脚底生成
if (!success)
{
success = TrySpawnAtPawnPosition(pawn, thing, ref spawnMethod);
}
// 优先级3: 尝试放入pawn物品栏
if (!success)
{
success = TrySpawnInInventory(pawn, thing, ref spawnMethod);
}
// 生成后校验
if (success)
{
bool verified = VerifySpawnSuccess(pawn, originalThingDef, originalStackCount, spawnMethod);
// 如果校验失败且不是在物品栏中生成的,尝试在物品栏中重新生成
if (!verified && spawnMethod != "inventory")
{
Warn($"Spawn verification failed for {spawnMethod}, attempting inventory fallback", this.myDebug);
// 重新创建物品
Thing fallbackThing = ThingMaker.MakeThing(originalThingDef, null);
fallbackThing.stackCount = originalStackCount;
success = TrySpawnInInventory(pawn, fallbackThing, ref spawnMethod);
if (success)
{
verified = VerifySpawnSuccess(pawn, originalThingDef, originalStackCount, "inventory_fallback");
if (!verified)
{
Warn("Inventory fallback also failed verification", this.myDebug);
success = false;
}
}
}
if (success && verified)
{
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")),
spawnMethod == "inventory" || spawnMethod == "inventory_fallback" ? pawn : thing,
MessageTypeDefOf.PositiveEvent, true);
}
Warn($"Successfully spawned {originalStackCount}x {originalThingDef.defName} via {spawnMethod}", this.myDebug);
return true;
}
}
Warn($"Failed to spawn {originalStackCount}x {originalThingDef.defName} after all attempts", this.myDebug);
return false;
}
// 新增:尝试在附近空地生成
private bool TrySpawnAtNearbyEmptyCell(Pawn pawn, Thing thing, ref string spawnMethod)
{
IntVec3 spawnCell;
if (TryFindSpawnCell(out spawnCell))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
if (GenPlace.TryPlaceThing(thing, spawnCell, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)))
{
spawnMethod = "nearby_cell";
return true;
}
}
return false;
}
// 新增尝试在pawn位置生成
private bool TrySpawnAtPawnPosition(Pawn pawn, Thing thing, ref string spawnMethod)
{
IntVec3 pawnPosition = pawn.Position;
if (pawnPosition.IsValid && pawnPosition.Walkable(pawn.Map))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
if (GenPlace.TryPlaceThing(thing, pawnPosition, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)))
{
spawnMethod = "pawn_position";
return true;
}
}
return false;
}
// 新增:尝试在物品栏生成
private bool TrySpawnInInventory(Pawn pawn, Thing thing, ref string spawnMethod)
{
if (pawn.inventory == null)
{
Warn($"Pawn {pawn.Label} does not have an inventory", this.myDebug);
return false;
}
if (pawn.inventory.innerContainer.TryAdd(thing))
{
spawnMethod = "inventory";
return true;
}
return false;
}
// 新增:生成后校验
private bool VerifySpawnSuccess(Pawn pawn, ThingDef thingDef, int expectedCount, string spawnMethod)
{
bool verificationSuccess = false;
switch (spawnMethod)
{
case "nearby_cell":
case "pawn_position":
case "inventory_fallback":
// 检查地图上是否有生成的物品
verificationSuccess = pawn.Map.listerThings.ThingsOfDef(thingDef)
.Any(t => t.stackCount >= expectedCount && t.Position.InHorDistOf(pawn.Position, 2f));
break;
case "inventory":
// 检查物品栏中是否有生成的物品
verificationSuccess = pawn.inventory.innerContainer.Any(t => t.def == thingDef && t.stackCount >= expectedCount);
break;
}
if (!verificationSuccess)
{
Warn($"Spawn verification failed for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
else
{
Warn($"Spawn verification successful for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
return verificationSuccess;
}
private bool TryFindSpawnCell(out IntVec3 result)
{
result = IntVec3.Invalid;
bool result2;
if (this.pawn == null)
{
result2 = false;
}
else
{
Map map = this.pawn.Map;
if (map == null)
{
result2 = false;
}
else
{
// 修改这里将半径从5减少到2让生成位置更靠近pawn
int searchRadius = 2;
// 首先尝试在pawn的相邻单元格生成半径为1
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null);
// 如果相邻单元格找不到合适位置再尝试稍远一点半径为2
if (!result.IsValid)
{
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null);
}
// 如果还是找不到尝试pawn当前位置作为最后手段
if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map))
{
result = this.pawn.Position;
}
result2 = result.IsValid;
}
}
return result2;
}
private void ResetCountdown()
{
this.ticksUntilSpawn = (int)(this.RandomDays2wait() * 60000f);
this.initialTicksUntilSpawn = this.ticksUntilSpawn;
}
private float RandomDays2wait()
{
return Rand.Range(this.calculatedMinDaysB4Next, this.calculatedMaxDaysB4Next);
}
private float RandomGraceDays()
{
return Rand.Range(this.Props.graceDays / 2f, this.Props.graceDays);
}
public override string CompTipStringExtra
{
get
{
if (!this.myDebug)
{
return null;
}
string text = "ticksUntilSpawn: " + this.ticksUntilSpawn.ToString() + "\n";
string text2 = text;
text = string.Concat(new string[]
{
text2,
"initialTicksUntilSpawn: ",
this.initialTicksUntilSpawn.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"graceTicks: ",
this.graceTicks.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"hunger resets: ",
this.hungerReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"health resets: ",
this.healthReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedQuantity: ",
this.calculatedQuantity.ToString(),
"\n"
});
return text + "blockSpawn: " + this.blockSpawn.ToString();
}
}
// === 整合的 Tools 方法 ===
private void DestroyParentHediff(Hediff parentHediff, bool debug = false)
{
if (parentHediff.pawn != null && parentHediff.def.defName != null && debug)
{
Warn(parentHediff.pawn.Label + "'s Hediff: " + parentHediff.def.defName + " says goodbye.", debug);
}
parentHediff.Severity = 0f;
}
private float GetPawnAgeOverlifeExpectancyRatio(Pawn pawn, bool debug = false)
{
float result = 1f;
if (pawn == null)
{
if (debug)
{
Warn("GetPawnAgeOverlifeExpectancyRatio pawn NOT OK", debug);
}
return result;
}
result = pawn.ageTracker.AgeBiologicalYearsFloat / pawn.RaceProps.lifeExpectancy;
if (debug)
{
Warn(string.Concat(new string[]
{
pawn.Label,
" Age: ",
pawn.ageTracker.AgeBiologicalYearsFloat.ToString(),
"; lifeExpectancy: ",
pawn.RaceProps.lifeExpectancy.ToString(),
"; ratio:",
result.ToString()
}), debug);
}
return result;
}
private bool OkPawn(Pawn pawn)
{
return pawn != null && pawn.Map != null;
}
private void Warn(string warning, bool debug = false)
{
if (debug)
{
Log.Message($"[HediffComp_Spawner] {warning}");
}
}
private int ticksUntilSpawn;
private int initialTicksUntilSpawn;
private int hungerReset;
private int healthReset;
private int graceTicks;
private Pawn pawn;
private float calculatedMaxDaysB4Next = 2f;
private float calculatedMinDaysB4Next = 1f;
private int calculatedQuantity = 1;
private bool blockSpawn;
private bool myDebug;
private readonly float errorMinDaysB4Next = 0.001f;
private readonly int errorExponentialLimit = 20;
private readonly int errorSpawnCount = 750;
}
}

View File

@@ -1,118 +0,0 @@
using System;
using Verse;
namespace ArachnaeSwarm.MoharHediffs
{
public class HediffCompProperties_Spawner : HediffCompProperties
{
public HediffCompProperties_Spawner()
{
this.compClass = typeof(HediffComp_Spawner);
}
/// <summary>
/// 要生成的物品的ThingDef。如果animalThing为false则使用此项。
/// </summary>
public ThingDef thingToSpawn;
/// <summary>
/// 每次生成的基础物品数量。
/// </summary>
public int spawnCount = 1;
/// <summary>
/// 如果为true则生成一个Pawn动物。如果为false则生成一个Thing。
/// </summary>
public bool animalThing;
/// <summary>
/// 要生成的动物的PawnKindDef。如果animalThing为true则使用此项。
/// </summary>
public PawnKindDef animalToSpawn;
/// <summary>
/// 如果为true生成的动物将属于玩家派系。
/// </summary>
public bool factionOfPlayerAnimal;
/// <summary>
/// 下一次生成事件发生前的最少天数。
/// </summary>
public float minDaysB4Next = 1f;
/// <summary>
/// 下一次生成事件发生前的最大天数。
/// </summary>
public float maxDaysB4Next = 2f;
/// <summary>
/// 生成后进入宽限期延迟下一次生成的几率0.0到1.0)。
/// </summary>
public float randomGrace;
/// <summary>
/// 如果触发,宽限期的持续时间(天)。
/// </summary>
public float graceDays = 0.5f;
/// <summary>
/// 附近允许的相同Pawn的最大数量。如果超过该数量则暂停生成。-1为禁用。
/// </summary>
public int spawnMaxAdjacent = -1;
/// <summary>
/// 如果为true生成的物品将被禁用。
/// </summary>
public bool spawnForbidden;
/// <summary>
/// 如果为true当宿主Pawn饥饿时生成将暂停。
/// </summary>
public bool hungerRelative;
/// <summary>
/// 如果为true当宿主Pawn受伤时生成将暂停。
/// </summary>
public bool healthRelative;
/// <summary>
/// 如果为true生成数量将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedQuantity;
/// <summary>
/// 如果为true生成周期两次生成之间的时间将根据宿主的年龄进行调整。
/// </summary>
public bool ageWeightedPeriod;
/// <summary>
/// 如果为true且ageWeightedPeriod为true则随着宿主年龄增长生成周期变短。如果为false则变长。
/// </summary>
public bool olderSmallerPeriod;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随着宿主年龄增长生成数量变多。如果为false则变少。
/// </summary>
public bool olderBiggerQuantity;
/// <summary>
/// 如果为true且ageWeightedQuantity为true则随年龄增长的数量缩放将是指数性的而非线性的。
/// </summary>
public bool exponentialQuantity;
/// <summary>
/// 指数级数量缩放的最大乘数,以防止出现荒谬的数字。
/// </summary>
public int exponentialRatioLimit = 15;
/// <summary>
/// 生成时显示的消息的翻译键(例如,“{PAWN}下了一个蛋。”)。
/// </summary>
public string spawnVerb = "delivery";
/// <summary>
/// 如果为true则为此组件启用详细的调试日志记录。
/// </summary>
public bool debug;
}
}

View File

@@ -1,739 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using RimWorld;
using RimWorld.Planet;
using Verse;
namespace ArachnaeSwarm.MoharHediffs
{
public class HediffComp_Spawner : HediffComp
{
public HediffCompProperties_Spawner Props
{
get
{
return (HediffCompProperties_Spawner)this.props;
}
}
public override void CompExposeData()
{
Scribe_Values.Look<int>(ref this.ticksUntilSpawn, "ticksUntilSpawn", 0, false);
Scribe_Values.Look<int>(ref this.initialTicksUntilSpawn, "initialTicksUntilSpawn", 0, false);
Scribe_Values.Look<float>(ref this.calculatedMinDaysB4Next, "calculatedMinDaysB4Next", 0f, false);
Scribe_Values.Look<float>(ref this.calculatedMaxDaysB4Next, "calculatedMaxDaysB4Next", 0f, false);
Scribe_Values.Look<int>(ref this.calculatedQuantity, "calculatedQuantity", 0, false);
Scribe_Values.Look<int>(ref this.graceTicks, "graceTicks", 0, false);
}
public override void CompPostMake()
{
this.myDebug = this.Props.debug;
Tools.Warn(string.Concat(new string[]
{
">>> ",
this.parent.pawn.Label,
" - ",
this.parent.def.defName,
" - CompPostMake start"
}), this.myDebug);
this.TraceProps();
this.CheckProps();
this.CalculateValues();
this.CheckCalculatedValues();
this.TraceCalculatedValues();
if (this.initialTicksUntilSpawn == 0)
{
Tools.Warn("Reseting countdown bc initialTicksUntilSpawn == 0 (comppostmake)", this.myDebug);
this.ResetCountdown();
}
}
public override void CompPostTick(ref float severityAdjustment)
{
this.pawn = this.parent.pawn;
if (!Tools.OkPawn(this.pawn))
{
return;
}
if (this.blockSpawn)
{
return;
}
if (this.graceTicks > 0)
{
this.graceTicks--;
return;
}
if (this.Props.hungerRelative && this.pawn.IsHungry(this.myDebug))
{
int num = (int)(this.RandomGraceDays() * 60000f);
this.hungerReset++;
this.graceTicks = num;
return;
}
if (this.Props.healthRelative && this.pawn.IsInjured(this.myDebug))
{
int num2 = (int)(this.RandomGraceDays() * 60000f);
this.healthReset++;
this.graceTicks = num2;
return;
}
this.hungerReset = (this.healthReset = 0);
if (this.CheckShouldSpawn())
{
Tools.Warn("Reseting countdown bc spawned thing", this.myDebug);
this.CalculateValues();
this.CheckCalculatedValues();
this.ResetCountdown();
if (Rand.Chance(this.Props.randomGrace))
{
int num3 = (int)(this.RandomGraceDays() * 60000f);
this.graceTicks = num3;
}
}
}
private void TraceProps()
{
Tools.Warn(string.Concat(new string[]
{
"Props => minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
"; maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
"; randomGrace: ",
this.Props.randomGrace.ToString(),
"; graceDays: ",
this.Props.graceDays.ToString(),
"; hungerRelative: ",
this.Props.hungerRelative.ToString(),
"; healthRelative: ",
this.Props.healthRelative.ToString(),
"; "
}), this.myDebug);
if (this.Props.animalThing)
{
Tools.Warn(string.Concat(new string[]
{
"animalThing: ",
this.Props.animalThing.ToString(),
"; animalName: ",
this.Props.animalToSpawn.defName,
"; factionOfPlayerAnimal: ",
this.Props.factionOfPlayerAnimal.ToString(),
"; "
}), this.myDebug);
}
if (this.Props.ageWeightedQuantity)
{
Tools.Warn(string.Concat(new string[]
{
"ageWeightedQuantity:",
this.Props.ageWeightedQuantity.ToString(),
"; olderBiggerQuantity:",
this.Props.olderBiggerQuantity.ToString(),
"; ",
this.myDebug.ToString()
}), false);
if (this.Props.exponentialQuantity)
{
Tools.Warn(string.Concat(new string[]
{
"exponentialQuantity:",
this.Props.exponentialQuantity.ToString(),
"; exponentialRatioLimit:",
this.Props.exponentialRatioLimit.ToString(),
"; "
}), this.myDebug);
}
}
Tools.Warn(string.Concat(new string[]
{
"ageWeightedPeriod:",
this.Props.ageWeightedPeriod.ToString(),
"; olderSmallerPeriod:",
this.Props.olderSmallerPeriod.ToString(),
"; ",
this.myDebug.ToString()
}), false);
}
private void CalculateValues()
{
float num = Tools.GetPawnAgeOverlifeExpectancyRatio(this.parent.pawn, this.myDebug);
num = ((num > 1f) ? 1f : num);
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next;
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next;
if (this.Props.ageWeightedPeriod)
{
float num2 = this.Props.olderSmallerPeriod ? (-num) : num;
this.calculatedMinDaysB4Next = this.Props.minDaysB4Next * (1f + num2);
this.calculatedMaxDaysB4Next = this.Props.maxDaysB4Next * (1f + num2);
Tools.Warn(string.Concat(new string[]
{
" ageWeightedPeriod: ",
this.Props.ageWeightedPeriod.ToString(),
" ageRatio: ",
num.ToString(),
" minDaysB4Next: ",
this.Props.minDaysB4Next.ToString(),
" maxDaysB4Next: ",
this.Props.maxDaysB4Next.ToString(),
" daysAgeRatio: ",
num2.ToString(),
" calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"; calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"; "
}), this.myDebug);
}
this.calculatedQuantity = this.Props.spawnCount;
if (this.Props.ageWeightedQuantity)
{
float num3 = this.Props.olderBiggerQuantity ? num : (-num);
Tools.Warn("quantityAgeRatio: " + num3.ToString(), this.myDebug);
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)(1f + num3));
if (this.Props.exponentialQuantity)
{
num3 = 1f - num;
if (num3 == 0f)
{
Tools.Warn(">ERROR< quantityAgeRatio is f* up : " + num3.ToString(), this.myDebug);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
float num4 = this.Props.olderBiggerQuantity ? (1f / num3) : (num3 * num3);
bool flag = false;
bool flag2 = false;
if (num4 > (float)this.Props.exponentialRatioLimit)
{
num4 = (float)this.Props.exponentialRatioLimit;
flag = true;
}
this.calculatedQuantity = (int)Math.Round((double)this.Props.spawnCount * (double)num4);
if (this.calculatedQuantity < 1)
{
this.calculatedQuantity = 1;
flag2 = true;
}
Tools.Warn(string.Concat(new string[]
{
" exponentialQuantity: ",
this.Props.exponentialQuantity.ToString(),
"; expoFactor: ",
num4.ToString(),
"; gotLimited: ",
flag.ToString(),
"; gotAugmented: ",
flag2.ToString()
}), this.myDebug);
}
Tools.Warn("; Props.spawnCount: " + this.Props.spawnCount.ToString() + "; calculatedQuantity: " + this.calculatedQuantity.ToString(), this.myDebug);
}
}
private void CheckCalculatedValues()
{
if (this.calculatedQuantity > this.errorSpawnCount)
{
Tools.Warn(string.Concat(new string[]
{
">ERROR< calculatedQuantity is too high: ",
this.calculatedQuantity.ToString(),
"(>",
this.errorSpawnCount.ToString(),
"), check and adjust your hediff props"
}), this.myDebug);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.calculatedMinDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMinDaysB4Next = this.errorMinDaysB4Next;
}
if (this.calculatedMaxDaysB4Next < this.errorMinDaysB4Next)
{
this.calculatedMaxDaysB4Next = this.errorMinDaysB4Next;
}
}
private void TraceCalculatedValues()
{
Tools.Warn("calculatedMinDaysB4Next:" + this.calculatedMinDaysB4Next.ToString(), this.myDebug);
Tools.Warn("calculatedMaxDaysB4Next:" + this.calculatedMaxDaysB4Next.ToString(), this.myDebug);
Tools.Warn("calculatedQuantity:" + this.calculatedQuantity.ToString(), this.myDebug);
}
private void CheckProps()
{
if (this.Props.animalThing && this.Props.animalToSpawn == null)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with animalflag but without animalToSpawn", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.minDaysB4Next <= 0f)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative minDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next <= 0f)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative maxDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.maxDaysB4Next < this.Props.minDaysB4Next)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with maxDaysB4Next < minDaysB4Next", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.spawnCount <= 0)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner with null/negative spawnCount", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (!this.Props.animalThing && this.Props.thingToSpawn == null)
{
Tools.Warn(this.parent.pawn.Label + " has a hediffcomp_spawner without thingToSpawn", true);
this.blockSpawn = true;
Tools.DestroyParentHediff(this.parent, this.myDebug);
return;
}
if (this.Props.ageWeightedQuantity && this.Props.exponentialQuantity && this.Props.exponentialRatioLimit > this.errorExponentialLimit)
{
Tools.Warn(string.Concat(new string[]
{
this.parent.pawn.Label,
" has a hediffcomp_spawner with exponentialRatioLimit>",
this.errorExponentialLimit.ToString(),
" this is not allowed. It will be set to ",
this.errorExponentialLimit.ToString()
}), true);
this.Props.exponentialRatioLimit = this.errorExponentialLimit;
}
}
private bool CheckShouldSpawn()
{
this.ticksUntilSpawn--;
if (this.ticksUntilSpawn <= 0)
{
if (this.TryDoSpawn())
{
return true;
}
Tools.Warn("Did not spawn, reseting countdown", this.myDebug);
this.ResetCountdown();
}
return false;
}
private PawnKindDef MyPawnKindDefNamed(string myDefName)
{
return DefDatabase<PawnKindDef>.GetNamed(myDefName, true);
}
public bool TryDoSpawn()
{
Pawn pawn = this.parent.pawn;
if (this.Props.animalThing)
{
// 动物生成逻辑保持不变
if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP)
{
ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn;
if (defToCompare?.race == null)
{
return false;
}
return mP.def == defToCompare && mP.Position.InHorDistOf(pawn.Position, (float)this.Props.spawnMaxAdjacent);
}).Count<Pawn>() >= this.Props.spawnMaxAdjacent)
{
return false;
}
if (this.Props.animalToSpawn == null)
{
return false;
}
Faction faction = this.Props.factionOfPlayerAnimal ? Faction.OfPlayer : null;
int i = 0;
while (i < this.calculatedQuantity)
{
IntVec3 intVec;
if (!this.TryFindSpawnCell(out intVec))
{
return false;
}
Pawn pawn2 = PawnGenerator.GeneratePawn(this.Props.animalToSpawn, faction);
if (pawn2 == null)
{
return false;
}
GenSpawn.Spawn(pawn2, intVec, pawn.Map, WipeMode.Vanish);
pawn2.SetFaction(faction, null);
FilthMaker.TryMakeFilth(intVec, pawn.Map, ThingDefOf.Filth_AmnioticFluid, pawn.LabelIndefinite(), 5, FilthSourceFlags.None);
if (!this.Props.spawnForbidden)
{
pawn2.playerSettings.Master = pawn;
pawn2.training.Train(TrainableDefOf.Obedience, pawn, true);
}
i++;
continue;
}
if (PawnUtility.ShouldSendNotificationAbout(pawn) || PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN")), pawn, MessageTypeDefOf.PositiveEvent, true);
}
return true;
}
else
{
// 重新设计物品生成逻辑:按优先级顺序尝试
return TrySpawnItemWithPriority(pawn);
}
}
// 新增:按优先级顺序尝试生成物品
private bool TrySpawnItemWithPriority(Pawn pawn)
{
Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null);
if (thing == null)
{
Tools.Warn("Failed to create thing: " + this.Props.thingToSpawn?.defName, this.myDebug);
return false;
}
thing.stackCount = this.calculatedQuantity;
// 记录原始物品用于校验
ThingDef originalThingDef = thing.def;
int originalStackCount = thing.stackCount;
// 按优先级顺序尝试生成
bool success = false;
string spawnMethod = "";
// 优先级1: 尝试在pawn附近空地生成
if (!success)
{
success = TrySpawnAtNearbyEmptyCell(pawn, thing, ref spawnMethod);
}
// 优先级2: 尝试在pawn脚底生成
if (!success)
{
success = TrySpawnAtPawnPosition(pawn, thing, ref spawnMethod);
}
// 优先级3: 尝试放入pawn物品栏
if (!success)
{
success = TrySpawnInInventory(pawn, thing, ref spawnMethod);
}
// 生成后校验
if (success)
{
bool verified = VerifySpawnSuccess(pawn, originalThingDef, originalStackCount, spawnMethod);
// 如果校验失败且不是在物品栏中生成的,尝试在物品栏中重新生成
if (!verified && spawnMethod != "inventory")
{
Tools.Warn($"Spawn verification failed for {spawnMethod}, attempting inventory fallback", this.myDebug);
// 重新创建物品
Thing fallbackThing = ThingMaker.MakeThing(originalThingDef, null);
fallbackThing.stackCount = originalStackCount;
success = TrySpawnInInventory(pawn, fallbackThing, ref spawnMethod);
if (success)
{
verified = VerifySpawnSuccess(pawn, originalThingDef, originalStackCount, "inventory_fallback");
if (!verified)
{
Tools.Warn("Inventory fallback also failed verification", this.myDebug);
success = false;
}
}
}
if (success && verified)
{
if (PawnUtility.ShouldSendNotificationAbout(pawn))
{
Messages.Message(this.Props.spawnVerb.Translate(pawn.Named("PAWN"), thing.Named("THING")),
spawnMethod == "inventory" || spawnMethod == "inventory_fallback" ? pawn : thing,
MessageTypeDefOf.PositiveEvent, true);
}
Tools.Warn($"Successfully spawned {originalStackCount}x {originalThingDef.defName} via {spawnMethod}", this.myDebug);
return true;
}
}
Tools.Warn($"Failed to spawn {originalStackCount}x {originalThingDef.defName} after all attempts", this.myDebug);
return false;
}
// 新增:尝试在附近空地生成
private bool TrySpawnAtNearbyEmptyCell(Pawn pawn, Thing thing, ref string spawnMethod)
{
IntVec3 spawnCell;
if (TryFindSpawnCell(out spawnCell))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
if (GenPlace.TryPlaceThing(thing, spawnCell, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)))
{
spawnMethod = "nearby_cell";
return true;
}
}
return false;
}
// 新增尝试在pawn位置生成
private bool TrySpawnAtPawnPosition(Pawn pawn, Thing thing, ref string spawnMethod)
{
IntVec3 pawnPosition = pawn.Position;
if (pawnPosition.IsValid && pawnPosition.Walkable(pawn.Map))
{
if (this.Props.spawnForbidden)
{
thing.SetForbidden(true, true);
}
if (GenPlace.TryPlaceThing(thing, pawnPosition, pawn.Map, ThingPlaceMode.Direct, null, null, default(Rot4)))
{
spawnMethod = "pawn_position";
return true;
}
}
return false;
}
// 新增:尝试在物品栏生成
private bool TrySpawnInInventory(Pawn pawn, Thing thing, ref string spawnMethod)
{
if (pawn.inventory == null)
{
Tools.Warn($"Pawn {pawn.Label} does not have an inventory", this.myDebug);
return false;
}
if (pawn.inventory.innerContainer.TryAdd(thing))
{
spawnMethod = "inventory";
return true;
}
return false;
}
// 新增:生成后校验
private bool VerifySpawnSuccess(Pawn pawn, ThingDef thingDef, int expectedCount, string spawnMethod)
{
bool verificationSuccess = false;
switch (spawnMethod)
{
case "nearby_cell":
case "pawn_position":
case "inventory_fallback":
// 检查地图上是否有生成的物品
verificationSuccess = pawn.Map.listerThings.ThingsOfDef(thingDef)
.Any(t => t.stackCount >= expectedCount && t.Position.InHorDistOf(pawn.Position, 2f));
break;
case "inventory":
// 检查物品栏中是否有生成的物品
verificationSuccess = pawn.inventory.innerContainer.Any(t => t.def == thingDef && t.stackCount >= expectedCount);
break;
}
if (!verificationSuccess)
{
Tools.Warn($"Spawn verification failed for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
else
{
Tools.Warn($"Spawn verification successful for {thingDef.defName} via {spawnMethod}", this.myDebug);
}
return verificationSuccess;
}
private bool TryFindSpawnCell(out IntVec3 result)
{
result = IntVec3.Invalid;
bool result2;
if (this.pawn == null)
{
result2 = false;
}
else
{
Map map = this.pawn.Map;
if (map == null)
{
result2 = false;
}
else
{
// 修改这里将半径从5减少到2让生成位置更靠近pawn
int searchRadius = 2;
// 首先尝试在pawn的相邻单元格生成半径为1
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null);
// 如果相邻单元格找不到合适位置再尝试稍远一点半径为2
if (!result.IsValid)
{
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null);
}
// 如果还是找不到尝试pawn当前位置作为最后手段
if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map))
{
result = this.pawn.Position;
}
result2 = result.IsValid;
}
}
return result2;
}
private void ResetCountdown()
{
this.ticksUntilSpawn = (int)(this.RandomDays2wait() * 60000f);
this.initialTicksUntilSpawn = this.ticksUntilSpawn;
}
private float RandomDays2wait()
{
return Rand.Range(this.calculatedMinDaysB4Next, this.calculatedMaxDaysB4Next);
}
private float RandomGraceDays()
{
return Rand.Range(this.Props.graceDays / 2f, this.Props.graceDays);
}
public override string CompTipStringExtra
{
get
{
if (!this.myDebug)
{
return null;
}
string text = "ticksUntilSpawn: " + this.ticksUntilSpawn.ToString() + "\n";
string text2 = text;
text = string.Concat(new string[]
{
text2,
"initialTicksUntilSpawn: ",
this.initialTicksUntilSpawn.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"graceTicks: ",
this.graceTicks.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"hunger resets: ",
this.hungerReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"health resets: ",
this.healthReset.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMinDaysB4Next: ",
this.calculatedMinDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedMaxDaysB4Next: ",
this.calculatedMaxDaysB4Next.ToString(),
"\n"
});
text2 = text;
text = string.Concat(new string[]
{
text2,
"calculatedQuantity: ",
this.calculatedQuantity.ToString(),
"\n"
});
return text + "blockSpawn: " + this.blockSpawn.ToString();
}
}
private int ticksUntilSpawn;
private int initialTicksUntilSpawn;
private int hungerReset;
private int healthReset;
private int graceTicks;
private Pawn pawn;
private float calculatedMaxDaysB4Next = 2f;
private float calculatedMinDaysB4Next = 1f;
private int calculatedQuantity = 1;
private bool blockSpawn;
private bool myDebug;
private readonly float errorMinDaysB4Next = 0.001f;
private readonly int errorExponentialLimit = 20;
private readonly int errorSpawnCount = 750;
}
}

View File

@@ -1,106 +0,0 @@
using System;
using System.Collections.Generic;
using RimWorld;
using UnityEngine;
using Verse;
namespace ArachnaeSwarm.MoharHediffs
{
public static class Tools
{
public static void DestroyParentHediff(Hediff parentHediff, bool debug = false)
{
if (parentHediff.pawn != null && parentHediff.def.defName != null && debug)
{
ArachnaeLog.Debug(parentHediff.pawn.Label + "'s Hediff: " + parentHediff.def.defName + " says goodbye.");
}
parentHediff.Severity = 0f;
}
public static float GetPawnAgeOverlifeExpectancyRatio(Pawn pawn, bool debug = false)
{
float result = 1f;
if (pawn == null)
{
if (debug)
{
ArachnaeLog.Debug("GetPawnAgeOverlifeExpectancyRatio pawn NOT OK");
}
return result;
}
result = pawn.ageTracker.AgeBiologicalYearsFloat / pawn.RaceProps.lifeExpectancy;
if (debug)
{
ArachnaeLog.Debug(string.Concat(new string[]
{
pawn.Label,
" Age: ",
pawn.ageTracker.AgeBiologicalYearsFloat.ToString(),
"; lifeExpectancy: ",
pawn.RaceProps.lifeExpectancy.ToString(),
"; ratio:",
result.ToString()
}));
}
return result;
}
public static bool IsInjured(this Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
ArachnaeLog.Debug("pawn is null - wounded ");
}
return false;
}
float num = 0f;
List<Hediff> hediffs = pawn.health.hediffSet.hediffs;
for (int i = 0; i < hediffs.Count; i++)
{
if (hediffs[i] is Hediff_Injury && !hediffs[i].IsPermanent())
{
num += hediffs[i].Severity;
}
}
if (debug && num > 0f)
{
ArachnaeLog.Debug(pawn.Label + " is wounded ");
}
return num > 0f;
}
public static bool IsHungry(this Pawn pawn, bool debug = false)
{
if (pawn == null)
{
if (debug)
{
ArachnaeLog.Debug("pawn is null - IsHungry ");
}
return false;
}
bool flag = pawn.needs.food != null && pawn.needs.food.CurCategory == HungerCategory.Starving;
if (debug && flag)
{
ArachnaeLog.Debug(pawn.Label + " is hungry ");
}
return flag;
}
public static bool OkPawn(Pawn pawn)
{
return pawn != null && pawn.Map != null;
}
public static void Warn(string warning, bool debug = false)
{
if (debug)
{
ArachnaeLog.Debug(warning);
}
}
}
}