Compare commits

...
2 Commits
Author SHA1 Message Date
cthangover 62f99f11c6 LF .gitattributes of the new mods 2026-10-21 14:59:25 +02:00
cthangover 0d7c4c365f Engine is genre-neutral in docs and site; RPG fixes
- Site (7 languages), docs index, scene patch and collection docs, concepts:
  the engine is not a novel engine; games are collections (cthangover,
  rpg_demo, novel_template) over the same engine mods.
- Collections: mechanic novel_engine renamed to engine_2d ("2D engine").
- dialog_base: the dialog box starts hidden (empty box after loading a save
  on a map).
- characters_base owns the action card texts (keys shown in rpg_demo battles).
- rpg_walk covers save/menu/load and translated action texts.
- Path tests skip releases/ (build output).
- Godot .uid files of existing scripts.
2026-10-21 14:59:11 +02:00
51 changed files with 323 additions and 144 deletions

No files matched your search

+1 -1
View File
@@ -213,7 +213,7 @@ their load contexts released), shared mods stay loaded.
- Built-in collection packages (`collections/`):
- `cthangover` — the main game, all non-test mods;
- `novel_template` — the modder's starting point: a town with a bar and a home, nothing but text, backgrounds and
scene switches, on the bare novel engine. It shows the intended split: the game's own content in
scene switches, on the bare 2D engine mods. It shows the intended split: the game's own content in
`my_novel_game`, reusable backgrounds/sound in `town_pack` (shared with `cthangover`), engine mods;
- `novel_editor` and the toolset `dev_tools` — the editors (`dev_tools` also carries `scenario_studio`, so a game
that includes it gets the Studio button on its toolbar);
@@ -0,0 +1 @@
uid://fwacu8dqql60
+1
View File
@@ -0,0 +1 @@
uid://be6pp6x8l87o6
+1
View File
@@ -0,0 +1 @@
uid://cvcx1vfeaqq8g
+1 -1
View File
@@ -12,7 +12,7 @@
{
/// <summary>The main menu scene.</summary>
MainMenu,
/// <summary>The base visual novel scene used for all scenario-driven content.</summary>
/// <summary>The 2D shell scene that shows every logical scene (scenario-driven content, RPG maps).</summary>
BaseScene,
};
+2 -2
View File
@@ -5,7 +5,7 @@ using Cthangover.Core.Utils;
namespace Cthangover.Core.Scenes
{
/// <summary>
/// Defines a visual novel scene's default configuration as loaded from mod JSON
/// Defines a logical scene's default configuration as loaded from mod JSON
/// manifests. Specifies fallback visuals (background images with optional randomization),
/// ambient audio, and a default scenario script that executes when no other events
/// are registered for the scene. Instantiated by <see cref="ModManager"/> during
@@ -40,7 +40,7 @@ namespace Cthangover.Core.Scenes
/// <summary>
/// The file path to a .scenario file that executes when no registered events
/// exist for the scene. Acts as the visual novel fallback script.
/// exist for the scene. Acts as the scene's fallback script.
/// </summary>
public string DefaultScenario { get; set; }
}
+1 -1
View File
@@ -19,7 +19,7 @@ using Godot;
namespace Cthangover.Core.Scenes
{
/// <summary>
/// Central orchestrator for visual novel scene progression. Composes and schedules
/// Central orchestrator of logical scene progression. Composes and schedules
/// executable events from C# attributes and .scenario files, applies scene defaults
/// (random background, ambient audio), and routes to native Godot scenes for menus
/// and to mod-registered scene routes (see <see cref="SceneRouteRegistry"/>).
+3 -3
View File
@@ -23,11 +23,11 @@
"mods": ["cthangover_game>=2", "cthangover_murakami>=1", "cthangover_party>=1", "town_pack>=1"]
},
{
"id": "novel_engine",
"name": "Novel engine",
"id": "engine_2d",
"name": "2D engine",
"role": "engine",
"description": "2D shell, dialogs and scenario DSL, interactive objects, effects, in-game time and lighting, the world map.",
"locale": [ { "lang": "ru", "name": "Движок новеллы", "description": "2D-оболочка, диалоги и сценарный DSL, интерактивные объекты, эффекты, игровое время и свет, карта мира." } ],
"locale": [ { "lang": "ru", "name": "2D-движок", "description": "2D-оболочка, диалоги и сценарный DSL, интерактивные объекты, эффекты, игровое время и свет, карта мира." } ],
"mods": ["shell2d_base>=1", "dialog_base>=1", "interactive_base>=1", "effects_base>=1", "time_base>=1", "map_base>=1"]
},
{
+6 -6
View File
@@ -4,12 +4,12 @@
"name": "Novel Editor",
"author": "ct",
"version": "1.0.0",
"description": "Authoring workspace instead of a game: opens a screen with every installed editor — scenarios, scenes, maps, interactive objects, lighting, catalog export — over the novel engine and the story content they edit.",
"description": "Authoring workspace instead of a game: opens a screen with every installed editor — scenarios, scenes, maps, interactive objects, lighting, catalog export — over the 2D engine mods and the story content they edit.",
"entry": { "scene": "novel_editor" },
"tags": ["tools", "editor"],
"locale": [
{ "lang": "en", "name": "Novel Editor", "description": "Authoring workspace instead of a game: opens a screen with every installed editor — scenarios, scenes, maps, interactive objects, lighting, catalog export — over the novel engine and the story content they edit." },
{ "lang": "ru", "name": "Редактор новелл", "description": "Рабочее место автора вместо игры: открывает экран со всеми установленными редакторами — сценариев, сцен, карт, интерактива, света, экспорта каталога — поверх движка новеллы и сюжета, который они правят." }
{ "lang": "en", "name": "Novel Editor", "description": "Authoring workspace instead of a game: opens a screen with every installed editor — scenarios, scenes, maps, interactive objects, lighting, catalog export — over the 2D engine mods and the story content they edit." },
{ "lang": "ru", "name": "Редактор новелл", "description": "Рабочее место автора вместо игры: открывает экран со всеми установленными редакторами — сценариев, сцен, карт, интерактива, света, экспорта каталога — поверх модов 2D-движка и сюжета, который они правят." }
],
"mechanics": [
{
@@ -21,11 +21,11 @@
"mods": ["editor_tools>=1"]
},
{
"id": "novel_engine",
"name": "Novel engine",
"id": "engine_2d",
"name": "2D engine",
"role": "engine",
"description": "Scenes, scenarios, dialogs, interactive objects, effects, time and lighting, the world map: what the editors preview and play.",
"locale": [ { "lang": "ru", "name": "Движок новеллы", "description": "Сцены, сценарии, диалоги, интерактив, эффекты, время и свет, карта мира: то, что редакторы показывают и проигрывают." } ],
"locale": [ { "lang": "ru", "name": "2D-движок", "description": "Сцены, сценарии, диалоги, интерактив, эффекты, время и свет, карта мира: то, что редакторы показывают и проигрывают." } ],
"mods": ["cthangover_game>=1", "shell2d_base>=1", "dialog_base>=1", "interactive_base>=1", "effects_base>=1", "time_base>=1", "map_base>=1"]
},
{
+3 -3
View File
@@ -30,11 +30,11 @@
"mods": ["town_pack>=1"]
},
{
"id": "novel_engine",
"name": "Novel engine",
"id": "engine_2d",
"name": "2D engine",
"role": "engine",
"description": "2D shell, dialogs and the scenario DSL, audio.",
"locale": [ { "lang": "ru", "name": "Движок новеллы", "description": "2D-оболочка, диалоги и сценарный DSL, звук." } ],
"locale": [ { "lang": "ru", "name": "2D-движок", "description": "2D-оболочка, диалоги и сценарный DSL, звук." } ],
"mods": ["shell2d_base>=1", "dialog_base>=1", "audio_base>=1"]
}
]
+5 -5
View File
@@ -34,16 +34,16 @@
"id": "rpg",
"name": "Tile world",
"role": "gameplay",
"description": "Tile maps in three layers with collision, grid walking, map events, teleports and random encounters on top of the novel engine.",
"locale": [ { "lang": "ru", "name": "Тайловый мир", "description": "Тайловые карты в три слоя с коллизиями, ходьба по клеткам, события на карте, телепорты и случайные встречи поверх движка новеллы." } ],
"description": "Tile maps in three layers with collision, grid walking, map events, teleports and random encounters on top of the 2D engine mods.",
"locale": [ { "lang": "ru", "name": "Тайловый мир", "description": "Тайловые карты в три слоя с коллизиями, ходьба по клеткам, события на карте, телепорты и случайные встречи поверх модов 2D-движка." } ],
"mods": ["rpg>=1"]
},
{
"id": "novel_engine",
"name": "Novel engine",
"id": "engine_2d",
"name": "2D engine",
"role": "engine",
"description": "2D shell, dialogs and the scenario DSL, characters, audio.",
"locale": [ { "lang": "ru", "name": "Движок новеллы", "description": "2D-оболочка, диалоги и сценарный DSL, персонажи, звук." } ],
"locale": [ { "lang": "ru", "name": "2D-движок", "description": "2D-оболочка, диалоги и сценарный DSL, персонажи, звук." } ],
"mods": ["shell2d_base>=1", "dialog_base>=1", "characters_base>=1", "audio_base>=1"]
},
{
+3 -2
View File
@@ -1,8 +1,9 @@
# Collections and games (`Core/Mods/Collections`, `Core/Games`)
A game is not a mod. It is a **collection**: a package that names the mechanics a game is built from,
while the mods themselves stay reusable across games. The same engine runs `cthangover`,
`novel_template` and any player-made game, and switching between them happens inside one process.
while the mods themselves stay reusable across games. No genre is built in: the same engine mods run
the visual novel `cthangover`, the tile RPG `rpg_demo`, `novel_template` and any player-made game, and
switching between them happens inside one process.
## Model
+2 -1
View File
@@ -1,6 +1,7 @@
# RPG world (`mods/rpg`, `mods/rpg_editor`, `mods/village_pack`, `mods/rpg_demo_game`)
A top-down tile world in the spirit of RPG Maker, built on the novel engine instead of beside it.
A top-down tile world in the spirit of RPG Maker, built from the same engine mods as the other games
(2D shell, dialogues, battles) instead of beside them.
A **map is a logical scene**: entering scene `X` (any `switch_scene X`, a teleport, the return from a
battle, loading a save) mounts map `X` over the 2D shell. Everything the engine attaches to scenes
keeps working on maps — entry scenarios, ambience, the default background, saving rules, the dialog
@@ -1,2 +1,8 @@
mod_characters_base_name = Characters Engine
mod_characters_base_desc = Engine characters subsystem: character and action-card factories, the avatar factory and the character panel UI. Kernel resolves templates through ICharacterTemplateProvider/IActionTemplateProvider/IAvatarProvider contracts.
characters/action/physics_attack/name = Basic attack
characters/action/physics_attack/description = Attacks an enemy with a weapon, with a damage multiplier of <g>1.0</g>
characters/action/defence/name = Defence
characters/action/defence/description = Protects the target with a defence multiplier of <g>1.0</g>
characters/action/stun/name = Stun
characters/action/stun/description = Stuns the target for 2 turns
@@ -1,2 +1,8 @@
mod_characters_base_name = Движок персонажей
mod_characters_base_desc = Подсистема персонажей движка: фабрики персонажей и боевых карточек, фабрика аватаров и панель персонажа. Ядро получает шаблоны через контракты ICharacterTemplateProvider/IActionTemplateProvider/IAvatarProvider.
characters/action/physics_attack/name = Простая атака
characters/action/physics_attack/description = Атакует врага оружием, с коэффициентом урона <g>1.0</g>
characters/action/defence/name = Защита
characters/action/defence/description = Защищает цель с коэффициентом защиты <g>1.0</g>
characters/action/stun/name = Ошеломление
characters/action/stun/description = Ошеломляет цель на 2 хода
@@ -11,6 +11,7 @@ anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
visible = false
script = ExtResource("1_dialogbox")
answerBoxScene = ExtResource("2_answerbox")
+1 -1
View File
@@ -53,7 +53,7 @@ namespace Cthangover.Mods.Map
/// <summary>
/// Target scene identifier passed directly to
/// <c>SceneManager.SwitchScene</c> when the marker is clicked.
/// Causes the current visual-novel scene to transition to this zone.
/// Causes the current logical scene to transition to this zone.
/// An empty or unresolved scene marks the zone as closed: the map shows
/// its marker and polygon dimmed and blocks the transition button, which
/// lets authors sketch future regions without breaking navigation.
+1 -1
View File
@@ -3,7 +3,7 @@
"name": "RPG World",
"version": "1.0.0",
"author": "ct",
"description": "Top-down tile world in the spirit of RPG Maker on top of the novel engine: maps of square tiles in three layers (back, main, front) with a collision layer, walking on the grid with WASD or the arrows, events that talk, teleport or start battles with the action key E/F or on touch, random encounters. A map is a logical scene, so entry scenarios, ambience, saving and the dialog box work on maps unchanged; battles run on the turn-based platform with the game's battle core and return to the same cell. Scenario actions: rpg.battle, rpg.teleport.",
"description": "Top-down tile world in the spirit of RPG Maker on top of the 2D engine mods: maps of square tiles in three layers (back, main, front) with a collision layer, walking on the grid with WASD or the arrows, events that talk, teleport or start battles with the action key E/F or on touch, random encounters. A map is a logical scene, so entry scenarios, ambience, saving and the dialog box work on maps unchanged; battles run on the turn-based platform with the game's battle core and return to the same cell. Scenario actions: rpg.battle, rpg.teleport.",
"category": "gameplay",
"sources": [
"src/**/*.cs"
+1 -1
View File
@@ -9,7 +9,7 @@ namespace Cthangover.Mods.Rpg
/// scene that shows it: entering scene <c>X</c> (any <c>switch_scene X</c>, a
/// teleport, the return from a battle, loading a save) mounts map <c>X</c>
/// over the shell. Because the scene stays an ordinary scene, everything the
/// novel engine attaches to scenes works on a map unchanged — entry
/// engine attaches to scenes works on a map unchanged — entry
/// scenarios, ambience and music from <c>scenes/X.json</c>, the default
/// background (which also becomes the backdrop of battles started here),
/// saving rules.
+1 -1
View File
@@ -14,7 +14,7 @@ namespace Cthangover.Mods.Rpg
/// It follows the scene lifecycle: entering a scene whose name is a map id
/// mounts that map (<see cref="RpgWorldView"/>) and shows the dark backdrop;
/// any other scene leaves the host empty and invisible, so a game can mix
/// novel scenes and map scenes freely. The shell is rebuilt after battles,
/// picture-and-dialogue scenes and map scenes freely. The shell is rebuilt after battles,
/// so the host is too; the position survives in <see cref="RpgState"/>.
/// <para/>
/// <see cref="Current"/> is the host of the live shell, used by teleports
@@ -1,6 +1,9 @@
using System.Collections.Generic;
using Cthangover.Core.Battle;
using Cthangover.Core.Bootstrap;
using Cthangover.Core.Characters;
using Cthangover.Core.Factories.Impls;
using Cthangover.Core.Games;
using Cthangover.Core.Scenes;
using Cthangover.Core.Settings;
using Cthangover.Core.Utils;
@@ -26,6 +29,7 @@ namespace Cthangover.Mods.Rpg.Tests
private const string Village = "rpg_village";
private const string House = "rpg_elder_house";
private const string WinFlag = "rpg_test_win";
private const string SaveSlot = "slot_9901";
private readonly string shotsDir;
private readonly List<string> failures = new();
@@ -83,6 +87,7 @@ namespace Cthangover.Mods.Rpg.Tests
return;
Check(GameData.Instance.Runtime.CharacterData.Characters.ContainsKey("rpg_hero"), "party.add put the hero into the party");
Check(View.Player.Cell == new Vector2I(15, 14), $"player at the spawn (15,14), got {View.Player.Cell}");
CheckActionTexts();
Shot("village");
Press(Key.W, true);
Next();
@@ -188,11 +193,46 @@ namespace Cthangover.Mods.Rpg.Tests
Godot.Engine.TimeScale = 1;
Check(View.Player.Cell == new Vector2I(6, 8), $"the battle returns to the same cell, got {View.Player.Cell}");
Check(GameData.Instance.Runtime.Flags.TryGetValue(WinFlag, out var won) && won == "1", "the win flag is set after the victory");
// The menu's path: save in the game, go to the main menu, load.
SaveService.Save(SaveSlot);
GameSession.OpenMainMenu(GetTree());
Next();
return;
case 16:
if (RpgWorldHost.Current != null || ++waitTicks < 5)
return;
Check(GameSession.LoadAndEnter(this, SaveSlot), "the save loads");
Next();
return;
case 17:
if (View?.Map?.ID != House || GodotSceneService.IsTransitioning || ++waitTicks < 5)
return;
Shot("loaded");
var dialog = RpgInput.FindDialogBox();
Check(dialog != null && !dialog.IsRunning && !dialog.IsVisibleInTree(), $"no dialog box after loading (running={dialog?.IsRunning}, visible={dialog?.IsVisibleInTree()})");
Check(View.Player.Cell == new Vector2I(6, 8), $"the loaded game puts the player on the saved cell, got {View.Player.Cell}");
SaveService.DeleteSave(SaveSlot);
Finish();
return;
}
}
/// <summary>
/// The battle shows the names and descriptions of the hero's action cards:
/// their keys must be translated by a mod of this game, not only by
/// another game's content mod.
/// </summary>
private void CheckActionTexts()
{
var hero = CharacterFactory.Instance.Get("rpg_hero");
Check(hero?.Actions?.Count > 0, "the hero has action cards");
foreach (var action in hero?.Actions ?? new List<ActionCharacter>())
{
foreach (var key in new[] { action.Name, action.Description })
Check(!string.IsNullOrEmpty(key) && TranslationServer.Translate(key) != key, $"action text '{key}' is translated");
}
}
/// <summary>Advances a running dialog with the action key; true while it is still running.</summary>
private bool AdvanceDialog()
{
+30
View File
@@ -0,0 +1,30 @@
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
* text=auto eol=lf
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf
# Binaries: never converted, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.svg text
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dll binary
*.so binary
*.dylib binary
*.pdb binary
*.exe binary
*.zip binary
*.pck binary
*.moc3 binary
*.res binary
*.scn binary
+30
View File
@@ -0,0 +1,30 @@
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
* text=auto eol=lf
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf
# Binaries: never converted, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.svg text
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dll binary
*.so binary
*.dylib binary
*.pdb binary
*.exe binary
*.zip binary
*.pck binary
*.moc3 binary
*.res binary
*.scn binary
+30
View File
@@ -0,0 +1,30 @@
# Line endings: LF everywhere, on every OS, whatever core.autocrlf says.
* text=auto eol=lf
# cmd.exe misreads labels and goto in LF batch files, so they stay CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf
# Binaries: never converted, never diffed as text.
*.png binary
*.jpg binary
*.jpeg binary
*.webp binary
*.svg text
*.ogg binary
*.wav binary
*.mp3 binary
*.ttf binary
*.otf binary
*.woff binary
*.woff2 binary
*.dll binary
*.so binary
*.dylib binary
*.pdb binary
*.exe binary
*.zip binary
*.pck binary
*.moc3 binary
*.res binary
*.scn binary
+1 -1
View File
@@ -1 +1 @@
{"_tree": [{"title": "Documentation", "slug": "index"}, {"title": "mods", "slug": "", "children": [{"title": "Avatars", "slug": "mods/avatars/index", "children": []}, {"title": "Backgrounds", "slug": "mods/backgrounds/index", "children": []}, {"title": "Battle System", "slug": "mods/battle/index", "children": []}, {"title": "Mod Catalog & Network Repository", "slug": "mods/catalog/index", "children": []}, {"title": "Characters", "slug": "mods/characters/index", "children": []}, {"title": "Mod Collections", "slug": "mods/collections/index", "children": []}, {"title": "Configs — manifest.json", "slug": "mods/configs/index", "children": [{"title": "Game Config — game_config.json", "slug": "mods/configs/game_config"}, {"title": "Mod Config — mod_config.json", "slug": "mods/configs/mod_config"}]}, {"title": "Developer Tools", "slug": "mods/editor_tools/index", "children": [{"title": "Creating a Custom Tool", "slug": "mods/editor_tools/tutorial"}]}, {"title": "Event Bus", "slug": "mods/events/index", "children": []}, {"title": "Interactive Objects", "slug": "mods/interactives/index", "children": []}, {"title": "Items", "slug": "mods/items/index", "children": []}, {"title": "Mod Load Order", "slug": "mods/load-order/index", "children": []}, {"title": "Locale — Localization", "slug": "mods/locale/index", "children": []}, {"title": "Map and Travel Zones", "slug": "mods/map/index", "children": []}, {"title": "Scene Event Metadata", "slug": "mods/metadata/index", "children": []}, {"title": "Music & Playlists", "slug": "mods/musics/index", "children": [{"title": "Playlists — JSON Reference", "slug": "mods/musics/playlists/index", "children": []}]}, {"title": "Patches", "slug": "mods/patches/index", "children": [{"title": "Array Operations & Identity Keys", "slug": "mods/patches/arrays-and-identity"}, {"title": "Scene Patches", "slug": "mods/patches/scenes"}]}, {"title": "Quests", "slug": "mods/quests/index", "children": []}, {"title": "Recruiting", "slug": "mods/recruiting/index", "children": []}, {"title": "Save System", "slug": "mods/save/index", "children": [{"title": "Mod Save API", "slug": "mods/save/api"}, {"title": "Save Data Model", "slug": "mods/save/data-model"}, {"title": "Versioning & Compatibility", "slug": "mods/save/versioning"}]}, {"title": "Scenes", "slug": "mods/scenes/index", "children": [{"title": "Scenarios — DSL Reference", "slug": "mods/scenes/scenarios/index", "children": [{"title": "Scenario Actions", "slug": "mods/scenes/scenarios/actions/index", "children": []}, {"title": "DSL Command Reference", "slug": "mods/scenes/scenarios/dsl/index", "children": [{"title": "Conditions — Quest Expression Language", "slug": "mods/scenes/scenarios/dsl/conditions/index", "children": []}]}]}, {"title": "Runtime TSCN Scene Loading", "slug": "mods/scenes/tscn-runtime/index", "children": []}]}, {"title": "Mod Settings", "slug": "mods/settings/index", "children": []}, {"title": "Shaders", "slug": "mods/shaders/index", "children": []}, {"title": "Skills", "slug": "mods/skills/index", "children": []}, {"title": "Sounds — Sound Effects", "slug": "mods/sounds/index", "children": []}, {"title": "C# Source Code in Mods", "slug": "mods/src/index", "children": [{"title": "GDScript in Mods", "slug": "mods/src/gdscript/index", "children": [{"title": "Scenario Actions", "slug": "mods/src/gdscript/actions"}, {"title": "Battle Action Executors", "slug": "mods/src/gdscript/battle"}, {"title": "Custom Conditions", "slug": "mods/src/gdscript/conditions"}, {"title": "Item Actions", "slug": "mods/src/gdscript/items"}, {"title": "Mod Lifecycle", "slug": "mods/src/gdscript/lifecycle"}]}]}, {"title": "Status Effects", "slug": "mods/status_effects/index", "children": []}, {"title": "Time System", "slug": "mods/time/index", "children": []}, {"title": "Wrappers — UI Templates", "slug": "mods/wrappers/index", "children": []}, {"title": "Mod Packaging — .zip Distribution", "slug": "mods/zip/index", "children": []}]}, {"title": "Resources — Pluggable Mod Infrastructure", "slug": "resources/index", "children": [{"title": "Discovery Registry — `ModDiscoveryRegistry`", "slug": "resources/discovery-registry/index", "children": []}, {"title": "Loaders — `IModLoader`", "slug": "resources/loaders/index", "children": []}, {"title": "Providers — `IModFileProvider`", "slug": "resources/providers/index", "children": []}, {"title": "Scanners — `IModScanner`", "slug": "resources/scanners/index", "children": []}]}], "index": "![cthangover logo](../img/logo.png)\n\n# Documentation\n\nWelcome to the cthangover documentation. Here you'll find guides, code examples, and configuration references for mod and core development.\n\n## Kernel and game cores\n\ncthangover is not tied to a single genre. The C# kernel (`Core/`) provides genre-neutral infrastructure only: scenes and scenarios with the DSL, the mod registry, saves, resources, UI hosting, events, audio, and pluggable battle rulesets. It makes no assumptions about dialogueLine truncated
{"_tree": [{"title": "Documentation", "slug": "index"}, {"title": "mods", "slug": "", "children": [{"title": "Avatars", "slug": "mods/avatars/index", "children": []}, {"title": "Backgrounds", "slug": "mods/backgrounds/index", "children": []}, {"title": "Battle System", "slug": "mods/battle/index", "children": []}, {"title": "Mod Catalog & Network Repository", "slug": "mods/catalog/index", "children": []}, {"title": "Characters", "slug": "mods/characters/index", "children": []}, {"title": "Mod Collections", "slug": "mods/collections/index", "children": []}, {"title": "Configs — manifest.json", "slug": "mods/configs/index", "children": [{"title": "Game Config — game_config.json", "slug": "mods/configs/game_config"}, {"title": "Mod Config — mod_config.json", "slug": "mods/configs/mod_config"}]}, {"title": "Developer Tools", "slug": "mods/editor_tools/index", "children": [{"title": "Creating a Custom Tool", "slug": "mods/editor_tools/tutorial"}]}, {"title": "Event Bus", "slug": "mods/events/index", "children": []}, {"title": "Interactive Objects", "slug": "mods/interactives/index", "children": []}, {"title": "Items", "slug": "mods/items/index", "children": []}, {"title": "Mod Load Order", "slug": "mods/load-order/index", "children": []}, {"title": "Locale — Localization", "slug": "mods/locale/index", "children": []}, {"title": "Map and Travel Zones", "slug": "mods/map/index", "children": []}, {"title": "Scene Event Metadata", "slug": "mods/metadata/index", "children": []}, {"title": "Music & Playlists", "slug": "mods/musics/index", "children": [{"title": "Playlists — JSON Reference", "slug": "mods/musics/playlists/index", "children": []}]}, {"title": "Patches", "slug": "mods/patches/index", "children": [{"title": "Array Operations & Identity Keys", "slug": "mods/patches/arrays-and-identity"}, {"title": "Scene Patches", "slug": "mods/patches/scenes"}]}, {"title": "Quests", "slug": "mods/quests/index", "children": []}, {"title": "Recruiting", "slug": "mods/recruiting/index", "children": []}, {"title": "Save System", "slug": "mods/save/index", "children": [{"title": "Mod Save API", "slug": "mods/save/api"}, {"title": "Save Data Model", "slug": "mods/save/data-model"}, {"title": "Versioning & Compatibility", "slug": "mods/save/versioning"}]}, {"title": "Scenes", "slug": "mods/scenes/index", "children": [{"title": "Scenarios — DSL Reference", "slug": "mods/scenes/scenarios/index", "children": [{"title": "Scenario Actions", "slug": "mods/scenes/scenarios/actions/index", "children": []}, {"title": "DSL Command Reference", "slug": "mods/scenes/scenarios/dsl/index", "children": [{"title": "Conditions — Quest Expression Language", "slug": "mods/scenes/scenarios/dsl/conditions/index", "children": []}]}]}, {"title": "Runtime TSCN Scene Loading", "slug": "mods/scenes/tscn-runtime/index", "children": []}]}, {"title": "Mod Settings", "slug": "mods/settings/index", "children": []}, {"title": "Shaders", "slug": "mods/shaders/index", "children": []}, {"title": "Skills", "slug": "mods/skills/index", "children": []}, {"title": "Sounds — Sound Effects", "slug": "mods/sounds/index", "children": []}, {"title": "C# Source Code in Mods", "slug": "mods/src/index", "children": [{"title": "GDScript in Mods", "slug": "mods/src/gdscript/index", "children": [{"title": "Scenario Actions", "slug": "mods/src/gdscript/actions"}, {"title": "Battle Action Executors", "slug": "mods/src/gdscript/battle"}, {"title": "Custom Conditions", "slug": "mods/src/gdscript/conditions"}, {"title": "Item Actions", "slug": "mods/src/gdscript/items"}, {"title": "Mod Lifecycle", "slug": "mods/src/gdscript/lifecycle"}]}]}, {"title": "Status Effects", "slug": "mods/status_effects/index", "children": []}, {"title": "Time System", "slug": "mods/time/index", "children": []}, {"title": "Wrappers — UI Templates", "slug": "mods/wrappers/index", "children": []}, {"title": "Mod Packaging — .zip Distribution", "slug": "mods/zip/index", "children": []}]}, {"title": "Resources — Pluggable Mod Infrastructure", "slug": "resources/index", "children": [{"title": "Discovery Registry — `ModDiscoveryRegistry`", "slug": "resources/discovery-registry/index", "children": []}, {"title": "Loaders — `IModLoader`", "slug": "resources/loaders/index", "children": []}, {"title": "Providers — `IModFileProvider`", "slug": "resources/providers/index", "children": []}, {"title": "Scanners — `IModScanner`", "slug": "resources/scanners/index", "children": []}]}], "index": "![cthangover logo](../img/logo.png)\n\n# Documentation\n\nWelcome to the cthangover documentation. Here you'll find guides, code examples, and configuration references for mod and core development.\n\n## Kernel, engine mods and games\n\ncthangover is a game engine, not a game of one genre. The C# kernel (`Core/`) is a **mod player**: it loads, compiles and\nregisters mods, resolves their data, keeps saves, runs the event bus and hosts the UI. It ships no game, no scene and no\ngenre — a visual novelLine truncated
+21 -8
View File
@@ -4,21 +4,34 @@
Welcome to the cthangover documentation. Here you'll find guides, code examples, and configuration references for mod and core development.
## Kernel and game cores
## Kernel, engine mods and games
cthangover is not tied to a single genre. The C# kernel (`Core/`) provides genre-neutral infrastructure only: scenes and scenarios with the DSL, the mod registry, saves, resources, UI hosting, events, audio, and pluggable battle rulesets. It makes no assumptions about dialogue, portraits, or the shape of the game.
cthangover is a game engine, not a game of one genre. The C# kernel (`Core/`) is a **mod player**: it loads, compiles and
registers mods, resolves their data, keeps saves, runs the event bus and hosts the UI. It ships no game, no scene and no
genre — a visual novel and a top-down RPG are both just sets of mods.
A **game core** is an ordinary mod that turns the kernel into a concrete game. `cthangover_game` is the reference core shipped with the project: it defines characters, avatars, backgrounds, quests, items, and scenarios for a dialogue-driven story game. It is only one possible core — the kernel does not require it, and it uses the same public interfaces available to every mod.
**Engine mods** (`*_base`) are the building blocks a game picks: the 2D shell (`shell2d_base`), dialogues with the
scenario DSL (`dialog_base`), characters, items, quests, skills, the world map, time and lighting, the turn-based battle
platform with its cores (`card_battle`, `ff_battle`), the tile world (`rpg`), the UI toolkit and the main menu.
Any genre can be built the same way. For example, a 2D sprite RPG with tile movement, turn-based encounters, and its own inventory UI can be plugged in as a core mod: it reuses the kernel (scenes, saves, events, resource pipeline) and brings its own assets, scenarios, actions, and UI scenes. The same approach works for other genres.
A **game** is a [collection](mods/collections/): the game's own content mod plus the mechanics it chooses. The project
ships three of them, all running on the same engine mods and switching inside one process:
| Game | Content | Built from |
|---|---|---|
| `cthangover` | `cthangover_game`, `cthangover_murakami` | a visual novel with characters, quests, items, cooking and turn-based battles |
| `rpg_demo` | `rpg_demo_game`, `village_pack` | an RPG Maker style game: tile maps walked with WASD, events on E/F, battles, the map editor |
| `novel_template` | `my_novel_game`, `town_pack` | the smallest playable novel, a starting point for a new story |
| Layer | Location | Responsibility |
|---|---|---|
| Kernel | `Core/` | Genre-neutral services: scenes, scenario DSL, mods, saves, events, resources, UI, audio |
| Game core | `mods/<core>` | Defines a concrete game: content, rules, scenes, actions, UI |
| Gameplay mods | `mods/<mod>` | Extend a core or the kernel: patches, assets, custom actions, battle rulesets |
| Kernel | `Core/` | Genre-neutral services: mod pipeline, data resolvers, scenes, saves, events, UI hosting |
| Engine mods | `mods/<subject>_base`, mechanics | Subsystems a game may take: shell, dialogues and DSL, characters, battles, tile world, menu |
| Game | `collections/<game>` | The set of mechanics and the entry scene of one game |
| Content mods | `mods/<game>_game`, `mods/<theme>_pack` | Scenes, maps, scenarios, characters, art and text of a game, or reusable art |
A core can be disabled or replaced. Mods that depend on a specific core must declare it in `depends` — see [Configs — manifest.json](mods/configs/). The kernel itself never depends on a core.
The kernel never depends on a game or a genre. A mod that needs another one declares it in `depends` — see
[Configs — manifest.json](mods/configs/).
## Mod assets & data
+12 -8
View File
@@ -71,15 +71,15 @@ is not read.
],
"mechanics": [ // Ordered mechanics — drives the scheme layout
{
"id": "vn_core", // Stable id, unique within the collection
"name": "Novel Core", // Frame title in the scheme
"id": "scenes", // Stable id, unique within the collection
"name": "Scenes and dialogues", // Frame title in the scheme
"description": "Scenes, scenarios, dialogue.", // Tooltip
"role": "story", // Slot role the mechanic fills in the scheme editor
"mods": ["cthangover_game>=1"], // Versioned mod references (same syntax as manifest depends)
"optional": false, // Marks the mechanic as skippable
"selected": null, // Player's choice for an optional mechanic (null = on)
"locale": [ // Per-mechanic localized strings, same shape as below
{ "lang": "ru", "name": "Ядро новеллы", "description": "Сцены, сценарии, диалоги." }
{ "lang": "ru", "name": "Сцены и диалоги", "description": "Сцены, сценарии, диалоги." }
]
}
],
@@ -214,7 +214,7 @@ collection carries its texts with it:
# en.properties
collections/my_novel/name = My Novel
collections/my_novel/description = A visual novel with an optional card-battle layer.
collections/my_novel/mechanic/vn_core/name = Novel Core
collections/my_novel/mechanic/scenes/name = Scenes and dialogues
collections/my_novel/mechanic/cards/name = Card Battle
# ru.properties — same keys, Russian values
@@ -482,11 +482,15 @@ picker.
- `collections/novel_template/` — the smallest playable
game: the game's own content (`my_novel_game`: scenes, scenarios, text),
reusable assets (`town_pack`: town, home and bar backgrounds, bar ambience —
the main game uses the same pack) and the novel engine (`shell2d_base`,
the main game uses the same pack) and the engine mods it needs (`shell2d_base`,
`dialog_base`, `audio_base`). See "Start your own game" below.
- `collections/cthangover/` — the main game: story,
novel engine, characters, quests, items, battles with an optional second
battle core, Live2D and tools, each as its own mechanic.
- `collections/cthangover/` — the main game, a visual novel: story, the 2D
engine, characters, quests, items, battles with an optional second battle
core, Live2D and tools, each as its own mechanic.
- `collections/rpg_demo/` — an RPG Maker style game on the same engine mods:
its content (`rpg_demo_game`), pixel art (`village_pack`), the tile world
(`rpg`), the FF battle core with the card core as an option, and the map
editor (`rpg_editor`) as an optional tool.
- `collections/novel_editor/` — a "game" whose entry scene
is the editor hub instead of a story scene.
- `collections/dev_tools/` — the developer toolset to add to any game: the
+7 -5
View File
@@ -39,16 +39,18 @@ Patches are applied on top of the scene that won the override battle in `TscnSce
Every layer sees the result of the previous ones, so a later mod can override a value set by an earlier patch.
## Cross-mod base scene (shell2d_base + visual novel)
## Cross-mod base scene (shell2d_base + dialog_base + rpg)
`shell2d_base` owns the 2D shell scene; content mods add their layers to it with a patch instead of shipping their own shell:
`shell2d_base` owns the 2D shell scene; engine mods add their layers to it with a patch instead of shipping their own shell. Two mods of different genres extend the same shell:
```filestree
mods/
├── shell2d_base/
│ └── scenes/ui/base_scene.tscn ← BaseScene / Scene / ViewBox / TopView / slots / notifications
└── cthangover_game/
└── patches/scenes/ui/base_scene.tscn ← DialogCanvas on top of the shell
├── dialog_base/
│ └── patches/scenes/ui/base_scene.tscn ← DialogCanvas over the view box
└── rpg/
└── patches/scenes/ui/base_scene.tscn ← RpgWorldHost (the tile world) between the view box and the dialog
```
```ini
@@ -62,7 +64,7 @@ mods/
[node name="DialogCanvas" parent="Scene" index="2" instance=ExtResource("2_dialog")]
```
The base shell is a single flat scene, so `index="2"` inserts the dialog exactly between `ViewBox` and `TopView`, and later mods can override or delete shell nodes the same way. The shell contract content mods rely on: root `BaseScene` in group `main_event_chain`, `Scene/ViewBox`, `Scene/TopView`, slots `ModFirstPanel` / `ModBeforeUIPanel` / `ModLastPanel`, and `Lastground` inside the view box. Both the target path and the added resource paths may use the editor's `res://mods/<mod>/...` form — it is normalised to the mod-relative key at load time.
The base shell is a single flat scene, so `index="2"` inserts the dialog exactly between `ViewBox` and `TopView`. Patches cascade in load order: `rpg` depends on `dialog_base`, so its `RpgWorldHost` with the same `index="2"` lands before the `DialogCanvas`, and dialogs stay on top of the map. Later mods can override or delete shell nodes the same way. The shell contract mods rely on: root `BaseScene` in group `main_event_chain`, `Scene/ViewBox`, `Scene/TopView`, slots `ModFirstPanel` / `ModBeforeUIPanel` / `ModLastPanel`, and `Lastground` inside the view box. Both the target path and the added resource paths may use the editor's `res://mods/<mod>/...` form — it is normalised to the mod-relative key at load time.
## Operations
+7 -7
View File
@@ -4,11 +4,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>cthangover — a visual novel assembled from mods</title>
<title>cthangover — a game engine assembled from mods</title>
<meta name="description" content="A resource-free C# kernel loads mods from mods/, compiles them with Roslyn and assembles the game at runtime — battle rules, dialogues, quests and the mod manager are all mods. Godot 4.6 · C# / .NET 8 · MIT.">
<link rel="canonical" href="https://cthangover.ru/">
<meta property="og:type" content="website">
<meta property="og:title" content="cthangover — a visual novel assembled from mods">
<meta property="og:title" content="cthangover — a game engine assembled from mods">
<meta property="og:description" content="A resource-free C# kernel assembles the game from mods at runtime. 29 DSL commands, 3 battle rulesets, in-game mod manager.">
<meta property="og:url" content="https://cthangover.ru/">
<meta property="og:image" content="https://cthangover.ru/img/logo.png">
@@ -54,7 +54,7 @@
<section class="hero">
<div class="wrap">
<div class="kicker" data-i18n="kicker">mod-oriented engine · Godot 4.6 · C# / .NET 8 · MIT</div>
<h1 class="title"><span data-i18n="hero_h1a">A visual novel</span> <span class="amp" data-i18n="hero_h1b">assembled from mods</span></h1>
<h1 class="title"><span data-i18n="hero_h1a">A game engine</span> <span class="amp" data-i18n="hero_h1b">assembled from mods</span></h1>
<p class="tagline" data-i18n="hero_tag">The Core/ kernel ships no game scene at all…</p>
<div class="ctarow">
<a class="btn btn-gold" href="/quickstart.html" data-i18n="cta_start">Start in 5 minutes</a>
@@ -122,12 +122,12 @@
<section>
<div class="wrap">
<div class="cap" data-i18n="story_cap">Story</div>
<h2 class="sec" data-i18n="story_title">The story lives in content mods</h2>
<div class="cap" data-i18n="story_cap">Games</div>
<h2 class="sec" data-i18n="story_title">One engine, different games</h2>
<div class="storygrid">
<div>
<p data-i18n="story_p1">The first line is Murakami…</p>
<p data-i18n="story_p2">A new story is a new mod…</p>
<p data-i18n="story_p1">Cthangover, RPG Demo, Novel Template…</p>
<p data-i18n="story_p2">A new game is a new collection…</p>
</div>
<figure class="shot">
<img src="/img/screen3.jpg" alt="Murakami line">
+49 -49
View File
@@ -8,14 +8,14 @@ var LANGS = [["ru","Русский"],["en","English"],["es","Español"],["zh","
var I18N = {
ru:{qs_overview:"Обзор",nav_home:"Главная",nav_docs:"Документация",nav_quickstart:"Быстрый старт",nav_repo:"Репозиторий",
kicker:"мод-ориентированный движок · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"Визуальная новелла, которую",hero_h1b:"собирают моды",
hero_tag:"Ядро Core/ не содержит ни одной игровой сцены. При запуске оно загружает моды из mods/, компилирует их C#-код через Roslyn и собирает игру — от боёвки до диалогов. Каждая механика — обычная папка, которую можно открыть, изменить и убрать.",
hero_h1a:"Игровой движок, который",hero_h1b:"собирают моды",
hero_tag:"Ядро Core/ не содержит ни одной игры — и даже жанра. При запуске оно загружает моды из mods/, компилирует их C#-код через Roslyn и собирает из них игру: визуальную новеллу, RPG на тайловых картах, бои, диалоги. Каждая механика — обычная папка, которую можно открыть, изменить и убрать.",
cta_start:"Начать за 5 минут",cta_docs:"Документация",
st_dsl_l:"команд DSL",st_mods_l:"модов в репозитории",st_loc_l:"строк C# ядра и модов",st_battle_l:"боевых системы",
arch_cap:"Сборочная линия",arch_title:"Игра собирается слоями",
arch_l1t:"Ядро · Core/",arch_l1d:"Сценарии DSL, реестр модов, сохранения, события, ресурсы, UI, аудио. Контента нет — только сервисы.",
arch_l2t:"Платформа · shell2d_base",arch_l2d:"Сцена-оболочка 2D: окно вида, тулбар, слоты панелей, уведомления.",
arch_l3t:"Игровое ядро · cthangover_game",arch_l3d:"Диалоги, персонажи, предметы, квесты и карта зон поверх платформы.",
arch_l2t:"Движковые моды · *_base",arch_l2d:"2D-оболочка, диалоги и сценарный DSL, персонажи, предметы, квесты, пошаговые бои, тайловый мир — каждый такой мод игра берёт или не берёт.",
arch_l3t:"Игра · коллекция",arch_l3d:"Игра — это коллекция: собственный мод контента плюс выбранные механики — новелла с боями, прогулка в духе RPG Maker, шаблон.",
arch_l4t:"Ваш мод",arch_l4d:"Линия сюжета, механика или новые правила боя — обычная папка в mods/.",
feat_cap:"Возможности",feat_title:"Что уже в ядре",
f1t:"Roslyn в рантайме",f1d:"C#-исходники модов компилируются при загрузке; кэш по хэшу содержимого.",
@@ -37,9 +37,9 @@ dsl_p2:"Никакого редактора: scenario-файлы правятс
mods_cap:"Каталог",mods_title:"{n} модов уже в репозитории",
mods_note:"Названия и описания — как в manifest.json (english). Тестовые моды помечены.",
cat_engine:"движок",cat_gameplay:"геймплей",cat_story:"сюжет",cat_library:"библиотека",cat_ui:"интерфейс",cat_test:"тест",cat_tools:"инструменты",
story_cap:"Сюжет",story_title:"История живёт в модах контента",
story_p1:"Первая линия — Murakami: город после вторжения, встреча с волком у запертого дома и спасение незнакомки. Квесты, реплики и локации линии — обычные файлы мода.",
story_p2:"Новая история — это новый мод: свои сцены на DSL, персонажи в JSON, локализации в .properties. Ядро не знает ни про одну из них — и в этом смысл.",
story_cap:"Игры",story_title:"Один движок — разные игры",
story_p1:"Cthangover — визуальная новелла с пошаговыми боями и линией Мураками. RPG-демо — пиксельные деревня и лес, по которым ходишь на WASD и дерёшься на тех же боевых ядрах. Шаблон новеллы — самая маленькая играбельная новелла. Все они переключаются внутри одного запущенного процесса.",
story_p2:"Новая игра — это новая коллекция: ваш мод контента со сценами, персонажами и переводами плюс нужные механики. Ядро не знает ни про одну из них — и в этом смысл.",
shots_cap:"Кадры",shots_title:"Скриншоты",
cta_title:"Открытый код — открытый конвейер",
cta_p:"Проект распространяется по MIT. Репозиторий на собственном Gitea: ядро, моды, документация и сайт собираются из одного дерева исходников.",
@@ -57,14 +57,14 @@ not_found:"Раздел ещё не написан",docs_here:"Документ
en:{qs_overview:"Overview",nav_home:"Home",nav_docs:"Documentation",nav_quickstart:"Quick Starts",nav_repo:"Repository",
kicker:"mod-oriented engine · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"A visual novel",hero_h1b:"assembled from mods",
hero_tag:"The Core/ kernel ships no game scene at all. At boot it loads mods from mods/, compiles their C# through Roslyn and assembles the game — from the battle rules to the dialogues. Every mechanic is a plain folder you can open, edit and remove.",
hero_h1a:"A game engine",hero_h1b:"assembled from mods",
hero_tag:"The Core/ kernel ships no game at all — not even a genre. At boot it loads mods from mods/, compiles their C# through Roslyn and assembles the game from them: a visual novel, an RPG on tile maps, battles, dialogues. Every mechanic is a plain folder you can open, edit and remove.",
cta_start:"Start in 5 minutes",cta_docs:"Documentation",
st_dsl_l:"DSL commands",st_mods_l:"mods in the repository",st_loc_l:"lines of C#, kernel and mods",st_battle_l:"battle rulesets",
arch_cap:"Assembly line",arch_title:"The game is built in layers",
arch_l1t:"Kernel · Core/",arch_l1d:"Scenario DSL, mod registry, saves, events, resources, UI hosting, audio. No content — services only.",
arch_l2t:"Platform · shell2d_base",arch_l2d:"The 2D shell scene: view box, toolbar, panel slots, notifications.",
arch_l3t:"Game core · cthangover_game",arch_l3d:"Dialogues, characters, items, quests and the map zones on top of the platform.",
arch_l2t:"Engine mods · *_base",arch_l2d:"The 2D shell, dialogues and the scenario DSL, characters, items, quests, turn-based battles, the tile world — each is a mod a game may take or leave.",
arch_l3t:"Game · collection",arch_l3d:"A game is a collection: its own content mod plus the mechanics it picks — a novel with battles, an RPG Maker style walk, a template.",
arch_l4t:"Your mod",arch_l4d:"A story line, a mechanic or new battle rules — a plain folder in mods/.",
feat_cap:"Features",feat_title:"Already in the kernel",
f1t:"Roslyn at runtime",f1d:"Mod C# sources compile on load; the cache is keyed by content hash.",
@@ -86,9 +86,9 @@ dsl_p2:"No editor required: scenario files are edited in the same editor as the
mods_cap:"Catalog",mods_title:"{n} mods already in the repository",
mods_note:"Names and descriptions exactly as in manifest.json (english). Test mods are tagged.",
cat_engine:"engine",cat_gameplay:"gameplay",cat_story:"story",cat_library:"library",cat_ui:"interface",cat_test:"test",cat_tools:"tools",
story_cap:"Story",story_title:"The story lives in content mods",
story_p1:"The first line is Murakami: a city after the invasion, a wolf at a locked door and a stranger to rescue. The line's quests, lines and locations are plain mod files.",
story_p2:"A new story is a new mod: its DSL scenes, JSON characters, .properties localizations. The kernel knows nothing about any of them — that is the point.",
story_cap:"Games",story_title:"One engine, different games",
story_p1:"Cthangover is a visual novel with turn-based battles and Murakami's story line. RPG Demo walks a pixel village and forest with WASD and fights on the same battle cores. Novel Template is the smallest playable novel. All of them switch inside one running process.",
story_p2:"A new game is a new collection: your content mod with its scenes, characters and localizations, plus the mechanics it needs. The kernel knows nothing about any of them — that is the point.",
shots_cap:"Frames",shots_title:"Screenshots",
cta_title:"Open code, open pipeline",
cta_p:"The project is MIT-licensed. The repository lives on a self-hosted Gitea: kernel, mods, documentation and this site are built from one source tree.",
@@ -106,14 +106,14 @@ not_found:"Section not written yet",docs_here:"Documentation is English-first: t
es:{qs_overview:"Resumen",nav_home:"Inicio",nav_docs:"Documentación",nav_quickstart:"Inicios rápidos",nav_repo:"Repositorio",
kicker:"motor orientado a mods · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"Una novela visual",hero_h1b:"ensamblada con mods",
hero_tag:"El núcleo Core/ no incluye ni una escena de juego. Al arrancar carga los mods de mods/, compila su C# con Roslyn y ensambla el juego — del combate a los diálogos. Cada mecánica es una carpeta que puedes abrir, editar y quitar.",
hero_h1a:"Un motor de juego",hero_h1b:"ensamblado con mods",
hero_tag:"El núcleo Core/ no trae ningún juego, ni siquiera un género. Al arrancar carga los mods de mods/, compila su C# con Roslyn y monta con ellos el juego: una novela visual, un RPG sobre mapas de tiles, combates, diálogos. Cada mecánica es una carpeta normal que puedes abrir, editar y quitar.",
cta_start:"Empieza en 5 minutos",cta_docs:"Documentación",
st_dsl_l:"comandos DSL",st_mods_l:"mods en el repositorio",st_loc_l:"líneas de C# núcleo y mods",st_battle_l:"sistemas de combate",
arch_cap:"Línea de montaje",arch_title:"El juego se construye por capas",
arch_l1t:"Núcleo · Core/",arch_l1d:"DSL de escenas, registro de mods, guardados, eventos, recursos, UI y audio. Sin contenido: solo servicios.",
arch_l2t:"Plataforma · shell2d_base",arch_l2d:"Escena-base 2D: ventana de vista, barra, huecos de paneles, notificaciones.",
arch_l3t:"Núcleo de juego · cthangover_game",arch_l3d:"Diálogos, personajes, objetos, misiones y zonas de mapa sobre la plataforma.",
arch_l2t:"Mods de motor · *_base",arch_l2d:"El shell 2D, los diálogos y el DSL de escenarios, personajes, objetos, misiones, combates por turnos, el mundo de tiles: cada uno es un mod que un juego toma o deja.",
arch_l3t:"Juego · colección",arch_l3d:"Un juego es una colección: su propio mod de contenido más las mecánicas que elige — una novela con combates, un paseo al estilo RPG Maker, una plantilla.",
arch_l4t:"Tu mod",arch_l4d:"Una línea argumental, una mecánica o reglas de combate nuevas: una carpeta más en mods/.",
feat_cap:"Características",feat_title:"Ya en el núcleo",
f1t:"Roslyn en runtime",f1d:"El C# de los mods se compila al cargar; caché por hash del contenido.",
@@ -135,9 +135,9 @@ dsl_p2:"Sin editor: los .scenario se editan con el mismo editor que el código.
mods_cap:"Catálogo",mods_title:"{n} mods ya en el repositorio",
mods_note:"Nombres y descripciones tal cual en manifest.json (inglés). Los mods de prueba van etiquetados.",
cat_engine:"motor",cat_gameplay:"jugabilidad",cat_story:"historia",cat_library:"librería",cat_ui:"interfaz",cat_test:"prueba",cat_tools:"herramientas",
story_cap:"Historia",story_title:"La historia vive en los mods de contenido",
story_p1:"La primera línea es Murakami: la ciudad tras la invasión, un lobo en una puerta cerrada y una desconocida que rescatar. Misiones, réplicas y localizaciones: archivos normales del mod.",
story_p2:"Una historia nueva es un mod nuevo: sus escenas DSL, personajes JSON, localizaciones .properties. El núcleo no sabe nada de ellas: ese es el punto.",
story_cap:"Juegos",story_title:"Un motor, juegos distintos",
story_p1:"Cthangover es una novela visual con combates por turnos y la línea de Murakami. RPG Demo recorre un pueblo y un bosque pixelados con WASD y combate con los mismos núcleos de batalla. Novel Template es la novela jugable más pequeña. Todos cambian dentro de un mismo proceso en ejecución.",
story_p2:"Un juego nuevo es una colección nueva: tu mod de contenido con sus escenas, personajes y traducciones, más las mecánicas que necesita. El núcleo no sabe nada de ninguno, y esa es la idea.",
shots_cap:"Fotogramas",shots_title:"Capturas",
cta_title:"Código abierto, pipeline abierto",
cta_p:"El proyecto se distribuye bajo MIT. El repositorio vive en un Gitea propio: núcleo, mods, documentación y este sitio se construyen de un mismo árbol de fuentes.",
@@ -155,14 +155,14 @@ not_found:"Sección aún sin escribir",docs_here:"La documentación está en ing
zh:{qs_overview:"总览",nav_home:"首页",nav_docs:"文档",nav_quickstart:"快速上手",nav_repo:"代码仓库",
kicker:"面向 Mod 的引擎 · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"一款",hero_h1b:"由模组拼成的视觉小说",
hero_tag:"Core/ 内核不包含任何游戏场景。启动时它从 mods/ 加载模组,用 Roslyn 编译其中的 C# 代码,再组装出整款游戏——从战斗规则到对话。每个机制都是一个普通文件夹:可打开、可修改、可移除。",
hero_h1a:"一款",hero_h1b:"由模组拼成的游戏引擎",
hero_tag:"Core/ 内核不包含任何游戏,甚至不限定类型。启动时它从 mods/ 加载模组,用 Roslyn 编译其 C# 代码,再用它们组装出游戏:视觉小说、瓦片地图上的 RPG、战斗、对话。每个机制都是一个普通文件夹,可以打开、修改和删除。",
cta_start:"5 分钟上手",cta_docs:"文档",
st_dsl_l:"条 DSL 指令",st_mods_l:"个仓库内模组",st_loc_l:"行 C#(内核+模组)",st_battle_l:"套战斗规则",
arch_cap:"装配线",arch_title:"游戏分层组装",
arch_l1t:"内核 · Core/",arch_l1d:"场景 DSL、模组注册表、存档、事件、资源、UI、音频。没有内容——只有服务。",
arch_l2t:"平台 · shell2d_base",arch_l2d:"2D 壳场景:视图框、工具栏、面板槽位、通知。",
arch_l3t:"游戏内核 · cthangover_game",arch_l3d:"在平台之上提供对话、角色、物品、任务与地图区域。",
arch_l2t:"引擎模组 · *_base",arch_l2d:"2D 外壳、对话与剧本 DSL、角色、物品、任务、回合制战斗、瓦片世界——每一个都是游戏可选可弃的模组。",
arch_l3t:"游戏 · 合集",arch_l3d:"游戏就是一个合集:它自己的内容模组,加上所选的机制——带战斗的小说、RPG Maker 风格的漫游、模板。",
arch_l4t:"你的模组",arch_l4d:"一条剧情线、一个机制或新的战斗规则——mods/ 里多一个文件夹而已。",
feat_cap:"特性",feat_title:"内核已具备",
f1t:"运行时 Roslyn",f1d:"模组的 C# 源码在加载时编译,按内容哈希缓存。",
@@ -184,9 +184,9 @@ dsl_p2:"不需要编辑器:.scenario 与代码用同一个编辑器修改。
mods_cap:"目录",mods_title:"仓库中已有 {n} 个模组",
mods_note:"名称与描述与 manifest.json 一致(英文)。测试模组已标注。",
cat_engine:"引擎",cat_gameplay:"玩法",cat_story:"剧情",cat_library:"库",cat_ui:"界面",cat_test:"测试",cat_tools:"工具",
story_cap:"剧情",story_title:"故事活在内容模组里",
story_p1:"第一条线是 Murakami:入侵后的城市、紧锁门前狼的踪迹、等待救援的陌生少女。这条线的任务、台词与地点都是模组的普通文件。",
story_p2:"新故事就是新模组:自己的 DSL 场景、JSON 角色、.properties 本地化。内核对它们一无所知——这正是意义所在。",
story_cap:"游戏",story_title:"一个引擎,不同的游戏",
story_p1:"Cthangover 是带回合制战斗和村上线的视觉小说。RPG Demo 用 WASD 走遍像素村庄和森林,并使用同样的战斗核心。Novel Template 是最小的可玩小说。它们都在同一个运行中的进程内切换。",
story_p2:"新游戏就是新合集:你的内容模组(场景、角色、翻译)加上需要的机制。内核对它们一无所知——这正是关键。",
shots_cap:"画面",shots_title:"截图",
cta_title:"开放代码,开放流水线",
cta_p:"项目以 MIT 许可发布。仓库托管在自建 Gitea 上:内核、模组、文档与本网站出自同一棵源码树。",
@@ -204,14 +204,14 @@ not_found:"章节尚未编写",docs_here:"文档以英文为主:左侧为目
ja:{qs_overview:"概要",nav_home:"ホーム",nav_docs:"ドキュメント",nav_quickstart:"クイックスタート",nav_repo:"リポジトリ",
kicker:"Mod指向エンジン · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"Modが組み上げる",hero_h1b:"ビジュアルノベル",
hero_tag:"Core/ のカーネルにはゲームシーンが一切ありません。起動時に mods/ からModを読み込み、Roslyn で C# をコンパイルし、戦闘ルールから会話までゲームを組み上げます。すべての仕組みは普通のフォルダ——開いて、書き換えて、外せます。",
hero_h1a:"Modが組み上げる",hero_h1b:"ゲームエンジン",
hero_tag:"Core/ カーネルにはゲームが一つも含まれず、ジャンルさえ決まっていません。起動時に mods/ からModを読み込み、C# を Roslyn でコンパイルし、それらからゲームを組み立てます:ビジュアルノベル、タイルマップのRPG、バトル、会話。どのメカニクスも、開いて編集し削除できる普通のフォルダです。",
cta_start:"5分で開始",cta_docs:"ドキュメント",
st_dsl_l:"個のDSLコマンド",st_mods_l:"個のModを同梱",st_loc_l:"行のC#(本体+Mod)",st_battle_l:"種類の戦闘ルール",
arch_cap:"組み立てライン",arch_title:"ゲームはレイヤーで組み上がる",
arch_l1t:"カーネル · Core/",arch_l1d:"シナリオDSL・Modレジストリ・セーブ・イベント・リソース・UI・オーディオ。コンテントなし、サービスだけ。",
arch_l2t:"プラットフォーム · shell2d_base",arch_l2d:"2Dの殻シーン:ビュー枠、ツールバー、パネルスロット、通知。",
arch_l3t:"ゲームコア · cthangover_game",arch_l3d:"プラットフォームの上に会話・キャラクター・アイテム・クエスト・マップゾーン。",
arch_l2t:"エンジンMod · *_base",arch_l2d:"2Dシェル、会話とシナリオDSL、キャラクター、アイテム、クエスト、ターン制バトル、タイルワールド——どれもゲームが選んで使うModです。",
arch_l3t:"ゲーム · コレクション",arch_l3d:"ゲームはコレクションです:独自のコンテンツModと選んだメカニクス——バトル付きノベル、RPGツクール風の探索、テンプレート。",
arch_l4t:"あなたのMod",arch_l4d:"新しい物語線・メカニクス・戦闘ルールは、mods/ の普通のフォルダ。",
feat_cap:"機能",feat_title:"カーネルに既にあるもの",
f1t:"ランタイム Roslyn",f1d:"ModのC#ソースをロード時にコンパイル。内容ハッシュでキャッシュ。",
@@ -233,9 +233,9 @@ dsl_p2:"エディタは不要:.scenario はコードと同じエディタで
mods_cap:"カタログ",mods_title:"リポジトリには既に{n}のMod",
mods_note:"名称・説明は manifest.json のまま(英語)。テスト用Modにはタグ付き。",
cat_engine:"エンジン",cat_gameplay:"ゲームプレイ",cat_story:"ストーリー",cat_library:"ライブラリ",cat_ui:"インターフェース",cat_test:"テスト",cat_tools:"ツール",
story_cap:"ストーリー",story_title:"物語はコンテントModの中に",
story_p1:"最初の線は Murakami:侵攻後の街、鍵のかかった扉の狼、救い出す少女。クエストもセリフもロケーションも、すべてModの普通のファイルです。",
story_p2:"新しい物語は新しいMod:独自のDSLシーン、JSONキャラクター、.properties ローカライズ。カーネルは何も知りません——それが肝心。",
story_cap:"ゲーム",story_title:"一つのエンジン、異なるゲーム",
story_p1:"Cthangover はターン制バトルと村上のストーリーを持つビジュアルノベル。RPG Demo はピクセルの村と森を WASD で歩き、同じバトルコアで戦います。Novel Template は最小のプレイ可能なノベル。どれも一つの実行中プロセスの中で切り替わります。",
story_p2:"新しいゲームは新しいコレクション:シーン、キャラクター、翻訳を持つコンテンツModと必要なメカニクス。カーネルはそのどれも知りません——それが狙いです。",
shots_cap:"スクリーンショット",shots_title:"画面",
cta_title:"オープンなコード、オープンなパイプライン",
cta_p:"プロジェクトはMITライセンス。リポジトリはセルフホストのGiteaにあり、カーネル・Mod・ドキュメント・このサイトも同じソースツリーから組み立てられます。",
@@ -253,14 +253,14 @@ not_found:"セクションは未作成",docs_here:"ドキュメントは英語
ko:{qs_overview:"개요",nav_home:"홈",nav_docs:"문서",nav_quickstart:"빠른 시작",nav_repo:"저장소",
kicker:"모드 지향 엔진 · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"모드가 조립하는",hero_h1b:"비주얼 노벨",
hero_tag:"Core/ 커널에는 게임 장면이 하나도 없습니다. 부팅 때 mods/ 에서 모드를 읽고 Roslyn으로 C#을 컴파일해 전투 규칙부터 대화까지 게임을 조립합니다. 모든 메커니즘은 평범한 폴더—열고, 고치고, 뗄 수 있습니다.",
hero_h1a:"모드가 조립하는",hero_h1b:"게임 엔진",
hero_tag:"Core/ 커널에는 게임이 하나도 없고 장르조차 정해져 있지 않습니다. 시작할 때 mods/에서 모드를 불러와 C#을 Roslyn으로 컴파일하고, 그것들로 게임을 조립합니다: 비주얼 노벨, 타일 맵 위의 RPG, 전투, 대화. 모든 메커니즘은 열고 고치고 지울 수 있는 평범한 폴더입니다.",
cta_start:"5분 만에 시작",cta_docs:"문서",
st_dsl_l:"개의 DSL 명령",st_mods_l:"개의 모드 동봉",st_loc_l:"줄의 C#(커널+모드)",st_battle_l:"가지 전투 규칙",
arch_cap:"조립 라인",arch_title:"게임은 층층이 조립됩니다",
arch_l1t:"커널 · Core/",arch_l1d:"시나리오 DSL, 모드 레지스트리, 저장, 이벤트, 리소스, UI, 오디오. 콘텐츠 없이 서비스만.",
arch_l2t:"플랫폼 · shell2d_base",arch_l2d:"2D 껍데기 장면: 뷰 박스, 툴바, 패널 슬롯, 알림.",
arch_l3t:"게임 코어 · cthangover_game",arch_l3d:"플랫폼 위에 대화·캐릭터·아이템·퀘스트·맵 구역.",
arch_l2t:"엔진 모드 · *_base",arch_l2d:"2D 셸, 대화와 시나리오 DSL, 캐릭터, 아이템, 퀘스트, 턴제 전투, 타일 월드 — 모두 게임이 고르는 모드입니다.",
arch_l3t:"게임 · 컬렉션",arch_l3d:"게임은 컬렉션입니다: 자체 콘텐츠 모드와 고른 메커니즘 — 전투가 있는 노벨, RPG 메이커 풍의 탐험, 템플릿.",
arch_l4t:"당신의 모드",arch_l4d:"새 이야기 선, 메커니즘, 전투 규칙—mods/ 의 평범한 폴더 하나.",
feat_cap:"기능",feat_title:"커널에 이미 있는 것",
f1t:"런타임 Roslyn",f1d:"모드의 C# 소스를 적재 시 컴파일, 내용 해시로 캐시.",
@@ -282,9 +282,9 @@ dsl_p2:"에디터 불필요: .scenario 는 코드와 같은 에디터에서 고
mods_cap:"카탈로그",mods_title:"저장소에 이미 {n}개의 모드",
mods_note:"이름과 설명은 manifest.json 그대로(영어). 테스트 모드는 태그 표시.",
cat_engine:"엔진",cat_gameplay:"게임플레이",cat_story:"스토리",cat_library:"라이브러리",cat_ui:"인터페이스",cat_test:"테스트",cat_tools:"도구",
story_cap:"스토리",story_title:"이야기는 콘텐츠 모드 안에",
story_p1:"첫 선은 Murakami: 침공 뒤의 도시, 잠긴 문 앞의 늑대, 구해야 할 소녀. 퀘스트·대사·장소 모두 모드의 평범한 파일입니다.",
story_p2:"새 이야기는 새 모드: 자체 DSL 장면, JSON 캐릭터, .properties 현지화. 커널은 그 어떤 것도 모릅니다—그게 핵심.",
story_cap:"게임",story_title:"하나의 엔진, 서로 다른 게임",
story_p1:"Cthangover는 턴제 전투와 무라카미 이야기를 가진 비주얼 노벨입니다. RPG Demo는 픽셀 마을과 숲을 WASD로 걸으며 같은 전투 코어로 싸웁니다. Novel Template은 가장 작은 플레이 가능한 노벨입니다. 모두 실행 중인 하나의 프로세스 안에서 전환됩니다.",
story_p2:"새 게임은 새 컬렉션입니다: 장면, 캐릭터, 번역을 담은 콘텐츠 모드와 필요한 메커니즘. 커널은 그 어느 것도 모릅니다 — 그것이 핵심입니다.",
shots_cap:"장면",shots_title:"스크린샷",
cta_title:"열린 코드, 열린 파이프라인",
cta_p:"프로젝트는 MIT 라이선스. 저장소는 자체 Gitea에서: 커널·모드·문서·이 사이트까지 하나의 소스 트리에서 빌드됩니다.",
@@ -302,14 +302,14 @@ not_found:"아직 작성되지 않은 섹션",docs_here:"문서는 영어 우선
vi:{qs_overview:"Tổng quan",nav_home:"Trang chính",nav_docs:"Tài liệu",nav_quickstart:"Bắt đầu nhanh",nav_repo:"Kho mã",
kicker:"engine hướng mod · Godot 4.6 · C# / .NET 8 · MIT",
hero_h1a:"Tiểu thuyết tương tác",hero_h1b:"do mod lắp ráp",
hero_tag:"Nhân Core/ không chứa bất kỳ cảnh trò chơi nào. Khi khởi động, nó tải mod từ mods/, biên dịch C# bằng Roslyn và lắp ráp trò chơi—từ luật chiến đấu tới hội thoại. Mỗi cơ chế là một thư mục thường: mở ra, sửa, tháo ra.",
hero_h1a:"Một engine trò chơi",hero_h1b:"do mod lắp ráp",
hero_tag:"Nhân Core/ không chứa trò chơi nào, kể cả thể loại. Khi khởi động, nó nạp mod từ mods/, biên dịch C# của chúng bằng Roslyn và lắp ráp trò chơi từ chúng: visual novel, RPG trên bản đồ ô, chiến đấu, hội thoại. Mỗi cơ chế là một thư mục bình thường mà bạn có thể mở, sửa và xóa.",
cta_start:"Bắt đầu trong 5 phút",cta_docs:"Tài liệu",
st_dsl_l:"lệnh DSL",st_mods_l:"mod trong kho",st_loc_l:"dòng C# (nhân + mod)",st_battle_l:"bộ luật chiến đấu",
arch_cap:"Dây chuyền lắp ráp",arch_title:"Trò chơi được lắp theo tầng",
arch_l1t:"Nhân · Core/",arch_l1d:"DSL kịch bản, registry mod, lưu game, sự kiện, tài nguyên, UI, âm thanh. Không có nội dung—chỉ có dịch vụ.",
arch_l2t:"Nền tảng · shell2d_base",arch_l2d:"Cảnh vỏ 2D: khung nhìn, thanh công cụ, khe panel, thông báo.",
arch_l3t:"Nhân trò chơi · cthangover_game",arch_l3d:"Hội thoại, nhân vật, vật phẩm, nhiệm vụ và vùng bản đồ trên nền tảng.",
arch_l2t:"Mod engine · *_base",arch_l2d:"Vỏ 2D, hội thoại và DSL kịch bản, nhân vật, vật phẩm, nhiệm vụ, chiến đấu theo lượt, thế giới ô — mỗi thứ là một mod mà trò chơi chọn dùng hay không.",
arch_l3t:"Trò chơi · bộ sưu tập",arch_l3d:"Trò chơi là một bộ sưu tập: mod nội dung riêng cộng với các cơ chế được chọn — novel có chiến đấu, khám phá kiểu RPG Maker, một mẫu.",
arch_l4t:"Mod của bạn",arch_l4d:"Một tuyến truyện, một cơ chế hay luật chiến đấu mới—một thư mục trong mods/.",
feat_cap:"Tính năng",feat_title:"Đã có trong nhân",
f1t:"Roslyn lúc chạy",f1d:"Mã C# của mod được biên dịch khi tải; cache theo hash nội dung.",
@@ -331,9 +331,9 @@ dsl_p2:"Không cần trình soạn thảo: .scenario sửa bằng chính trình
mods_cap:"Danh mục",mods_title:"Kho đã có {n} mod",
mods_note:"Tên và mô tả nguyên như manifest.json (tiếng Anh). Mod kiểm thử được gắn thẻ.",
cat_engine:"engine",cat_gameplay:"lối chơi",cat_story:"cốt truyện",cat_library:"thư viện",cat_ui:"giao diện",cat_test:"kiểm thử",cat_tools:"công cụ",
story_cap:"Cốt truyện",story_title:"Câu chuyện sống trong mod nội dung",
story_p1:"Tuyến đầu tiên là Murakami: thành phố sau xâm lược, con sóng trước cánh cửa khóa, cô gái cần cứu. Nhiệm vụ, lời thoại, địa điểm—tất cả là tệp thường của mod.",
story_p2:"Câu chuyện mới là mod mới: cảnh DSL riêng, nhân vật JSON, bản địa hóa .properties. Nhân không biết gì về chúng—đó chính là ý nghĩa.",
story_cap:"Trò chơi",story_title:"Một engine, nhiều trò chơi",
story_p1:"Cthangover là visual novel có chiến đấu theo lượt và tuyến truyện Murakami. RPG Demo đi bộ quanh ngôi làng và khu rừng pixel bằng WASD và chiến đấu trên cùng các lõi chiến đấu. Novel Template là novel nhỏ nhất có thể chơi. Tất cả chuyển đổi trong cùng một tiến trình đang chạy.",
story_p2:"Trò chơi mới là một bộ sưu tập mới: mod nội dung của bạn với cảnh, nhân vật, bản dịch, cộng các cơ chế cần thiết. Nhân không biết gì về chúng — đó chính là mục đích.",
shots_cap:"Khung hình",shots_title:"Ảnh chụp",
cta_title:"Mã mở, pipeline mở",
cta_p:"Dự án phát hành theo MIT. Kho mã nằm trên Gitea tự quản: nhân, mod, tài liệu và chính trang web được dựng từ một cây mã nguồn.",
+1 -1
View File
@@ -44,7 +44,7 @@ mods/
"name": "My Booze Bar", // Mod name shown in the mod menu
"description": "Adds a booze bar scene to the town", // Mod description shown in the mod menu
"author": "modder", // Mod author
"depends": ["cthangover_game"] // Dependency on the core game (so manual mod load ordering is not required)
"depends": ["cthangover_game"] // Dependency on the main game's content (so manual mod load ordering is not required)
}
```
+1 -1
View File
@@ -34,7 +34,7 @@ Every mod needs a manifest. Create `mods/quickstart_player/manifest.json`:
"name": "Marao Buff", // Display name shown in the mod management menu
"description": "My first mod!", // Description visible in the mod list and catalog
"author": "player", // Mod author (arbitrary string)
"depends": ["cthangover_game"] // Dependency on the core game (so manual mod load ordering is not required)
"depends": ["cthangover_game"] // Dependency on the main game's content (so manual mod load ordering is not required)
}
```
+1 -1
View File
@@ -40,7 +40,7 @@ mods/
"description": "New action and mod settings", // Mod description shown in the mod list
"sources": ["src/*.cs"], // Pattern for collecting source files (all ".cs" files)
"author": "programmer", // Author
"depends": ["cthangover_game"] // Dependency on the core game (so manual mod load ordering is not required)
"depends": ["cthangover_game"] // Dependency on the main game's content (so manual mod load ordering is not required)
}
```
+1 -1
View File
@@ -1 +1 @@
{"en/index": "# Quick Start\n\nChoose your path — each one leads to a working result.\n\n<div class=\"quickstart-cards\">\n\n<a href=\"#quickstart/player\" class=\"qs-card\">\n<h3>Level 1 — Player</h3>\n<p>Modify character stats and add a new ability — without a single line of code.</p>\n<span class=\"qs-card-meta\">Text editor · 5 minutes</span>\n</a>\n\n<a href=\"#quickstart/modder\" class=\"qs-card\">\n<h3>Level 2 — Modder</h3>\n<p>Create your own scene with dialogue, choices, and transitions using the DSL scenario language.</p>\n<span class=\"qs-card-meta\">JSON + DSL · 15 minutes</span>\n</a>\n\n<a href=\"#quickstart/programmer\" class=\"qs-card\">\n<h3>Level 3 — Programmer</h3>\n<p>Write a C# mod with a custom DSL action and settings in the menu.</p>\n<span class=\"qs-card-meta\">C# + Godot API · 20 minutes</span>\n</a>\n\n</div>\n\n---\n\n| | Level 1 | Level 2 | Level 3 |\n|---|---|---|---|\n| **C# needed** | No | No | Yes |\n| **Godot needed** | No | No | No |\n| **What we make** | Character patch | Scene & scenario | C# mod |\n| **Difficulty** | ★☆☆☆☆ | ★★★☆☆ | ★★★★☆ |\n", "en/modder": "# Level 2 — Modder: DSL Scene\n\n<div class=\"qs-meta\">\n<span>JSON + DSL</span>\n<span>15 minutes</span>\n<span>No code</span>\n</div>\n\nYou'll create a mod with your own scene — a bar with dialogue, choices, and transitions. And you'll add an entrance to the bar to the existing town scene without touching the original files.\n\n---\n\n## Step 1: Mod structure\n\nCreate the folder and base files:\n\n```filestree\nmods/\n└── quickstart_modder/\n ├── manifest.json\n ├── scenes/\n │ └── my_alko_bar.json # Our new location — the booze bar!\n ├── scenarios/\n │ ├── my_alko_bar.scenario # Scenario inside the bar, let the player interact with the new location\n │ └── town_entry_override.scenario # Extend the town scene to add a transition to our new location\n ├── backgrounds/\n │ └── my_alko_bar/\n │ └── bg.png # Bar interior image\n └── sounds/\n └── locations/\n └── alkobar/\n └── bar_ambient.ogg # Background bar noise sound\n```\n\n---\n\n## Step 2: manifest.json\n\n`mods/quickstart_modder/manifest.json`:\n\n```jsonc\n{\n \"id\": \"quickstart_modder\", // Mod ID\n \"name\": \"My Booze Bar\", // Mod name shown in the mod menu\n \"description\": \"Adds a booze bar scene to the town\", // Mod description shown in the mod menu\n \"author\": \"modder\", // Mod author\n \"depends\": [\"cthangover_game\"] // Dependency on the core game (so manual mod load ordering is not required)\n}\n```\n\n---\n\n## Step 3: Scene definition\n\n`mods/quickstart_modder/scenes/my_alko_bar.json`:\n\n```jsonc\n{\n \"name\": \"my_alko_bar\", // Create a new, unique scene identifier (used in switch_scene), this will be the new scene.\n \"defaultBackground\": \"my_alko_bar/bg\", // Path to the PNG background inside backgrounds/ (without extension)\n \"defaultAmbient\": \"locations/alkobar/bar_ambient\", // ID of the ambient sound for this location (path inside sounds/ without extension), e.g. \"bar noise\" that plays on entering the scene\n \"defaultScenario\": \"scenarios/my_alko_bar.scenario\" // Default scenario that will run in this scene if nothing else is specified\n}\n```\n\n| Field | Description |\n|---|---|\n| `name` | Unique scene ID. Used in `switch_scene` |\n| `defaultBackground` | Path to background inside `backgrounds/` (no extension) |\n| `defaultAmbient` | Ambient sound: path inside `sounds/` without extension. Empty string — no sound |\n| `defaultScenario` | Path to the `.scenario` file that runs on scene entry |\n\n---\n\n## Step 4: Background and sound\n\nPlace an image at `mods/quickstart_modder/backgrounds/my_alko_bar/bg.png`, and the ambient sound at `mods/quickstart_modder/sounds/locations/alkobar/bar_ambient.ogg`.\n\nAny PNG image will work. The game supports shader-based lighting and time of day — the background will be automatically lit.\n\n> If you don't have an image, temporarily copy `mods/cthangover_game/backgrounds/home/kitchen.png` → `mods/quickstart_modder/backgrounds/my_alko_bar/bg.png`. You can also skip the sound: set `\"defaultAmbient\": \"\"`.\n\n---\n\n## Step 5: DSL Scenario\n\n`mods/quickstart_modder/scenarios/my_alko_bar.scenario`:\n\n```scenario\nscene: my_alko_bar\npriority: 10\n---\ntext \"You walk into a half-empty bar.\"\ntext \"A bored bartender stands behind the counter.\"\n\n:menu\nselect \"[What will you do?]\"\noption \"Approach the counter\" -> :bar\noption \"Leave\" -> :leave\n\n:bar\ntext \"The bartender nods at you.\"\ntext \"— What can I get you?\"\ngoto -> :meLine truncated
{"en/index": "# Quick Start\n\nChoose your path — each one leads to a working result.\n\n<div class=\"quickstart-cards\">\n\n<a href=\"#quickstart/player\" class=\"qs-card\">\n<h3>Level 1 — Player</h3>\n<p>Modify character stats and add a new ability — without a single line of code.</p>\n<span class=\"qs-card-meta\">Text editor · 5 minutes</span>\n</a>\n\n<a href=\"#quickstart/modder\" class=\"qs-card\">\n<h3>Level 2 — Modder</h3>\n<p>Create your own scene with dialogue, choices, and transitions using the DSL scenario language.</p>\n<span class=\"qs-card-meta\">JSON + DSL · 15 minutes</span>\n</a>\n\n<a href=\"#quickstart/programmer\" class=\"qs-card\">\n<h3>Level 3 — Programmer</h3>\n<p>Write a C# mod with a custom DSL action and settings in the menu.</p>\n<span class=\"qs-card-meta\">C# + Godot API · 20 minutes</span>\n</a>\n\n</div>\n\n---\n\n| | Level 1 | Level 2 | Level 3 |\n|---|---|---|---|\n| **C# needed** | No | No | Yes |\n| **Godot needed** | No | No | No |\n| **What we make** | Character patch | Scene & scenario | C# mod |\n| **Difficulty** | ★☆☆☆☆ | ★★★☆☆ | ★★★★☆ |\n", "en/modder": "# Level 2 — Modder: DSL Scene\n\n<div class=\"qs-meta\">\n<span>JSON + DSL</span>\n<span>15 minutes</span>\n<span>No code</span>\n</div>\n\nYou'll create a mod with your own scene — a bar with dialogue, choices, and transitions. And you'll add an entrance to the bar to the existing town scene without touching the original files.\n\n---\n\n## Step 1: Mod structure\n\nCreate the folder and base files:\n\n```filestree\nmods/\n└── quickstart_modder/\n ├── manifest.json\n ├── scenes/\n │ └── my_alko_bar.json # Our new location — the booze bar!\n ├── scenarios/\n │ ├── my_alko_bar.scenario # Scenario inside the bar, let the player interact with the new location\n │ └── town_entry_override.scenario # Extend the town scene to add a transition to our new location\n ├── backgrounds/\n │ └── my_alko_bar/\n │ └── bg.png # Bar interior image\n └── sounds/\n └── locations/\n └── alkobar/\n └── bar_ambient.ogg # Background bar noise sound\n```\n\n---\n\n## Step 2: manifest.json\n\n`mods/quickstart_modder/manifest.json`:\n\n```jsonc\n{\n \"id\": \"quickstart_modder\", // Mod ID\n \"name\": \"My Booze Bar\", // Mod name shown in the mod menu\n \"description\": \"Adds a booze bar scene to the town\", // Mod description shown in the mod menu\n \"author\": \"modder\", // Mod author\n \"depends\": [\"cthangover_game\"] // Dependency on the main game's content (so manual mod load ordering is not required)\n}\n```\n\n---\n\n## Step 3: Scene definition\n\n`mods/quickstart_modder/scenes/my_alko_bar.json`:\n\n```jsonc\n{\n \"name\": \"my_alko_bar\", // Create a new, unique scene identifier (used in switch_scene), this will be the new scene.\n \"defaultBackground\": \"my_alko_bar/bg\", // Path to the PNG background inside backgrounds/ (without extension)\n \"defaultAmbient\": \"locations/alkobar/bar_ambient\", // ID of the ambient sound for this location (path inside sounds/ without extension), e.g. \"bar noise\" that plays on entering the scene\n \"defaultScenario\": \"scenarios/my_alko_bar.scenario\" // Default scenario that will run in this scene if nothing else is specified\n}\n```\n\n| Field | Description |\n|---|---|\n| `name` | Unique scene ID. Used in `switch_scene` |\n| `defaultBackground` | Path to background inside `backgrounds/` (no extension) |\n| `defaultAmbient` | Ambient sound: path inside `sounds/` without extension. Empty string — no sound |\n| `defaultScenario` | Path to the `.scenario` file that runs on scene entry |\n\n---\n\n## Step 4: Background and sound\n\nPlace an image at `mods/quickstart_modder/backgrounds/my_alko_bar/bg.png`, and the ambient sound at `mods/quickstart_modder/sounds/locations/alkobar/bar_ambient.ogg`.\n\nAny PNG image will work. The game supports shader-based lighting and time of day — the background will be automatically lit.\n\n> If you don't have an image, temporarily copy `mods/cthangover_game/backgrounds/home/kitchen.png` → `mods/quickstart_modder/backgrounds/my_alko_bar/bg.png`. You can also skip the sound: set `\"defaultAmbient\": \"\"`.\n\n---\n\n## Step 5: DSL Scenario\n\n`mods/quickstart_modder/scenarios/my_alko_bar.scenario`:\n\n```scenario\nscene: my_alko_bar\npriority: 10\n---\ntext \"You walk into a half-empty bar.\"\ntext \"A bored bartender stands behind the counter.\"\n\n:menu\nselect \"[What will you do?]\"\noption \"Approach the counter\" -> :bar\noption \"Leave\" -> :leave\n\n:bar\ntext \"The bartender nods at you.\"\ntext \"— What can I get you?\"\ngLine truncated
+1 -1
View File
@@ -44,7 +44,7 @@ mods/
"name": "我的酒吧", // 模组菜单中显示的名称
"description": "为城镇添加酒吧场景", // 模组菜单中显示的描述
"author": "modder", // 模组作者
"depends": ["cthangover_game"] // 依赖核心游戏(无需手动排列模组加载顺序)
"depends": ["cthangover_game"] // 依赖主游戏的内容(无需手动排列模组加载顺序)
}
```
+1 -1
View File
@@ -34,7 +34,7 @@ mods/
"name": "Marao Buff", // 在模组管理菜单中显示的名称
"description": "我的第一个模组!", // 在模组列表和目录中显示的描述
"author": "player", // 模组作者(任意字符串)
"depends": ["cthangover_game"] // 依赖核心游戏(无需手动排列模组加载顺序)
"depends": ["cthangover_game"] // 依赖主游戏的内容(无需手动排列模组加载顺序)
}
```
+1 -1
View File
@@ -40,7 +40,7 @@ mods/
"description": "新动作和模组设置", // 模组列表中显示的描述
"sources": ["src/*.cs"], // 收集源文件的模式(所有 ".cs" 文件)
"author": "programmer", // 作者
"depends": ["cthangover_game"] // 依赖核心游戏(无需手动排列模组加载顺序)
"depends": ["cthangover_game"] // 依赖主游戏的内容(无需手动排列模组加载顺序)
}
```
+28 -28
View File
@@ -2,7 +2,7 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://cthangover.ru/</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs.html</loc>
@@ -14,7 +14,7 @@
</url>
<url>
<loc>https://cthangover.ru/docs/index</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/avatars/index</loc>
@@ -38,11 +38,11 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/collections/index</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/configs/game_config</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/configs/index</loc>
@@ -50,11 +50,11 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/configs/mod_config</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/editor_tools/index</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-19</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/editor_tools/tutorial</loc>
@@ -62,11 +62,11 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/events/index</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-20</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/interactives/index</loc>
<lastmod>2026-08-25</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/items/index</loc>
@@ -94,7 +94,7 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/musics/playlists/index</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/patches/arrays-and-identity</loc>
@@ -102,11 +102,11 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/patches/index</loc>
<lastmod>2026-10-17</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/patches/scenes</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/quests/index</loc>
@@ -138,19 +138,19 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/scenes/scenarios/index</loc>
<lastmod>2026-10-17</lastmod>
<lastmod>2026-10-18</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/scenes/scenarios/actions/index</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/scenes/scenarios/dsl/index</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/scenes/scenarios/dsl/conditions/index</loc>
<lastmod>2026-09-27</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/scenes/tscn-runtime/index</loc>
@@ -158,7 +158,7 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/settings/index</loc>
<lastmod>2026-09-10</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/shaders/index</loc>
@@ -174,7 +174,7 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/src/index</loc>
<lastmod>2026-10-05</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/src/gdscript/actions</loc>
@@ -202,11 +202,11 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/status_effects/index</loc>
<lastmod>2026-09-28</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/time/index</loc>
<lastmod>2026-08-25</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/mods/wrappers/index</loc>
@@ -214,7 +214,7 @@
</url>
<url>
<loc>https://cthangover.ru/docs/mods/zip/index</loc>
<lastmod>2026-09-28</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/docs/resources/index</loc>
@@ -238,19 +238,19 @@
</url>
<url>
<loc>https://cthangover.ru/quickstart/en/index</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/en/modder</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/en/player</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/en/programmer</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/es/index</loc>
@@ -334,18 +334,18 @@
</url>
<url>
<loc>https://cthangover.ru/quickstart/zh/index</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/zh/modder</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/zh/player</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
<url>
<loc>https://cthangover.ru/quickstart/zh/programmer</loc>
<lastmod>2026-10-18</lastmod>
<lastmod>2026-10-21</lastmod>
</url>
</urlset>
@@ -0,0 +1 @@
uid://dalckug4bd6fn
@@ -0,0 +1 @@
uid://c21jphyegs6ni
@@ -0,0 +1 @@
uid://bm8wl5sccjuxy
@@ -0,0 +1 @@
uid://dnk7tpqcouwti
@@ -0,0 +1 @@
uid://drsj27pvxhrow
@@ -0,0 +1 @@
uid://yylkek51xbyk
@@ -0,0 +1 @@
uid://cryjr5q4rjxev
+2 -2
View File
@@ -12,11 +12,11 @@ namespace Engine.Tests.Arch
/// </summary>
internal static class RepoPaths
{
/// <summary>Folders that hold tooling, caches or vendored code rather than project assets.</summary>
/// <summary>Folders that hold tooling, caches, build output (releases/) or vendored code rather than project assets.</summary>
private static readonly HashSet<string> SkippedDirectories = new(StringComparer.Ordinal)
{
".git", ".godot", ".idea", ".opencode", ".omo", ".dsh", "node_modules", "bin", "obj",
"sdk", "site", "mockups", "gen", "artifacts", "source_code", "undefined",
"sdk", "site", "mockups", "gen", "artifacts", "releases", "source_code", "undefined",
};
public static string FindRepoRoot()
@@ -0,0 +1 @@
uid://byb6swknuipit
@@ -0,0 +1 @@
uid://d2164oco3i6rt
@@ -0,0 +1 @@
uid://bty1f3ln2kae
@@ -0,0 +1 @@
uid://dgsbh1m8034u8
@@ -0,0 +1 @@
uid://ij7gphhwv0go