Merge branch 'worktree-agent-a220f513d68e27a82'
This commit is contained in:
commit
537e72c207
17 files changed
+6863
-65
No files matched your search
+11
-1
@@ -40,7 +40,7 @@ TERMINOLOGY (owner-corrected — use precisely):
|
|||||||
`body_spike` "BODY: …", `collapse_spike` "COLLAPSE: …", `arm_spike` "ARM: overall PASS",
|
`body_spike` "BODY: …", `collapse_spike` "COLLAPSE: …", `arm_spike` "ARM: overall PASS",
|
||||||
`sixdof_spike` "6DOF: overall PASS" (run FIRST with `weld_spike` whenever the addon binary changes),
|
`sixdof_spike` "6DOF: overall PASS" (run FIRST with `weld_spike` whenever the addon binary changes),
|
||||||
`truebody_spike` "TRUEBODY: overall PASS" (no physics; plain `--headless`), `menu_spike` "MENU: overall PASS"
|
`truebody_spike` "TRUEBODY: overall PASS" (no physics; plain `--headless`), `menu_spike` "MENU: overall PASS"
|
||||||
(in-VR menu input injection, no XR). `ragdoll_spike` "RAGDOLL: overall PASS" (`-- only=a,b` runs the phases whose names contain a or b), `crag_spike` "CRAG: overall PASS" (layout geometry only). Only run the spikes your change touches — the full set takes a while. All nine PASS as of this handoff.
|
(in-VR menu input injection, no XR). `ragdoll_spike` "RAGDOLL: overall PASS" (`-- only=a,b` runs the phases whose names contain a or b), `crag_spike` "CRAG: overall PASS" (layout geometry of the SAVED crag scene; see Maps below). Only run the spikes your change touches — the full set takes a while. All nine PASS as of this handoff.
|
||||||
**Fresh checkout / new worktree: run `godot-mono --headless --import` FIRST** (it may segfault at
|
**Fresh checkout / new worktree: run `godot-mono --headless --import` FIRST** (it may segfault at
|
||||||
exit — harmless) — without `.godot/extension_list.cfg` the Box3D extension silently doesn't load,
|
exit — harmless) — without `.godot/extension_list.cfg` the Box3D extension silently doesn't load,
|
||||||
Godot falls back to its default physics and weld/grab/arm/6dof spikes report CHECK. Headless asset import:
|
Godot falls back to its default physics and weld/grab/arm/6dof spikes report CHECK. Headless asset import:
|
||||||
@@ -111,6 +111,16 @@ TERMINOLOGY (owner-corrected — use precisely):
|
|||||||
- **Playground.cs** — the body sandbox around the table (east climbing/mantling, west physics toys, south
|
- **Playground.cs** — the body sandbox around the table (east climbing/mantling, west physics toys, south
|
||||||
crawl/duck/door); dynamic pieces are "prop"s reset by the menu's Reset props. `tools/playground_shots.tscn` renders
|
crawl/duck/door); dynamic pieces are "prop"s reset by the menu's Reset props. `tools/playground_shots.tscn` renders
|
||||||
overview PNGs (`godot-mono --path . tools/playground_shots.tscn -- <dir>`).
|
overview PNGs (`godot-mono --path . tools/playground_shots.tscn -- <dir>`).
|
||||||
|
- **Maps are EDITOR-BUILT scenes** (`MapScene.cs`): `scenes/maps/playground.tscn` and `scenes/maps/crag.tscn` (roots =
|
||||||
|
the `[Tool]` scripts Playground / Crag, instanced by main.tscn). The game NEVER regenerates them — it loads what is
|
||||||
|
saved, so hand edits in the editor stick. Regenerate from code with the map's inspector button "Rebuild (replaces
|
||||||
|
all children)" (open the map's own scene, then save), or headless for agents:
|
||||||
|
`godot-mono --headless --path . tools/rebuild_maps.tscn [-- only=playground|crag]` → "MAPS: rebuilt …" / "MAPS: done"
|
||||||
|
(rebuild REPLACES all children, discarding hand edits). After changing Playground.cs/Crag.cs builders, rebuild,
|
||||||
|
then run `crag_spike` (it checks the SAVED crag: route = the Marker3D children of its "Route" node, metadata
|
||||||
|
kind/label; route bodies in group `crag_route`, rock in `crag_mass`). Joint node paths are saved relative
|
||||||
|
(`../Name`); groups must be added persistent (`AddToGroup(g, true)`) or they aren't saved. `tools/map_dump.tscn`
|
||||||
|
dumps a layout (type/transform/size/groups) for before/after comparisons.
|
||||||
- **AnchorMoveMode.cs** is now SPECTATOR-only (`AnchorMoveMode.Enabled`, menu → Player → Spectator), where the
|
- **AnchorMoveMode.cs** is now SPECTATOR-only (`AnchorMoveMode.Enabled`, menu → Player → Spectator), where the
|
||||||
ragdoll is cleared; leaving rebuilds it at the current pose.
|
ragdoll is cleared; leaving rebuilds it at the current pose.
|
||||||
- **LEGACY (not in main.tscn since step 2; kept for their spikes): PhysicsHands / PhysicsTorso / PhysicsArms / BodyRig.**
|
- **LEGACY (not in main.tscn since step 2; kept for their spikes): PhysicsHands / PhysicsTorso / PhysicsArms / BodyRig.**
|
||||||
|
|||||||
+4
-6
@@ -10,8 +10,8 @@
|
|||||||
[ext_resource type="Script" path="res://scripts/Calibration.cs" id="12_calibration"]
|
[ext_resource type="Script" path="res://scripts/Calibration.cs" id="12_calibration"]
|
||||||
[ext_resource type="Script" path="res://scripts/TrueBody.cs" id="13_truebody"]
|
[ext_resource type="Script" path="res://scripts/TrueBody.cs" id="13_truebody"]
|
||||||
[ext_resource type="Script" path="res://scripts/PlayerRig.cs" id="14_playerrig"]
|
[ext_resource type="Script" path="res://scripts/PlayerRig.cs" id="14_playerrig"]
|
||||||
[ext_resource type="Script" path="res://scripts/Playground.cs" id="15_playground"]
|
[ext_resource type="PackedScene" path="res://scenes/maps/playground.tscn" id="15_playground"]
|
||||||
[ext_resource type="Script" path="res://scripts/Crag.cs" id="16_crag"]
|
[ext_resource type="PackedScene" path="res://scenes/maps/crag.tscn" id="16_crag"]
|
||||||
|
|
||||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_sky"]
|
||||||
sky_horizon_color = Color(0.66, 0.72, 0.81, 1)
|
sky_horizon_color = Color(0.66, 0.72, 0.81, 1)
|
||||||
@@ -109,11 +109,9 @@ script = ExtResource("3_locomotion")
|
|||||||
[node name="AnchorMoveMode" type="Node" parent="."]
|
[node name="AnchorMoveMode" type="Node" parent="."]
|
||||||
script = ExtResource("5_anchormove")
|
script = ExtResource("5_anchormove")
|
||||||
|
|
||||||
[node name="Playground" type="Node3D" parent="."]
|
[node name="Playground" parent="." instance=ExtResource("15_playground")]
|
||||||
script = ExtResource("15_playground")
|
|
||||||
|
|
||||||
[node name="Crag" type="Node3D" parent="."]
|
[node name="Crag" parent="." instance=ExtResource("16_crag")]
|
||||||
script = ExtResource("16_crag")
|
|
||||||
|
|
||||||
[node name="PropShop" type="Node3D" parent="."]
|
[node name="PropShop" type="Node3D" parent="."]
|
||||||
script = ExtResource("6_propshop")
|
script = ExtResource("6_propshop")
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+89
-26
@@ -12,11 +12,20 @@ using System.Collections.Generic;
|
|||||||
// 6. a 0.9 m chimney with holds on both walls up the third tier;
|
// 6. a 0.9 m chimney with holds on both walls up the third tier;
|
||||||
// 7. the summit (26 m) with throwable props. Way down: aim at the "Crag" teleport marker at the
|
// 7. the summit (26 m) with throwable props. Way down: aim at the "Crag" teleport marker at the
|
||||||
// base (or jump — no fall damage).
|
// base (or jump — no fall damage).
|
||||||
// The cliff faces south (+Z) toward spawn. Every route point is recorded in Route so crag_spike
|
// The cliff faces south (+Z) toward spawn. EDITOR-BUILT: this is the root of scenes/maps/crag.tscn
|
||||||
// can check reach; route bodies in RoutePieces so it can check nothing intersects them.
|
// and Rebuild() generates everything into that scene (MapScene: inspector button, or
|
||||||
|
// tools/rebuild_maps.tscn headless); the game loads the saved scene. The route is saved too: every
|
||||||
|
// route point is a Marker3D under "Route" (metadata kind/label, in climbing order), route bodies are in
|
||||||
|
// group "crag_route" and rock in "crag_mass" — Route/RoutePieces/MassPieces are read back from the
|
||||||
|
// saved nodes on _Ready, so crag_spike checks the scene as saved (and as hand-edited).
|
||||||
|
[Tool]
|
||||||
public partial class Crag : Node3D
|
public partial class Crag : Node3D
|
||||||
{
|
{
|
||||||
|
[ExportToolButton("Rebuild (replaces all children)")]
|
||||||
|
public Callable RebuildButton => Callable.From(RebuildInEditor);
|
||||||
|
|
||||||
public enum Step { Stand, Hold }
|
public enum Step { Stand, Hold }
|
||||||
|
public const string RouteNode = "Route", RouteGroup = "crag_route", MassGroup = "crag_mass";
|
||||||
public readonly List<(Step Kind, Vector3 Point, string Name)> Route = new();
|
public readonly List<(Step Kind, Vector3 Point, string Name)> Route = new();
|
||||||
public readonly List<(CollisionObject3D Body, Aabb Box)> RoutePieces = new();
|
public readonly List<(CollisionObject3D Body, Aabb Box)> RoutePieces = new();
|
||||||
public readonly List<(CollisionObject3D Body, Aabb Box)> MassPieces = new();
|
public readonly List<(CollisionObject3D Body, Aabb Box)> MassPieces = new();
|
||||||
@@ -37,25 +46,76 @@ public partial class Crag : Node3D
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
if (Engine.IsEditorHint())
|
||||||
|
return;
|
||||||
|
MapScene.CollectProps(this, _dynamic);
|
||||||
|
ReadLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() => MapScene.Reset(_dynamic);
|
||||||
|
|
||||||
|
private void RebuildInEditor()
|
||||||
|
{
|
||||||
|
if (!MapScene.CanRebuildInEditor(this))
|
||||||
|
return;
|
||||||
|
Rebuild();
|
||||||
|
MapScene.MarkUnsaved();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces every child with the generated layout, owned by this map (saved with its scene).
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
MapScene.Clear(this);
|
||||||
|
_rng.Seed = 26; // same rubble/talus every rebuild
|
||||||
|
_route = new Node3D { Name = RouteNode };
|
||||||
|
AddChild(_route);
|
||||||
BuildMass();
|
BuildMass();
|
||||||
BuildRoute();
|
BuildRoute();
|
||||||
BuildBase();
|
BuildBase();
|
||||||
BuildSummitProps();
|
BuildSummitProps();
|
||||||
|
MapScene.Adopt(this);
|
||||||
|
ReadLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
// Route points and piece bounds from the (saved) nodes.
|
||||||
|
public void ReadLayout()
|
||||||
{
|
{
|
||||||
foreach (var (body, start) in _dynamic)
|
Route.Clear();
|
||||||
|
RoutePieces.Clear();
|
||||||
|
MassPieces.Clear();
|
||||||
|
if (GetNodeOrNull<Node>(RouteNode) is Node route)
|
||||||
|
foreach (Node child in route.GetChildren())
|
||||||
|
if (child is Marker3D m)
|
||||||
|
Route.Add(((Step)(int)m.GetMeta("kind"), m.GlobalPosition, (string)m.GetMeta("label")));
|
||||||
|
foreach (Node child in GetChildren())
|
||||||
{
|
{
|
||||||
if (!IsInstanceValid(body))
|
if (child is not CollisionObject3D body)
|
||||||
continue;
|
continue;
|
||||||
body.GlobalTransform = start;
|
if (body.IsInGroup(RouteGroup))
|
||||||
body.LinearVelocity = Vector3.Zero;
|
RoutePieces.Add((body, Bounds(body)));
|
||||||
body.AngularVelocity = Vector3.Zero;
|
else if (body.IsInGroup(MassGroup))
|
||||||
body.ResetPhysicsInterpolation();
|
MassPieces.Add((body, Bounds(body)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// World bounds of a box or a cylinder piece (a cylinder's ends are flat: radius across its axis only).
|
||||||
|
private static Aabb Bounds(CollisionObject3D body)
|
||||||
|
{
|
||||||
|
CollisionShape3D cs = body.GetChildOrNull<CollisionShape3D>(0);
|
||||||
|
Transform3D t = body.GlobalTransform * cs.Transform;
|
||||||
|
if (cs.Shape is CylinderShape3D cyl)
|
||||||
|
{
|
||||||
|
Vector3 from = t * new Vector3(0f, -cyl.Height * 0.5f, 0f), to = t * new Vector3(0f, cyl.Height * 0.5f, 0f);
|
||||||
|
Vector3 d = (to - from).Normalized(), r = new(
|
||||||
|
cyl.Radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.X * d.X)), cyl.Radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.Y * d.Y)), cyl.Radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.Z * d.Z)));
|
||||||
|
return new Aabb(from - r, Vector3.Zero).Expand(from + r).Expand(to - r).Expand(to + r);
|
||||||
|
}
|
||||||
|
Vector3 size = ((BoxShape3D)cs.Shape).Size;
|
||||||
|
return t * new Aabb(-size * 0.5f, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node3D _route;
|
||||||
|
|
||||||
// ---------------------------------------------------------------- the rock
|
// ---------------------------------------------------------------- the rock
|
||||||
|
|
||||||
private void BuildMass()
|
private void BuildMass()
|
||||||
@@ -241,8 +301,9 @@ public partial class Crag : Node3D
|
|||||||
// The way down: a teleport marker at the base (aim at it from the summit).
|
// The way down: a teleport marker at the base (aim at it from the summit).
|
||||||
Node3D marker = GD.Load<PackedScene>("res://scenes/teleport_marker.tscn").Instantiate<Node3D>();
|
Node3D marker = GD.Load<PackedScene>("res://scenes/teleport_marker.tscn").Instantiate<Node3D>();
|
||||||
marker.Set("MarkerLabel", "Crag");
|
marker.Set("MarkerLabel", "Crag");
|
||||||
|
marker.Name = "CragMarker";
|
||||||
marker.Position = new Vector3(1.5f, 0f, -9.5f);
|
marker.Position = new Vector3(1.5f, 0f, -9.5f);
|
||||||
AddChild(marker);
|
AddChild(marker, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildSummitProps()
|
private void BuildSummitProps()
|
||||||
@@ -258,45 +319,47 @@ public partial class Crag : Node3D
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- builders
|
// ---------------------------------------------------------------- builders
|
||||||
|
|
||||||
private void AddStep(Step kind, Vector3 point, string name) => Route.Add((kind, point, name));
|
// A route point: an editor-visible Marker3D under "Route", in climbing order.
|
||||||
|
private void AddStep(Step kind, Vector3 point, string name)
|
||||||
|
{
|
||||||
|
Marker3D m = new() { Name = kind == Step.Hold ? "Hold" : "Stand", Position = point };
|
||||||
|
m.SetMeta("kind", (int)kind);
|
||||||
|
m.SetMeta("label", name);
|
||||||
|
_route.AddChild(m, true);
|
||||||
|
}
|
||||||
|
|
||||||
private StaticBody3D Mass(string name, Vector3 size, Vector3 center, Color color, Basis? basis = null)
|
private StaticBody3D Mass(string name, Vector3 size, Vector3 center, Color color, Basis? basis = null)
|
||||||
{
|
{
|
||||||
StaticBody3D b = Block(name, size, center, color, true, basis);
|
StaticBody3D b = Block(name, size, center, color, true, basis);
|
||||||
MassPieces.Add((b, BoxAabb(size, b.Transform)));
|
b.AddToGroup(MassGroup, true);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StaticBody3D RouteBlock(string name, Vector3 size, Vector3 center, Color color, bool castShadow = true)
|
private StaticBody3D RouteBlock(string name, Vector3 size, Vector3 center, Color color, bool castShadow = true)
|
||||||
{
|
{
|
||||||
StaticBody3D b = Block(name, size, center, color, castShadow);
|
StaticBody3D b = Block(name, size, center, color, castShadow);
|
||||||
RoutePieces.Add((b, BoxAabb(size, b.Transform)));
|
b.AddToGroup(RouteGroup, true);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StaticBody3D RouteBar(string name, Vector3 from, Vector3 to, float radius, Color color)
|
private StaticBody3D RouteBar(string name, Vector3 from, Vector3 to, float radius, Color color)
|
||||||
{
|
{
|
||||||
StaticBody3D b = Bar(name, from, to, radius, color);
|
StaticBody3D b = Bar(name, from, to, radius, color);
|
||||||
// Bounds of the cylinder: radius across the axis only (the ends are flat).
|
b.AddToGroup(RouteGroup, true);
|
||||||
Vector3 d = (to - from).Normalized(), r = new Vector3(
|
|
||||||
radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.X * d.X)), radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.Y * d.Y)), radius * Mathf.Sqrt(Mathf.Max(0f, 1f - d.Z * d.Z)));
|
|
||||||
RoutePieces.Add((b, new Aabb(from - r, Vector3.Zero).Expand(from + r).Expand(to - r).Expand(to + r)));
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Aabb BoxAabb(Vector3 size, Transform3D t) => t * new Aabb(-size * 0.5f, size);
|
|
||||||
|
|
||||||
private StaticBody3D Block(string name, Vector3 size, Vector3 center, Color color, bool castShadow, Basis? basis = null, bool grab = true)
|
private StaticBody3D Block(string name, Vector3 size, Vector3 center, Color color, bool castShadow, Basis? basis = null, bool grab = true)
|
||||||
{
|
{
|
||||||
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(basis ?? Basis.Identity, center) };
|
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(basis ?? Basis.Identity, center) };
|
||||||
if (!grab)
|
if (!grab)
|
||||||
body.AddToGroup(PropGrabber.NoGrabGroup);
|
body.AddToGroup(PropGrabber.NoGrabGroup, true);
|
||||||
body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = size } });
|
body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = size } });
|
||||||
MeshInstance3D mesh = Mesh(new BoxMesh { Size = size }, color);
|
MeshInstance3D mesh = Mesh(new BoxMesh { Size = size }, color);
|
||||||
if (!castShadow)
|
if (!castShadow)
|
||||||
mesh.CastShadow = GeometryInstance3D.ShadowCastingSetting.Off;
|
mesh.CastShadow = GeometryInstance3D.ShadowCastingSetting.Off;
|
||||||
body.AddChild(mesh);
|
body.AddChild(mesh);
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +372,7 @@ public partial class Crag : Node3D
|
|||||||
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(new Basis(x, y, x.Cross(y)), (from + to) * 0.5f) };
|
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(new Basis(x, y, x.Cross(y)), (from + to) * 0.5f) };
|
||||||
body.AddChild(new CollisionShape3D { Shape = new CylinderShape3D { Radius = radius, Height = len } });
|
body.AddChild(new CollisionShape3D { Shape = new CylinderShape3D { Radius = radius, Height = len } });
|
||||||
body.AddChild(Mesh(new CylinderMesh { TopRadius = radius, BottomRadius = radius, Height = len, RadialSegments = 12 }, color));
|
body.AddChild(Mesh(new CylinderMesh { TopRadius = radius, BottomRadius = radius, Height = len, RadialSegments = 12 }, color));
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,11 +388,10 @@ public partial class Crag : Node3D
|
|||||||
MaxContactsReported = 4,
|
MaxContactsReported = 4,
|
||||||
PhysicsMaterialOverride = new PhysicsMaterial { Friction = 0.8f, Bounce = 0.05f },
|
PhysicsMaterialOverride = new PhysicsMaterial { Friction = 0.8f, Bounce = 0.05f },
|
||||||
};
|
};
|
||||||
body.AddToGroup("prop");
|
body.AddToGroup(MapScene.PropGroup, true);
|
||||||
body.AddChild(new CollisionShape3D { Shape = shape });
|
body.AddChild(new CollisionShape3D { Shape = shape });
|
||||||
body.AddChild(Mesh(mesh, color));
|
body.AddChild(Mesh(mesh, color));
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
_dynamic.Add((body, body.GlobalTransform));
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,12 +409,13 @@ public partial class Crag : Node3D
|
|||||||
{
|
{
|
||||||
AddChild(new Label3D
|
AddChild(new Label3D
|
||||||
{
|
{
|
||||||
|
Name = "Sign",
|
||||||
Text = text,
|
Text = text,
|
||||||
FontSize = 96,
|
FontSize = 96,
|
||||||
PixelSize = 0.002f * scale,
|
PixelSize = 0.002f * scale,
|
||||||
OutlineSize = 12,
|
OutlineSize = 12,
|
||||||
Billboard = BaseMaterial3D.BillboardModeEnum.FixedY,
|
Billboard = BaseMaterial3D.BillboardModeEnum.FixedY,
|
||||||
Position = at,
|
Position = at,
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
// Regression for the crag (no player): the route is climbable on paper — consecutive route points
|
// Regression for the crag (no player), checked on the SAVED scenes/maps/crag.tscn (route = its
|
||||||
|
// Route markers; rebuild with tools/rebuild_maps.tscn): the route is climbable on paper — consecutive route points
|
||||||
// within human reach, every point in free space, route pieces not buried in each other or the
|
// within human reach, every point in free space, route pieces not buried in each other or the
|
||||||
// rock — the summit is high enough, and it's in plain view from spawn. Prints "CRAG: overall PASS".
|
// rock — the summit is high enough, and it's in plain view from spawn. Prints "CRAG: overall PASS".
|
||||||
public partial class CragSpike : Node3D
|
public partial class CragSpike : Node3D
|
||||||
@@ -23,8 +24,9 @@ public partial class CragSpike : Node3D
|
|||||||
floor.AddToGroup(PropGrabber.NoGrabGroup);
|
floor.AddToGroup(PropGrabber.NoGrabGroup);
|
||||||
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(80, 0.1f, 80) }, Position = new Vector3(0, -0.05f, 0) });
|
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(80, 0.1f, 80) }, Position = new Vector3(0, -0.05f, 0) });
|
||||||
AddChild(floor);
|
AddChild(floor);
|
||||||
AddChild(new Playground());
|
// The SAVED scenes (as the game loads them, hand edits included), not a fresh rebuild.
|
||||||
_crag = new Crag();
|
AddChild(GD.Load<PackedScene>("res://scenes/maps/playground.tscn").Instantiate());
|
||||||
|
_crag = GD.Load<PackedScene>("res://scenes/maps/crag.tscn").Instantiate<Crag>();
|
||||||
AddChild(_crag);
|
AddChild(_crag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://d0oo5qx6v57bs
|
||||||
+42
-26
@@ -1,7 +1,9 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
// The body-physics sandbox (Nimso-sandbox style), built in code around the spawn/table area:
|
// The body-physics sandbox (Nimso-sandbox style) around the spawn/table area. EDITOR-BUILT: this is the
|
||||||
|
// root of scenes/maps/playground.tscn and Rebuild() generates the pieces below into that scene
|
||||||
|
// (MapScene: inspector button, or tools/rebuild_maps.tscn headless); the game loads the saved scene.
|
||||||
// EAST — mantling ledges (0.3 … 1.5 m), a 1 m platform with stairs, a walkable 18° ramp and a
|
// EAST — mantling ledges (0.3 … 1.5 m), a 1 m platform with stairs, a walkable 18° ramp and a
|
||||||
// too-steep 40° one, a climbing wall with holds onto a 2.4 m top, a pull-up bar, monkey bars.
|
// too-steep 40° one, a climbing wall with holds onto a 2.4 m top, a pull-up bar, monkey bars.
|
||||||
// WEST — physics toys: a plank bridge on two blocks (walk on it / knock it off), loose planks,
|
// WEST — physics toys: a plank bridge on two blocks (walk on it / knock it off), loose planks,
|
||||||
@@ -10,8 +12,12 @@ using System.Collections.Generic;
|
|||||||
// SOUTH — a crawl tunnel (0.6 m clearance), a duck-under bar (1.2 m) and a doorway.
|
// SOUTH — a crawl tunnel (0.6 m clearance), a duck-under bar (1.2 m) and a doorway.
|
||||||
// Static pieces are world (layer 1) and grabbable for climbing (PropGrabber grabs any static body
|
// Static pieces are world (layer 1) and grabbable for climbing (PropGrabber grabs any static body
|
||||||
// not in "no_grab"); dynamic pieces are "prop"s (grab/push) and come back with Reset().
|
// not in "no_grab"); dynamic pieces are "prop"s (grab/push) and come back with Reset().
|
||||||
|
[Tool]
|
||||||
public partial class Playground : Node3D
|
public partial class Playground : Node3D
|
||||||
{
|
{
|
||||||
|
[ExportToolButton("Rebuild (replaces all children)")]
|
||||||
|
public Callable RebuildButton => Callable.From(RebuildInEditor);
|
||||||
|
|
||||||
private readonly List<(RigidBody3D Body, Transform3D Start)> _dynamic = new();
|
private readonly List<(RigidBody3D Body, Transform3D Start)> _dynamic = new();
|
||||||
private const uint WorldLayer = 1, WorldMask = 0b11101, PropMask = 0b11101;
|
private const uint WorldLayer = 1, WorldMask = 0b11101, PropMask = 0b11101;
|
||||||
|
|
||||||
@@ -21,22 +27,28 @@ public partial class Playground : Node3D
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
if (!Engine.IsEditorHint())
|
||||||
|
MapScene.CollectProps(this, _dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset() => MapScene.Reset(_dynamic);
|
||||||
|
|
||||||
|
private void RebuildInEditor()
|
||||||
|
{
|
||||||
|
if (!MapScene.CanRebuildInEditor(this))
|
||||||
|
return;
|
||||||
|
Rebuild();
|
||||||
|
MapScene.MarkUnsaved();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replaces every child with the generated layout, owned by this map (saved with its scene).
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
MapScene.Clear(this);
|
||||||
BuildEast();
|
BuildEast();
|
||||||
BuildWest();
|
BuildWest();
|
||||||
BuildSouth();
|
BuildSouth();
|
||||||
}
|
MapScene.Adopt(this);
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
foreach (var (body, start) in _dynamic)
|
|
||||||
{
|
|
||||||
if (!IsInstanceValid(body))
|
|
||||||
continue;
|
|
||||||
body.GlobalTransform = start;
|
|
||||||
body.LinearVelocity = Vector3.Zero;
|
|
||||||
body.AngularVelocity = Vector3.Zero;
|
|
||||||
body.ResetPhysicsInterpolation();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- builders
|
// ---------------------------------------------------------------- builders
|
||||||
@@ -46,7 +58,7 @@ public partial class Playground : Node3D
|
|||||||
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(basis ?? Basis.Identity, center) };
|
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(basis ?? Basis.Identity, center) };
|
||||||
body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = size } });
|
body.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = size } });
|
||||||
body.AddChild(Mesh(new BoxMesh { Size = size }, color));
|
body.AddChild(Mesh(new BoxMesh { Size = size }, color));
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +71,7 @@ public partial class Playground : Node3D
|
|||||||
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(new Basis(x, y, x.Cross(y)), (from + to) * 0.5f) };
|
StaticBody3D body = new() { Name = name, CollisionLayer = WorldLayer, CollisionMask = WorldMask, Transform = new Transform3D(new Basis(x, y, x.Cross(y)), (from + to) * 0.5f) };
|
||||||
body.AddChild(new CollisionShape3D { Shape = new CylinderShape3D { Radius = radius, Height = len } });
|
body.AddChild(new CollisionShape3D { Shape = new CylinderShape3D { Radius = radius, Height = len } });
|
||||||
body.AddChild(Mesh(new CylinderMesh { TopRadius = radius, BottomRadius = radius, Height = len, RadialSegments = 12 }, color));
|
body.AddChild(Mesh(new CylinderMesh { TopRadius = radius, BottomRadius = radius, Height = len, RadialSegments = 12 }, color));
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +87,10 @@ public partial class Playground : Node3D
|
|||||||
MaxContactsReported = 4,
|
MaxContactsReported = 4,
|
||||||
PhysicsMaterialOverride = new PhysicsMaterial { Friction = friction, Bounce = 0.05f },
|
PhysicsMaterialOverride = new PhysicsMaterial { Friction = friction, Bounce = 0.05f },
|
||||||
};
|
};
|
||||||
body.AddToGroup("prop");
|
body.AddToGroup(MapScene.PropGroup, true);
|
||||||
body.AddChild(new CollisionShape3D { Shape = shape });
|
body.AddChild(new CollisionShape3D { Shape = shape });
|
||||||
body.AddChild(Mesh(mesh, color));
|
body.AddChild(Mesh(mesh, color));
|
||||||
AddChild(body);
|
AddChild(body, true);
|
||||||
_dynamic.Add((body, body.GlobalTransform));
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,13 +108,14 @@ public partial class Playground : Node3D
|
|||||||
{
|
{
|
||||||
AddChild(new Label3D
|
AddChild(new Label3D
|
||||||
{
|
{
|
||||||
|
Name = "Sign",
|
||||||
Text = text,
|
Text = text,
|
||||||
FontSize = 96,
|
FontSize = 96,
|
||||||
PixelSize = 0.002f,
|
PixelSize = 0.002f,
|
||||||
OutlineSize = 12,
|
OutlineSize = 12,
|
||||||
Billboard = BaseMaterial3D.BillboardModeEnum.FixedY,
|
Billboard = BaseMaterial3D.BillboardModeEnum.FixedY,
|
||||||
Position = at,
|
Position = at,
|
||||||
});
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- zones
|
// ---------------------------------------------------------------- zones
|
||||||
@@ -183,13 +195,16 @@ public partial class Playground : Node3D
|
|||||||
// Seesaw: a plank hinged on a pivot block.
|
// Seesaw: a plank hinged on a pivot block.
|
||||||
Block("SeesawPivot", new Vector3(0.3f, 0.45f, 0.5f), new Vector3(-8.5f, 0.225f, -1.5f), Metal);
|
Block("SeesawPivot", new Vector3(0.3f, 0.45f, 0.5f), new Vector3(-8.5f, 0.225f, -1.5f), Metal);
|
||||||
RigidBody3D seesaw = BoxProp("Seesaw", new Vector3(3.2f, 0.06f, 0.45f), 12f, new Vector3(-8.5f, 0.48f, -1.5f), DarkWood);
|
RigidBody3D seesaw = BoxProp("Seesaw", new Vector3(3.2f, 0.06f, 0.45f), 12f, new Vector3(-8.5f, 0.48f, -1.5f), DarkWood);
|
||||||
|
// Joint paths resolve relative to the joint (a sibling here) and are saved that way; set
|
||||||
|
// before AddChild so the joint never exists without its bodies.
|
||||||
HingeJoint3D pivot = new()
|
HingeJoint3D pivot = new()
|
||||||
{
|
{
|
||||||
|
Name = "SeesawHinge",
|
||||||
Transform = new Transform3D(new Basis(Vector3.Right, Vector3.Up, Vector3.Back), new Vector3(-8.5f, 0.48f, -1.5f)),
|
Transform = new Transform3D(new Basis(Vector3.Right, Vector3.Up, Vector3.Back), new Vector3(-8.5f, 0.48f, -1.5f)),
|
||||||
NodeA = GetNode<Node>("SeesawPivot").GetPath(), // absolute: joint paths resolve relative to the joint
|
NodeA = $"../{GetNode<Node>("SeesawPivot").Name}",
|
||||||
NodeB = seesaw.GetPath(),
|
NodeB = $"../{seesaw.Name}",
|
||||||
};
|
};
|
||||||
AddChild(pivot);
|
AddChild(pivot, true);
|
||||||
Sign("Seesaw", new Vector3(-8.5f, 1.3f, -1.5f));
|
Sign("Seesaw", new Vector3(-8.5f, 1.3f, -1.5f));
|
||||||
|
|
||||||
// Swing board: a 10 kg board hinged at its top edge from a 2.4 m beam — grab it and hang.
|
// Swing board: a 10 kg board hinged at its top edge from a 2.4 m beam — grab it and hang.
|
||||||
@@ -199,11 +214,12 @@ public partial class Playground : Node3D
|
|||||||
RigidBody3D board = BoxProp("SwingBoard", new Vector3(1.2f, 1.2f, 0.05f), 10f, new Vector3(-6f, 1.75f, 1.5f), Wood);
|
RigidBody3D board = BoxProp("SwingBoard", new Vector3(1.2f, 1.2f, 0.05f), 10f, new Vector3(-6f, 1.75f, 1.5f), Wood);
|
||||||
HingeJoint3D swing = new()
|
HingeJoint3D swing = new()
|
||||||
{
|
{
|
||||||
|
Name = "SwingHinge",
|
||||||
Transform = new Transform3D(new Basis(Vector3.Back, Vector3.Up, Vector3.Left), new Vector3(-6f, 2.35f, 1.5f)),
|
Transform = new Transform3D(new Basis(Vector3.Back, Vector3.Up, Vector3.Left), new Vector3(-6f, 2.35f, 1.5f)),
|
||||||
NodeA = beam.GetPath(),
|
NodeA = $"../{beam.Name}",
|
||||||
NodeB = board.GetPath(),
|
NodeB = $"../{board.Name}",
|
||||||
};
|
};
|
||||||
AddChild(swing);
|
AddChild(swing, true);
|
||||||
Sign("Hanging board", new Vector3(-6f, 2.9f, 1.5f));
|
Sign("Hanging board", new Vector3(-6f, 2.9f, 1.5f));
|
||||||
|
|
||||||
// Crate pyramid (4 kg each), a heavy 25 kg box, barrels.
|
// Crate pyramid (4 kg each), a heavy 25 kg box, barrels.
|
||||||
|
|||||||
@@ -1193,7 +1193,8 @@ public partial class RagdollSpike : Node3D
|
|||||||
private void SetupPlayground()
|
private void SetupPlayground()
|
||||||
{
|
{
|
||||||
Rest();
|
Rest();
|
||||||
_playground = new Playground { Position = new Vector3(0f, 0f, 40f) }; // far from the player
|
_playground = GD.Load<PackedScene>("res://scenes/maps/playground.tscn").Instantiate<Playground>(); // the saved scene
|
||||||
|
_playground.Position = new Vector3(0f, 0f, 40f); // far from the player
|
||||||
_stage.AddChild(_playground);
|
_stage.AddChild(_playground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Godot;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
// Dumps the map layout (type, transform, shape/mesh size, groups) sorted, for before/after comparison.
|
||||||
|
// godot-mono --headless --path . tools/map_dump.tscn -- out=<file>
|
||||||
|
public partial class MapDump : Node3D
|
||||||
|
{
|
||||||
|
private int _frame;
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
foreach (string path in RebuildMaps.Maps)
|
||||||
|
AddChild(GD.Load<PackedScene>(path).Instantiate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (++_frame < 3)
|
||||||
|
return;
|
||||||
|
string outPath = "/tmp/claude-1000/maps/dump.txt";
|
||||||
|
foreach (string a in OS.GetCmdlineUserArgs())
|
||||||
|
if (a.StartsWith("out="))
|
||||||
|
outPath = a[4..];
|
||||||
|
var lines = new List<string>();
|
||||||
|
foreach (Node map in GetChildren())
|
||||||
|
Walk(map, map.GetType().Name, lines);
|
||||||
|
lines.Sort(System.StringComparer.Ordinal);
|
||||||
|
System.IO.File.WriteAllLines(outPath, lines);
|
||||||
|
GD.Print($"MAPDUMP: {lines.Count} nodes → {outPath}");
|
||||||
|
GetTree().Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string R(Vector3 v) => $"({v.X:F3},{v.Y:F3},{v.Z:F3})";
|
||||||
|
|
||||||
|
private void Walk(Node n, string map, List<string> lines)
|
||||||
|
{
|
||||||
|
foreach (Node ch in n.GetChildren())
|
||||||
|
{
|
||||||
|
string info = ch.GetClass();
|
||||||
|
if (ch is Node3D n3)
|
||||||
|
{
|
||||||
|
Transform3D t = n3.GlobalTransform;
|
||||||
|
info += $" o{R(t.Origin)} x{R(t.Basis.X)} y{R(t.Basis.Y)}";
|
||||||
|
}
|
||||||
|
if (ch is CollisionShape3D cs && cs.Shape is BoxShape3D bs) info += $" box{R(bs.Size)}";
|
||||||
|
if (ch is CollisionShape3D cc && cc.Shape is CylinderShape3D cy) info += $" cyl{cy.Radius:F3}/{cy.Height:F3}";
|
||||||
|
if (ch is CollisionShape3D cp && cp.Shape is SphereShape3D sp) info += $" sph{sp.Radius:F3}";
|
||||||
|
if (ch is MeshInstance3D mi && mi.GetSurfaceOverrideMaterial(0) is StandardMaterial3D m) info += $" col{m.AlbedoColor.ToHtml()} sh{(int)mi.CastShadow}";
|
||||||
|
if (ch is Label3D l) info += $" text[{l.Text}] px{l.PixelSize:F4}";
|
||||||
|
if (ch is CollisionObject3D co) info += $" L{co.CollisionLayer}/{co.CollisionMask}";
|
||||||
|
if (ch is RigidBody3D rb) info += $" m{rb.Mass:F2} f{rb.PhysicsMaterialOverride?.Friction:F2}";
|
||||||
|
var groups = ch.GetGroups().Select(g => g.ToString()).Where(g => !g.StartsWith("_")).OrderBy(g => g);
|
||||||
|
info += " g[" + string.Join(",", groups) + "]";
|
||||||
|
if (ch is Joint3D j) info += $" joint {j.GetNodeOrNull(j.NodeA)?.GetClass()}@{R((j.GetNodeOrNull(j.NodeA) as Node3D)?.GlobalPosition ?? Vector3.Zero)}-{j.GetNodeOrNull(j.NodeB)?.GetClass()}@{R((j.GetNodeOrNull(j.NodeB) as Node3D)?.GlobalPosition ?? Vector3.Zero)}";
|
||||||
|
if (ch.GetType().Name == "TeleportMarker" || ch.SceneFilePath != "") info += $" scene[{ch.SceneFilePath}] label[{ch.Get("MarkerLabel")}]";
|
||||||
|
lines.Add($"{map}: {info}");
|
||||||
|
if (ch.SceneFilePath == "")
|
||||||
|
Walk(ch, map, lines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bt87gcewn6ihe
|
||||||
@@ -31,8 +31,8 @@ public partial class PlaygroundShots : Node3D
|
|||||||
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(80, 0.1f, 80) }, Position = new Vector3(0, -0.05f, 0) });
|
floor.AddChild(new CollisionShape3D { Shape = new BoxShape3D { Size = new Vector3(80, 0.1f, 80) }, Position = new Vector3(0, -0.05f, 0) });
|
||||||
floor.AddChild(new MeshInstance3D { Mesh = new PlaneMesh { Size = new Vector2(80, 80) } });
|
floor.AddChild(new MeshInstance3D { Mesh = new PlaneMesh { Size = new Vector2(80, 80) } });
|
||||||
AddChild(floor);
|
AddChild(floor);
|
||||||
AddChild(new Playground());
|
AddChild(GD.Load<PackedScene>("res://scenes/maps/playground.tscn").Instantiate()); // the saved maps
|
||||||
AddChild(new Crag());
|
AddChild(GD.Load<PackedScene>("res://scenes/maps/crag.tscn").Instantiate());
|
||||||
_cam = new Camera3D { Current = true };
|
_cam = new Camera3D { Current = true };
|
||||||
AddChild(_cam);
|
AddChild(_cam);
|
||||||
SetView();
|
SetView();
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using Godot;
|
||||||
|
|
||||||
|
// Headless map rebuild for agents (same as pressing each map's "Rebuild" button in the editor and
|
||||||
|
// saving): loads each map scene, regenerates its children from code and saves it back.
|
||||||
|
// godot-mono --headless --path . tools/rebuild_maps.tscn [-- only=playground|crag]
|
||||||
|
// Prints "MAPS: rebuilt <path> (<n> nodes)" per map and "MAPS: done". Overwrites hand edits in those
|
||||||
|
// scenes — that's what Rebuild means.
|
||||||
|
public partial class RebuildMaps : Node
|
||||||
|
{
|
||||||
|
public static readonly string[] Maps = { "res://scenes/maps/playground.tscn", "res://scenes/maps/crag.tscn" };
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
string only = null;
|
||||||
|
foreach (string a in OS.GetCmdlineUserArgs())
|
||||||
|
if (a.StartsWith("only="))
|
||||||
|
only = a[5..];
|
||||||
|
bool ok = true;
|
||||||
|
foreach (string path in Maps)
|
||||||
|
{
|
||||||
|
if (only != null && !path.Contains(only))
|
||||||
|
continue;
|
||||||
|
ok &= RebuildOne(path);
|
||||||
|
}
|
||||||
|
GD.Print(ok ? "MAPS: done" : "MAPS: FAILED");
|
||||||
|
GetTree().Quit(ok ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RebuildOne(string path)
|
||||||
|
{
|
||||||
|
Node map = ResourceLoader.Load<PackedScene>(path, null, ResourceLoader.CacheMode.Ignore).Instantiate();
|
||||||
|
AddChild(map); // in the tree, so joint paths and globals resolve like in the editor
|
||||||
|
switch (map)
|
||||||
|
{
|
||||||
|
case Playground p:
|
||||||
|
p.Rebuild();
|
||||||
|
break;
|
||||||
|
case Crag c:
|
||||||
|
c.Rebuild();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GD.PushError($"MAPS: {path} root is not a map ({map.GetType().Name})");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RemoveChild(map);
|
||||||
|
PackedScene packed = new();
|
||||||
|
Error err = packed.Pack(map);
|
||||||
|
if (err == Error.Ok)
|
||||||
|
err = ResourceSaver.Save(packed, path);
|
||||||
|
int count = CountOwned(map, map);
|
||||||
|
map.Free();
|
||||||
|
if (err != Error.Ok)
|
||||||
|
{
|
||||||
|
GD.PushError($"MAPS: saving {path} failed: {err}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
GD.Print($"MAPS: rebuilt {path} ({count} nodes)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CountOwned(Node root, Node n)
|
||||||
|
{
|
||||||
|
int c = 0;
|
||||||
|
foreach (Node child in n.GetChildren())
|
||||||
|
c += (child.Owner == root ? 1 : 0) + CountOwned(root, child);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cd3m3hyr27lpy
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tools/MapDump.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="MapDump" type="Node3D"]
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tools/RebuildMaps.cs" id="1"]
|
||||||
|
|
||||||
|
[node name="RebuildMaps" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
Reference in new issue
Block a user