Files
FoxyzandClaude Opus 5.5 df7a2c5a89 Maps are editor-built scenes: [Tool] Playground/Crag, headless rebuild for agents
scenes/maps/playground.tscn and crag.tscn are generated by the map scripts' Rebuild (inspector
button, or godot-mono --headless --path . tools/rebuild_maps.tscn) and loaded as saved by the game,
so hand edits stick. The crag route is saved as Marker3D nodes and crag_spike checks the saved
scene. Layout verified identical to the code-built one (tools/map_dump.tscn).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:10:10 +03:00

81 lines
2.8 KiB
C#

using Godot;
using System.Collections.Generic;
// Shared plumbing for the editor-built maps (Playground, Crag). Each map is the ROOT of its own scene
// (scenes/maps/*.tscn) with a [Tool] script whose Rebuild() regenerates every child from code and
// gives it to the scene (Owner = the map root) so it is saved into the .tscn like hand-made nodes.
// The game never regenerates: it loads what was saved, so edits made in the editor stick (until the
// next Rebuild, which replaces ALL children). Rebuild from the inspector button with the map scene
// open, or headless for agents: godot-mono --headless --path . tools/rebuild_maps.tscn
public static class MapScene
{
public const string PropGroup = "prop";
// The inspector button only works with the map scene itself open (nodes owned by an instanced
// sub-scene's root aren't saved into the outer scene).
public static bool CanRebuildInEditor(Node map)
{
if (map.GetTree()?.EditedSceneRoot == map)
return true;
GD.PushWarning($"{map.Name}: open its own scene (scenes/maps/) to rebuild it — it's instanced here.");
return false;
}
public static void Clear(Node map)
{
foreach (Node child in map.GetChildren())
{
map.RemoveChild(child);
child.Free();
}
}
// Every generated node belongs to the map scene; children of instanced sub-scenes (e.g. a
// teleport marker) keep their own owner.
public static void Adopt(Node map)
{
foreach (Node child in map.GetChildren())
Adopt(map, child);
}
private static void Adopt(Node root, Node node)
{
if (node.Owner == null)
node.Owner = root;
if (node.SceneFilePath != "")
return;
foreach (Node child in node.GetChildren())
Adopt(root, child);
}
public static void MarkUnsaved()
{
#if TOOLS
if (Engine.IsEditorHint())
EditorInterface.Singleton.MarkSceneAsUnsaved();
#endif
}
// Runtime: the dynamic props (group "prop") and where they start, for Reset().
public static void CollectProps(Node map, List<(RigidBody3D Body, Transform3D Start)> props)
{
props.Clear();
foreach (Node child in map.GetChildren())
if (child is RigidBody3D body && body.IsInGroup(PropGroup))
props.Add((body, body.GlobalTransform));
}
public static void Reset(List<(RigidBody3D Body, Transform3D Start)> props)
{
foreach (var (body, start) in props)
{
if (!GodotObject.IsInstanceValid(body))
continue;
body.GlobalTransform = start;
body.LinearVelocity = Vector3.Zero;
body.AngularVelocity = Vector3.Zero;
body.ResetPhysicsInterpolation();
}
}
}