removed a ton of xml comments and such
This commit is contained in:
@@ -3,30 +3,19 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>One dialog line — an id/text pair in a <see cref="DialogLineLibrary"/>.</summary>
|
||||
[Serializable]
|
||||
public class DialogLine {
|
||||
/// <summary>Stable key referenced by <see cref="DialogLineRef"/> (e.g. "common.farewell").</summary>
|
||||
public string id;
|
||||
|
||||
/// <summary>The actual text shown to the player.</summary>
|
||||
[TextArea(2, 6)]
|
||||
public string text;
|
||||
[TextArea(2, 6)] public string text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Single-asset registry of reusable dialog lines. One library file holds many lines; dialog
|
||||
/// options reference them by id via <see cref="DialogLineRef"/>. Split into multiple libraries
|
||||
/// (e.g. CommonLines, TownDialogue) only when a single file becomes unwieldy.
|
||||
/// </summary>
|
||||
/// <summary>Flat registry of reusable dialog lines. Referenced via <see cref="DialogLineRef"/>.</summary>
|
||||
[CreateAssetMenu(fileName = "DialogLineLibrary", menuName = "Jovian/Encounter System/Dialog Line Library", order = 4)]
|
||||
public class DialogLineLibrary : ScriptableObject {
|
||||
/// <summary>All lines in the library.</summary>
|
||||
public List<DialogLine> lines = new();
|
||||
|
||||
private Dictionary<string, string> cache;
|
||||
|
||||
/// <summary>Return the text for <paramref name="id"/>, or <c>null</c> if not found.</summary>
|
||||
public string Resolve(string id) {
|
||||
if(string.IsNullOrEmpty(id)) {
|
||||
return null;
|
||||
@@ -36,8 +25,6 @@ namespace Jovian.EncounterSystem {
|
||||
return cache.TryGetValue(id, out var text) ? text : null;
|
||||
}
|
||||
|
||||
/// <summary>Force the next <see cref="Resolve"/> call to rebuild the id → text cache.
|
||||
/// Called automatically from <c>OnValidate</c> after inspector edits.</summary>
|
||||
public void InvalidateCache() {
|
||||
cache = null;
|
||||
}
|
||||
|
||||
@@ -2,28 +2,13 @@ using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Reference to a dialog line. Looks up <see cref="id"/> inside <see cref="library"/> when both
|
||||
/// are set; otherwise falls back to <see cref="inlineText"/>. This lets designers prototype
|
||||
/// one-off lines inline and promote common ones to the shared library later without changing
|
||||
/// the field's type.
|
||||
/// </summary>
|
||||
/// <summary>Looks up <see cref="id"/> in the library passed to <see cref="Resolve"/>; falls back to <see cref="inlineText"/>.</summary>
|
||||
[Serializable]
|
||||
public struct DialogLineRef {
|
||||
/// <summary>Shared library to resolve <see cref="id"/> against. Optional.</summary>
|
||||
public DialogLineLibrary library;
|
||||
|
||||
/// <summary>Line id inside <see cref="library"/>. Ignored if <see cref="library"/> is null.</summary>
|
||||
public string id;
|
||||
[TextArea(2, 6)] public string inlineText;
|
||||
|
||||
/// <summary>Fallback text used when the library reference is missing or fails to resolve.
|
||||
/// Authored directly in the inspector for one-off lines.</summary>
|
||||
[TextArea(2, 6)]
|
||||
public string inlineText;
|
||||
|
||||
/// <summary>Resolve to final text. Returns <see cref="inlineText"/> if no library reference
|
||||
/// resolves, or <c>null</c> if both paths yield nothing.</summary>
|
||||
public string Resolve() {
|
||||
public string Resolve(DialogLineLibrary library) {
|
||||
if(library != null && !string.IsNullOrEmpty(id)) {
|
||||
var text = library.Resolve(id);
|
||||
if(!string.IsNullOrEmpty(text)) {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Per-resolution state passed into every event handler invoked by <see cref="EncounterResolver.Resolve"/>.
|
||||
/// Holds "who/what is currently being resolved" — not long-lived services. Extend with additional
|
||||
/// fields (party, selected option, acting character, …) as handlers need them.
|
||||
/// </summary>
|
||||
/// <summary>Per-resolution scratch object passed to every event handler. Extend with fields as handlers need them.</summary>
|
||||
public class EncounterContext {
|
||||
/// <summary>The encounter whose dialog option fired this resolution.</summary>
|
||||
public IEncounter CurrentEncounter { get; }
|
||||
|
||||
public EncounterContext(IEncounter currentEncounter) {
|
||||
|
||||
@@ -2,17 +2,14 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Reusable asset containing the dialog options shown for an encounter. Stored as an asset so a
|
||||
/// single set can be shared across encounters when the same choices apply. When <see cref="id"/>
|
||||
/// changes the asset file auto-renames to match (editor-only).
|
||||
/// </summary>
|
||||
/// <summary>Reusable dialog option list. Asset file auto-renames to match <see cref="id"/>.</summary>
|
||||
[CreateAssetMenu(fileName = "EncounterDialogOptionSet", menuName = "Jovian/Encounter System/Dialog Option Set", order = 2)]
|
||||
public class EncounterDialogOptionSet : ScriptableObject {
|
||||
/// <summary>Designer-facing identifier for this option set. The asset file renames to match.</summary>
|
||||
public string id;
|
||||
|
||||
/// <summary>Ordered list of options presented to the player.</summary>
|
||||
/// <summary>Shared library for every option's <see cref="EncounterDialogOption.text"/> lookup.</summary>
|
||||
public DialogLineLibrary library;
|
||||
|
||||
public List<EncounterDialogOption> options;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -21,7 +18,7 @@ namespace Jovian.EncounterSystem {
|
||||
return;
|
||||
}
|
||||
|
||||
// Deferred — AssetDatabase calls are unsafe from OnValidate.
|
||||
// AssetDatabase calls are unsafe from OnValidate — defer.
|
||||
UnityEditor.EditorApplication.delayCall += RenameToMatchId;
|
||||
}
|
||||
|
||||
@@ -31,10 +28,7 @@ namespace Jovian.EncounterSystem {
|
||||
}
|
||||
|
||||
var path = UnityEditor.AssetDatabase.GetAssetPath(this);
|
||||
if(string.IsNullOrEmpty(path)) {
|
||||
return;
|
||||
}
|
||||
if(name == id) {
|
||||
if(string.IsNullOrEmpty(path) || name == id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Cross-table reference to an <see cref="IEncounter"/>. Stores the owning
|
||||
/// <see cref="EncounterTable"/> asset and the target encounter's <see cref="EncounterDefinition.internalId"/>.
|
||||
/// Rename-safe because the stored key is a GUID.
|
||||
/// </summary>
|
||||
/// <summary>Cross-table reference. Rename-safe — the stored key is a GUID.</summary>
|
||||
[Serializable]
|
||||
public struct EncounterLink {
|
||||
/// <summary>The table that owns the linked encounter.</summary>
|
||||
public EncounterTable table;
|
||||
|
||||
/// <summary>The target encounter's stable GUID (<see cref="EncounterDefinition.internalId"/>).</summary>
|
||||
public string internalId;
|
||||
|
||||
/// <summary>Look up the referenced encounter, or <c>null</c> if the table is missing or the id
|
||||
/// no longer exists in it.</summary>
|
||||
public IEncounter Resolve() {
|
||||
if(table == null || table.encounters == null || string.IsNullOrEmpty(internalId)) {
|
||||
return null;
|
||||
|
||||
@@ -3,11 +3,7 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Scene-side view scaffold for presenting an encounter. Provides wired-up references to the
|
||||
/// common UI widgets (name/description/art/options container/submit button) so a game-specific
|
||||
/// view controller can bind an <see cref="IEncounter"/> to them without duplicating boilerplate.
|
||||
/// </summary>
|
||||
/// <summary>Shared view scaffold — game code binds an <see cref="IEncounter"/> to these widgets.</summary>
|
||||
public class EncounterReference : MonoBehaviour {
|
||||
public TextMeshProUGUI encounterName;
|
||||
public TextMeshProUGUI encounterDescription;
|
||||
|
||||
@@ -4,39 +4,27 @@ using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Central lookup cache mapping <see cref="EncounterDefinition.internalId"/> to its owning
|
||||
/// <see cref="IEncounter"/> across one or more <see cref="EncountersCollection"/> assets.
|
||||
/// In editor the registry auto-populates via an asset postprocessor; at runtime call
|
||||
/// <see cref="PopulateEncounters"/> explicitly after load.
|
||||
/// </summary>
|
||||
/// <summary>id → encounter cache. Editor auto-repopulates on asset changes; runtime must call <see cref="PopulateEncounters"/>.</summary>
|
||||
[CreateAssetMenu(fileName = "EncounterRegistry", menuName = "Jovian/Encounter System/Encounter Registry")]
|
||||
public class EncounterRegistry : ScriptableObject {
|
||||
/// <summary>Collections whose encounters should be indexed.</summary>
|
||||
public EncountersCollection[] encounterCollections = Array.Empty<EncountersCollection>();
|
||||
|
||||
private readonly Dictionary<string, IEncounter> encounters = new();
|
||||
|
||||
/// <summary>The live id → encounter map.</summary>
|
||||
public Dictionary<string, IEncounter> GetEncounters() => encounters;
|
||||
|
||||
/// <summary>Add an encounter to the cache if its id isn't already present.</summary>
|
||||
public void RegisterEncounter(IEncounter encounter) {
|
||||
encounters?.TryAdd(encounter?.EncounterDefinition?.internalId, encounter);
|
||||
}
|
||||
|
||||
/// <summary>Remove an encounter from the cache by its id.</summary>
|
||||
public void UnregisterEncounter(IEncounter encounter) {
|
||||
encounters.Remove(encounter.EncounterDefinition.internalId);
|
||||
}
|
||||
|
||||
/// <summary>Clear the cache. Call before a full re-population to avoid stale entries.</summary>
|
||||
public void ClearEncounters() {
|
||||
encounters.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Walk <see cref="encounterCollections"/> and register every encounter found.
|
||||
/// Call <see cref="ClearEncounters"/> first if you need a clean state.</summary>
|
||||
public void PopulateEncounters() {
|
||||
foreach(var collection in encounterCollections) {
|
||||
foreach(var encounter in collection.encounterTables) {
|
||||
@@ -49,11 +37,7 @@ namespace Jovian.EncounterSystem {
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Editor-time hook that keeps an <see cref="EncounterRegistry"/> asset (located via Addressables
|
||||
/// key <c>"EncounterRegistry"</c>) in sync with asset edits: clears and repopulates on any asset
|
||||
/// import/move/delete.
|
||||
/// </summary>
|
||||
/// <summary>Rebuilds the registry (Addressables key "EncounterRegistry") on any asset import/move/delete.</summary>
|
||||
public class EncounterRegister : UnityEditor.AssetPostprocessor {
|
||||
private static EncounterRegistry registryCache;
|
||||
|
||||
|
||||
@@ -2,34 +2,20 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Dispatches <see cref="IEncounterEvent"/> instances (authored on dialog options) to per-type
|
||||
/// handlers registered at composition time. Unknown event types are silently skipped.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The resolver stores handlers keyed by concrete event <see cref="Type"/>. Registration wraps a
|
||||
/// typed delegate in a closure that casts back to the concrete type — the cast is safe because
|
||||
/// we only ever invoke the wrapped delegate via the dictionary lookup under the same key.
|
||||
/// </remarks>
|
||||
/// <summary>Dispatches <see cref="IEncounterEvent"/> instances to per-type handlers. Unknown types are skipped.</summary>
|
||||
public class EncounterResolver {
|
||||
private readonly Dictionary<Type, Action<IEncounterEvent, EncounterContext>> handlers = new();
|
||||
|
||||
/// <summary>Register a handler for a concrete event type. Replaces any prior registration.</summary>
|
||||
/// <typeparam name="T">The event type to handle.</typeparam>
|
||||
/// <param name="handler">The delegate invoked with the cast event and the resolution context.</param>
|
||||
public void Register<T>(Action<T, EncounterContext> handler) where T : IEncounterEvent {
|
||||
// Wrap the typed delegate so the dictionary can hold handlers for any event type uniformly.
|
||||
// Cast is safe because the wrapper is only invoked via lookup under typeof(T).
|
||||
handlers[typeof(T)] = (evt, ctx) => handler((T)evt, ctx);
|
||||
}
|
||||
|
||||
/// <summary>Remove the handler registered for event type <typeparamref name="T"/>, if any.</summary>
|
||||
public void Unregister<T>() where T : IEncounterEvent {
|
||||
handlers.Remove(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>Dispatch each event in <paramref name="events"/> to its registered handler, in order.
|
||||
/// Null events and events with no registered handler are skipped.</summary>
|
||||
/// <param name="events">The ordered event list (typically from an <see cref="EncounterDialogOption"/>).</param>
|
||||
/// <param name="context">Per-resolution context passed to every handler.</param>
|
||||
public void Resolve(IEnumerable<IEncounterEvent> events, EncounterContext context) {
|
||||
if(events == null) {
|
||||
return;
|
||||
|
||||
@@ -3,22 +3,11 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// A ScriptableObject asset holding a named list of encounters. Encounters inside the list are
|
||||
/// authored via <c>[SerializeReference]</c> so different <see cref="IEncounter"/> implementations
|
||||
/// and <see cref="IEncounterKind"/> payloads can coexist.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "EncounterTable", menuName = "Jovian/Encounter System/Encounter Table", order = 1)]
|
||||
public class EncounterTable : ScriptableObject {
|
||||
/// <summary>Designer-facing table identifier (free-form).</summary>
|
||||
public string id;
|
||||
|
||||
/// <summary>Ordered encounter list. Each element is a concrete <see cref="Encounter"/> whose
|
||||
/// type-specific payload is carried by its <see cref="IEncounterKind"/> (set in the inspector
|
||||
/// via the SubclassSelector dropdown on <see cref="Encounter.Kind"/>).</summary>
|
||||
public List<Encounter> encounters;
|
||||
|
||||
/// <summary>Pick a uniformly random encounter, or <c>null</c> if the table is empty.</summary>
|
||||
public IEncounter GetRandomEncounter() {
|
||||
if(encounters == null || encounters.Count == 0) {
|
||||
return null;
|
||||
@@ -27,9 +16,6 @@ namespace Jovian.EncounterSystem {
|
||||
return encounters[UnityEngine.Random.Range(0, encounters.Count)];
|
||||
}
|
||||
|
||||
/// <summary>Pick a uniformly random encounter whose runtime type matches <paramref name="type"/>,
|
||||
/// or <c>null</c> if none match.</summary>
|
||||
/// <param name="type">The concrete <see cref="IEncounter"/> runtime type to filter on.</param>
|
||||
public IEncounter GetRandomEncounter(Type type) {
|
||||
if(encounters == null || encounters.Count == 0) {
|
||||
return null;
|
||||
@@ -43,10 +29,7 @@ namespace Jovian.EncounterSystem {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Pick a uniformly random encounter matching <paramref name="filter"/>, or <c>null</c>
|
||||
/// if no element passes. Used by <see cref="QuestProgress.IsGated"/> integration to exclude
|
||||
/// gated quest encounters from rolls.</summary>
|
||||
/// <param name="filter">Predicate applied to each encounter before rolling.</param>
|
||||
/// <summary>Random pick limited by a predicate. Used with <see cref="QuestProgress.IsGated"/> to exclude gated encounters.</summary>
|
||||
public IEncounter GetRandomEncounter(Predicate<IEncounter> filter) {
|
||||
if(encounters == null || encounters.Count == 0 || filter == null) {
|
||||
return GetRandomEncounter();
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Top-level asset that groups multiple <see cref="EncounterTable"/> references into a single
|
||||
/// browsable unit. Pass one of these to <see cref="QuestProgress"/> to seed its gating graph.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "EncountersCollection", menuName = "Jovian/Encounter System/Encounters Collection", order = 0)]
|
||||
public class EncountersCollection : ScriptableObject {
|
||||
/// <summary>The tables grouped by this collection.</summary>
|
||||
public EncounterTable[] encounterTables;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,32 +3,15 @@ using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Runtime contract for any encounter authored in the system.
|
||||
/// An encounter aggregates designer-facing metadata, visuals, dialog, and a polymorphic
|
||||
/// <see cref="IEncounterKind"/> payload that expresses its type-specific data.
|
||||
/// </summary>
|
||||
public interface IEncounter {
|
||||
/// <summary>Stable identity, display name, and designer description.</summary>
|
||||
EncounterDefinition EncounterDefinition { get; set; }
|
||||
|
||||
/// <summary>Numeric configuration shared by every encounter kind.</summary>
|
||||
EncounterProperties EncounterProperties { get; set; }
|
||||
|
||||
/// <summary>Visual assets displayed when the encounter is presented.</summary>
|
||||
EncounterVisuals EncounterVisuals { get; set; }
|
||||
|
||||
/// <summary>Dialog options shown to the player, or <c>null</c> if the encounter has no dialog.</summary>
|
||||
EncounterDialogOptionSet EncounterDialogOptionSet { get; set; }
|
||||
|
||||
/// <summary>Type-specific payload (combat, quest, social, ...). Authored via the SubclassSelector drawer.</summary>
|
||||
IEncounterKind Kind { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default concrete encounter. Holds the shared fields and a polymorphic <see cref="IEncounterKind"/>.
|
||||
/// Prefer adding new behaviour by introducing a new <see cref="IEncounterKind"/> rather than subclassing this type.
|
||||
/// </summary>
|
||||
/// <summary>Default concrete encounter. Extend via a new <see cref="IEncounterKind"/>, not by subclassing.</summary>
|
||||
[Serializable]
|
||||
public class Encounter : IEncounter {
|
||||
[field: SerializeField] public EncounterDefinition EncounterDefinition { get; set; }
|
||||
@@ -40,43 +23,25 @@ namespace Jovian.EncounterSystem {
|
||||
public IEncounterKind Kind { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Designer-facing identity for an encounter. <see cref="internalId"/> is the stable GUID used for
|
||||
/// cross-references (<see cref="EncounterLink"/>, quest progress, save data); <see cref="id"/> is a
|
||||
/// human-readable slug; <see cref="name"/> and <see cref="description"/> are display strings.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EncounterDefinition {
|
||||
/// <summary>Stable GUID, assigned once at creation. Never edit manually.</summary>
|
||||
/// <summary>Stable GUID assigned at creation. Never edit manually.</summary>
|
||||
[HideInInspector]
|
||||
public string internalId = Guid.NewGuid().ToString();
|
||||
|
||||
/// <summary>Designer-authored short slug (e.g. "goblin_ambush").</summary>
|
||||
public string id;
|
||||
|
||||
/// <summary>Display name shown to the player.</summary>
|
||||
public string name;
|
||||
|
||||
/// <summary>Flavour text shown when the encounter opens.</summary>
|
||||
public string description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single choice presented to the player inside an <see cref="EncounterDialogOptionSet"/>.
|
||||
/// Events fire in order when the option is picked and are dispatched through <see cref="EncounterResolver"/>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class EncounterDialogOption {
|
||||
/// <summary>Option text shown in the UI. Resolved through a shared
|
||||
/// <see cref="DialogLineLibrary"/> or falls back to inline text.</summary>
|
||||
public DialogLineRef text;
|
||||
|
||||
/// <summary>Ordered events executed by the resolver when this option is chosen.</summary>
|
||||
[SerializeReference, SubclassSelector]
|
||||
public List<IEncounterEvent> events;
|
||||
}
|
||||
|
||||
/// <summary>Visual assets for an encounter.</summary>
|
||||
[Serializable]
|
||||
public class EncounterVisuals {
|
||||
public Sprite icon;
|
||||
@@ -84,7 +49,6 @@ namespace Jovian.EncounterSystem {
|
||||
public Sprite encounterArt;
|
||||
}
|
||||
|
||||
/// <summary>Numeric/tuning configuration shared across every encounter kind.</summary>
|
||||
[Serializable]
|
||||
public class EncounterProperties {
|
||||
public int difficulty;
|
||||
|
||||
@@ -1,38 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Marker interface for designer-authored dialog option side effects. Events carry data only;
|
||||
/// behaviour is registered on <see cref="EncounterResolver"/> and dispatched by concrete type.
|
||||
/// Add a new event by creating a new <see cref="IEncounterEvent"/> implementation and registering
|
||||
/// a handler for it with the resolver.
|
||||
/// </summary>
|
||||
/// <summary>Data-only dialog option side effect. Handlers are registered on <see cref="EncounterResolver"/>.</summary>
|
||||
public interface IEncounterEvent {
|
||||
}
|
||||
|
||||
/// <summary>Transitions the flow to another encounter identified by <see cref="nextEncounterId"/>.</summary>
|
||||
[Serializable]
|
||||
public class ChainToEncounterEvent : IEncounterEvent {
|
||||
public string nextEncounterId;
|
||||
}
|
||||
|
||||
/// <summary>Starts a combat encounter by id. Intended to be handled by the combat play mode.</summary>
|
||||
[Serializable]
|
||||
public class StartCombatEvent : IEncounterEvent {
|
||||
public string combatEncounterId;
|
||||
}
|
||||
|
||||
/// <summary>Writes a line to whatever log sink the resolver's handler is configured with. Useful for debugging.</summary>
|
||||
[Serializable]
|
||||
public class LogEvent : IEncounterEvent {
|
||||
public string message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grants the referenced <see cref="Reward"/> asset to the party. The actual application is
|
||||
/// handled by a game-side delegate registered on <see cref="EncounterResolver"/>. Add multiple
|
||||
/// <see cref="GiveRewardEvent"/>s to a dialog option's event list to grant multiple rewards.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class GiveRewardEvent : IEncounterEvent {
|
||||
public Reward reward;
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Marker interface for the polymorphic payload of an <see cref="IEncounter"/>.
|
||||
/// Each concrete kind carries its own designer-facing data. Behaviour lives in resolvers
|
||||
/// and play modes — kinds are pure data. Add new kinds by creating a new <see cref="IEncounterKind"/>
|
||||
/// implementation; the SubclassSelector drawer will surface it automatically.
|
||||
/// </summary>
|
||||
/// <summary>Polymorphic payload on an <see cref="IEncounter"/>. Add a new kind by implementing
|
||||
/// this interface; the SubclassSelector drawer surfaces it automatically.</summary>
|
||||
public interface IEncounterKind {
|
||||
}
|
||||
|
||||
/// <summary>Combat encounter: triggers combat play mode with the specified enemy group.</summary>
|
||||
[Serializable]
|
||||
public class CombatKind : IEncounterKind {
|
||||
public string enemyGroupId;
|
||||
public string rewardTableId;
|
||||
}
|
||||
|
||||
/// <summary>Quest encounter: a step in a quest chain. The <see cref="nextEncounter"/> link is what
|
||||
/// <see cref="QuestProgress"/> walks to build the gated progression graph.</summary>
|
||||
[Serializable]
|
||||
public class QuestKind : IEncounterKind {
|
||||
public EncounterLink nextEncounter;
|
||||
public string questTitle;
|
||||
}
|
||||
|
||||
/// <summary>Dialogue-driven encounter with an NPC and optional faction reputation impact.</summary>
|
||||
[Serializable]
|
||||
public class SocialKind : IEncounterKind {
|
||||
public string npcId;
|
||||
@@ -33,32 +25,27 @@ namespace Jovian.EncounterSystem {
|
||||
public int reputationDelta;
|
||||
}
|
||||
|
||||
/// <summary>Puzzle encounter gated by a skill check against <see cref="difficultyClass"/>.</summary>
|
||||
[Serializable]
|
||||
public class PuzzleKind : IEncounterKind {
|
||||
public string puzzleId;
|
||||
public int difficultyClass;
|
||||
}
|
||||
|
||||
/// <summary>Exploration/discovery encounter gated by a perception check (<see cref="perceptionDC"/>).</summary>
|
||||
[Serializable]
|
||||
public class ExplorationKind : IEncounterKind {
|
||||
public int perceptionDC;
|
||||
}
|
||||
|
||||
/// <summary>Tutorial encounter driving a tutorial subsystem by id.</summary>
|
||||
[Serializable]
|
||||
public class TutorialKind : IEncounterKind {
|
||||
public string tutorialId;
|
||||
}
|
||||
|
||||
/// <summary>Environmental hazard that applies damage or a status without player choice.</summary>
|
||||
[Serializable]
|
||||
public class HazardKind : IEncounterKind {
|
||||
public int damageAmount;
|
||||
}
|
||||
|
||||
/// <summary>Catch-all with no kind-specific data — useful while prototyping.</summary>
|
||||
[Serializable]
|
||||
public class OtherKind : IEncounterKind {
|
||||
}
|
||||
|
||||
@@ -1,45 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Marker interface for the polymorphic payload of a <see cref="Reward"/>.
|
||||
/// Each concrete kind carries its own designer-facing data. Behaviour lives in a reward-applying
|
||||
/// handler registered on <see cref="EncounterResolver"/> for <see cref="GiveRewardEvent"/> —
|
||||
/// kinds are pure data. Add a new reward type by creating a new <see cref="IRewardKind"/>
|
||||
/// implementation; the SubclassSelector drawer will surface it automatically.
|
||||
/// </summary>
|
||||
/// <summary>Polymorphic payload on a <see cref="Reward"/>. Add a new kind by implementing this interface.</summary>
|
||||
public interface IRewardKind {
|
||||
}
|
||||
|
||||
/// <summary>Grants an amount of a named currency (gold, silver, faction-specific scrip, ...).</summary>
|
||||
[Serializable]
|
||||
public class CurrencyRewardKind : IRewardKind {
|
||||
public string currencyId;
|
||||
public int amount;
|
||||
}
|
||||
|
||||
/// <summary>Grants one or more copies of an item identified by id.</summary>
|
||||
[Serializable]
|
||||
public class ItemRewardKind : IRewardKind {
|
||||
public string itemId;
|
||||
public int quantity;
|
||||
}
|
||||
|
||||
/// <summary>Grants experience points to the party or a specific recipient.</summary>
|
||||
[Serializable]
|
||||
public class ExperienceRewardKind : IRewardKind {
|
||||
public int amount;
|
||||
}
|
||||
|
||||
/// <summary>Unlocks something identified by id (recipe, area, journal entry, achievement, ...).
|
||||
/// What is actually unlocked is decided by the game-side handler.</summary>
|
||||
[Serializable]
|
||||
public class UnlockableRewardKind : IRewardKind {
|
||||
public string unlockableId;
|
||||
}
|
||||
|
||||
/// <summary>Catch-all with no kind-specific data — useful for prototyping or one-off rewards
|
||||
/// whose semantics are carried by the <see cref="Reward.id"/> alone.</summary>
|
||||
[Serializable]
|
||||
public class OtherRewardKind : IRewardKind {
|
||||
}
|
||||
|
||||
@@ -3,17 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>Tag for a quest log entry.</summary>
|
||||
public enum QuestLogEventType {
|
||||
Started,
|
||||
Advanced,
|
||||
Completed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One chronological entry in the quest log. Names are cached alongside ids so a loaded save
|
||||
/// can display the log even if the underlying encounter has since been renamed or removed.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class QuestLogEntry {
|
||||
public QuestLogEventType type;
|
||||
@@ -23,34 +18,24 @@ namespace Jovian.EncounterSystem {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Chronological, serializable record of quest events. Subscribes to <see cref="QuestProgress"/>
|
||||
/// events at construction time — build it before any encounter fires so nothing is missed.
|
||||
/// Chronological, serialisable record of quest events. Subscribes at construction —
|
||||
/// build before any encounter fires or early entries will be missed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is the save payload for quest progression. On load, call <see cref="Restore"/> with the
|
||||
/// saved entries and then <see cref="QuestProgress.LoadResolvedIds"/> with
|
||||
/// <see cref="ResolvedEncounterIds"/> to rehydrate the gating set.
|
||||
/// </remarks>
|
||||
public class QuestLog {
|
||||
private readonly List<QuestLogEntry> entries = new();
|
||||
|
||||
/// <summary>The log in chronological order.</summary>
|
||||
public IReadOnlyList<QuestLogEntry> Entries => entries;
|
||||
|
||||
/// <summary>Subscribe to <paramref name="progress"/>'s quest events; every fire appends an entry.</summary>
|
||||
public QuestLog(QuestProgress progress) {
|
||||
progress.QuestStarted += quest => Record(QuestLogEventType.Started, null, quest);
|
||||
progress.QuestAdvanced += (from, to) => Record(QuestLogEventType.Advanced, from, to);
|
||||
progress.QuestCompleted += quest => Record(QuestLogEventType.Completed, null, quest);
|
||||
}
|
||||
|
||||
/// <summary>Return a copy of the current entries suitable for serialization.</summary>
|
||||
public List<QuestLogEntry> CreateSnapshot() {
|
||||
return new List<QuestLogEntry>(entries);
|
||||
}
|
||||
|
||||
/// <summary>Replace the current entries with those from a save. Pass the list straight from
|
||||
/// the save payload.</summary>
|
||||
public void Restore(IEnumerable<QuestLogEntry> saved) {
|
||||
entries.Clear();
|
||||
if(saved == null) {
|
||||
@@ -60,8 +45,6 @@ namespace Jovian.EncounterSystem {
|
||||
entries.AddRange(saved);
|
||||
}
|
||||
|
||||
/// <summary>Enumerate the distinct encounter ids present in the log — what
|
||||
/// <see cref="QuestProgress.LoadResolvedIds"/> needs on load.</summary>
|
||||
public IEnumerable<string> ResolvedEncounterIds() {
|
||||
return entries
|
||||
.Select(entry => entry.encounterInternalId)
|
||||
|
||||
@@ -3,39 +3,24 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Tracks which <see cref="QuestKind"/> encounters the party has resolved and gates any quest
|
||||
/// encounter whose predecessor hasn't fired yet. Consumers use <see cref="IsGated"/> to exclude
|
||||
/// blocked encounters from rolls, and <see cref="OnEncounterTriggered"/> to mark progress.
|
||||
/// Gated quest progression. Encounter E is gated iff some QuestKind encounter P has
|
||||
/// <c>P.nextEncounter == E</c> and P hasn't been resolved. Predecessor map is built once at
|
||||
/// construction; rolling and advancement are O(1).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Gating rule: encounter <c>E</c> is gated iff some other quest encounter <c>P</c> has
|
||||
/// <c>P.nextEncounter == E</c> and <c>P</c> hasn't been resolved yet. Build the predecessor map
|
||||
/// once in <c>IndexQuests</c>; rolling and advancement are both O(1).
|
||||
/// </remarks>
|
||||
public class QuestProgress {
|
||||
private readonly HashSet<string> resolvedIds = new();
|
||||
private readonly Dictionary<string, IEncounter> predecessorOf = new();
|
||||
|
||||
/// <summary>Fires when a root quest encounter (no predecessor) is resolved.</summary>
|
||||
public event Action<IEncounter> QuestStarted;
|
||||
|
||||
/// <summary>Fires when a chained quest encounter is resolved. Args: (previous, current).</summary>
|
||||
public event Action<IEncounter, IEncounter> QuestAdvanced;
|
||||
|
||||
/// <summary>Fires when a quest encounter with no <c>nextEncounter</c> is resolved.</summary>
|
||||
public event Action<IEncounter> QuestCompleted;
|
||||
|
||||
/// <summary>Snapshot of every quest encounter id the party has resolved. Save this in the save file.</summary>
|
||||
public IReadOnlyCollection<string> ResolvedIds => resolvedIds;
|
||||
|
||||
/// <summary>Construct and index the quest graph from the given collection.</summary>
|
||||
/// <param name="encountersCollection">The collection whose tables will be walked for quest chains.</param>
|
||||
public QuestProgress(EncountersCollection encountersCollection) {
|
||||
IndexQuests(encountersCollection);
|
||||
}
|
||||
|
||||
/// <summary>Returns <c>true</c> if the encounter is currently blocked because its quest
|
||||
/// predecessor hasn't been resolved yet.</summary>
|
||||
public bool IsGated(IEncounter encounter) {
|
||||
var id = encounter?.EncounterDefinition?.internalId;
|
||||
if(string.IsNullOrEmpty(id)) {
|
||||
@@ -49,9 +34,6 @@ namespace Jovian.EncounterSystem {
|
||||
return !resolvedIds.Contains(predecessor.EncounterDefinition.internalId);
|
||||
}
|
||||
|
||||
/// <summary>Inform the progress service that an encounter was just shown to the party.
|
||||
/// Non-quest encounters and already-resolved ones are no-ops. Fires one or two of the
|
||||
/// <see cref="QuestStarted"/>/<see cref="QuestAdvanced"/>/<see cref="QuestCompleted"/> events.</summary>
|
||||
public void OnEncounterTriggered(IEncounter encounter) {
|
||||
if(encounter?.Kind is not QuestKind questKind) {
|
||||
return;
|
||||
@@ -77,8 +59,6 @@ namespace Jovian.EncounterSystem {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Restore resolved quest ids from save data. Call after construction, before the
|
||||
/// first <see cref="OnEncounterTriggered"/>.</summary>
|
||||
public void LoadResolvedIds(IEnumerable<string> ids) {
|
||||
resolvedIds.Clear();
|
||||
if(ids == null) {
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Authored reward asset. Designers create one file per reusable reward ("10 Gold", "Rusty Sword",
|
||||
/// "Town Unlock: Riverfort") and reference it from <see cref="GiveRewardEvent"/> on dialog options.
|
||||
/// The <see cref="kind"/> payload chooses the reward type; the enclosing asset carries identity
|
||||
/// and presentation data shared across all types.
|
||||
/// </summary>
|
||||
/// <summary>Reusable reward asset referenced by <see cref="GiveRewardEvent"/>.</summary>
|
||||
[CreateAssetMenu(fileName = "Reward", menuName = "Jovian/Encounter System/Reward", order = 3)]
|
||||
public class Reward : ScriptableObject {
|
||||
/// <summary>Designer-facing short slug (e.g. "gold_10").</summary>
|
||||
public string id;
|
||||
|
||||
/// <summary>Display name shown to the player when the reward is granted.</summary>
|
||||
public string displayName;
|
||||
|
||||
/// <summary>Type-specific payload (currency, item, experience, ...). Authored via the SubclassSelector drawer.</summary>
|
||||
[SerializeReference, SubclassSelector]
|
||||
public IRewardKind kind;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,7 @@ using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.EncounterSystem {
|
||||
/// <summary>
|
||||
/// Marker attribute instructing the editor to render a concrete-type picker for a
|
||||
/// <c>[SerializeReference]</c> field (or list/array element). The drawer scans the declared
|
||||
/// base type's assembly for non-abstract implementations and offers them in a dropdown.
|
||||
/// </summary>
|
||||
/// <summary>Renders a concrete-type picker dropdown for a <c>[SerializeReference]</c> field.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||
public class SubclassSelectorAttribute : PropertyAttribute {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user