diff --git a/1.6/1.6/Assemblies/WulaFallenEmpire.dll b/1.6/1.6/Assemblies/WulaFallenEmpire.dll index 3e3c176a..36f6f24a 100644 Binary files a/1.6/1.6/Assemblies/WulaFallenEmpire.dll and b/1.6/1.6/Assemblies/WulaFallenEmpire.dll differ diff --git a/1.6/1.6/Defs/PrefabDefs/WULA_Prefabs.xml b/1.6/1.6/Defs/PrefabDefs/WULA_Prefabs.xml index 52e9df80..6a1b3f79 100644 --- a/1.6/1.6/Defs/PrefabDefs/WULA_Prefabs.xml +++ b/1.6/1.6/Defs/PrefabDefs/WULA_Prefabs.xml @@ -144,9 +144,129 @@ + + + WULA_StorageBase + (13,14) + + + (5, 0, 6) + + + +
  • (6,0,6,0)
  • +
  • (6,12,6,12)
  • +
    +
    + + +
  • (0,6,0,6)
  • +
  • (12,6,12,6)
  • +
    + Clockwise +
    + + +
  • (6,1,6,11)
  • +
  • (1,6,5,6)
  • +
  • (7,6,11,6)
  • +
    +
    + + +
  • (4,1,4,1)
  • +
  • (8,1,8,1)
  • +
    + Opposite +
    + + +
  • (1,4,1,4)
  • +
  • (1,8,1,8)
  • +
    + Counterclockwise +
    + + +
  • (11,4,11,4)
  • +
  • (11,8,11,8)
  • +
    + Clockwise +
    + + +
  • (4,11,4,11)
  • +
  • (8,11,8,11)
  • +
    +
    + + (7, 0, 6) + + + +
  • (0,0,5,0)
  • +
  • (7,0,12,0)
  • +
  • (0,1,0,5)
  • +
  • (12,1,12,5)
  • +
  • (0,7,0,12)
  • +
  • (12,7,12,12)
  • +
  • (1,12,5,12)
  • +
  • (7,12,11,12)
  • +
    +
    + + +
  • (3, 0, 2)
  • +
  • (5, 0, 2)
  • +
  • (8, 0, 2)
  • +
  • (10, 0, 2)
  • +
  • (3, 0, 4)
  • +
  • (5, 0, 4)
  • +
  • (8, 0, 4)
  • +
  • (10, 0, 4)
  • +
  • (3, 0, 7)
  • +
  • (5, 0, 7)
  • +
  • (8, 0, 7)
  • +
  • (10, 0, 7)
  • +
  • (3, 0, 9)
  • +
  • (5, 0, 9)
  • +
  • (8, 0, 9)
  • +
  • (10, 0, 9)
  • +
    + Opposite + WULA_Alloy +
    + + +
  • (2, 0, 3)
  • +
  • (4, 0, 3)
  • +
  • (7, 0, 3)
  • +
  • (9, 0, 3)
  • +
  • (2, 0, 5)
  • +
  • (4, 0, 5)
  • +
  • (7, 0, 5)
  • +
  • (9, 0, 5)
  • +
  • (2, 0, 8)
  • +
  • (4, 0, 8)
  • +
  • (7, 0, 8)
  • +
  • (9, 0, 8)
  • +
  • (2, 0, 10)
  • +
  • (4, 0, 10)
  • +
  • (7, 0, 10)
  • +
  • (9, 0, 10)
  • +
    + WULA_Alloy +
    + + (6, 0, 6) + +
    +
    + + WULA_KitchenBase - (13,13) + (13,14) (3, 0, 11) @@ -285,7 +405,7 @@ WULA_HospitalBase - (13,13) + (13,14) (10, 0, 6) @@ -423,7 +543,7 @@ WULA_DormitoryBase - (13,13) + (13,14) diff --git a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs index 5ceccf27..36c6aada 100644 --- a/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs +++ b/Source/WulaFallenEmpire/EventSystem/AI/AIIntelligenceCore.cs @@ -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()); diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetColonistStatus.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetColonistStatus.cs deleted file mode 100644 index 5c070219..00000000 --- a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetColonistStatus.cs +++ /dev/null @@ -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 => "string (optional, can be 'lowest_mood', 'most_injured', 'hungriest', 'most_tired')true/false (optional, default true)float 0-1 (optional, default 0.3)"; - - 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 allColonists = new List(); - 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 colonistsToReport = new List(); - - 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(); - } - } - } -} diff --git a/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetPawnStatus.cs b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetPawnStatus.cs new file mode 100644 index 00000000..7afcd805 --- /dev/null +++ b/Source/WulaFallenEmpire/EventSystem/AI/Tools/Tool_GetPawnStatus.cs @@ -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 => "optional_partial_namecolonist/animal/prisoner/guest/all (default: all)sick/injured/downed/dead (optional)"; + + 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 pawns = map.mapPawns.AllPawnsSpawned.ToList(); + var matches = new List(); + + 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")}"); + } + } + } +}