整理scoure
This commit is contained in:
61
Source/WulaFallenEmpire/EventSystem/CompOpenCustomUI.cs
Normal file
61
Source/WulaFallenEmpire/EventSystem/CompOpenCustomUI.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System; // Required for Activator
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Collections.Generic;
|
||||
using Verse.AI;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class CompProperties_OpenCustomUI : CompProperties
|
||||
{
|
||||
public string uiDefName;
|
||||
public string label; // The text to display in the float menu
|
||||
public string failReason; // Optional: Custom text to show if the pawn can't reach the building
|
||||
|
||||
public CompProperties_OpenCustomUI()
|
||||
{
|
||||
this.compClass = typeof(CompOpenCustomUI);
|
||||
}
|
||||
}
|
||||
|
||||
public class CompOpenCustomUI : ThingComp
|
||||
{
|
||||
public CompProperties_OpenCustomUI Props => (CompProperties_OpenCustomUI)this.props;
|
||||
|
||||
public override IEnumerable<FloatMenuOption> CompFloatMenuOptions(Pawn selPawn)
|
||||
{
|
||||
// Check if the pawn can interact with the building
|
||||
if (!selPawn.CanReserveAndReach(this.parent, PathEndMode.InteractionCell, Danger.Deadly))
|
||||
{
|
||||
string reason = Props.failReason ?? "CannotUseNoPath".Translate();
|
||||
yield return new FloatMenuOption(reason, null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Check for power if the building has a power component
|
||||
CompPowerTrader powerComp = this.parent.GetComp<CompPowerTrader>();
|
||||
if (powerComp != null && !powerComp.PowerOn)
|
||||
{
|
||||
yield return new FloatMenuOption("CannotUseNoPower".Translate(), null);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string label = Props.label ?? "Open Custom UI"; // Use default label if not provided
|
||||
|
||||
FloatMenuOption option = new FloatMenuOption(label, delegate()
|
||||
{
|
||||
EventDef uiDef = DefDatabase<EventDef>.GetNamed(Props.uiDefName, false);
|
||||
if (uiDef != null)
|
||||
{
|
||||
Find.WindowStack.Add((Window)Activator.CreateInstance(uiDef.windowType, uiDef));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"[CompOpenCustomUI] Could not find EventDef named '{Props.uiDefName}'.");
|
||||
}
|
||||
});
|
||||
|
||||
yield return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
240
Source/WulaFallenEmpire/EventSystem/Condition.cs
Normal file
240
Source/WulaFallenEmpire/EventSystem/Condition.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public abstract class Condition
|
||||
{
|
||||
public abstract bool IsMet(out string reason);
|
||||
}
|
||||
|
||||
public class Condition_VariableEquals : Condition
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
public string valueVariableName;
|
||||
|
||||
public override bool IsMet(out string reason)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!eventVarManager.HasVariable(name))
|
||||
{
|
||||
reason = $"Variable '{name}' not found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
object variable = eventVarManager.GetVariable<object>(name);
|
||||
string compareValueStr = value;
|
||||
|
||||
if (!string.IsNullOrEmpty(valueVariableName))
|
||||
{
|
||||
compareValueStr = eventVarManager.GetVariable<object>(valueVariableName)?.ToString();
|
||||
if (compareValueStr == null)
|
||||
{
|
||||
reason = $"Comparison variable '{valueVariableName}' not set.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool met = false;
|
||||
try
|
||||
{
|
||||
if (variable is int)
|
||||
{
|
||||
met = (int)variable == int.Parse(compareValueStr);
|
||||
}
|
||||
else if (variable is float)
|
||||
{
|
||||
met = (float)variable == float.Parse(compareValueStr);
|
||||
}
|
||||
else if (variable is bool)
|
||||
{
|
||||
met = (bool)variable == bool.Parse(compareValueStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
met = variable?.ToString() == compareValueStr;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Warning($"[EventSystem] Condition_VariableEquals: Could not compare '{variable}' and '{compareValueStr}'. Error: {e.Message}");
|
||||
reason = "Type mismatch or parsing error during comparison.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!met)
|
||||
{
|
||||
reason = $"Requires {name} = {compareValueStr} (Current: {variable})";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = "";
|
||||
}
|
||||
return met;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Condition_CompareVariable : Condition
|
||||
{
|
||||
public string name;
|
||||
public float value;
|
||||
public string valueVariableName;
|
||||
|
||||
protected abstract bool Compare(float var1, float var2);
|
||||
protected abstract string GetOperatorString();
|
||||
|
||||
public override bool IsMet(out string reason)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!eventVarManager.HasVariable(name))
|
||||
{
|
||||
Log.Message($"[EventSystem] {GetType().Name}: Variable '{name}' not found, defaulting to 0f.");
|
||||
eventVarManager.SetVariable(name, 0f);
|
||||
}
|
||||
|
||||
float variable = eventVarManager.GetVariable<float>(name);
|
||||
|
||||
float compareValue = value;
|
||||
if (!string.IsNullOrEmpty(valueVariableName))
|
||||
{
|
||||
compareValue = eventVarManager.GetVariable<float>(valueVariableName, float.NaN);
|
||||
if (float.IsNaN(compareValue))
|
||||
{
|
||||
reason = $"Comparison variable '{valueVariableName}' not set or not a number.";
|
||||
Log.Warning($"[EventSystem] {GetType().Name} check for '{name}' failed: {reason}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool met = Compare(variable, compareValue);
|
||||
Log.Message($"[EventSystem] {GetType().Name} check: Name='{name}', CurrentValue='{variable}', CompareValue='{compareValue}', Met={met}");
|
||||
if (!met)
|
||||
{
|
||||
reason = $"Requires {name} {GetOperatorString()} {compareValue} (Current: {variable})";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = "";
|
||||
}
|
||||
return met;
|
||||
}
|
||||
}
|
||||
|
||||
public class Condition_VariableGreaterThan : Condition_CompareVariable
|
||||
{
|
||||
protected override bool Compare(float var1, float var2) => var1 > var2;
|
||||
protected override string GetOperatorString() => ">";
|
||||
}
|
||||
|
||||
public class Condition_VariableLessThan : Condition_CompareVariable
|
||||
{
|
||||
protected override bool Compare(float var1, float var2) => var1 < var2;
|
||||
protected override string GetOperatorString() => "<";
|
||||
}
|
||||
|
||||
public class Condition_VariableGreaterThanOrEqual : Condition_CompareVariable
|
||||
{
|
||||
protected override bool Compare(float var1, float var2) => var1 >= var2;
|
||||
protected override string GetOperatorString() => ">=";
|
||||
}
|
||||
|
||||
public class Condition_VariableLessThanOrEqual : Condition_CompareVariable
|
||||
{
|
||||
protected override bool Compare(float var1, float var2) => var1 <= var2;
|
||||
protected override string GetOperatorString() => "<=";
|
||||
}
|
||||
|
||||
public class Condition_VariableNotEqual : Condition
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
public string valueVariableName;
|
||||
|
||||
public override bool IsMet(out string reason)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!eventVarManager.HasVariable(name))
|
||||
{
|
||||
reason = $"Variable '{name}' not found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
object variable = eventVarManager.GetVariable<object>(name);
|
||||
string compareValueStr = value;
|
||||
|
||||
if (!string.IsNullOrEmpty(valueVariableName))
|
||||
{
|
||||
compareValueStr = eventVarManager.GetVariable<object>(valueVariableName)?.ToString();
|
||||
if (compareValueStr == null)
|
||||
{
|
||||
reason = $"Comparison variable '{valueVariableName}' not set.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool met = false;
|
||||
try
|
||||
{
|
||||
if (variable is int)
|
||||
{
|
||||
met = (int)variable != int.Parse(compareValueStr);
|
||||
}
|
||||
else if (variable is float)
|
||||
{
|
||||
met = (float)variable != float.Parse(compareValueStr);
|
||||
}
|
||||
else if (variable is bool)
|
||||
{
|
||||
met = (bool)variable != bool.Parse(compareValueStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
met = variable?.ToString() != compareValueStr;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Warning($"[EventSystem] Condition_VariableNotEqual: Could not compare '{variable}' and '{compareValueStr}'. Error: {e.Message}");
|
||||
reason = "Type mismatch or parsing error during comparison.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.Message($"[EventSystem] Condition_VariableNotEqual check: Name='{name}', Type='{variable?.GetType().Name ?? "null"}', CurrentValue='{variable}', CompareValue='{compareValueStr}', Met={met}");
|
||||
if (!met)
|
||||
{
|
||||
reason = $"Requires {name} != {compareValueStr} (Current: {variable})";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = "";
|
||||
}
|
||||
return met;
|
||||
}
|
||||
}
|
||||
|
||||
public class Condition_FactionExists : Condition
|
||||
{
|
||||
public FactionDef factionDef;
|
||||
|
||||
public override bool IsMet(out string reason)
|
||||
{
|
||||
if (factionDef == null)
|
||||
{
|
||||
reason = "FactionDef not specified in Condition_FactionExists.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool exists = Find.FactionManager.FirstFactionOfDef(factionDef) != null;
|
||||
if (!exists)
|
||||
{
|
||||
reason = $"Faction '{factionDef.label}' does not exist in the world.";
|
||||
}
|
||||
else
|
||||
{
|
||||
reason = "";
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Source/WulaFallenEmpire/EventSystem/DebugActions.cs
Normal file
65
Source/WulaFallenEmpire/EventSystem/DebugActions.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System; // Required for Activator
|
||||
using System.Collections.Generic;
|
||||
using LudeonTK;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public static class WulaDebugActions
|
||||
{
|
||||
[DebugAction("Wula Fallen Empire", "Open Custom UI...", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.Playing)]
|
||||
private static void OpenCustomUI()
|
||||
{
|
||||
List<DebugMenuOption> list = new List<DebugMenuOption>();
|
||||
foreach (EventDef localDef in DefDatabase<EventDef>.AllDefs)
|
||||
{
|
||||
EventDef currentDef = localDef;
|
||||
list.Add(new DebugMenuOption(currentDef.defName, DebugMenuOptionMode.Action, delegate
|
||||
{
|
||||
if (currentDef.hiddenWindow)
|
||||
{
|
||||
if (!currentDef.dismissEffects.NullOrEmpty())
|
||||
{
|
||||
foreach (var conditionalEffect in currentDef.dismissEffects)
|
||||
{
|
||||
string reason;
|
||||
bool conditionsMet = true;
|
||||
if (!conditionalEffect.conditions.NullOrEmpty())
|
||||
{
|
||||
foreach (var condition in conditionalEffect.conditions)
|
||||
{
|
||||
if (!condition.IsMet(out reason))
|
||||
{
|
||||
conditionsMet = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conditionsMet)
|
||||
{
|
||||
conditionalEffect.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Find.WindowStack.Add((Window)Activator.CreateInstance(currentDef.windowType, currentDef));
|
||||
}
|
||||
}));
|
||||
}
|
||||
Find.WindowStack.Add(new Dialog_DebugOptionListLister(list));
|
||||
}
|
||||
}
|
||||
|
||||
public static class WulaDebugActionsVariables
|
||||
{
|
||||
[DebugAction("Wula Fallen Empire", "Manage Event Variables", actionType = DebugActionType.Action, allowedGameStates = AllowedGameStates.PlayingOnMap)]
|
||||
private static void ManageEventVariables()
|
||||
{
|
||||
Find.WindowStack.Add(new Dialog_ManageEventVariables());
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Source/WulaFallenEmpire/EventSystem/DelayedActionManager.cs
Normal file
95
Source/WulaFallenEmpire/EventSystem/DelayedActionManager.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using RimWorld;
|
||||
using RimWorld.Planet;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class DelayedActionManager : WorldComponent
|
||||
{
|
||||
// Nested class must be public to be accessible for serialization
|
||||
public class DelayedAction : IExposable
|
||||
{
|
||||
public int TicksRemaining;
|
||||
public string eventDefName;
|
||||
|
||||
// Parameterless constructor for Scribe
|
||||
public DelayedAction() { }
|
||||
|
||||
public DelayedAction(string eventDefName, int ticks)
|
||||
{
|
||||
this.eventDefName = eventDefName;
|
||||
this.TicksRemaining = ticks;
|
||||
}
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
Scribe_Values.Look(ref TicksRemaining, "ticksRemaining", 0);
|
||||
Scribe_Values.Look(ref eventDefName, "eventDefName");
|
||||
}
|
||||
}
|
||||
|
||||
private List<DelayedAction> actions = new List<DelayedAction>();
|
||||
|
||||
public DelayedActionManager(World world) : base(world)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddAction(string eventDefName, int delayTicks)
|
||||
{
|
||||
if (string.IsNullOrEmpty(eventDefName) || delayTicks <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
actions.Add(new DelayedAction(eventDefName, delayTicks));
|
||||
}
|
||||
|
||||
public override void WorldComponentTick()
|
||||
{
|
||||
base.WorldComponentTick();
|
||||
for (int i = actions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DelayedAction delayedAction = actions[i];
|
||||
delayedAction.TicksRemaining--;
|
||||
if (delayedAction.TicksRemaining <= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
ExecuteAction(delayedAction.eventDefName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Error executing delayed action for event '{delayedAction.eventDefName}': {ex}");
|
||||
}
|
||||
actions.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteAction(string defName)
|
||||
{
|
||||
EventDef nextDef = DefDatabase<EventDef>.GetNamed(defName, false);
|
||||
if (nextDef != null)
|
||||
{
|
||||
// This logic is simplified from Effect_OpenCustomUI.OpenUI
|
||||
// It assumes delayed actions always open a new dialog.
|
||||
Find.WindowStack.Add((Window)Activator.CreateInstance(nextDef.windowType, nextDef));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] DelayedActionManager could not find EventDef named '{defName}'");
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Collections.Look(ref actions, "delayedActions", LookMode.Deep);
|
||||
if (actions == null)
|
||||
{
|
||||
actions = new List<DelayedAction>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
274
Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs
Normal file
274
Source/WulaFallenEmpire/EventSystem/Dialog_CustomDisplay.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Dialog_CustomDisplay : Window
|
||||
{
|
||||
private EventDef def;
|
||||
private Texture2D portrait;
|
||||
private Texture2D background;
|
||||
private string selectedDescription;
|
||||
|
||||
private static EventUIConfigDef config;
|
||||
public static EventUIConfigDef Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
config = DefDatabase<EventUIConfigDef>.GetNamed("Wula_EventUIConfig");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 InitialSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (def.windowSize != Vector2.zero)
|
||||
{
|
||||
return def.windowSize;
|
||||
}
|
||||
return Config.defaultWindowSize;
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog_CustomDisplay(EventDef def)
|
||||
{
|
||||
this.def = def;
|
||||
this.forcePause = true;
|
||||
this.absorbInputAroundWindow = true;
|
||||
this.doCloseX = true;
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!def.descriptions.NullOrEmpty())
|
||||
{
|
||||
if (def.descriptionMode == DescriptionSelectionMode.Random)
|
||||
{
|
||||
selectedDescription = def.descriptions.RandomElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
string indexVarName = $"_seq_desc_index_{def.defName}";
|
||||
int currentIndex = eventVarManager.GetVariable<int>(indexVarName, 0);
|
||||
|
||||
selectedDescription = def.descriptions[currentIndex];
|
||||
|
||||
int nextIndex = (currentIndex + 1) % def.descriptions.Count;
|
||||
eventVarManager.SetVariable(indexVarName, nextIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedDescription = "Error: No descriptions found in def.";
|
||||
}
|
||||
}
|
||||
|
||||
public override void PreOpen()
|
||||
{
|
||||
base.PreOpen();
|
||||
if (!def.portraitPath.NullOrEmpty())
|
||||
{
|
||||
portrait = ContentFinder<Texture2D>.Get(def.portraitPath);
|
||||
}
|
||||
|
||||
string bgPath = !def.backgroundImagePath.NullOrEmpty() ? def.backgroundImagePath : Config.defaultBackgroundImagePath;
|
||||
if (!bgPath.NullOrEmpty())
|
||||
{
|
||||
background = ContentFinder<Texture2D>.Get(bgPath);
|
||||
}
|
||||
|
||||
HandleAction(def.immediateEffects);
|
||||
|
||||
if (!def.conditionalDescriptions.NullOrEmpty())
|
||||
{
|
||||
foreach (var condDesc in def.conditionalDescriptions)
|
||||
{
|
||||
string reason;
|
||||
if (AreConditionsMet(condDesc.conditions, out reason))
|
||||
{
|
||||
selectedDescription += "\n\n" + condDesc.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedDescription = FormatDescription(selectedDescription);
|
||||
}
|
||||
|
||||
public override void DoWindowContents(Rect inRect)
|
||||
{
|
||||
if (background != null)
|
||||
{
|
||||
GUI.DrawTexture(inRect, background, ScaleMode.ScaleToFit);
|
||||
}
|
||||
|
||||
if (Config.showDefName)
|
||||
{
|
||||
Text.Font = GameFont.Tiny;
|
||||
GUI.color = Color.gray;
|
||||
Widgets.Label(new Rect(5, 5, inRect.width - 10, 20f), def.defName);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
if (Config.showLabel)
|
||||
{
|
||||
Text.Font = Config.labelFont;
|
||||
Widgets.Label(new Rect(5, 20f, inRect.width - 10, 30f), def.label);
|
||||
Text.Font = GameFont.Small;
|
||||
}
|
||||
|
||||
float virtualWidth = Config.lihuiSize.x + Config.textSize.x;
|
||||
float virtualHeight = Config.lihuiSize.y;
|
||||
|
||||
float scaleX = inRect.width / virtualWidth;
|
||||
float scaleY = inRect.height / virtualHeight;
|
||||
float scale = Mathf.Min(scaleX, scaleY) * 0.95f;
|
||||
|
||||
float scaledLihuiWidth = Config.lihuiSize.x * scale;
|
||||
float scaledLihuiHeight = Config.lihuiSize.y * scale;
|
||||
float scaledNameWidth = Config.nameSize.x * scale;
|
||||
float scaledNameHeight = Config.nameSize.y * scale;
|
||||
float scaledTextWidth = Config.textSize.x * scale;
|
||||
float scaledTextHeight = Config.textSize.y * scale;
|
||||
float scaledOptionsWidth = Config.optionsWidth * scale;
|
||||
|
||||
float totalContentWidth = scaledLihuiWidth + scaledTextWidth;
|
||||
float totalContentHeight = scaledLihuiHeight;
|
||||
float startX = (inRect.width - totalContentWidth) / 2;
|
||||
float startY = (inRect.height - totalContentHeight) / 2;
|
||||
|
||||
Rect lihuiRect = new Rect(startX, startY, scaledLihuiWidth, scaledLihuiHeight);
|
||||
if (portrait != null)
|
||||
{
|
||||
GUI.DrawTexture(lihuiRect, portrait, ScaleMode.ScaleToFit);
|
||||
}
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(lihuiRect);
|
||||
}
|
||||
|
||||
Rect nameRect = new Rect(lihuiRect.xMax, lihuiRect.y, scaledNameWidth, scaledNameHeight);
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(nameRect);
|
||||
}
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Text.Font = GameFont.Medium;
|
||||
Widgets.Label(nameRect, def.characterName);
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
Rect textRect = new Rect(nameRect.x, nameRect.yMax + Config.textNameOffset * scale, scaledTextWidth, scaledTextHeight);
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(textRect);
|
||||
}
|
||||
Rect textInnerRect = textRect.ContractedBy(10f * scale);
|
||||
Widgets.Label(textInnerRect, selectedDescription);
|
||||
|
||||
Rect optionRect = new Rect(nameRect.x, textRect.yMax + Config.optionsTextOffset * scale, scaledOptionsWidth, lihuiRect.height - nameRect.height - textRect.height - (Config.textNameOffset + Config.optionsTextOffset) * scale);
|
||||
|
||||
Listing_Standard listing = new Listing_Standard();
|
||||
listing.Begin(optionRect.ContractedBy(10f * scale));
|
||||
if (def.options != null)
|
||||
{
|
||||
foreach (var option in def.options)
|
||||
{
|
||||
string reason;
|
||||
bool conditionsMet = AreConditionsMet(option.conditions, out reason);
|
||||
|
||||
if (conditionsMet)
|
||||
{
|
||||
if (listing.ButtonText(option.label))
|
||||
{
|
||||
HandleAction(option.optionEffects);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (option.hideWhenDisabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rect rect = listing.GetRect(30f);
|
||||
Widgets.ButtonText(rect, option.label, false, true, false);
|
||||
TooltipHandler.TipRegion(rect, GetDisabledReason(option, reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
listing.End();
|
||||
}
|
||||
|
||||
private void HandleAction(List<ConditionalEffects> conditionalEffects)
|
||||
{
|
||||
if (conditionalEffects.NullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var ce in conditionalEffects)
|
||||
{
|
||||
if (AreConditionsMet(ce.conditions, out _))
|
||||
{
|
||||
ce.Execute(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool AreConditionsMet(List<Condition> conditions, out string reason)
|
||||
{
|
||||
reason = "";
|
||||
if (conditions.NullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
if (!condition.IsMet(out string singleReason))
|
||||
{
|
||||
reason = singleReason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetDisabledReason(EventOption option, string reason)
|
||||
{
|
||||
if (!option.disabledReason.NullOrEmpty())
|
||||
{
|
||||
return option.disabledReason;
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
public override void PostClose()
|
||||
{
|
||||
base.PostClose();
|
||||
HandleAction(def.dismissEffects);
|
||||
}
|
||||
|
||||
private string FormatDescription(string description)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
// Use regex to find all placeholders like {variableName}
|
||||
return Regex.Replace(description, @"\{(.+?)\}", match =>
|
||||
{
|
||||
string varName = match.Groups[1].Value;
|
||||
if (eventVarManager.HasVariable(varName))
|
||||
{
|
||||
// Important: GetVariable<object> to get any type
|
||||
return eventVarManager.GetVariable<object>(varName)?.ToString() ?? "";
|
||||
}
|
||||
return match.Value; // Keep placeholder if variable not found
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Dialog_ManageEventVariables : Window
|
||||
{
|
||||
private Vector2 scrollPosition;
|
||||
private Dictionary<string, string> editBuffers = new Dictionary<string, string>();
|
||||
private EventVariableManager manager;
|
||||
|
||||
public override Vector2 InitialSize => new Vector2(800f, 600f);
|
||||
|
||||
public Dialog_ManageEventVariables()
|
||||
{
|
||||
forcePause = true;
|
||||
doCloseX = true;
|
||||
doCloseButton = true;
|
||||
closeOnClickedOutside = true;
|
||||
absorbInputAroundWindow = true;
|
||||
manager = Find.World.GetComponent<EventVariableManager>();
|
||||
RefreshBuffers();
|
||||
}
|
||||
|
||||
private void RefreshBuffers()
|
||||
{
|
||||
editBuffers.Clear();
|
||||
foreach (var kvp in manager.GetAllVariables())
|
||||
{
|
||||
editBuffers[kvp.Key] = kvp.Value?.ToString() ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public override void DoWindowContents(Rect inRect)
|
||||
{
|
||||
Listing_Standard listing = new Listing_Standard();
|
||||
listing.Begin(inRect);
|
||||
|
||||
if (listing.ButtonText("Refresh"))
|
||||
{
|
||||
RefreshBuffers();
|
||||
}
|
||||
if (listing.ButtonText("Clear All Variables"))
|
||||
{
|
||||
manager.ClearAll();
|
||||
RefreshBuffers();
|
||||
}
|
||||
|
||||
listing.GapLine();
|
||||
|
||||
Rect viewRect = new Rect(0f, 0f, inRect.width - 16f, manager.GetAllVariables().Count * 32f);
|
||||
Widgets.BeginScrollView(listing.GetRect(inRect.height - 100f), ref scrollPosition, viewRect);
|
||||
|
||||
Listing_Standard varListing = new Listing_Standard();
|
||||
varListing.Begin(viewRect);
|
||||
|
||||
var allVars = manager.GetAllVariables().OrderBy(kvp => kvp.Key).ToList();
|
||||
|
||||
foreach (var kvp in allVars)
|
||||
{
|
||||
Rect rowRect = varListing.GetRect(30f);
|
||||
string key = kvp.Key;
|
||||
object value = kvp.Value;
|
||||
string typeName = value?.GetType().Name ?? "null";
|
||||
|
||||
Widgets.Label(rowRect.LeftPart(0.4f).Rounded(), $"{key} ({typeName})");
|
||||
|
||||
string buffer = editBuffers[key];
|
||||
string newValue = Widgets.TextField(rowRect.RightPart(0.6f).LeftPart(0.8f).Rounded(), buffer);
|
||||
editBuffers[key] = newValue;
|
||||
|
||||
if (Widgets.ButtonText(rowRect.RightPart(0.1f).Rounded(), "Set"))
|
||||
{
|
||||
// Attempt to parse and set the variable
|
||||
if (value is int)
|
||||
{
|
||||
if (int.TryParse(newValue, out int intVal)) manager.SetVariable(key, intVal);
|
||||
}
|
||||
else if (value is float)
|
||||
{
|
||||
if (float.TryParse(newValue, out float floatVal)) manager.SetVariable(key, floatVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
manager.SetVariable(key, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
varListing.End();
|
||||
Widgets.EndScrollView();
|
||||
listing.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
277
Source/WulaFallenEmpire/EventSystem/Dialog_NewLayoutDisplay.cs
Normal file
277
Source/WulaFallenEmpire/EventSystem/Dialog_NewLayoutDisplay.cs
Normal file
@@ -0,0 +1,277 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Dialog_NewLayoutDisplay : Window
|
||||
{
|
||||
private EventDef def;
|
||||
private Texture2D portrait;
|
||||
private Texture2D background;
|
||||
private string selectedDescription;
|
||||
|
||||
private static EventUIConfigDef config;
|
||||
public static EventUIConfigDef Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
config = DefDatabase<EventUIConfigDef>.GetNamed("Wula_EventUIConfig");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2 InitialSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (def.windowSize != Vector2.zero)
|
||||
{
|
||||
return def.windowSize;
|
||||
}
|
||||
return Config.defaultWindowSize;
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog_NewLayoutDisplay(EventDef def)
|
||||
{
|
||||
this.def = def;
|
||||
this.forcePause = true;
|
||||
this.absorbInputAroundWindow = true;
|
||||
this.doCloseX = true;
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!def.descriptions.NullOrEmpty())
|
||||
{
|
||||
if (def.descriptionMode == DescriptionSelectionMode.Random)
|
||||
{
|
||||
selectedDescription = def.descriptions.RandomElement();
|
||||
}
|
||||
else
|
||||
{
|
||||
string indexVarName = $"_seq_desc_index_{def.defName}";
|
||||
int currentIndex = eventVarManager.GetVariable<int>(indexVarName, 0);
|
||||
|
||||
selectedDescription = def.descriptions[currentIndex];
|
||||
|
||||
int nextIndex = (currentIndex + 1) % def.descriptions.Count;
|
||||
eventVarManager.SetVariable(indexVarName, nextIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedDescription = "Error: No descriptions found in def.";
|
||||
}
|
||||
}
|
||||
|
||||
public override void PreOpen()
|
||||
{
|
||||
base.PreOpen();
|
||||
if (!def.portraitPath.NullOrEmpty())
|
||||
{
|
||||
portrait = ContentFinder<Texture2D>.Get(def.portraitPath);
|
||||
}
|
||||
|
||||
string bgPath = !def.backgroundImagePath.NullOrEmpty() ? def.backgroundImagePath : Config.defaultBackgroundImagePath;
|
||||
if (!bgPath.NullOrEmpty())
|
||||
{
|
||||
background = ContentFinder<Texture2D>.Get(bgPath);
|
||||
}
|
||||
|
||||
HandleAction(def.immediateEffects);
|
||||
|
||||
if (!def.conditionalDescriptions.NullOrEmpty())
|
||||
{
|
||||
foreach (var condDesc in def.conditionalDescriptions)
|
||||
{
|
||||
string reason;
|
||||
if (AreConditionsMet(condDesc.conditions, out reason))
|
||||
{
|
||||
selectedDescription += "\n\n" + condDesc.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectedDescription = FormatDescription(selectedDescription);
|
||||
}
|
||||
|
||||
public override void DoWindowContents(Rect inRect)
|
||||
{
|
||||
if (background != null)
|
||||
{
|
||||
GUI.DrawTexture(inRect, background, ScaleMode.ScaleToFit);
|
||||
}
|
||||
|
||||
if (Config.showDefName)
|
||||
{
|
||||
Text.Font = GameFont.Tiny;
|
||||
GUI.color = Color.gray;
|
||||
Widgets.Label(new Rect(5, 5, inRect.width - 10, 20f), def.defName);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
if (Config.showLabel)
|
||||
{
|
||||
Text.Font = Config.labelFont;
|
||||
Widgets.Label(new Rect(5, 20f, inRect.width - 10, 30f), def.label);
|
||||
Text.Font = GameFont.Small;
|
||||
}
|
||||
|
||||
// 假设一个统一的边距
|
||||
float padding = Config.newLayoutPadding;
|
||||
|
||||
// 名称区域
|
||||
float nameHeight = Config.newLayoutNameSize.y;
|
||||
float nameWidth = Config.newLayoutNameSize.x;
|
||||
Rect nameRect = new Rect(inRect.x + (inRect.width - nameWidth) / 2f, inRect.y + padding, nameWidth, nameHeight);
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(nameRect);
|
||||
}
|
||||
Text.Anchor = TextAnchor.MiddleCenter;
|
||||
Text.Font = GameFont.Medium;
|
||||
Widgets.Label(nameRect, def.characterName);
|
||||
Text.Font = GameFont.Small;
|
||||
Text.Anchor = TextAnchor.UpperLeft;
|
||||
|
||||
// 立绘区域
|
||||
float lihuiWidth = Config.newLayoutLihuiSize.x;
|
||||
float lihuiHeight = Config.newLayoutLihuiSize.y;
|
||||
Rect lihuiRect = new Rect(inRect.x + (inRect.width - lihuiWidth) / 2f, nameRect.yMax + padding, lihuiWidth, lihuiHeight);
|
||||
if (portrait != null)
|
||||
{
|
||||
GUI.DrawTexture(lihuiRect, portrait, ScaleMode.ScaleToFit);
|
||||
}
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(lihuiRect);
|
||||
}
|
||||
|
||||
// 选项区域 (预先计算高度)
|
||||
float optionButtonHeight = 30f; // 每个按钮的高度
|
||||
float optionSpacing = 5f; // 按钮之间的间距
|
||||
float calculatedOptionHeight = 0f;
|
||||
if (def.options != null && def.options.Any())
|
||||
{
|
||||
calculatedOptionHeight = def.options.Count * optionButtonHeight + (def.options.Count - 1) * optionSpacing;
|
||||
}
|
||||
calculatedOptionHeight = Mathf.Max(calculatedOptionHeight, 100f); // 最小高度
|
||||
|
||||
float optionsWidth = Config.newLayoutOptionsWidth;
|
||||
Rect optionRect = new Rect(inRect.x + (inRect.width - optionsWidth) / 2f, inRect.yMax - padding - calculatedOptionHeight, optionsWidth, calculatedOptionHeight);
|
||||
|
||||
// 描述区域
|
||||
float textWidth = Config.newLayoutTextSize.x;
|
||||
Rect textRect = new Rect(inRect.x + (inRect.width - textWidth) / 2f, lihuiRect.yMax + padding, textWidth, optionRect.y - (lihuiRect.yMax + padding) - padding);
|
||||
if (Config.drawBorders)
|
||||
{
|
||||
Widgets.DrawBox(textRect);
|
||||
}
|
||||
Rect textInnerRect = textRect.ContractedBy(padding);
|
||||
Widgets.Label(textInnerRect, selectedDescription);
|
||||
|
||||
// 选项列表的绘制
|
||||
Listing_Standard listing = new Listing_Standard();
|
||||
listing.Begin(optionRect); // 使用完整的 optionRect
|
||||
if (def.options != null)
|
||||
{
|
||||
foreach (var option in def.options)
|
||||
{
|
||||
string reason;
|
||||
bool conditionsMet = AreConditionsMet(option.conditions, out reason);
|
||||
|
||||
if (conditionsMet)
|
||||
{
|
||||
if (listing.ButtonText(option.label))
|
||||
{
|
||||
HandleAction(option.optionEffects);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (option.hideWhenDisabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rect rect = listing.GetRect(30f);
|
||||
Widgets.ButtonText(rect, option.label, false, true, false);
|
||||
TooltipHandler.TipRegion(rect, GetDisabledReason(option, reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
listing.End();
|
||||
}
|
||||
|
||||
private void HandleAction(List<ConditionalEffects> conditionalEffects)
|
||||
{
|
||||
if (conditionalEffects.NullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var ce in conditionalEffects)
|
||||
{
|
||||
if (AreConditionsMet(ce.conditions, out _))
|
||||
{
|
||||
ce.Execute(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool AreConditionsMet(List<Condition> conditions, out string reason)
|
||||
{
|
||||
reason = "";
|
||||
if (conditions.NullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
if (!condition.IsMet(out string singleReason))
|
||||
{
|
||||
reason = singleReason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetDisabledReason(EventOption option, string reason)
|
||||
{
|
||||
if (!option.disabledReason.NullOrEmpty())
|
||||
{
|
||||
return option.disabledReason;
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
public override void PostClose()
|
||||
{
|
||||
base.PostClose();
|
||||
HandleAction(def.dismissEffects);
|
||||
}
|
||||
|
||||
private string FormatDescription(string description)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
// Use regex to find all placeholders like {variableName}
|
||||
return Regex.Replace(description, @"\{(.+?)\}", match =>
|
||||
{
|
||||
string varName = match.Groups[1].Value;
|
||||
if (eventVarManager.HasVariable(varName))
|
||||
{
|
||||
// Important: GetVariable<object> to get any type
|
||||
return eventVarManager.GetVariable<object>(varName)?.ToString() ?? "";
|
||||
}
|
||||
return match.Value; // Keep placeholder if variable not found
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
655
Source/WulaFallenEmpire/EventSystem/Effect.cs
Normal file
655
Source/WulaFallenEmpire/EventSystem/Effect.cs
Normal file
@@ -0,0 +1,655 @@
|
||||
using System; // Required for Activator
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public abstract class Effect
|
||||
{
|
||||
public float weight = 1.0f;
|
||||
public abstract void Execute(Window dialog = null);
|
||||
}
|
||||
|
||||
public class Effect_OpenCustomUI : Effect
|
||||
{
|
||||
public string defName;
|
||||
public int delayTicks = 0;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (delayTicks > 0)
|
||||
{
|
||||
var actionManager = Find.World.GetComponent<DelayedActionManager>();
|
||||
if (actionManager != null)
|
||||
{
|
||||
actionManager.AddAction(defName, delayTicks);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] DelayedActionManager not found. Cannot schedule delayed UI opening.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenUI()
|
||||
{
|
||||
EventDef nextDef = DefDatabase<EventDef>.GetNamed(defName);
|
||||
if (nextDef != null)
|
||||
{
|
||||
if (nextDef.hiddenWindow)
|
||||
{
|
||||
if (!nextDef.dismissEffects.NullOrEmpty())
|
||||
{
|
||||
foreach (var conditionalEffect in nextDef.dismissEffects)
|
||||
{
|
||||
string reason;
|
||||
if (AreConditionsMet(conditionalEffect.conditions, out reason))
|
||||
{
|
||||
conditionalEffect.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Find.WindowStack.Add((Window)Activator.CreateInstance(nextDef.windowType, nextDef));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Effect_OpenCustomUI could not find EventDef named '{defName}'");
|
||||
}
|
||||
}
|
||||
|
||||
private bool AreConditionsMet(List<Condition> conditions, out string reason)
|
||||
{
|
||||
reason = "";
|
||||
if (conditions.NullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
if (!condition.IsMet(out string singleReason))
|
||||
{
|
||||
reason = singleReason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_CloseDialog : Effect
|
||||
{
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
dialog?.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_ShowMessage : Effect
|
||||
{
|
||||
public string message;
|
||||
public MessageTypeDef messageTypeDef;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (messageTypeDef == null)
|
||||
{
|
||||
messageTypeDef = MessageTypeDefOf.PositiveEvent;
|
||||
}
|
||||
Messages.Message(message, messageTypeDef);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_FireIncident : Effect
|
||||
{
|
||||
public IncidentDef incident;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (incident == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_FireIncident has a null incident Def.");
|
||||
return;
|
||||
}
|
||||
|
||||
IncidentParms parms = new IncidentParms
|
||||
{
|
||||
target = Find.CurrentMap,
|
||||
forced = true
|
||||
};
|
||||
|
||||
if (!incident.Worker.TryExecute(parms))
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Could not fire incident {incident.defName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_ChangeFactionRelation : Effect
|
||||
{
|
||||
public FactionDef faction;
|
||||
public int goodwillChange;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (faction == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_ChangeFactionRelation has a null faction Def.");
|
||||
return;
|
||||
}
|
||||
|
||||
Faction targetFaction = Find.FactionManager.FirstFactionOfDef(faction);
|
||||
if (targetFaction == null)
|
||||
{
|
||||
Log.Warning($"[WulaFallenEmpire] Could not find an active faction for FactionDef '{faction.defName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
Faction.OfPlayer.TryAffectGoodwillWith(targetFaction, goodwillChange, canSendMessage: true, canSendHostilityLetter: true, reason: null, lookTarget: null);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_SetVariable : Effect
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
public string type; // Int, Float, String, Bool
|
||||
public bool forceSet = false;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
if (!forceSet && eventVarManager.HasVariable(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
object realValue = value;
|
||||
if (!string.IsNullOrEmpty(type))
|
||||
{
|
||||
if (type.Equals("int", System.StringComparison.OrdinalIgnoreCase) && int.TryParse(value, out int intVal))
|
||||
{
|
||||
realValue = intVal;
|
||||
}
|
||||
else if (type.Equals("float", System.StringComparison.OrdinalIgnoreCase) && float.TryParse(value, out float floatVal))
|
||||
{
|
||||
realValue = floatVal;
|
||||
}
|
||||
else if (type.Equals("bool", System.StringComparison.OrdinalIgnoreCase) && bool.TryParse(value, out bool boolVal))
|
||||
{
|
||||
realValue = boolVal;
|
||||
}
|
||||
}
|
||||
eventVarManager.SetVariable(name, realValue);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_ChangeFactionRelation_FromVariable : Effect
|
||||
{
|
||||
public FactionDef faction;
|
||||
public string goodwillVariableName;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (faction == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_ChangeFactionRelation_FromVariable has a null faction Def.");
|
||||
return;
|
||||
}
|
||||
|
||||
Faction targetFaction = Find.FactionManager.FirstFactionOfDef(faction);
|
||||
if (targetFaction == null)
|
||||
{
|
||||
Log.Warning($"[WulaFallenEmpire] Could not find an active faction for FactionDef '{faction.defName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
int goodwillChange = Find.World.GetComponent<EventVariableManager>().GetVariable<int>(goodwillVariableName);
|
||||
Faction.OfPlayer.TryAffectGoodwillWith(targetFaction, goodwillChange, canSendMessage: true, canSendHostilityLetter: true, reason: null, lookTarget: null);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_SpawnPawnAndStore : Effect
|
||||
{
|
||||
public PawnKindDef kindDef;
|
||||
public int count = 1;
|
||||
public string storeAs;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (kindDef == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_SpawnPawnAndStore has a null kindDef.");
|
||||
return;
|
||||
}
|
||||
if (storeAs.NullOrEmpty())
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_SpawnPawnAndStore needs a 'storeAs' variable name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
List<Pawn> spawnedPawns = new List<Pawn>();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Pawn newPawn = PawnGenerator.GeneratePawn(kindDef, Faction.OfPlayer);
|
||||
IntVec3 loc = CellFinder.RandomSpawnCellForPawnNear(Find.CurrentMap.mapPawns.FreeColonists.First().Position, Find.CurrentMap, 10);
|
||||
GenSpawn.Spawn(newPawn, loc, Find.CurrentMap);
|
||||
spawnedPawns.Add(newPawn);
|
||||
}
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
eventVarManager.SetVariable(storeAs, spawnedPawns.First());
|
||||
}
|
||||
else
|
||||
{
|
||||
eventVarManager.SetVariable(storeAs, spawnedPawns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_GiveThing : Effect
|
||||
{
|
||||
public ThingDef thingDef;
|
||||
public int count = 1;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (thingDef == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_GiveThing has a null thingDef.");
|
||||
return;
|
||||
}
|
||||
|
||||
Map currentMap = Find.CurrentMap;
|
||||
if (currentMap == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_GiveThing cannot execute without a current map.");
|
||||
return;
|
||||
}
|
||||
|
||||
Thing thing = ThingMaker.MakeThing(thingDef);
|
||||
thing.stackCount = count;
|
||||
|
||||
IntVec3 dropCenter = DropCellFinder.TradeDropSpot(currentMap);
|
||||
DropPodUtility.DropThingsNear(dropCenter, currentMap, new List<Thing> { thing }, 110, false, false, false, false);
|
||||
|
||||
Messages.Message("LetterLabelCargoPodCrash".Translate(), new TargetInfo(dropCenter, currentMap), MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_SpawnPawn : Effect
|
||||
{
|
||||
public PawnKindDef kindDef;
|
||||
public int count = 1;
|
||||
public bool joinPlayerFaction = true;
|
||||
public string letterLabel;
|
||||
public string letterText;
|
||||
public LetterDef letterDef;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (kindDef == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_SpawnPawn has a null kindDef.");
|
||||
return;
|
||||
}
|
||||
|
||||
Map map = Find.CurrentMap;
|
||||
if (map == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_SpawnPawn cannot execute without a current map.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Faction faction = joinPlayerFaction ? Faction.OfPlayer : null;
|
||||
PawnGenerationRequest request = new PawnGenerationRequest(
|
||||
kindDef, faction, PawnGenerationContext.NonPlayer, -1, true, false, false, false,
|
||||
true, 20f, false, true, false, true, true, false, false, false, false, 0f, 0f, null, 1f,
|
||||
null, null, null, null, null, null, null, null, null, null, null, null, false
|
||||
);
|
||||
Pawn pawn = PawnGenerator.GeneratePawn(request);
|
||||
|
||||
if (!CellFinder.TryFindRandomEdgeCellWith((IntVec3 c) => map.reachability.CanReachColony(c) && !c.Fogged(map), map, CellFinder.EdgeRoadChance_Neutral, out IntVec3 cell))
|
||||
{
|
||||
cell = DropCellFinder.RandomDropSpot(map);
|
||||
}
|
||||
|
||||
GenSpawn.Spawn(pawn, cell, map, WipeMode.Vanish);
|
||||
|
||||
if (!string.IsNullOrEmpty(letterLabel) && !string.IsNullOrEmpty(letterText))
|
||||
{
|
||||
TaggedString finalLabel = letterLabel.Formatted(pawn.Named("PAWN")).AdjustedFor(pawn);
|
||||
TaggedString finalText = letterText.Formatted(pawn.Named("PAWN")).AdjustedFor(pawn);
|
||||
PawnRelationUtility.TryAppendRelationsWithColonistsInfo(ref finalText, ref finalLabel, pawn);
|
||||
Find.LetterStack.ReceiveLetter(finalLabel, finalText, letterDef ?? LetterDefOf.PositiveEvent, pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum VariableOperation
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide
|
||||
}
|
||||
|
||||
public class Effect_ModifyVariable : Effect
|
||||
{
|
||||
public string name;
|
||||
public string value;
|
||||
public string valueVariableName;
|
||||
public VariableOperation operation;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_ModifyVariable has a null or empty name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
|
||||
// Determine the value to modify by
|
||||
string valueStr = value;
|
||||
if (!string.IsNullOrEmpty(valueVariableName))
|
||||
{
|
||||
valueStr = eventVarManager.GetVariable<object>(valueVariableName)?.ToString();
|
||||
if (valueStr == null)
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable: valueVariableName '{valueVariableName}' not found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the target variable, or initialize it
|
||||
object variable = eventVarManager.GetVariable<object>(name);
|
||||
if (variable == null)
|
||||
{
|
||||
Log.Message($"[EventSystem] Effect_ModifyVariable: Variable '{name}' not found, initializing to 0.");
|
||||
variable = 0;
|
||||
}
|
||||
|
||||
object originalValue = variable;
|
||||
object newValue = null;
|
||||
|
||||
// Perform operation based on type
|
||||
try
|
||||
{
|
||||
if (variable is int || (variable is float && !valueStr.Contains("."))) // Allow int ops
|
||||
{
|
||||
int currentVal = System.Convert.ToInt32(variable);
|
||||
int modVal = int.Parse(valueStr);
|
||||
newValue = (int)Modify((float)currentVal, (float)modVal, operation);
|
||||
}
|
||||
else // Default to float operation
|
||||
{
|
||||
float currentVal = System.Convert.ToSingle(variable);
|
||||
float modVal = float.Parse(valueStr);
|
||||
newValue = Modify(currentVal, modVal, operation);
|
||||
}
|
||||
|
||||
Log.Message($"[EventSystem] Modifying variable '{name}'. Operation: {operation}. Value: {valueStr}. From: {originalValue} To: {newValue}");
|
||||
eventVarManager.SetVariable(name, newValue);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable: Could not parse or operate on value '{valueStr}' for variable '{name}'. Error: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private float Modify(float current, float modifier, VariableOperation op)
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
case VariableOperation.Add: return current + modifier;
|
||||
case VariableOperation.Subtract: return current - modifier;
|
||||
case VariableOperation.Multiply: return current * modifier;
|
||||
case VariableOperation.Divide:
|
||||
if (modifier != 0) return current / modifier;
|
||||
Log.Error($"[WulaFallenEmpire] Effect_ModifyVariable tried to divide by zero.");
|
||||
return current;
|
||||
default: return current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_ClearVariable : Effect
|
||||
{
|
||||
public string name;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_ClearVariable has a null or empty name.");
|
||||
return;
|
||||
}
|
||||
Find.World.GetComponent<EventVariableManager>().ClearVariable(name);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_AddQuest : Effect
|
||||
{
|
||||
public QuestScriptDef quest;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (quest == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_AddQuest has a null quest Def.");
|
||||
return;
|
||||
}
|
||||
|
||||
Quest newQuest = Quest.MakeRaw();
|
||||
newQuest.root = quest;
|
||||
newQuest.id = Find.UniqueIDsManager.GetNextQuestID();
|
||||
Find.QuestManager.Add(newQuest);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_FinishResearch : Effect
|
||||
{
|
||||
public ResearchProjectDef research;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (research == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_FinishResearch has a null research Def.");
|
||||
return;
|
||||
}
|
||||
|
||||
Find.ResearchManager.FinishProject(research);
|
||||
}
|
||||
}
|
||||
public class Effect_TriggerRaid : Effect
|
||||
{
|
||||
public float points;
|
||||
public FactionDef faction;
|
||||
public RaidStrategyDef raidStrategy;
|
||||
public PawnsArrivalModeDef raidArrivalMode;
|
||||
public PawnGroupKindDef groupKind;
|
||||
public List<PawnGroupMaker> pawnGroupMakers;
|
||||
public string letterLabel;
|
||||
public string letterText;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
Map map = Find.CurrentMap;
|
||||
if (map == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_TriggerRaid cannot execute without a current map.");
|
||||
return;
|
||||
}
|
||||
|
||||
Faction factionInst = Find.FactionManager.FirstFactionOfDef(this.faction);
|
||||
if (factionInst == null)
|
||||
{
|
||||
Log.Error($"[WulaFallenEmpire] Effect_TriggerRaid could not find an active faction for FactionDef '{this.faction?.defName}'.");
|
||||
return;
|
||||
}
|
||||
|
||||
IncidentParms parms = new IncidentParms
|
||||
{
|
||||
target = map,
|
||||
points = this.points,
|
||||
faction = factionInst,
|
||||
raidStrategy = this.raidStrategy,
|
||||
raidArrivalMode = this.raidArrivalMode,
|
||||
pawnGroupMakerSeed = Rand.Int,
|
||||
forced = true
|
||||
};
|
||||
|
||||
if (!RCellFinder.TryFindRandomPawnEntryCell(out parms.spawnCenter, map, CellFinder.EdgeRoadChance_Hostile))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_TriggerRaid could not find a valid spawn center.");
|
||||
return;
|
||||
}
|
||||
|
||||
PawnGroupMakerParms groupMakerParms = new PawnGroupMakerParms
|
||||
{
|
||||
groupKind = this.groupKind ?? PawnGroupKindDefOf.Combat,
|
||||
tile = map.Tile,
|
||||
points = this.points,
|
||||
faction = factionInst,
|
||||
raidStrategy = this.raidStrategy,
|
||||
seed = parms.pawnGroupMakerSeed
|
||||
};
|
||||
|
||||
List<Pawn> pawns;
|
||||
if (!pawnGroupMakers.NullOrEmpty())
|
||||
{
|
||||
var groupMaker = pawnGroupMakers.RandomElementByWeight(x => x.commonality);
|
||||
pawns = groupMaker.GeneratePawns(groupMakerParms).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
pawns = PawnGroupMakerUtility.GeneratePawns(groupMakerParms).ToList();
|
||||
}
|
||||
|
||||
if (pawns.Any())
|
||||
{
|
||||
raidArrivalMode.Worker.Arrive(pawns, parms);
|
||||
// Assign Lord and LordJob to make the pawns actually perform the raid.
|
||||
raidStrategy.Worker.MakeLords(parms, pawns);
|
||||
|
||||
if (!string.IsNullOrEmpty(letterLabel) && !string.IsNullOrEmpty(letterText))
|
||||
{
|
||||
Find.LetterStack.ReceiveLetter(letterLabel, letterText, LetterDefOf.ThreatBig, pawns[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_CheckFactionGoodwill : Effect
|
||||
{
|
||||
public FactionDef factionDef;
|
||||
public string variableName;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (factionDef == null || string.IsNullOrEmpty(variableName))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_CheckFactionGoodwill is not configured correctly.");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
Faction faction = Find.FactionManager.FirstFactionOfDef(factionDef);
|
||||
|
||||
if (faction != null)
|
||||
{
|
||||
int goodwill = faction.GoodwillWith(Faction.OfPlayer);
|
||||
Log.Message($"[EventSystem] Storing goodwill for faction '{faction.Name}' ({goodwill}) into variable '{variableName}'.");
|
||||
eventVarManager.SetVariable(variableName, goodwill);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning($"[EventSystem] Effect_CheckFactionGoodwill: Faction '{factionDef.defName}' not found. Storing 0 in variable '{variableName}'.");
|
||||
eventVarManager.SetVariable(variableName, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_StoreRealPlayTime : Effect
|
||||
{
|
||||
public string variableName;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(variableName))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_StoreRealPlayTime is not configured correctly (missing variableName).");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
float realPlayTime = Find.GameInfo.RealPlayTimeInteracting;
|
||||
Log.Message($"[EventSystem] Storing real play time ({realPlayTime}s) into variable '{variableName}'.");
|
||||
eventVarManager.SetVariable(variableName, realPlayTime);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_StoreDaysPassed : Effect
|
||||
{
|
||||
public string variableName;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(variableName))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_StoreDaysPassed is not configured correctly (missing variableName).");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
int daysPassed = GenDate.DaysPassed;
|
||||
Log.Message($"[EventSystem] Storing days passed ({daysPassed}) into variable '{variableName}'.");
|
||||
eventVarManager.SetVariable(variableName, daysPassed);
|
||||
}
|
||||
}
|
||||
|
||||
public class Effect_StoreColonyWealth : Effect
|
||||
{
|
||||
public string variableName;
|
||||
|
||||
public override void Execute(Window dialog = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(variableName))
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_StoreColonyWealth is not configured correctly (missing variableName).");
|
||||
return;
|
||||
}
|
||||
|
||||
Map currentMap = Find.CurrentMap;
|
||||
if (currentMap == null)
|
||||
{
|
||||
Log.Error("[WulaFallenEmpire] Effect_StoreColonyWealth cannot execute without a current map.");
|
||||
return;
|
||||
}
|
||||
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
float wealth = currentMap.wealthWatcher.WealthTotal;
|
||||
Log.Message($"[EventSystem] Storing colony wealth ({wealth}) into variable '{variableName}'.");
|
||||
eventVarManager.SetVariable(variableName, wealth);
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Source/WulaFallenEmpire/EventSystem/EventDef.cs
Normal file
148
Source/WulaFallenEmpire/EventSystem/EventDef.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System; // Add this line
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public enum DescriptionSelectionMode
|
||||
{
|
||||
Random,
|
||||
Sequential
|
||||
}
|
||||
|
||||
public class EventDef : Def
|
||||
{
|
||||
public string portraitPath;
|
||||
public string characterName;
|
||||
|
||||
// New system: list of descriptions
|
||||
public List<string> descriptions;
|
||||
public DescriptionSelectionMode descriptionMode = DescriptionSelectionMode.Random;
|
||||
public bool hiddenWindow = false;
|
||||
|
||||
// Backwards compatibility: old single description field
|
||||
public new string description = null;
|
||||
|
||||
public Vector2 windowSize = Vector2.zero;
|
||||
|
||||
public Type windowType = typeof(Dialog_CustomDisplay); // 默认窗口类型
|
||||
public List<EventOption> options;
|
||||
public string backgroundImagePath;
|
||||
public List<ConditionalEffects> immediateEffects;
|
||||
public List<ConditionalEffects> dismissEffects;
|
||||
public List<ConditionalDescription> conditionalDescriptions;
|
||||
|
||||
public override void PostLoad()
|
||||
{
|
||||
base.PostLoad();
|
||||
#pragma warning disable 0618
|
||||
// If the old description field is used, move its value to the new list for processing.
|
||||
if (!description.NullOrEmpty())
|
||||
{
|
||||
if (descriptions.NullOrEmpty())
|
||||
{
|
||||
descriptions = new List<string>();
|
||||
}
|
||||
descriptions.Insert(0, description);
|
||||
description = null; // Clear the old field to prevent confusion
|
||||
}
|
||||
#pragma warning restore 0618
|
||||
// If hiddenWindow is true, merge immediateEffects into dismissEffects at load time.
|
||||
if (hiddenWindow && !immediateEffects.NullOrEmpty())
|
||||
{
|
||||
if (dismissEffects.NullOrEmpty())
|
||||
{
|
||||
dismissEffects = new List<ConditionalEffects>();
|
||||
}
|
||||
dismissEffects.AddRange(immediateEffects);
|
||||
immediateEffects = null; // Clear to prevent double execution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class EventOption
|
||||
{
|
||||
public string label;
|
||||
public List<ConditionalEffects> optionEffects;
|
||||
public List<Condition> conditions;
|
||||
public string disabledReason;
|
||||
public bool hideWhenDisabled = false;
|
||||
}
|
||||
|
||||
public class LoopEffects
|
||||
{
|
||||
public int count = 1;
|
||||
public string countVariableName;
|
||||
public List<Effect> effects;
|
||||
}
|
||||
|
||||
public class ConditionalEffects
|
||||
{
|
||||
public List<Condition> conditions;
|
||||
public List<Effect> effects;
|
||||
public List<Effect> randomlistEffects;
|
||||
public List<LoopEffects> loopEffects;
|
||||
|
||||
public void Execute(Window dialog)
|
||||
{
|
||||
// Execute all standard effects
|
||||
if (!effects.NullOrEmpty())
|
||||
{
|
||||
foreach (var effect in effects)
|
||||
{
|
||||
effect.Execute(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute one random effect from the random list
|
||||
if (!randomlistEffects.NullOrEmpty())
|
||||
{
|
||||
float totalWeight = randomlistEffects.Sum(e => e.weight);
|
||||
float randomPoint = Rand.Value * totalWeight;
|
||||
|
||||
foreach (var effect in randomlistEffects)
|
||||
{
|
||||
if (randomPoint < effect.weight)
|
||||
{
|
||||
effect.Execute(dialog);
|
||||
break;
|
||||
}
|
||||
randomPoint -= effect.weight;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute looped effects
|
||||
if (!loopEffects.NullOrEmpty())
|
||||
{
|
||||
var eventVarManager = Find.World.GetComponent<EventVariableManager>();
|
||||
foreach (var loop in loopEffects)
|
||||
{
|
||||
int loopCount = loop.count;
|
||||
if (!loop.countVariableName.NullOrEmpty() && eventVarManager.HasVariable(loop.countVariableName))
|
||||
{
|
||||
loopCount = eventVarManager.GetVariable<int>(loop.countVariableName);
|
||||
}
|
||||
|
||||
for (int i = 0; i < loopCount; i++)
|
||||
{
|
||||
if (!loop.effects.NullOrEmpty())
|
||||
{
|
||||
foreach (var effect in loop.effects)
|
||||
{
|
||||
effect.Execute(dialog);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ConditionalDescription
|
||||
{
|
||||
public List<Condition> conditions;
|
||||
public string text;
|
||||
}
|
||||
}
|
||||
34
Source/WulaFallenEmpire/EventSystem/EventUIConfigDef.cs
Normal file
34
Source/WulaFallenEmpire/EventSystem/EventUIConfigDef.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class EventUIConfigDef : Def
|
||||
{
|
||||
// General Style
|
||||
public GameFont labelFont = GameFont.Small;
|
||||
public bool drawBorders = true;
|
||||
public bool showDefName = true;
|
||||
public bool showLabel = true;
|
||||
public string defaultBackgroundImagePath;
|
||||
public Vector2 defaultWindowSize = new Vector2(750f, 500f);
|
||||
|
||||
// Virtual Layout Dimensions
|
||||
public Vector2 lihuiSize = new Vector2(500f, 800f);
|
||||
public Vector2 nameSize = new Vector2(260f, 130f);
|
||||
public Vector2 textSize = new Vector2(650f, 500f);
|
||||
public float optionsWidth = 610f;
|
||||
|
||||
// Virtual Layout Offsets
|
||||
public float textNameOffset = 20f;
|
||||
public float optionsTextOffset = 20f;
|
||||
// New Layout Dimensions
|
||||
public Vector2 newLayoutNameSize = new Vector2(200f, 50f);
|
||||
public Vector2 newLayoutLihuiSize = new Vector2(300f, 400f);
|
||||
public Vector2 newLayoutTextSize = new Vector2(600f, 200f);
|
||||
public float newLayoutOptionsWidth = 600f;
|
||||
public float newLayoutPadding = 20f;
|
||||
public float newLayoutTextNameOffset = 20f;
|
||||
public float newLayoutOptionsTextOffset = 20f;
|
||||
}
|
||||
}
|
||||
177
Source/WulaFallenEmpire/EventSystem/EventVariableManager.cs
Normal file
177
Source/WulaFallenEmpire/EventSystem/EventVariableManager.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
using RimWorld;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class EventVariableManager : WorldComponent
|
||||
{
|
||||
private Dictionary<string, int> intVars = new Dictionary<string, int>();
|
||||
private Dictionary<string, float> floatVars = new Dictionary<string, float>();
|
||||
private Dictionary<string, string> stringVars = new Dictionary<string, string>();
|
||||
private Dictionary<string, Pawn> pawnVars = new Dictionary<string, Pawn>();
|
||||
private Dictionary<string, List<Pawn>> pawnListVars = new Dictionary<string, List<Pawn>>();
|
||||
|
||||
// 用于Scribe的辅助列表
|
||||
private List<string> pawnVarKeys;
|
||||
private List<Pawn> pawnVarValues;
|
||||
private List<string> pawnListVarKeys;
|
||||
private List<List<Pawn>> pawnListVarValues;
|
||||
|
||||
// Required for WorldComponent
|
||||
public EventVariableManager(World world) : base(world)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_Collections.Look(ref intVars, "intVars", LookMode.Value, LookMode.Value);
|
||||
Scribe_Collections.Look(ref floatVars, "floatVars", LookMode.Value, LookMode.Value);
|
||||
Scribe_Collections.Look(ref stringVars, "stringVars", LookMode.Value, LookMode.Value);
|
||||
Scribe_Collections.Look(ref pawnVars, "pawnVars", LookMode.Value, LookMode.Reference, ref pawnVarKeys, ref pawnVarValues);
|
||||
Scribe_Collections.Look(ref pawnListVars, "pawnListVars", LookMode.Value, LookMode.Reference, ref pawnListVarKeys, ref pawnListVarValues);
|
||||
|
||||
// Ensure dictionaries are not null after loading
|
||||
if (Scribe.mode == LoadSaveMode.PostLoadInit)
|
||||
{
|
||||
intVars ??= new Dictionary<string, int>();
|
||||
floatVars ??= new Dictionary<string, float>();
|
||||
stringVars ??= new Dictionary<string, string>();
|
||||
pawnVars ??= new Dictionary<string, Pawn>();
|
||||
pawnListVars ??= new Dictionary<string, List<Pawn>>();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVariable(string name, object value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return;
|
||||
|
||||
// Log the variable change
|
||||
Log.Message($"[EventSystem] Setting variable '{name}' to value '{value}' of type {value?.GetType().Name ?? "null"}.");
|
||||
|
||||
// Clear any existing variable with the same name to prevent type confusion
|
||||
ClearVariable(name);
|
||||
|
||||
if (value is int intValue)
|
||||
{
|
||||
intVars[name] = intValue;
|
||||
}
|
||||
else if (value is float floatValue)
|
||||
{
|
||||
floatVars[name] = floatValue;
|
||||
}
|
||||
else if (value is string stringValue)
|
||||
{
|
||||
stringVars[name] = stringValue;
|
||||
}
|
||||
else if (value is Pawn pawnValue)
|
||||
{
|
||||
pawnVars[name] = pawnValue;
|
||||
}
|
||||
else if (value is List<Pawn> pawnListValue)
|
||||
{
|
||||
pawnListVars[name] = pawnListValue;
|
||||
}
|
||||
else if (value != null)
|
||||
{
|
||||
stringVars[name] = value.ToString();
|
||||
Log.Warning($"[WulaFallenEmpire] EventVariableManager: Variable '{name}' of type {value.GetType()} was converted to string for storage. This may lead to unexpected behavior.");
|
||||
}
|
||||
}
|
||||
|
||||
public T GetVariable<T>(string name, T defaultValue = default)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return defaultValue;
|
||||
|
||||
object value = null;
|
||||
if (pawnListVars.TryGetValue(name, out var pawnListVal))
|
||||
{
|
||||
value = pawnListVal;
|
||||
}
|
||||
else if (pawnVars.TryGetValue(name, out var pawnVal))
|
||||
{
|
||||
value = pawnVal;
|
||||
}
|
||||
else if (floatVars.TryGetValue(name, out var floatVal))
|
||||
{
|
||||
value = floatVal;
|
||||
}
|
||||
else if (intVars.TryGetValue(name, out var intVal))
|
||||
{
|
||||
value = intVal;
|
||||
}
|
||||
else if (stringVars.TryGetValue(name, out var stringVal))
|
||||
{
|
||||
value = stringVal;
|
||||
}
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
try
|
||||
{
|
||||
// Handle cases where T is object but the stored value is, e.g., an int
|
||||
if (typeof(T) == typeof(object))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
return (T)System.Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Log.Warning($"[WulaFallenEmpire] EventVariableManager: Variable '{name}' of type {value.GetType()} could not be converted to {typeof(T)}. Error: {e.Message}");
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public bool HasVariable(string name)
|
||||
{
|
||||
return intVars.ContainsKey(name) ||
|
||||
floatVars.ContainsKey(name) ||
|
||||
stringVars.ContainsKey(name) ||
|
||||
pawnVars.ContainsKey(name) ||
|
||||
pawnListVars.ContainsKey(name);
|
||||
}
|
||||
|
||||
public void ClearVariable(string name)
|
||||
{
|
||||
if (HasVariable(name))
|
||||
{
|
||||
Log.Message($"[EventSystem] Clearing variable '{name}'.");
|
||||
}
|
||||
intVars.Remove(name);
|
||||
floatVars.Remove(name);
|
||||
stringVars.Remove(name);
|
||||
pawnVars.Remove(name);
|
||||
pawnListVars.Remove(name);
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
intVars.Clear();
|
||||
floatVars.Clear();
|
||||
stringVars.Clear();
|
||||
pawnVars.Clear();
|
||||
pawnListVars.Clear();
|
||||
}
|
||||
|
||||
public Dictionary<string, object> GetAllVariables()
|
||||
{
|
||||
var allVars = new Dictionary<string, object>();
|
||||
foreach (var kvp in intVars) allVars[kvp.Key] = kvp.Value;
|
||||
foreach (var kvp in floatVars) allVars[kvp.Key] = kvp.Value;
|
||||
foreach (var kvp in stringVars) allVars[kvp.Key] = kvp.Value;
|
||||
foreach (var kvp in pawnVars) allVars[kvp.Key] = kvp.Value;
|
||||
foreach (var kvp in pawnListVars) allVars[kvp.Key] = kvp.Value;
|
||||
return allVars;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Source/WulaFallenEmpire/EventSystem/Letter_EventChoice.cs
Normal file
94
Source/WulaFallenEmpire/EventSystem/Letter_EventChoice.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using RimWorld;
|
||||
using RimWorld.QuestGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class Letter_EventChoice : ChoiceLetter
|
||||
{
|
||||
// These fields are now inherited from the base Letter class
|
||||
// public string letterLabel;
|
||||
// public string letterTitle;
|
||||
// public string letterText;
|
||||
public List<QuestNode_Root_EventLetter.Option> options;
|
||||
public new Quest quest;
|
||||
|
||||
public override IEnumerable<DiaOption> Choices
|
||||
{
|
||||
get
|
||||
{
|
||||
if (options.NullOrEmpty())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var optionDef in options)
|
||||
{
|
||||
var currentOption = optionDef;
|
||||
Action choiceAction = delegate
|
||||
{
|
||||
if (!currentOption.optionEffects.NullOrEmpty())
|
||||
{
|
||||
foreach (var conditionalEffect in currentOption.optionEffects)
|
||||
{
|
||||
string reason;
|
||||
if (AreConditionsMet(conditionalEffect.conditions, out reason))
|
||||
{
|
||||
conditionalEffect.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (quest != null && !quest.hidden && !quest.Historical)
|
||||
{
|
||||
quest.End(QuestEndOutcome.Success);
|
||||
}
|
||||
Find.LetterStack.RemoveLetter(this);
|
||||
};
|
||||
|
||||
var diaOption = new DiaOption(currentOption.label)
|
||||
{
|
||||
action = choiceAction,
|
||||
resolveTree = true
|
||||
};
|
||||
yield return diaOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanDismissWithRightClick => false;
|
||||
|
||||
private bool AreConditionsMet(List<Condition> conditions, out string reason)
|
||||
{
|
||||
reason = "";
|
||||
if (conditions.NullOrEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var condition in conditions)
|
||||
{
|
||||
if (!condition.IsMet(out string singleReason))
|
||||
{
|
||||
reason = singleReason;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
// Scribe_Values.Look(ref letterLabel, "letterLabel"); // Now uses base.label
|
||||
// Scribe_Values.Look(ref letterTitle, "letterTitle"); // Now uses base.title
|
||||
// Scribe_Values.Look(ref letterText, "letterText"); // Now uses base.text
|
||||
Scribe_Collections.Look(ref options, "options", LookMode.Deep);
|
||||
if (Scribe.mode != LoadSaveMode.Saving || quest != null)
|
||||
{
|
||||
Scribe_References.Look(ref quest, "quest");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using RimWorld;
|
||||
using RimWorld.QuestGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
public class QuestNode_Root_EventLetter : QuestNode
|
||||
{
|
||||
// Fields to be set from the QuestScriptDef XML
|
||||
public SlateRef<string> letterLabel;
|
||||
public SlateRef<string> letterTitle;
|
||||
public SlateRef<string> letterText;
|
||||
public List<Option> options = new List<Option>();
|
||||
|
||||
// This is a root node, so it doesn't have a parent signal.
|
||||
// It runs immediately when the quest starts.
|
||||
protected override void RunInt()
|
||||
{
|
||||
// Get the current slate
|
||||
Slate slate = QuestGen.slate;
|
||||
|
||||
var letter = (Letter_EventChoice)LetterMaker.MakeLetter(DefDatabase<LetterDef>.GetNamed("Wula_EventChoiceLetter"));
|
||||
letter.Label = letterLabel.GetValue(slate);
|
||||
letter.title = letterTitle.GetValue(slate);
|
||||
letter.Text = letterText.GetValue(slate);
|
||||
letter.options = options;
|
||||
letter.quest = QuestGen.quest;
|
||||
letter.lookTargets = slate.Get<LookTargets>("lookTargets");
|
||||
|
||||
Find.LetterStack.ReceiveLetter(letter);
|
||||
}
|
||||
|
||||
protected override bool TestRunInt(Slate slate)
|
||||
{
|
||||
// This node can always run as long as the slate refs are valid.
|
||||
// We can add more complex checks here if needed.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Inner class to hold option data from XML
|
||||
public class Option
|
||||
{
|
||||
public string label;
|
||||
public List<ConditionalEffects> optionEffects;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user