This commit is contained in:
2025-09-01 17:24:54 +08:00
11 changed files with 224 additions and 25 deletions

Binary file not shown.

View File

@@ -26,6 +26,15 @@
<li Class="CompProperties_AbilityLaunchProjectile">
<projectileDef>ARA_Proj_EggSac</projectileDef>
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityNeedCost">
<needDef>Food</needDef>
<needCost>4</needCost>
<failMessage>食物不足</failMessage>
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityBodyPartCheck">
<requiredPart>ARA_Ovary</requiredPart>
<failMessage>卵巢受损或缺失,无法生育</failMessage>
</li>
</comps>
</AbilityDef>
<AbilityDef>
@@ -59,6 +68,11 @@
<shotCount>32</shotCount> <!-- 总共发射5次 -->
<ticksBetweenShots>3</ticksBetweenShots> <!-- 每次发射间隔12 Ticks (0.2秒) -->
</li>
<li Class="ArachnaeSwarm.CompProperties_AbilityNeedCost">
<needDef>Food</needDef>
<needCost>0.5</needCost>
<failMessage>食物不足</failMessage>
</li>
</comps>
</AbilityDef>

View File

@@ -14,7 +14,7 @@
<!-- 背部组织,仅包含骨骼和甲片 -->
<li>
<def>ARA_Dorsum</def>
<coverage>0.036</coverage>
<coverage>0</coverage>
<groups>
<li>Torso</li>
</groups>
@@ -42,7 +42,7 @@
<!-- 胸部组织,包含骨骼和各类重点维生器官 -->
<li>
<def>ARA_Sternum</def>
<coverage>0.036</coverage>
<coverage>0</coverage>
<groups>
<li>Torso</li>
</groups>
@@ -154,7 +154,7 @@
<!-- 尾部组织群 -->
<li>
<def>ARA_Tail</def>
<coverage>0.025</coverage>
<coverage>0</coverage>
<height>Bottom</height>
<depth>Inside</depth>
<groups>
@@ -355,6 +355,7 @@
<flipGraphic>true</flipGraphic>
<groups>
<li>Hands</li>
<li>HeadClaw</li>
</groups>
<parts>
<li>
@@ -410,6 +411,7 @@
<flipGraphic>true</flipGraphic>
<groups>
<li>Hands</li>
<li>HeadClaw</li>
</groups>
<parts>
<li>

View File

@@ -33,6 +33,7 @@
<isTargetable>true</isTargetable>
<expandHomeArea>false</expandHomeArea>
</building>
<comps>
<li Class="CompProperties_Glower">
<glowRadius>6</glowRadius>
@@ -47,8 +48,13 @@
<whitelist>
<li>ARA_ArachnaeQueen</li>
</whitelist>
<delay>300</delay> <!-- 5 seconds -->
<delay>300</delay> <!-- 5 seconds -->
<destroyOnSpawn>true</destroyOnSpawn>
<hatchingGraphicData>
<texPath>Things/Building/Natural/Hive</texPath>
<graphicClass>Graphic_Random</graphicClass>
<drawSize>1.6</drawSize>
</hatchingGraphicData>
</li>
<li Class="CompProperties_SpawnEffecterOnDestroy">
<effect>CocoonDestroyed</effect>

View File

@@ -73,7 +73,10 @@
<Compile Include="JobDriver_Incubate.cs" />
<Compile Include="CompProperties_AbilitySprayLiquidMulti.cs" />
<Compile Include="CompAbilityEffect_SprayLiquidMulti.cs" />
<Compile Include="Building_Incubator.cs" />
<Compile Include="Hediffs\Hediff_CurseFlame.cs" />
<Compile Include="CompAbilityEffect_NeedCost.cs" />
<Compile Include="CompAbilityEffect_BodyPartCheck.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -0,0 +1,22 @@
using UnityEngine;
using Verse;
namespace ArachnaeSwarm
{
public class Building_Incubator : Building
{
public CompSpawnPawnFromList SpawnComp => GetComp<CompSpawnPawnFromList>();
public override Graphic Graphic
{
get
{
if (SpawnComp != null && SpawnComp.IsHatching && SpawnComp.Props.hatchingGraphicData != null)
{
return SpawnComp.Props.hatchingGraphicData.Graphic;
}
return base.Graphic;
}
}
}
}

View File

@@ -0,0 +1,49 @@
using System.Linq;
using RimWorld;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_AbilityBodyPartCheck : CompProperties_AbilityEffect
{
public BodyPartDef requiredPart;
public float minimumHealth = 0.8f;
public string failMessage = "Missing or damaged body part.";
public CompProperties_AbilityBodyPartCheck()
{
compClass = typeof(CompAbilityEffect_BodyPartCheck);
}
}
public class CompAbilityEffect_BodyPartCheck : CompAbilityEffect
{
public new CompProperties_AbilityBodyPartCheck Props => (CompProperties_AbilityBodyPartCheck)props;
public override bool GizmoDisabled(out string reason)
{
Pawn caster = parent.pawn;
if (caster != null && caster.health != null && caster.health.hediffSet != null)
{
var part = caster.health.hediffSet.GetNotMissingParts()
.FirstOrDefault(p => p.def == Props.requiredPart);
if (part == null)
{
reason = Props.failMessage;
return true;
}
float partHealth = caster.health.hediffSet.GetPartHealth(part) / part.def.GetMaxHealth(caster);
if (partHealth < Props.minimumHealth)
{
reason = Props.failMessage;
return true;
}
}
reason = null;
return false;
}
}
}

View File

@@ -0,0 +1,54 @@
using RimWorld;
using RimWorld.Planet;
using Verse;
namespace ArachnaeSwarm
{
public class CompProperties_AbilityNeedCost : CompProperties_AbilityEffect
{
public NeedDef needDef;
public float needCost;
public string failMessage;
public CompProperties_AbilityNeedCost()
{
compClass = typeof(CompAbilityEffect_NeedCost);
}
}
public class CompAbilityEffect_NeedCost : CompAbilityEffect
{
public new CompProperties_AbilityNeedCost Props => (CompProperties_AbilityNeedCost)props;
public override bool GizmoDisabled(out string reason)
{
Pawn caster = parent.pawn;
if (caster != null && caster.needs != null)
{
if (caster.needs.TryGetNeed(Props.needDef, out Need need))
{
if (need.CurLevel < Props.needCost)
{
reason = Props.failMessage;
return true;
}
}
}
reason = null;
return false;
}
public override void Apply(LocalTargetInfo target, LocalTargetInfo dest)
{
base.Apply(target, dest);
Pawn caster = parent.pawn;
if (caster != null && caster.needs != null)
{
if (caster.needs.TryGetNeed(Props.needDef, out Need need))
{
need.CurLevel -= Props.needCost;
}
}
}
}
}

View File

@@ -13,6 +13,7 @@ namespace ArachnaeSwarm
public bool destroyOnSpawn = false;
public IntRange spawnCount = new IntRange(1, 1);
public Type lordJob;
public GraphicData hatchingGraphicData;
public CompProperties_SpawnPawnFromList()
{

View File

@@ -13,6 +13,7 @@ namespace ArachnaeSwarm
private int spawnUntilTick = -1;
private PawnKindDef spawningPawnKind;
private PawnKindDef selectedPawnKind;
public bool IsHatching => spawnUntilTick > 0;
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
{
@@ -40,6 +41,7 @@ namespace ArachnaeSwarm
}
}
public void StartIncubation()
{
spawningPawnKind = selectedPawnKind;
@@ -55,6 +57,8 @@ namespace ArachnaeSwarm
}
}
private void SpawnPawn(PawnKindDef pawnKind)
{
try
@@ -71,34 +75,38 @@ namespace ArachnaeSwarm
return;
}
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(pawnKind, parent.Faction));
if (pawn == null)
int count = Props.spawnCount.RandomInRange;
for (int i = 0; i < count; i++)
{
Log.Error($"CompSpawnPawnFromList: Failed to generate pawn of kind {pawnKind.defName} for faction {parent.Faction?.Name ?? "null"}.");
return;
}
if (GenSpawn.Spawn(pawn, parent.Position, parent.Map) == null)
{
Log.Error($"CompSpawnPawnFromList: Failed to spawn pawn {pawn} at {parent.Position}.");
if (!pawn.Destroyed)
Pawn pawn = PawnGenerator.GeneratePawn(new PawnGenerationRequest(pawnKind, parent.Faction));
if (pawn == null)
{
pawn.Destroy();
Log.Error($"CompSpawnPawnFromList: Failed to generate pawn of kind {pawnKind.defName} for faction {parent.Faction?.Name ?? "null"}.");
continue;
}
return;
}
if (Props.lordJob != null)
{
try
if (GenSpawn.Spawn(pawn, parent.Position, parent.Map) == null)
{
LordJob lordJobInstance = (LordJob)System.Activator.CreateInstance(Props.lordJob);
Lord lord = LordMaker.MakeNewLord(parent.Faction, lordJobInstance, parent.Map);
lord.AddPawn(pawn);
Log.Error($"CompSpawnPawnFromList: Failed to spawn pawn {pawn} at {parent.Position}.");
if (!pawn.Destroyed)
{
pawn.Destroy();
}
continue;
}
catch (System.Exception e)
if (Props.lordJob != null)
{
Log.Error($"CompSpawnPawnFromList: Error creating LordJob {Props.lordJob?.Name ?? "null"} or assigning pawn {pawn}. Exception: {e}");
try
{
LordJob lordJobInstance = (LordJob)System.Activator.CreateInstance(Props.lordJob);
Lord lord = LordMaker.MakeNewLord(parent.Faction, lordJobInstance, parent.Map);
lord.AddPawn(pawn);
}
catch (System.Exception e)
{
Log.Error($"CompSpawnPawnFromList: Error creating LordJob {Props.lordJob?.Name ?? "null"} or assigning pawn {pawn}. Exception: {e}");
}
}
}
@@ -114,6 +122,7 @@ namespace ArachnaeSwarm
}
}
public override string CompInspectStringExtra()
{
if (spawnUntilTick > 0)
@@ -136,6 +145,7 @@ namespace ArachnaeSwarm
base.PostExposeData();
Scribe_Values.Look(ref spawnUntilTick, "spawnUntilTick", -1);
Scribe_Defs.Look(ref spawningPawnKind, "spawningPawnKind");
Scribe_Defs.Look(ref selectedPawnKind, "selectedPawnKind");
}
}
}

View File

@@ -0,0 +1,38 @@
# 项目:可交互的虫卵囊
## 1. 核心目标
创建一个可交互的虫卵囊,它允许一个特定的 Pawn阿拉克涅女皇种通过右键菜单与它交互从一个可配置的列表中选择一个 Pawn并在经过一段可配置的延迟后生成这个 Pawn。
## 2. 已完成的功能
* **创建了新的 VS 项目**: [`ArachnaeSwarm.csproj`](Source/ArachnaeSwarm/ArachnaeSwarm.csproj)
* **实现了核心的生成逻辑**:
* `CompProperties_SpawnPawnFromList.cs`: 定义了 XML 中可配置的属性,包括:
* `pawnKinds`: 可生成的 Pawn 列表。
* `whitelist`: 可以与虫卵囊交互的 Pawn 列表。
* `delay`: 孵化延迟。
* `spawnCount`: 生成数量。
* `destroyOnSpawn`: 生成后是否摧毁自身。
* `lordJob`: 生成的 Pawn 要执行的集体任务。
* `CompSpawnPawnFromList.cs`: 实现了核心的生成逻辑,包括:
* 生成右键菜单。
* 处理孵化倒计时。
* 生成指定数量的 Pawn。
* 在检查面板上显示孵化状态和提示信息。
* **实现了交互的 Job**:
* `ARA_Jobs.xml`: 定义了 `ARA_IncubateJob`
* `JobDriver_Incubate.cs`: 实现了让 Pawn 走到虫卵囊旁边并启动孵化过程的逻辑。
* **实现了动态的图形切换**:
* `Building_Incubator.cs`: 创建了一个新的建筑基类,它会根据虫卵囊是否正在孵化来动态地改变自身的图形。
* **创建了测试用的 Defs**:
* `ARA_InteractiveEggSac.xml`: 定义了一个可交互的虫卵囊,用于在游戏中测试新功能。
* `ArachnaeSwarm_Keys.xml`: 定义了相关的本地化 `key`
## 3. 当前状态
目前,项目已经基本完成了所有的核心功能,并且能够成功编译。但是,在最后一次构建时,我们遇到了一个编译错误,导致我们无法进行最终的测试。
## 4. 下一步计划
解决当前的编译错误,并成功构建项目,以便在游戏中进行最终的测试。