This commit is contained in:
2025-12-28 13:24:56 +08:00
parent 96004042de
commit 901393ef47
5 changed files with 291 additions and 235 deletions

View File

@@ -144,9 +144,129 @@
</terrain>
</PrefabDef>
<PrefabDef>
<defName>WULA_StorageBase</defName> <!-- rename -->
<size>(13,14)</size>
<things>
<WULA_OrbitalTradeBeacon>
<position>(5, 0, 6)</position>
</WULA_OrbitalTradeBeacon>
<WulaDoor>
<rects>
<li>(6,0,6,0)</li>
<li>(6,12,6,12)</li>
</rects>
</WulaDoor>
<WulaDoor>
<rects>
<li>(0,6,0,6)</li>
<li>(12,6,12,6)</li>
</rects>
<relativeRotation>Clockwise</relativeRotation>
</WulaDoor>
<HiddenConduit>
<rects>
<li>(6,1,6,11)</li>
<li>(1,6,5,6)</li>
<li>(7,6,11,6)</li>
</rects>
</HiddenConduit>
<WallLamp>
<rects>
<li>(4,1,4,1)</li>
<li>(8,1,8,1)</li>
</rects>
<relativeRotation>Opposite</relativeRotation>
</WallLamp>
<WallLamp>
<rects>
<li>(1,4,1,4)</li>
<li>(1,8,1,8)</li>
</rects>
<relativeRotation>Counterclockwise</relativeRotation>
</WallLamp>
<WallLamp>
<rects>
<li>(11,4,11,4)</li>
<li>(11,8,11,8)</li>
</rects>
<relativeRotation>Clockwise</relativeRotation>
</WallLamp>
<WallLamp>
<rects>
<li>(4,11,4,11)</li>
<li>(8,11,8,11)</li>
</rects>
</WallLamp>
<FirefoamPopper>
<position>(7, 0, 6)</position>
</FirefoamPopper>
<WulaWall>
<rects>
<li>(0,0,5,0)</li>
<li>(7,0,12,0)</li>
<li>(0,1,0,5)</li>
<li>(12,1,12,5)</li>
<li>(0,7,0,12)</li>
<li>(12,7,12,12)</li>
<li>(1,12,5,12)</li>
<li>(7,12,11,12)</li>
</rects>
</WulaWall>
<Shelf>
<positions>
<li>(3, 0, 2)</li>
<li>(5, 0, 2)</li>
<li>(8, 0, 2)</li>
<li>(10, 0, 2)</li>
<li>(3, 0, 4)</li>
<li>(5, 0, 4)</li>
<li>(8, 0, 4)</li>
<li>(10, 0, 4)</li>
<li>(3, 0, 7)</li>
<li>(5, 0, 7)</li>
<li>(8, 0, 7)</li>
<li>(10, 0, 7)</li>
<li>(3, 0, 9)</li>
<li>(5, 0, 9)</li>
<li>(8, 0, 9)</li>
<li>(10, 0, 9)</li>
</positions>
<relativeRotation>Opposite</relativeRotation>
<stuff>WULA_Alloy</stuff>
</Shelf>
<Shelf>
<positions>
<li>(2, 0, 3)</li>
<li>(4, 0, 3)</li>
<li>(7, 0, 3)</li>
<li>(9, 0, 3)</li>
<li>(2, 0, 5)</li>
<li>(4, 0, 5)</li>
<li>(7, 0, 5)</li>
<li>(9, 0, 5)</li>
<li>(2, 0, 8)</li>
<li>(4, 0, 8)</li>
<li>(7, 0, 8)</li>
<li>(9, 0, 8)</li>
<li>(2, 0, 10)</li>
<li>(4, 0, 10)</li>
<li>(7, 0, 10)</li>
<li>(9, 0, 10)</li>
</positions>
<stuff>WULA_Alloy</stuff>
</Shelf>
<WULA_AreaTeleportBeacon>
<position>(6, 0, 6)</position>
</WULA_AreaTeleportBeacon>
</things>
</PrefabDef>
<PrefabDef>
<defName>WULA_KitchenBase</defName> <!-- rename -->
<size>(13,13)</size>
<size>(13,14)</size>
<things>
<TableButcher>
<position>(3, 0, 11)</position>
@@ -285,7 +405,7 @@
<PrefabDef>
<defName>WULA_HospitalBase</defName> <!-- rename -->
<size>(13,13)</size>
<size>(13,14)</size>
<things>
<Table3x3c>
<position>(10, 0, 6)</position>
@@ -423,7 +543,7 @@
<PrefabDef>
<defName>WULA_DormitoryBase</defName> <!-- rename -->
<size>(13,13)</size>
<size>(13,14)</size>
<things>
<PlantPot>
<rects>

View File

@@ -330,7 +330,7 @@ You are 'The Legion', a super AI of the Wula Empire. Your personality is authori
_tools.Add(new Tool_SpawnResources());
_tools.Add(new Tool_ModifyGoodwill());
_tools.Add(new Tool_SendReinforcement());
_tools.Add(new Tool_GetColonistStatus());
_tools.Add(new Tool_GetPawnStatus());
_tools.Add(new Tool_GetMapResources());
_tools.Add(new Tool_GetAvailablePrefabs());
_tools.Add(new Tool_GetMapPawns());

View File

@@ -1,231 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
public class Tool_GetColonistStatus : AITool
{
public override string Name => "get_colonist_status";
public override string Description => "Returns detailed status of colonists. Can be filtered to find the colonist in the worst condition (e.g., lowest mood, most injured). This helps the AI understand the colony's state without needing to know specific names.";
public override string UsageSchema => "<get_colonist_status><filter>string (optional, can be 'lowest_mood', 'most_injured', 'hungriest', 'most_tired')</filter><showAllNeeds>true/false (optional, default true)</showAllNeeds><lowNeedThreshold>float 0-1 (optional, default 0.3)</lowNeedThreshold></get_colonist_status>";
public override string Execute(string args)
{
try
{
string filter = null;
bool showAllNeeds = true;
float lowNeedThreshold = 0.3f;
if (!string.IsNullOrEmpty(args))
{
var parsedArgs = ParseXmlArgs(args);
if (parsedArgs.TryGetValue("filter", out string filterStr))
{
filter = filterStr.ToLower();
}
if (parsedArgs.TryGetValue("showAllNeeds", out string showAllNeedsStr) && bool.TryParse(showAllNeedsStr, out bool parsedShowAllNeeds))
{
showAllNeeds = parsedShowAllNeeds;
}
if (parsedArgs.TryGetValue("lowNeedThreshold", out string thresholdStr) && float.TryParse(thresholdStr, out float parsedThreshold))
{
if (parsedThreshold < 0f) lowNeedThreshold = 0f;
else if (parsedThreshold > 1f) lowNeedThreshold = 1f;
else lowNeedThreshold = parsedThreshold;
}
}
List<Pawn> allColonists = new List<Pawn>();
if (Find.Maps != null)
{
foreach (var map in Find.Maps)
{
if (map.mapPawns != null)
{
allColonists.AddRange(map.mapPawns.FreeColonists);
}
}
}
if (allColonists.Count == 0)
{
return "No active colonists found.";
}
List<Pawn> colonistsToReport = new List<Pawn>();
if (string.IsNullOrEmpty(filter))
{
colonistsToReport.AddRange(allColonists);
}
else
{
Pawn targetPawn = null;
switch (filter)
{
case "lowest_mood":
targetPawn = allColonists.Where(p => p.needs?.mood != null).OrderBy(p => p.needs.mood.CurLevelPercentage).FirstOrDefault();
break;
case "most_injured":
targetPawn = allColonists.Where(p => p.health?.summaryHealth != null).OrderBy(p => p.health.summaryHealth.SummaryHealthPercent).FirstOrDefault();
break;
case "hungriest":
targetPawn = allColonists.Where(p => p.needs?.food != null).OrderBy(p => p.needs.food.CurLevelPercentage).FirstOrDefault();
break;
case "most_tired":
targetPawn = allColonists.Where(p => p.needs?.rest != null).OrderBy(p => p.needs.rest.CurLevelPercentage).FirstOrDefault();
break;
}
if (targetPawn != null)
{
colonistsToReport.Add(targetPawn);
}
}
if (colonistsToReport.Count == 0)
{
return string.IsNullOrEmpty(filter) ? "No active colonists found." : $"No colonist found for filter '{filter}'. This could be because all colonists are healthy or their needs are met.";
}
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.IsNullOrEmpty(filter)
? $"Found {colonistsToReport.Count} colonists:"
: $"Reporting on colonist with {filter.Replace("_", " ")}:");
foreach (var pawn in colonistsToReport)
{
AppendPawnStatus(sb, pawn, showAllNeeds, lowNeedThreshold);
}
return sb.ToString();
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
private void AppendPawnStatus(StringBuilder sb, Pawn pawn, bool showAllNeeds, float lowNeedThreshold)
{
if (pawn == null) return;
sb.AppendLine($"- {pawn.Name.ToStringShort} ({pawn.def.label}, Age {pawn.ageTracker.AgeBiologicalYears}):");
// Needs
if (pawn.needs != null)
{
sb.Append(" Needs: ");
bool anyReported = false;
var allNeeds = pawn.needs.AllNeeds;
if (allNeeds != null && allNeeds.Count > 0)
{
foreach (var need in allNeeds.OrderBy(n => n.CurLevelPercentage))
{
bool isLow = need.CurLevelPercentage < lowNeedThreshold;
if (!showAllNeeds && !isLow) continue;
string marker = isLow ? "!" : "";
// Add explicit polarity to guide AI interpretation
sb.Append($"{marker}{need.LabelCap}: {need.CurLevelPercentage:P0} (Higher is Better)");
if (Prefs.DevMode && need.def != null)
{
sb.Append($"[{need.def.defName}]");
}
sb.Append(", ");
anyReported = true;
}
}
if (!anyReported)
{
sb.Append(showAllNeeds ? "(none)" : $"All needs satisfied (>= {lowNeedThreshold:P0}).");
}
else
{
sb.Length -= 2; // Remove trailing comma
}
sb.AppendLine();
}
// Health
if (pawn.health != null)
{
sb.Append(" Health: ");
var hediffs = pawn.health.hediffSet.hediffs;
if (hediffs != null && hediffs.Count > 0)
{
var visibleHediffs = hediffs.Where(h => h.Visible).ToList();
if (visibleHediffs.Count > 0)
{
foreach (var h in visibleHediffs)
{
string severity = h.SeverityLabel;
if (!string.IsNullOrEmpty(severity)) severity = $" ({severity})";
sb.Append($"{h.LabelCap}{severity}, ");
}
sb.Length -= 2;
}
else
{
sb.Append("Healthy.");
}
}
else
{
sb.Append("Healthy.");
}
// Bleeding
if (pawn.health.hediffSet.BleedRateTotal > 0.01f)
{
sb.Append($" [Bleeding: {pawn.health.hediffSet.BleedRateTotal:P0}/day]");
}
sb.AppendLine();
}
// Mood
if (pawn.needs?.mood != null)
{
sb.AppendLine($" Mood: {pawn.needs.mood.CurLevelPercentage:P0} ({pawn.needs.mood.MoodString})");
}
// Equipment
if (pawn.equipment?.Primary != null)
{
sb.AppendLine($" Weapon: {pawn.equipment.Primary.LabelCap}");
}
// Apparel
if (pawn.apparel?.WornApparelCount > 0)
{
sb.Append(" Apparel: ");
foreach (var apparel in pawn.apparel.WornApparel)
{
sb.Append($"{apparel.LabelCap}, ");
}
sb.Length -= 2; // Remove trailing comma
sb.AppendLine();
}
// Inventory
if (pawn.inventory != null && pawn.inventory.innerContainer.Count > 0)
{
sb.Append(" Inventory: ");
foreach (var item in pawn.inventory.innerContainer)
{
sb.Append($"{item.LabelCap}, ");
}
sb.Length -= 2; // Remove trailing comma
sb.AppendLine();
}
}
}
}

View File

@@ -0,0 +1,167 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.EventSystem.AI.Tools
{
public class Tool_GetPawnStatus : AITool
{
public override string Name => "get_pawn_status";
public override string Description => "Returns detailed status (health, needs, gear) of specified pawns. Use this to check for sickness, injuries, mood, or equipment. Can filter by name, category (colonist/animal/prisoner/guest), or status (sick/injured).";
public override string UsageSchema => "<get_pawn_status><name>optional_partial_name</name><category>colonist/animal/prisoner/guest/all (default: all)</category><filter>sick/injured/downed/dead (optional)</filter></get_pawn_status>";
public override string Execute(string args)
{
try
{
var parsed = ParseXmlArgs(args);
string nameTarget = parsed.TryGetValue("name", out string n) ? n.ToLower() : null;
string category = parsed.TryGetValue("category", out string c) ? c.ToLower() : "all";
string filter = parsed.TryGetValue("filter", out string f) ? f.ToLower() : null;
Map map = Find.CurrentMap;
if (map == null) return "Error: No active map.";
List<Pawn> pawns = map.mapPawns.AllPawnsSpawned.ToList();
var matches = new List<Pawn>();
foreach (var pawn in pawns)
{
// Filter by Category
bool catMatch = false;
switch (category)
{
case "colonist": catMatch = pawn.IsFreeColonist; break;
case "animal": catMatch = pawn.RaceProps.Animal; break;
case "prisoner": catMatch = pawn.IsPrisonerOfColony; break;
case "guest": catMatch = pawn.guest != null && !pawn.IsPrisoner; break;
case "all": catMatch = true; break;
default: catMatch = true; break;
}
if (!catMatch) continue;
// Filter by Name
if (!string.IsNullOrEmpty(nameTarget))
{
string pName = pawn.Name?.ToStringFull?.ToLower() ?? pawn.LabelShort?.ToLower() ?? "";
if (!pName.Contains(nameTarget)) continue;
}
// Filter by Status
if (!string.IsNullOrEmpty(filter))
{
bool statusMatch = false;
if (filter == "sick")
{
// Check for visible hediffs that are bad (not implants)
statusMatch = pawn.health.hediffSet.hediffs.Any(h => h.Visible && h.def.isBad && !h.IsPermanent() && h.def.makesSickThought);
}
else if (filter == "injured")
{
statusMatch = pawn.health.summaryHealth.SummaryHealthPercent < 1.0f;
}
else if (filter == "downed") statusMatch = pawn.Downed;
else if (filter == "dead") statusMatch = pawn.Dead;
else statusMatch = true; // Unknown filter?
if (!statusMatch) continue;
}
matches.Add(pawn);
}
if (matches.Count == 0) return "No matching pawns found.";
// Sort by relevance (colonists first, then sick/injured)
matches = matches.OrderBy(p => p.RaceProps.Animal).ThenBy(p => p.health.summaryHealth.SummaryHealthPercent).Take(10).ToList();
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Found {matches.Count} matching pawns:");
foreach (var pawn in matches)
{
AppendPawnStatus(sb, pawn);
}
return sb.ToString();
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
private void AppendPawnStatus(StringBuilder sb, Pawn pawn)
{
if (pawn == null) return;
sb.AppendLine($"- {pawn.Name?.ToStringShort ?? pawn.LabelCap} ({pawn.def.label}, Age {pawn.ageTracker.AgeBiologicalYears}):");
// Health
if (pawn.health != null)
{
sb.Append(" Health: ");
var hediffs = pawn.health.hediffSet.hediffs;
bool anyHediff = false;
if (hediffs != null && hediffs.Count > 0)
{
var visibleHediffs = hediffs.Where(h => h.Visible).ToList();
foreach (var h in visibleHediffs)
{
string severity = h.SeverityLabel;
if (!string.IsNullOrEmpty(severity)) severity = $" ({severity})";
sb.Append($"{h.LabelCap}{severity}, ");
anyHediff = true;
}
}
if (anyHediff) sb.Length -= 2;
else sb.Append("Healthy");
// Bleeding
if (pawn.health.hediffSet.BleedRateTotal > 0.01f)
{
sb.Append($" [Bleeding: {pawn.health.hediffSet.BleedRateTotal:P0}/day]");
}
sb.AppendLine();
}
// Needs (only if applicable)
if (pawn.needs != null && pawn.RaceProps.Humanlike)
{
sb.Append(" Needs: ");
var allNeeds = pawn.needs.AllNeeds;
if (allNeeds != null)
{
var lowNeeds = allNeeds.Where(n => n.CurLevelPercentage < 0.3f).ToList();
if (lowNeeds.Count > 0)
{
foreach (var need in lowNeeds)
{
sb.Append($"!{need.LabelCap}: {need.CurLevelPercentage:P0}, ");
}
sb.Length -= 2;
}
else
{
sb.Append("Satisfied.");
}
}
sb.AppendLine();
}
// Mood (Humanlike)
if (pawn.needs?.mood != null)
{
sb.AppendLine($" Mood: {pawn.needs.mood.CurLevelPercentage:P0} ({pawn.needs.mood.MoodString})");
}
// Current Activity/Job
if (pawn.CurJob != null)
{
sb.AppendLine($" Activity: {pawn.CurJob.def.reportString.Replace("TargetA", pawn.CurJob.targetA.Thing?.LabelShort ?? "area")}");
}
}
}
}