extends Node
## GATE — keymap step 6.3, SELECT LINKED / GROUPED (Blender L / Shift+L / Ctrl+L / Shift+G):
## Ctrl+L grows the selection to everything TOUCHING it (transitively), not what is apart;
## Shift+G's rows select by group / material / texture / type like the active brush;
## L's unit is the whole shape / group.
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_select_linked.tscn
var fails := 0
var checks := 0
func _ok(what: String, cond: bool, note: Variant = "") -> void:
checks += 1
if not cond:
fails += 1
print(" %-64s %s %s" % [what, "OK" if cond else "**FAIL**", str(note)])
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
get_tree().root.get_node("/root/GameMode").set_mode("build")
var map: Node3D = (load("res://scenes/maps/map_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 8:
await get_tree().process_frame
await _run(map)
get_tree().root.get_node("/root/GameMode").set_mode("play")
if checks < 10:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE select_linked: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _box(persist: Node, x0: float, mat: int, tex: String) -> String:
var ft: Array = []
for i in 6:
ft.append({"tex": tex, "mode": 1} if tex != "" else {})
return persist.record("brush.create", {"planes": [1, 0, 0, x0 + 1, -1, 0, 0, -x0, 0, 1, 0, 1, 0, -1, 0, 0,
0, 0, 1, 1, 0, 0, -1, 0], "proj": [], "material_id": mat, "level_id": 0, "sky": false, "group": "",
"face_tex": ft})
func _key(code: Key, ctrl := false, shift := false) -> InputEventKey:
var e := InputEventKey.new()
e.keycode = code
e.physical_keycode = code
e.pressed = true
e.ctrl_pressed = ctrl
e.shift_pressed = shift
return e
func _run(map: Node3D) -> void:
var persist: Node = map.get("_persist")
var level: Node = map.get("level")
# A chain of three TOUCHING boxes at x 100..103, and a lone one at x 110 (same material).
var a: String = _box(persist, 100.0, 3, "kenney-prototype/dark_01")
var b: String = _box(persist, 101.0, 3, "kenney-prototype/dark_01")
var c: String = _box(persist, 102.0, 4, "kenney-prototype/light_01")
var lone: String = _box(persist, 110.0, 3, "kenney-prototype/dark_01")
persist.refold()
var ia: int = level.brush_by_key(a)
var ib: int = level.brush_by_key(b)
var ic: int = level.brush_by_key(c)
var il: int = level.brush_by_key(lone)
print("-- Ctrl+L: everything touching, transitively --")
map.select_brushes(PackedInt32Array([ia]))
map._unhandled_key_input(_key(KEY_L, true))
var sel: Array = map.get("_sel_brushes")
_ok("the chain a-b-c is selected", sel.has(ia) and sel.has(ib) and sel.has(ic), sel)
_ok("... and the lone box apart from it is not", not sel.has(il))
print("-- Shift+G rows: like the active brush --")
map.select_brushes(PackedInt32Array([ia]))
map.select_grouped("material")
sel = map.get("_sel_brushes")
_ok("same material (3): a, b and the lone box, not c", sel.has(ia) and sel.has(ib) and sel.has(il) and not sel.has(ic), sel)
map.select_brushes(PackedInt32Array([ic]))
map.select_grouped("texture")
sel = map.get("_sel_brushes")
_ok("same texture (light_01): only c", sel.size() == 1 and sel.has(ic), sel)
map._unhandled_key_input(_key(KEY_G, false, true))
await get_tree().process_frame
var qm: Node = map.get("_q_menu")
_ok("Shift+G opens the select-grouped menu", qm != null and bool(qm.is_open()) and qm.has_action("builder.select_same_material"))
(qm.get("_menu") as PopupMenu).hide()
# A popup hidden and re-popped in the SAME frame closes again at once (the root's
# focus-in from the removal reaches the new popup's _parent_focused). No key press can
# do that; this gate can, so it lets the frame end before X re-pops the same menu.
await get_tree().process_frame
print("-- L's unit is the whole shape --")
var shp: String = persist.record("shape.create", {"type": "stairs", "gv": 1, "params": {"steps": 4},
"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, Vector3(120, 0, 0))),
"size": [4.0, 2.0, 2.0], "material_id": 2, "level_id": 0})
persist.refold()
var pieces: PackedInt32Array = level.get_group_brush_ids(shp)
var unit: PackedInt32Array = map._unit_of(pieces[0])
_ok("a stairs piece's linked unit is every piece of the stairs", unit.size() == pieces.size() and unit.size() >= 4,
[unit.size(), pieces.size()])
var solid := 0
for p: int in pieces:
if not bool((level.get_brush(p) as Dictionary).get("clearance", false)):
solid += 1
var first_solid := -1
for p2: int in pieces:
if not bool((level.get_brush(p2) as Dictionary).get("clearance", false)):
first_solid = p2
break
map.select_brushes(PackedInt32Array([first_solid]))
map.select_grouped("type")
_ok("same shape type: exactly the stairs' solid pieces (clearance is air)",
(map.get("_sel_brushes") as Array).size() == solid, [(map.get("_sel_brushes") as Array).size(), solid])
print("-- X: the confirming delete (step 6.4) --")
map.select_brushes(PackedInt32Array([ia, ib]))
var e := InputEventKey.new()
e.keycode = KEY_X
e.physical_keycode = KEY_X
e.pressed = true
map._unhandled_key_input(e)
await get_tree().process_frame
var qm2: Node = map.get("_q_menu")
_ok("X opens the delete popup naming the count", bool(qm2.is_open()) and String(qm2.item_text_of("builder.delete")) == "Delete 2 selected",
qm2.item_text_of("builder.delete"))
_ok("... and deleted NOTHING yet (the popup is the confirm)", (level.get_live_brush_ids() as PackedInt32Array).has(ia))
qm2.pick("builder.delete")
for i in 4:
await get_tree().process_frame
_ok("picking the row deletes the two", not (level.get_live_brush_ids() as PackedInt32Array).has(ia)
and not (level.get_live_brush_ids() as PackedInt32Array).has(ib))