extends SceneTree
## GATE — editor redesign E2 (docs/editor-redesign.md §4): snapping, on a real map_flat.
## - grid 0.25 – 256 Quake units, default 8;
## - snap toggle (top bar + Shift+Tab): OFF = steps of 1 unit (TrenchBroom's rule: the
## brushes stay on whole units);
## - WHILE A TOOL IS UP: Ctrl inverts the snap toggle, Shift = 1/8 steps (rule 1c);
## with no tool they do nothing to snapping (they fly);
## - every operator reads the EFFECTIVE units through LevelOperator.grid_units(), and the
## placement snap (snap_to_map_grid) follows them;
## - units switch u | m: grid dropdown, status bar and operator lines follow it.
##
## Run: "$HYPER_TPS_GODOT" --headless --path . --script res://scenes/test/gate_editor_e2.gd
var ok := true
var checks := 0
var _frame := 0
var _map: Node
var _km: Node
var _set: Node
var _hud: Node
var _top: Node
var _stack: Node
var _saved_grid: Variant = 5
var _saved_snap: Variant = true
var _saved_tv: Variant = false
var _saved_te: Variant = false
func _p(cond: bool, what: String, note: String = "") -> void:
checks += 1
if not cond:
ok = false
print(" %-62s %s %s" % [what, "OK" if cond else "**FAIL**", note])
func _key(code: Key, pressed: bool) -> void:
var e := InputEventKey.new()
e.keycode = code
e.physical_keycode = code
e.pressed = pressed
Input.parse_input_event(e)
func _process(_d: float) -> bool:
_frame += 1
match _frame:
1:
# KNOWN START, captured and forced BEFORE the map's _init reads the prefs:
# gates share the installed game's user://, so the player's saved grid/snap
# (or a leak from an interrupted run) must not become the gate's baseline.
var d0: Node = root.get_node("/root/BuildDock")
_saved_grid = d0.pref("grid", 5)
_saved_snap = d0.pref("snap", true)
_saved_tv = d0.pref("snap_vertex", false)
_saved_te = d0.pref("snap_edge", false)
d0.set_pref("grid", 5)
d0.set_pref("snap", true)
_map = (load("res://scenes/maps/map_flat.tscn") as PackedScene).instantiate()
root.add_child(_map)
30:
_km = root.get_node("/root/Keymap")
_set = root.get_node("/root/Settings")
_hud = root.get_node("/root/BuildHud")
root.get_node("/root/GameMode").set_mode("build")
_top = _map.get("_top_bar")
print("-- the grid --")
var g: Array = _map.GRID_UNITS
_p(g.has(0.25) and g.has(256.0), "grid spans 0.25 .. 256 units", str(g))
_p(is_equal_approx(float(_map.base_grid_units()), 8.0), "default grid 8u")
# The LABEL must match the result, and = / - must mean bigger / smaller as they
# do for the carve brush in play mode (keymap_gate): = coarser, - finer.
for pair: Array in [["builder.grid_finer", 4.0], ["builder.grid_coarser", 16.0]]:
_map.set_grid_index(5)
var b: Dictionary = _km.binding_of(String(pair[0]))
var ev := InputEventKey.new()
ev.physical_keycode = int(b.get("key", 0))
ev.keycode = ev.physical_keycode
ev.pressed = true
_map._unhandled_key_input(ev)
_p(is_equal_approx(float(_map.base_grid_units()), float(pair[1])),
"\"%s\" (%s) takes 8u to %su" % [String(b.get("label", "")), _km.describe(String(pair[0])), str(pair[1])],
"got %s" % str(_map.base_grid_units()))
_p(int(_km.binding_of("builder.grid_coarser").get("key", 0)) == KEY_EQUAL
and int(_km.binding_of("builder.grid_finer").get("key", 0)) == KEY_MINUS,
"= is the bigger grid, - the smaller (same as the carve brush)")
_map.set_grid_index(5)
print("\n-- the snap toggle --")
_p(bool(_map.get("snap_enabled")), "snap is ON by default")
_p(is_equal_approx(float(_map.grid_units()), 8.0), "snap on: effective step = the grid")
_km.run("builder.snap_toggle")
32:
_p(not bool(_map.get("snap_enabled")), "Shift+Tab turned snap OFF")
var dock: Node = root.get_node("/root/BuildDock")
_p(dock.pref("snap", true) == false, "snap state saved per player (user://editor_layout.json)")
_p(is_equal_approx(float(_map.grid_units()), 1.0), "snap off: steps of 1 unit (whole units)")
var s: float = _map.snap_to_map_grid(0.5, Vector3.RIGHT, _map.grid_units(), Vector3.ZERO, Vector3.ZERO)
_p(is_equal_approx(s, roundf(0.5 / 0.028) * 0.028), "placement snaps to 1u when off", "%.4f" % s)
_p(_top.snap_button() != null and not _top.snap_button().button_pressed, "the top-bar magnet shows it")
_top.snap_button().button_pressed = true
_top.snap_button().toggled.emit(true)
_p(bool(_map.get("snap_enabled")), "clicking the magnet turns it back ON")
print("\n-- no tool: Ctrl / Shift do nothing to snapping (they fly) --")
_key(KEY_CTRL, true)
34:
_p(is_equal_approx(float(_map.grid_units()), 8.0), "Ctrl held, no tool: still 8u")
_key(KEY_CTRL, false)
_stack = _map.call("_ensure_ops")
var op := LevelOperator.new()
op.map = _map
op.level = _map.get_node("DCLevel")
_map.add_child(op)
_stack.activate(op)
_key(KEY_CTRL, true)
36:
print("\n-- a tool is up --")
_p(is_equal_approx(float(_map.grid_units()), 1.0), "Ctrl held: snap INVERTED (1u)", str(_map.grid_units()))
_p(is_equal_approx(float(_stack.active.grid_units()), 1.0), "and the operator sees the same effective units")
_p(String(_stack.status_line()).contains("snap off: 1u steps"),
"the tool's status line SAYS snap is off and what a step is", _stack.status_line())
_key(KEY_CTRL, false)
_key(KEY_SHIFT, true)
38:
_p(is_equal_approx(float(_map.grid_units()), 1.0), "Shift held: precision = grid / 8", str(_map.grid_units()))
_p(is_equal_approx(float(_stack.active.grid_step()), 1.0 * 0.028), "operator step in metres follows")
_p(not String(_stack.status_line()).contains("snap off"),
"snap on (Shift is precision, not snap-off): no snap-off note", _stack.status_line())
_key(KEY_SHIFT, false)
_stack.cancel()
print("\n-- units: Quake units or metres --")
_p(String(_map.fmt_units(8.0)) == "8u", "u: 8 units reads '8u'", _map.fmt_units(8.0))
_set.set_value("editor_units", 1)
40:
_p(String(_map.fmt_units(8.0)) == "0.224 m", "m: 8 units reads '0.224 m'", _map.fmt_units(8.0))
_p(_hud.status_right().contains("0.224 m"), "the status bar follows", _hud.status_right())
_p(_top.grid_option().get_item_text(5) == "0.224 m", "the grid dropdown follows", _top.grid_option().get_item_text(5))
_set.set_value("editor_units", 0)
42:
_p(_top.grid_option().get_item_text(5) == "8u", "and back to units")
_map.set_grid_index(7)
var dock2: Node = root.get_node("/root/BuildDock")
_p(int(dock2.pref("grid", -1)) == 7, "grid choice saved per player", str(dock2.pref("grid", -1)))
# A fresh map instance picks both up (snap was turned back ON above).
var m2: Node = (load("res://scenes/maps/map_flat.gd") as GDScript).new()
_p(int(m2.get("_grid_idx")) == 7 and bool(m2.get("snap_enabled")), "a new map restores grid + snap",
"%d %s" % [int(m2.get("_grid_idx")), str(m2.get("snap_enabled"))])
m2.free()
_map.set_grid_index(5) # back to 8u for the outliner checks
_p(_hud.status_right().contains("snap"), "status bar shows the snap state", _hud.status_right())
print("\n-- the outliner's position / size boxes follow the units --")
var gs: Node = root.get_node_or_null("/root/GameState")
if gs != null:
gs.route("world:level:*", 1, "peer")
var keet: Node = root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
var s16 := 16.0 * 0.028 # a 16-unit cube
var persist: Node = _map.get("_persist")
var bdot: String = persist.record("brush.create", {
"planes": [1, 0, 0, s16, -1, 0, 0, 0, 0, 1, 0, s16, 0, -1, 0, 0, 0, 0, 1, s16, 0, 0, -1, 0],
"proj": [], "material_id": 1, "level_id": 0, "sky": false})
persist.refold()
_map.select_brushes(PackedInt32Array([_map.get_node("DCLevel").brush_by_key(bdot)]))
_ol = _map.get("_outliner")
_ol.call("_sync_position")
var sz: Array = _ol.get("_size")
_p(is_equal_approx(float(sz[0].value), 16.0) and String(sz[0].suffix) == "u", "units: size reads 16 u",
"%s %s" % [str(sz[0].value), sz[0].suffix])
_set.set_value("editor_units", 1)
44:
var sz2: Array = _ol.get("_size")
var pos2: Array = _ol.get("_pos")
_p(is_equal_approx(float(sz2[0].value), 0.448) and String(sz2[0].suffix) == "m", "metres: size reads 0.448 m",
"%s %s" % [str(sz2[0].value), sz2[0].suffix])
_p(is_equal_approx(float(pos2[0].value), 0.224) and String(pos2[0].suffix) == "m", "metres: centre X reads 0.224 m",
str(pos2[0].value))
sz2[0].value = 0.9 # ~32u, snapped to the 8u grid → 32u
46:
var sel_size: Vector3 = _map.selection_size()
_p(is_equal_approx(sel_size.x, 32.0 * 0.028), "typing 0.9 m snaps to 32u (0.896 m) on the grid",
"%.4f m" % sel_size.x)
_set.set_value("editor_units", 0)
48:
var sz3: Array = _ol.get("_size")
_p(is_equal_approx(float(sz3[0].value), 32.0), "and back in units it reads 32", str(sz3[0].value))
print("\n-- snap TARGETS: vertex / edge on the aimed face --")
var lvl: Node = _map.get_node("DCLevel")
var s8 := 32.0 * 0.028 # 32u: an edge midpoint is 0.448 m from any corner
var tdot: String = _map.get("_persist").record("brush.create", {
"planes": [1, 0, 0, 10 + s8, -1, 0, 0, -10, 0, 1, 0, s8, 0, -1, 0, 0, 0, 0, 1, s8, 0, 0, -1, 0],
"proj": [], "material_id": 1, "level_id": 0, "sky": false})
_map.get("_persist").refold()
var tb: int = lvl.brush_by_key(tdot)
var corner := Vector3(10 + s8, s8, s8) # a corner of the top face
var near_corner := corner + Vector3(-0.05, 0, -0.04) # on the top face, ~0.06 m away
var mid_edge := Vector3(10 + s8 * 0.5, s8, s8 - 0.03) # near the middle of an edge
_p(_map.snap_target_point(near_corner, tb) == null, "targets off: nothing")
_map.set_snap_target("vertex", true)
var v: Variant = _map.snap_target_point(near_corner, tb)
_p(v is Vector3 and (v as Vector3).is_equal_approx(corner), "vertex on: a point near a corner snaps TO it", str(v))
var e0: Variant = _map.snap_target_point(mid_edge, tb)
_p(e0 == null, "vertex only: the middle of an edge is not near a vertex", str(e0))
_map.set_snap_target("edge", true)
var e: Variant = _map.snap_target_point(mid_edge, tb)
_p(e is Vector3 and is_equal_approx((e as Vector3).z, s8) and is_equal_approx((e as Vector3).y, s8),
"edge on: a point near an edge snaps ONTO it", str(e))
_p(_map.get_node("/root/BuildDock").pref("snap_vertex", false) == true, "targets remembered per player")
_map.snap_enabled = false
_p(_map.snap_target_point(near_corner, tb) == null, "snap off: targets off too")
_map.snap_enabled = true
_map.set_snap_target("vertex", false)
_map.set_snap_target("edge", false)
var dock3: Node = root.get_node("/root/BuildDock")
dock3.set_pref("grid", _saved_grid)
dock3.set_pref("snap", _saved_snap)
dock3.set_pref("snap_vertex", _saved_tv)
dock3.set_pref("snap_edge", _saved_te)
if checks < 28:
_p(false, "only %d checks ran" % checks)
print("GATE editor_e2: %s (%d checks)" % ["PASS" if ok else "FAIL", checks])
quit(0 if ok else 1)
return true
return false
var _ol: Node