feat(levels): level catalogue with stable ids (slice 7)
LevelResource gains a stable Id and swaps the string ScenePath for an editor-tracked PackedScene. The old path field was never read and had rotted - forest_1 pointed at level_forest_5.tscn, which doesn't exist, and borrowed LEVEL_5_NAME so it would have displayed "VILLAGE - 5". LevelCatalogue lists levels in campaign order and produces the domain's Campaign; an authoring mistake fails at boot because Campaign.Of rejects blank and duplicate ids. Not a domain port - nothing in the domain resolves a level to a scene. All 8 scenes now have a resource; campaign keeps the existing 6 (tutorial_1, village_1..5). village_6 and forest_1 are catalogued but deliberately out of the campaign. GameManager.LevelScenes stays until slice 10 rewrites GameManager.
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://dsq0y2iw7tfmk"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://dsq0y2iw7tfmk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_8mmd7"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://cq4glvuewsno5" path="res://scenes/level_forest_1.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_8mmd7")
|
||||
LevelName = "LEVEL_5_NAME"
|
||||
ScenePath = "res://scenes/level_forest_5.tscn"
|
||||
script = ExtResource("1_script")
|
||||
Id = "forest_1"
|
||||
LevelName = "LEVEL_FOREST_1_NAME"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
13
resources/levels/level_catalogue.tres
Normal file
13
resources/levels/level_catalogue.tres
Normal file
@@ -0,0 +1,13 @@
|
||||
[gd_resource type="Resource" script_class="LevelCatalogue" load_steps=8 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/Resources/LevelCatalogue.cs" id="1_script"]
|
||||
[ext_resource type="Resource" path="res://resources/levels/tutorial_1.tres" id="2_tutorial_1"]
|
||||
[ext_resource type="Resource" uid="uid://cqtalsov2bkpo" path="res://resources/levels/village/village_1.tres" id="3_village_1"]
|
||||
[ext_resource type="Resource" uid="uid://dlwkbjw1l33uq" path="res://resources/levels/village/village_2.tres" id="4_village_2"]
|
||||
[ext_resource type="Resource" uid="uid://b63u5qfp8p7pv" path="res://resources/levels/village/village_3.tres" id="5_village_3"]
|
||||
[ext_resource type="Resource" uid="uid://cxfei28yu3buh" path="res://resources/levels/village/village_4.tres" id="6_village_4"]
|
||||
[ext_resource type="Resource" uid="uid://qld841xv5ui0" path="res://resources/levels/village/village_5.tres" id="7_village_5"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
Levels = Array[Resource]([ExtResource("2_tutorial_1"), ExtResource("3_village_1"), ExtResource("4_village_2"), ExtResource("5_village_3"), ExtResource("6_village_4"), ExtResource("7_village_5")])
|
||||
11
resources/levels/tutorial_1.tres
Normal file
11
resources/levels/tutorial_1.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://dwr3avefuwnpy" path="res://scenes/level_tutorial_1.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
Id = "tutorial_1"
|
||||
LevelName = "LEVEL_TUTORIAL_1_NAME"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
@@ -1,10 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://cqtalsov2bkpo"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://cqtalsov2bkpo"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_72j7f"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://bol7g83v2accs" path="res://scenes/level_village_1.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
resource_local_to_scene = true
|
||||
script = ExtResource("1_72j7f")
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_1"
|
||||
LevelName = "LEVEL_1_NAME"
|
||||
ScenePath = "res://scenes/level_village_1.tscn"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://dlwkbjw1l33uq"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://dlwkbjw1l33uq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_msres"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://chqb11pfoqmeb" path="res://scenes/level_village_2.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_msres")
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_2"
|
||||
LevelName = "LEVEL_2_NAME"
|
||||
ScenePath = "res://scenes/level_village_2.tscn"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://b63u5qfp8p7pv"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://b63u5qfp8p7pv"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_6vcr8"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://h60obxmju6mo" path="res://scenes/level_village_3.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_6vcr8")
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_3"
|
||||
LevelName = "LEVEL_3_NAME"
|
||||
ScenePath = "res://scenes/level_village_3.tscn"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://cxfei28yu3buh"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://cxfei28yu3buh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_h4twv"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://bhad760x3vvco" path="res://scenes/level_village_4.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_h4twv")
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_4"
|
||||
LevelName = "LEVEL_4_NAME"
|
||||
ScenePath = "res://scenes/level_village_4.tscn"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=2 format=3 uid="uid://qld841xv5ui0"]
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3 uid="uid://qld841xv5ui0"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_chped"]
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://bbwef3n2gjkt8" path="res://scenes/level_village_5.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_chped")
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_5"
|
||||
LevelName = "LEVEL_5_NAME"
|
||||
ScenePath = "res://scenes/level_village_5.tscn"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
11
resources/levels/village/village_6.tres
Normal file
11
resources/levels/village/village_6.tres
Normal file
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="LevelResource" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2h0pqhxiqswe" path="res://scripts/Resources/LevelResource.cs" id="1_script"]
|
||||
[ext_resource type="PackedScene" uid="uid://dqekccjkwev4y" path="res://scenes/level_village_6.tscn" id="2_scene"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
Id = "village_6"
|
||||
LevelName = "LEVEL_6_NAME"
|
||||
Scene = ExtResource("2_scene")
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
@@ -20,7 +20,6 @@
|
||||
[sub_resource type="Resource" id="Resource_asu0i"]
|
||||
script = ExtResource("3_ph1yb")
|
||||
LevelName = "Tutorial 1"
|
||||
ScenePath = "res://scenes/level_tutorial_1.tscn"
|
||||
metadata/_custom_type_script = "uid://c2h0pqhxiqswe"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_0nwt7"]
|
||||
|
||||
@@ -54,7 +54,6 @@ color_ramp = SubResource("GradientTexture1D_f1fvy")
|
||||
resource_local_to_scene = true
|
||||
script = ExtResource("4_84xgs")
|
||||
LevelName = "LEVEL_1_NAME"
|
||||
ScenePath = "res://scenes/level_village_1.tscn"
|
||||
metadata/_custom_type_script = "uid://cp68km8bykymb"
|
||||
|
||||
[sub_resource type="Resource" id="Resource_0nwt7"]
|
||||
|
||||
54
scripts/Resources/LevelCatalogue.cs
Normal file
54
scripts/Resources/LevelCatalogue.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Mr.BrickAdventures.Progression;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
/// <summary>
|
||||
/// The authored list of Levels in campaign order, and the bridge from a <see cref="LevelId"/> back to
|
||||
/// the Godot scene that implements it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Not a domain port. Nothing in the progression domain resolves a Level to a scene — the domain only
|
||||
/// needs the ordering, which it takes as a <see cref="Campaign"/>. Scene lookup is wanted solely by
|
||||
/// the composition root, which is engine-side anyway.
|
||||
/// </remarks>
|
||||
[GlobalClass]
|
||||
public partial class LevelCatalogue : Resource
|
||||
{
|
||||
/// <summary>
|
||||
/// Levels in campaign order. A Level absent from this list is not reachable, even if its
|
||||
/// <see cref="LevelResource"/> exists.
|
||||
/// </summary>
|
||||
[Export]
|
||||
public Array<LevelResource> Levels { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The ordering the domain works from. Throws if the authored data is inconsistent — a blank or
|
||||
/// duplicated id cannot produce a <see cref="Campaign"/>, so a mistake here fails at boot rather
|
||||
/// than corrupting a save later.
|
||||
/// </summary>
|
||||
public Campaign ToCampaign()
|
||||
{
|
||||
var ids = new List<LevelId>();
|
||||
|
||||
for (var i = 0; i < Levels.Count; i++)
|
||||
{
|
||||
var level = Levels[i];
|
||||
if (level == null)
|
||||
throw new System.InvalidOperationException(
|
||||
$"LevelCatalogue has an empty slot at index {i}.");
|
||||
|
||||
ids.Add(LevelId.Of(level.Id));
|
||||
}
|
||||
|
||||
return Campaign.Of(ids);
|
||||
}
|
||||
|
||||
public LevelResource Find(LevelId id) =>
|
||||
Levels.FirstOrDefault(level => level != null && level.Id == id.Value);
|
||||
|
||||
public PackedScene SceneFor(LevelId id) => Find(id)?.Scene;
|
||||
}
|
||||
@@ -2,12 +2,33 @@ using Godot;
|
||||
|
||||
namespace Mr.BrickAdventures.scripts.Resources;
|
||||
|
||||
/// <summary>
|
||||
/// One authored Level: its stable id, its display name key, and the scene to load.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="Id"/> is what Playthroughs and Best Times reference, so it must never change once a
|
||||
/// build has shipped — unlike position in a list, which is what the old
|
||||
/// <c>GameManager.LevelScenes</c> array used and which every save depended on.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The scene is a <see cref="PackedScene"/> rather than a path string. The previous
|
||||
/// <c>ScenePath</c> field was never read by anything and had rotted: <c>forest_1.tres</c> pointed at
|
||||
/// <c>res://scenes/level_forest_5.tscn</c>, which does not exist. An exported reference is tracked by
|
||||
/// the editor and cannot drift.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[GlobalClass]
|
||||
public partial class LevelResource : Resource
|
||||
{
|
||||
/// <summary>Stable identity. Referenced by saves — do not change after shipping.</summary>
|
||||
[Export]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Translation key for the player-facing name. Labels auto-translate it.</summary>
|
||||
[Export]
|
||||
public string LevelName { get; set; } = string.Empty;
|
||||
|
||||
[Export]
|
||||
public string ScenePath { get; set; } = string.Empty;
|
||||
public PackedScene Scene { get; set; }
|
||||
}
|
||||
@@ -61,3 +61,6 @@ MAGNETIC_DESCRIPTION,Attract coins,Przyciąga monety w pobliżu
|
||||
LEVEL_1_TOOLTIP_1,Some blocks might collapse,Niektóre bloki mogą się zapaść.
|
||||
LEVEL_1_TOOLTIP_2,Jump to the other side!,Przeskocz na drugą stronę!
|
||||
LEVEL_1_TOOLTIP_3,"To rescue your child, you need to find the lever.",Uratuj dziecko poprzez aktywację dźwigni.
|
||||
LEVEL_TUTORIAL_1_NAME,TUTORIAL,SAMOUCZEK
|
||||
LEVEL_6_NAME,VILLAGE – 6,WIOSKA – 6
|
||||
LEVEL_FOREST_1_NAME,FOREST – 1,LAS – 1
|
||||
|
||||
|
Reference in New Issue
Block a user