init mod
This commit is contained in:
commit
7a088b25a9
58 files changed
+6053
No files matched your search
@@ -0,0 +1,10 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>An answer of the current question with its resolved outcome (<see cref="Target"/>: scene or action id).</summary>
|
||||
public sealed class StudioBranch
|
||||
{
|
||||
public StudioCodeLine Line { get; set; }
|
||||
public StudioOutcomeKind Kind { get; set; }
|
||||
public string Target { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// A scenario that targets the current scene, as the scene composition saw
|
||||
/// it: owning mod, priority, header condition (DSL, null when there is none) and its result (null when
|
||||
/// it was not evaluated), and the final
|
||||
/// status. <see cref="Reason"/> spells out a skip or a shadowing (another
|
||||
/// mod won the same scenario name) in words.
|
||||
/// </summary>
|
||||
public sealed class StudioCandidate
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public StudioModScope Scope { get; set; }
|
||||
public int Priority { get; set; }
|
||||
public string Condition { get; set; }
|
||||
public bool? ConditionOk { get; set; }
|
||||
public bool IsOneRun { get; set; }
|
||||
public StudioCandidateStatus Status { get; set; }
|
||||
public StudioText Reason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>Outcome of a scene candidate as the dock shows it, the answer to "why did (not) it start".</summary>
|
||||
public enum StudioCandidateStatus
|
||||
{
|
||||
Running,
|
||||
Queued,
|
||||
Skipped,
|
||||
Completed,
|
||||
Shadowed,
|
||||
Failed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// One line of the scenario shown in the "Now" tab. <see cref="Text"/> is the
|
||||
/// DSL source for the author mode; the novice mode reads the structure
|
||||
/// instead: <see cref="Say"/> is what the player sees (a line, a question,
|
||||
/// an answer) and <see cref="Target"/> where the line points — the label of
|
||||
/// an option or goto, the name of a label, the scene of a switch, the id of
|
||||
/// an action. <see cref="StudioMoment"/> follows those links. The flags
|
||||
/// mirror what the debugger reports: lines already executed, the
|
||||
/// line the dialog stopped on, the lines it may be moved to, breakpoints, lines added but not yet applied
|
||||
/// and a parse error message.
|
||||
/// </summary>
|
||||
public sealed class StudioCodeLine
|
||||
{
|
||||
/// <summary>1-based line number in the scenario text (header included), as in <c>SourceLine</c> of actions.</summary>
|
||||
public int Number { get; set; }
|
||||
/// <summary>Number of this line in the text the running dialog was built from; 0 for a line added in the studio.</summary>
|
||||
public int SourceLine { get; set; }
|
||||
public string Text { get; set; }
|
||||
public StudioLineKind Kind { get; set; }
|
||||
public StudioText Say { get; set; }
|
||||
public string Target { get; set; }
|
||||
public bool IsDone { get; set; }
|
||||
public bool IsCurrent { get; set; }
|
||||
public bool HasBreakpoint { get; set; }
|
||||
/// <summary>The running dialog has an action of this line, so the ▶ marker may be dragged here.</summary>
|
||||
public bool CanRunNext { get; set; }
|
||||
public bool IsNew { get; set; }
|
||||
public StudioText Error { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// What a scenario line does, as <see cref="StudioLineReader"/> classifies it.
|
||||
/// The novice view only shows <see cref="Line"/>, <see cref="Choice"/> and
|
||||
/// <see cref="Option"/>; the rest is service lines it follows or hides.
|
||||
/// </summary>
|
||||
public enum StudioLineKind
|
||||
{
|
||||
Header,
|
||||
Comment,
|
||||
Light,
|
||||
Background,
|
||||
Line,
|
||||
Choice,
|
||||
Option,
|
||||
Label,
|
||||
Goto,
|
||||
Jump,
|
||||
Action,
|
||||
End,
|
||||
Command,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>Category of a studio log entry, shown as its badge.</summary>
|
||||
public enum StudioLogCategory
|
||||
{
|
||||
Scene,
|
||||
Skip,
|
||||
Scenario,
|
||||
Stop,
|
||||
End,
|
||||
Edit,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>One row of the studio event log; <see cref="Time"/> is game-session time as text.</summary>
|
||||
public sealed class StudioLogEntry
|
||||
{
|
||||
public string Time { get; set; }
|
||||
public StudioLogCategory Category { get; set; }
|
||||
public StudioText Text { get; set; }
|
||||
public bool IsWarning { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>A mod together with the way the studio may touch it; rendered as a scope badge.</summary>
|
||||
public sealed class StudioModScope
|
||||
{
|
||||
public string ModId { get; }
|
||||
public StudioScopeKind Kind { get; }
|
||||
|
||||
public StudioModScope(string modId, StudioScopeKind kind)
|
||||
{
|
||||
ModId = modId;
|
||||
Kind = kind;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// Presentation mode of the studio dock. Both modes show the same session;
|
||||
/// only the "Now" tab renders it differently: <see cref="Author"/> as script
|
||||
/// text with a gutter, <see cref="Novice"/> as one card per line with plain
|
||||
/// wording, so a player who never saw the DSL can still follow and extend it.
|
||||
/// </summary>
|
||||
public enum StudioMode
|
||||
{
|
||||
Author,
|
||||
Novice,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// What the novice mode shows instead of a listing: the line on screen now
|
||||
/// (<see cref="Say"/>), the lines said right before it, and — for a
|
||||
/// question — every answer with the place it leads to. Service lines
|
||||
/// (lights, labels, <c>end</c>) never appear; an answer's
|
||||
/// <c>-> :label</c> is followed to the first meaningful line after the
|
||||
/// label (through <c>goto</c> hops), so <c>option → :exit → switch_scene</c>
|
||||
/// reads as "goes to the scene". An answer without a target or with a
|
||||
/// missing label is <see cref="StudioOutcomeKind.Broken"/>. When no dialog
|
||||
/// runs, the moment is the scenario's first line or question.
|
||||
/// </summary>
|
||||
public sealed class StudioMoment
|
||||
{
|
||||
public StudioCodeLine Say { get; private set; }
|
||||
public List<StudioCodeLine> Before { get; } = new();
|
||||
public List<StudioBranch> Branches { get; } = new();
|
||||
|
||||
public bool IsQuestion => Say != null && Say.Kind == StudioLineKind.Choice;
|
||||
|
||||
public static StudioMoment Build(StudioSession session)
|
||||
{
|
||||
var moment = new StudioMoment();
|
||||
var lines = session.Lines;
|
||||
var index = FindAnchor(lines, session.CurrentIndex);
|
||||
if (index < 0)
|
||||
return moment;
|
||||
|
||||
moment.Say = lines[index];
|
||||
|
||||
for (var i = index - 1; i >= 0 && lines[i].Kind == StudioLineKind.Line; i--)
|
||||
moment.Before.Insert(0, lines[i]);
|
||||
|
||||
if (!moment.IsQuestion)
|
||||
return moment;
|
||||
|
||||
for (var next = index + 1; next < lines.Count && lines[next].Kind == StudioLineKind.Option; next++)
|
||||
moment.Branches.Add(Resolve(lines, lines[next]));
|
||||
return moment;
|
||||
}
|
||||
|
||||
private static int FindAnchor(List<StudioCodeLine> lines, int current)
|
||||
{
|
||||
if (current < 0)
|
||||
current = 0;
|
||||
if (lines.Count == 0)
|
||||
return -1;
|
||||
|
||||
if (lines[current].Kind == StudioLineKind.Option)
|
||||
{
|
||||
for (var i = current; i >= 0; i--)
|
||||
{
|
||||
if (lines[i].Kind == StudioLineKind.Choice)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i = current; i < lines.Count; i++)
|
||||
{
|
||||
if (lines[i].Kind == StudioLineKind.Line || lines[i].Kind == StudioLineKind.Choice)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static StudioBranch Resolve(List<StudioCodeLine> lines, StudioCodeLine option)
|
||||
{
|
||||
var branch = new StudioBranch { Line = option, Kind = StudioOutcomeKind.Broken };
|
||||
var label = string.IsNullOrEmpty(option.Target) ? -1 : FindLabel(lines, option.Target);
|
||||
if (label < 0)
|
||||
return branch;
|
||||
|
||||
var hops = 0;
|
||||
for (var i = label + 1; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
switch (line.Kind)
|
||||
{
|
||||
case StudioLineKind.Goto:
|
||||
i = string.IsNullOrEmpty(line.Target) ? -1 : FindLabel(lines, line.Target);
|
||||
if (i < 0 || ++hops > 8)
|
||||
return branch;
|
||||
continue;
|
||||
case StudioLineKind.Jump:
|
||||
branch.Kind = StudioOutcomeKind.Scene;
|
||||
branch.Target = line.Target;
|
||||
return branch;
|
||||
case StudioLineKind.Action:
|
||||
branch.Kind = StudioOutcomeKind.Action;
|
||||
branch.Target = line.Target;
|
||||
return branch;
|
||||
case StudioLineKind.End:
|
||||
branch.Kind = StudioOutcomeKind.End;
|
||||
return branch;
|
||||
case StudioLineKind.Line:
|
||||
case StudioLineKind.Choice:
|
||||
branch.Kind = StudioOutcomeKind.Continue;
|
||||
return branch;
|
||||
}
|
||||
}
|
||||
|
||||
branch.Kind = StudioOutcomeKind.End;
|
||||
return branch;
|
||||
}
|
||||
|
||||
private static int FindLabel(List<StudioCodeLine> lines, string name)
|
||||
{
|
||||
for (var i = 0; i < lines.Count; i++)
|
||||
{
|
||||
if (lines[i].Kind == StudioLineKind.Label && lines[i].Target == name)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>Role of a scene on the studio map relative to where the player is.</summary>
|
||||
public enum StudioNodeState
|
||||
{
|
||||
Here,
|
||||
Visited,
|
||||
Next,
|
||||
New,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>Where an answer leads once its label jump is followed, as shown to a novice.</summary>
|
||||
public enum StudioOutcomeKind
|
||||
{
|
||||
Scene,
|
||||
Action,
|
||||
End,
|
||||
Continue,
|
||||
Broken,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>The ways the dock can restart what it is debugging (the restart menu, in menu order).</summary>
|
||||
public enum StudioRestartMode
|
||||
{
|
||||
/// <summary>The scenario again from its first line; game state stays as it is now.</summary>
|
||||
Scenario,
|
||||
/// <summary>Game state rolled back to the scenario's start, then the scenario again.</summary>
|
||||
Rollback,
|
||||
/// <summary>The whole scene re-entered: its composition runs again.</summary>
|
||||
Scene,
|
||||
/// <summary>The scenario again from the label above the current line.</summary>
|
||||
FromLabel,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>State of the dialog runtime as the dock header shows it.</summary>
|
||||
public enum StudioRunState
|
||||
{
|
||||
/// <summary>A scenario runs freely.</summary>
|
||||
Running,
|
||||
/// <summary>Pause is on: the next action will stop before it runs.</summary>
|
||||
Paused,
|
||||
/// <summary>Stopped before an action (pause or breakpoint) until continue/step.</summary>
|
||||
Held,
|
||||
/// <summary>No scenario dialog is running.</summary>
|
||||
Idle,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>A scene on the studio map with the mod that defines it; <see cref="Via"/> names the link (a command or a choice).</summary>
|
||||
public sealed class StudioSceneNode
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public StudioNodeState State { get; set; }
|
||||
public StudioModScope Scope { get; set; }
|
||||
public StudioText Via { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// How the studio may touch content of a mod, shown as the colour and caption
|
||||
/// of the mod scope badge. Players mostly run zip mods, which are read-only:
|
||||
/// edits of their scenarios land in the studio layer that overrides them,
|
||||
/// and the badge pair "layer overrides zip" tells whose content is affected.
|
||||
/// </summary>
|
||||
public enum StudioScopeKind
|
||||
{
|
||||
/// <summary>Folder mod: edits are written into the mod itself.</summary>
|
||||
Own,
|
||||
/// <summary>Zip mod: read-only, edits go to the studio layer over it.</summary>
|
||||
Overlay,
|
||||
/// <summary>The studio layer of the current game that holds the player's edits.</summary>
|
||||
Layer,
|
||||
/// <summary>Compiled code of a mod: shown, never edited from the studio.</summary>
|
||||
ReadOnly,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Everything the dock shows at one moment, built by
|
||||
/// <see cref="StudioSessionBuilder"/> from the live game: where the player is
|
||||
/// (scene, scenario, label), the run state, which mod owns the scenario and
|
||||
/// where edits go, the scenario lines (with pending edits) and debugger
|
||||
/// marks, the scene candidates, game state, the log and the scene map. A
|
||||
/// plain view model: pages render it and never change the game through it.
|
||||
/// </summary>
|
||||
public sealed class StudioSession
|
||||
{
|
||||
public string SceneName { get; set; }
|
||||
public string ScenarioName { get; set; }
|
||||
|
||||
/// <summary>Label the current line belongs to; null before the first label.</summary>
|
||||
public string LabelName { get; set; }
|
||||
|
||||
public StudioRunState RunState { get; set; }
|
||||
|
||||
/// <summary>Mod (or layer) that owns the open scenario file; null without a scenario.</summary>
|
||||
public StudioModScope FileScope { get; set; }
|
||||
|
||||
/// <summary>Where edits are written: the file's own folder mod, or the studio layer.</summary>
|
||||
public StudioModScope EditScope { get; set; }
|
||||
|
||||
/// <summary>Unapplied edits of the open scenario.</summary>
|
||||
public int PendingCount { get; set; }
|
||||
|
||||
public List<StudioCodeLine> Lines { get; } = new();
|
||||
public List<StudioCandidate> Candidates { get; } = new();
|
||||
public List<StudioStateEntry> State { get; } = new();
|
||||
public List<StudioLogEntry> Log { get; } = new();
|
||||
public List<StudioSceneNode> Nodes { get; } = new();
|
||||
|
||||
public bool HasScenario => !string.IsNullOrEmpty(ScenarioName);
|
||||
|
||||
/// <summary>Index of the line the dialog stands on, or -1.</summary>
|
||||
public int CurrentIndex => Lines.FindIndex(IsCurrentLine);
|
||||
|
||||
/// <summary>
|
||||
/// Line index (into the scenario text, 0-based) where "insert after the
|
||||
/// current line" puts a line: right after it, or after the whole answer
|
||||
/// list of a question — a line between <c>select</c> and its options would
|
||||
/// cut the question short in the parser. Without a current line: the end.
|
||||
/// </summary>
|
||||
public int InsertionTextIndexAfterCurrent()
|
||||
{
|
||||
var index = CurrentIndex;
|
||||
if (index < 0)
|
||||
return Lines.Count == 0 ? 0 : Lines[^1].Number;
|
||||
|
||||
var at = index;
|
||||
if (Lines[index].Kind == StudioLineKind.Choice)
|
||||
{
|
||||
while (at + 1 < Lines.Count && Lines[at + 1].Kind == StudioLineKind.Option)
|
||||
at++;
|
||||
}
|
||||
return Lines[at].Number;
|
||||
}
|
||||
|
||||
private static bool IsCurrentLine(StudioCodeLine line) => line.IsCurrent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>How a value of the "State" tab can be changed in place; <see cref="None"/> is read-only.</summary>
|
||||
public enum StudioStateEditor
|
||||
{
|
||||
None,
|
||||
DialogVariable,
|
||||
Inventory,
|
||||
Quest,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>
|
||||
/// A key/value of game state in the "State" tab, grouped by its owner (time,
|
||||
/// quests, inventory…). Groups compare by <see cref="StudioText.Key"/>.
|
||||
/// <see cref="Id"/> is what <see cref="Editor"/> writes to (a variable name,
|
||||
/// item id, quest id).
|
||||
/// </summary>
|
||||
public sealed class StudioStateEntry
|
||||
{
|
||||
public StudioText Group { get; set; }
|
||||
public StudioText Key { get; set; }
|
||||
public StudioText Value { get; set; }
|
||||
public StudioStateEditor Editor { get; set; }
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
/// <summary>Tabs of the studio dock body, in the order of the tab row.</summary>
|
||||
public enum StudioTab
|
||||
{
|
||||
Now,
|
||||
Scene,
|
||||
State,
|
||||
Log,
|
||||
Map,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using Cthangover.Core.Utils;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// Player-facing text of the studio model kept as a localization key plus
|
||||
/// format arguments and translated only when shown, so the dock follows a
|
||||
/// language switch and no prose lives in code. Arguments may themselves be
|
||||
/// <see cref="StudioText"/> (e.g. a scenario's own <c>key=</c> inside a
|
||||
/// studio phrase) and are resolved recursively; plain arguments are data —
|
||||
/// scene, scenario, mod and quest ids, numbers. <see cref="Raw"/> wraps such
|
||||
/// data where a text is expected.
|
||||
/// </summary>
|
||||
public sealed class StudioText
|
||||
{
|
||||
private readonly string _raw;
|
||||
|
||||
public string Key { get; }
|
||||
public object[] Args { get; }
|
||||
|
||||
public StudioText(string key, params object[] args)
|
||||
{
|
||||
Key = key;
|
||||
Args = args ?? System.Array.Empty<object>();
|
||||
}
|
||||
|
||||
private StudioText(string raw, bool _)
|
||||
{
|
||||
_raw = raw;
|
||||
Args = System.Array.Empty<object>();
|
||||
}
|
||||
|
||||
/// <summary>Untranslated data (an id or a number) used where a text is expected.</summary>
|
||||
public static StudioText Raw(string value) => new(value, true);
|
||||
|
||||
public string Resolve()
|
||||
{
|
||||
if (Key == null)
|
||||
return _raw ?? string.Empty;
|
||||
|
||||
var format = TranslationServer.Translate(Key).ToString();
|
||||
if (Args.Length == 0)
|
||||
return format;
|
||||
|
||||
var resolved = new object[Args.Length];
|
||||
for (var i = 0; i < Args.Length; i++)
|
||||
resolved[i] = Args[i] is StudioText text ? text.Resolve() : Args[i];
|
||||
try
|
||||
{
|
||||
return string.Format(format, resolved);
|
||||
}
|
||||
catch (System.FormatException)
|
||||
{
|
||||
GameLogger.Log("UI", $"Scenario studio: '{Key}' expects other arguments than {Args.Length}", LogLevel.Warning);
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user