From 025a7aeee9e77a0f3cd72c5b34b460890e491a23 Mon Sep 17 00:00:00 2001 From: iisaev Date: Sun, 18 Oct 2026 12:41:42 +0300 Subject: [PATCH] init mod --- locale/en.properties | 2 + locale/ru.properties | 2 + manifest.json | 13 + src/Actions/MapCloseZoneAction.cs | 41 ++ src/Actions/MapHideZoneAction.cs | 40 ++ src/Actions/MapOpenZoneAction.cs | 43 ++ src/Actions/MapShowZoneAction.cs | 41 ++ src/Actions/MapZoneServiceImpl.cs | 33 ++ src/MapMarker.cs | 258 +++++++++ src/MapModInitializer.cs | 28 + src/MapWidget.cs | 890 ++++++++++++++++++++++++++++++ src/MapZone.cs | 121 ++++ src/MapZoneRules.cs | 40 ++ src/MapZoneService.cs | 333 +++++++++++ src/MapZoneStyle.cs | 175 ++++++ src/MapZonesLayer.cs | 231 ++++++++ src/SubsystemRegistration.cs | 12 + 17 files changed, 2303 insertions(+) create mode 100644 locale/en.properties create mode 100644 locale/ru.properties create mode 100644 manifest.json create mode 100644 src/Actions/MapCloseZoneAction.cs create mode 100644 src/Actions/MapHideZoneAction.cs create mode 100644 src/Actions/MapOpenZoneAction.cs create mode 100644 src/Actions/MapShowZoneAction.cs create mode 100644 src/Actions/MapZoneServiceImpl.cs create mode 100644 src/MapMarker.cs create mode 100644 src/MapModInitializer.cs create mode 100644 src/MapWidget.cs create mode 100644 src/MapZone.cs create mode 100644 src/MapZoneRules.cs create mode 100644 src/MapZoneService.cs create mode 100644 src/MapZoneStyle.cs create mode 100644 src/MapZonesLayer.cs create mode 100644 src/SubsystemRegistration.cs diff --git a/locale/en.properties b/locale/en.properties new file mode 100644 index 0000000..78d76b9 --- /dev/null +++ b/locale/en.properties @@ -0,0 +1,2 @@ +mod_map_base_name = World Map Engine +mod_map_base_desc = Engine world map subsystem: the fullscreen map overlay, polygon zones, markers, the zone list, the scenario-facing map service (open/close/show/hide actions) and the shared zone style and enterability rules. diff --git a/locale/ru.properties b/locale/ru.properties new file mode 100644 index 0000000..e5d5a1c --- /dev/null +++ b/locale/ru.properties @@ -0,0 +1,2 @@ +mod_map_base_name = Движок карты мира +mod_map_base_desc = Подсистема карты мира: полноэкранный оверлей карты, полигональные зоны, маркеры, список зон, сценарные действия open/close/show/hide и общие правила стиля и входа в зоны. diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..e12aec7 --- /dev/null +++ b/manifest.json @@ -0,0 +1,13 @@ +{ + "id": "map_base", + "name": "World Map Engine", + "version": "1.0.0", + "author": "ct", + "description": "Engine world map subsystem: the full-screen map overlay (MapWidget), polygon zones layer, markers, the zone list + runtime override singleton, the scenario-facing map service facade with the open/close/show/hide scenario actions, and the shared zone style and enterability rules. Replaces the former Core/UI/Tool Map* implementations and Core/Actions/MapZoneServiceImpl. The save-format DTO MapZoneStateData stays in the core because RuntimeSerializer owns the __core__ slot layout; the mod plugs into it through MapStateBridge.", + "category": "engine, library", + "sources": [ + "src/**/*.cs" + ], + "depends": ["interface>=1"], + "ui": [] +} diff --git a/src/Actions/MapCloseZoneAction.cs b/src/Actions/MapCloseZoneAction.cs new file mode 100644 index 0000000..87aedc4 --- /dev/null +++ b/src/Actions/MapCloseZoneAction.cs @@ -0,0 +1,41 @@ +using Cthangover.Core.Actions; + +namespace Cthangover.Mods.Map +{ + +/// +/// Closes a map zone for travel by ID. Used for zones that were open by +/// default but must become unavailable at some story point, or to take back +/// access granted earlier. The override is stored by the map service and +/// persisted in the save, so the closure survives scene changes and play +/// sessions. +/// +public class MapCloseZoneAction : IScenarioAction +{ + /// + /// Registered as "map.close_zone" — closes the zone named by the + /// required "zone_id" dialog variable. Unknown IDs are ignored with a + /// warning. + /// + public string Name => "map.close_zone"; + + /// + /// Reads "zone_id" and delegates to the map service. Missing + /// "zone_id" is logged and the action does nothing instead of throwing + /// inside dialog execution. + /// + public void Run(IActionContext ctx) + { + var zoneId = ctx.GetParam("zone_id"); + + if (string.IsNullOrEmpty(zoneId)) + { + ctx.Log("EVENT", "MapCloseZoneAction: missing 'zone_id' variable"); + return; + } + + ctx.Map.Close(zoneId); + ctx.Log("MAP", $"MapCloseZoneAction: close '{zoneId}'"); + } +} +} diff --git a/src/Actions/MapHideZoneAction.cs b/src/Actions/MapHideZoneAction.cs new file mode 100644 index 0000000..ae5f9df --- /dev/null +++ b/src/Actions/MapHideZoneAction.cs @@ -0,0 +1,40 @@ +using Cthangover.Core.Actions; + +namespace Cthangover.Mods.Map +{ + +/// +/// Hides a map zone by ID, removing it from the map until it is shown +/// again. Useful when a location is destroyed or temporarily inaccessible +/// in the story and the marker should disappear rather than appear closed. +/// The override is stored by the map service and persisted in the save. +/// +public class MapHideZoneAction : IScenarioAction +{ + /// + /// Registered as "map.hide_zone" — hides the zone named by the + /// required "zone_id" dialog variable. Unknown IDs are ignored with a + /// warning. + /// + public string Name => "map.hide_zone"; + + /// + /// Reads "zone_id" and delegates to the map service. Missing + /// "zone_id" is logged and the action does nothing instead of throwing + /// inside dialog execution. + /// + public void Run(IActionContext ctx) + { + var zoneId = ctx.GetParam("zone_id"); + + if (string.IsNullOrEmpty(zoneId)) + { + ctx.Log("EVENT", "MapHideZoneAction: missing 'zone_id' variable"); + return; + } + + ctx.Map.Hide(zoneId); + ctx.Log("MAP", $"MapHideZoneAction: hide '{zoneId}'"); + } +} +} diff --git a/src/Actions/MapOpenZoneAction.cs b/src/Actions/MapOpenZoneAction.cs new file mode 100644 index 0000000..66e7930 --- /dev/null +++ b/src/Actions/MapOpenZoneAction.cs @@ -0,0 +1,43 @@ +using Cthangover.Core.Actions; + +namespace Cthangover.Mods.Map +{ + +/// +/// Opens a map zone for travel by ID. The counterpart of +/// : authors close a zone in the map JSON +/// ("locked": true) and then open it from a scenario when the story +/// allows visiting it, e.g. after a quest tag is granted. The override is +/// stored by the map service and persisted in the save, so the zone stays +/// open across scene changes and play sessions. +/// +public class MapOpenZoneAction : IScenarioAction +{ + /// + /// Registered as "map.open_zone" — opens the zone named by the + /// required "zone_id" dialog variable. Unknown IDs are ignored with a + /// warning, so a scenario can safely reference a zone added by another + /// mod that may not be installed. + /// + public string Name => "map.open_zone"; + + /// + /// Reads "zone_id" and delegates to the map service. Missing + /// "zone_id" is a script authoring error: it is logged and the action + /// does nothing instead of throwing inside dialog execution. + /// + public void Run(IActionContext ctx) + { + var zoneId = ctx.GetParam("zone_id"); + + if (string.IsNullOrEmpty(zoneId)) + { + ctx.Log("EVENT", "MapOpenZoneAction: missing 'zone_id' variable"); + return; + } + + ctx.Map.Open(zoneId); + ctx.Log("MAP", $"MapOpenZoneAction: open '{zoneId}'"); + } +} +} diff --git a/src/Actions/MapShowZoneAction.cs b/src/Actions/MapShowZoneAction.cs new file mode 100644 index 0000000..f87fe70 --- /dev/null +++ b/src/Actions/MapShowZoneAction.cs @@ -0,0 +1,41 @@ +using Cthangover.Core.Actions; + +namespace Cthangover.Mods.Map +{ + +/// +/// Reveals a hidden map zone by ID. Hidden zones are completely absent from +/// the map — no polygon, no marker, no label, no zone counter entry — which +/// lets authors keep late-game locations out of the early map without +/// shipping separate map files. The override is stored by the map service +/// and persisted in the save. +/// +public class MapShowZoneAction : IScenarioAction +{ + /// + /// Registered as "map.show_zone" — reveals the zone named by the + /// required "zone_id" dialog variable. Unknown IDs are ignored with a + /// warning. + /// + public string Name => "map.show_zone"; + + /// + /// Reads "zone_id" and delegates to the map service. Missing + /// "zone_id" is logged and the action does nothing instead of throwing + /// inside dialog execution. + /// + public void Run(IActionContext ctx) + { + var zoneId = ctx.GetParam("zone_id"); + + if (string.IsNullOrEmpty(zoneId)) + { + ctx.Log("EVENT", "MapShowZoneAction: missing 'zone_id' variable"); + return; + } + + ctx.Map.Show(zoneId); + ctx.Log("MAP", $"MapShowZoneAction: show '{zoneId}'"); + } +} +} diff --git a/src/Actions/MapZoneServiceImpl.cs b/src/Actions/MapZoneServiceImpl.cs new file mode 100644 index 0000000..67ea442 --- /dev/null +++ b/src/Actions/MapZoneServiceImpl.cs @@ -0,0 +1,33 @@ +using Cthangover.Core.Actions; + +namespace Cthangover.Mods.Map +{ + /// + /// Thin adapter from the scenario action layer to the process-wide + /// singleton, following the same shape as + /// : the interface exists so + /// can be mocked in tests, while the actual + /// state always lives in the singleton and therefore survives for the whole + /// session and lands in the save through RuntimeSerializer. + /// + public class MapZoneServiceImpl : IMapZoneService + { + /// True when the zone is currently open for travel. + public bool IsOpen(string id) => MapZoneService.Instance.IsOpen(id); + + /// True when the zone is currently visible on the map. + public bool IsVisible(string id) => MapZoneService.Instance.IsVisible(id); + + /// Opens the zone for travel (overrides the map JSON's "locked" flag; persisted in the save). + public void Open(string id) => MapZoneService.Instance.Open(id); + + /// Closes the zone — the marker stays but travel is blocked (persisted in the save). + public void Close(string id) => MapZoneService.Instance.Close(id); + + /// Reveals a hidden zone on the map (persisted in the save). + public void Show(string id) => MapZoneService.Instance.Show(id); + + /// Hides a zone from the map entirely (persisted in the save). + public void Hide(string id) => MapZoneService.Instance.Hide(id); + } +} diff --git a/src/MapMarker.cs b/src/MapMarker.cs new file mode 100644 index 0000000..c9e1982 --- /dev/null +++ b/src/MapMarker.cs @@ -0,0 +1,258 @@ +using Cthangover.Core.UI; +using Cthangover.Core.UI.Theming; +using Godot; + +using Cthangover.Mods.Interface.Theming; +namespace Cthangover.Mods.Map +{ + /// + /// One location marker on the world map: a round emblem with the zone icon, + /// the localised name pill and an optional state note ("вы здесь" / + /// "закрыто"). The node structure lives in map_marker.tscn; this + /// class only binds zone data and repaints state-dependent styles from + /// . + /// + /// Visual state priority is current (green — the player's active scene) over + /// selected (gold) over locked (dimmed, with a drawn padlock) over hover. + /// The marker never navigates by itself: it reports presses and hover + /// changes to , which owns the selected zone, the + /// location card and the actual scene switch. That keeps a marker reusable + /// and the map behaviour in one place. + /// + public partial class MapMarker : VBoxContainer + { + [Signal] public delegate void ZonePressedEventHandler(string zoneId); + [Signal] public delegate void ZoneHoverChangedEventHandler(string zoneId, bool hovered); + + private const int EmblemRadius = 23; + private const float LockBadgeRadius = 9f; + + private Control _emblemWrap; + private Panel _emblem; + private TextureRect _glyph; + private Label _nameLabel; + private Label _noteLabel; + + private string _zoneId; + private bool _selected; + private bool _current; + private bool _locked; + private bool _hover; + + /// Resolves the prefab nodes and subscribes pointer events; the marker never navigates on its own, it only reports to . + public override void _Ready() + { + MouseFilter = MouseFilterEnum.Stop; + ResolveNodes(); + GuiInput += OnGuiInput; + MouseEntered += OnMouseEntered; + MouseExited += OnMouseExited; + } + + /// Binds a zone to the marker; is an already resolved marker texture. + public void Setup(MapZone zone, Texture2D icon, bool current, bool locked) + { + _zoneId = zone.ID; + _current = current; + _locked = locked; + _selected = false; + _hover = false; + + if (_glyph != null) + _glyph.Texture = icon; + + if (_nameLabel != null) + _nameLabel.Text = TranslationServer.Translate(zone.Title); + + RefreshStyle(); + } + + /// Re-applies the dynamic zone state after the player moved to another scene. + public void ApplyState(bool current, bool locked) + { + _current = current; + _locked = locked; + RefreshStyle(); + } + + /// Panel-owned selection: the selected marker gets the golden ring and surface. + public void SetSelected(bool selected) + { + if (_selected == selected) + return; + _selected = selected; + RefreshStyle(); + } + + /// + /// Hover can come from this marker's own pointer events or from the + /// paired polygon ( mirror); the + /// method only repaints and never re-emits, so the two layers cannot + /// loop signals through each other. + /// + public void SetHovered(bool hovered) + { + if (_hover == hovered) + return; + _hover = hovered; + RefreshStyle(); + } + + /// + /// Draws the padlock badge of a closed zone over the emblem corner. + /// A drawn badge is used instead of a texture so marker states need no + /// extra icons from mods and stay sharp at any UI scale. + /// + public override void _Draw() + { + if (!_locked || _emblemWrap == null) + return; + + var wrap = _emblemWrap.GetRect(); + var center = new Vector2(wrap.End.X - 3f, wrap.Position.Y + 3f); + var outline = UiTokens.BadgeLine(UiTokens.TypeEnemy); + + DrawCircle(center, LockBadgeRadius, UiTokens.Surface0); + DrawArc(center, LockBadgeRadius, 0f, Mathf.Tau, 28, outline, 1f, true); + DrawRect(new Rect2(center.X - 4.5f, center.Y - 0.5f, 9f, 6.5f), UiTokens.TypeEnemy, false, 1.2f); + DrawArc(center + new Vector2(0f, -0.5f), 3.2f, Mathf.Pi, Mathf.Tau, 14, UiTokens.TypeEnemy, 1.4f, true); + } + + private void ResolveNodes() + { + _emblemWrap = GetNodeOrNull("EmblemWrap"); + _emblem = GetNodeOrNull("EmblemWrap/Emblem"); + _glyph = GetNodeOrNull("EmblemWrap/Emblem/Glyph"); + _nameLabel = GetNodeOrNull