OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_collection_keys.gd

219 lines · gd ref:
extends Node
## GATE — OBJECTS AND COLLECTIONS ON KEYS AND IN THE OBJECT MENU (#120 F1, Blender 2.79's
## keys). Each action runs through Keymap.run — the path a menu click takes (it replays the
## bound key into the map) — so this covers the key and the menu row at once.
##   K1  the Object menu lists parent, join, separate and the four collection commands
##   K2  Ctrl+G with a shape selected: a new collection holding it
##   K3  M opens a picker: + New Collection first, then the collections; picking one moves
##       the selection there
##   K4  Shift+M's pick links it into a second collection (now in both)
##   K5  Ctrl+Alt+G lists ONLY the collections the selection is in; picking one unlinks it
##   K6  Ctrl+J joins two loose brushes; P separates one again
##   K7  F2 with a collection row selected opens its name for editing
##   K8  H / Shift+H / Alt+H work on OBJECTS (audit bug 7): H hides the selected object,
##       Shift+H the other objects (not the selection's parent), Alt+H shows them all
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_collection_keys.tscn

const TopBar := preload("res://scenes/game/editor_top_bar.gd")

var fails := 0
var checks := 0
var map: Node3D
var persist: Node
var level: Node


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")
	map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
	add_child(map)
	for i in 6:
		await get_tree().process_frame
	await _run()
	get_tree().root.get_node("/root/GameMode").set_mode("play")
	if checks < 21:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE collection_keys: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _frames(n: int) -> void:
	for i in n:
		await get_tree().process_frame


func _run_action(a: String) -> void:
	Keymap.run(a)
	await _frames(3)
	persist.refold()
	await _frames(1)


func _cols() -> Dictionary:
	return persist.collection_tree()["cols"]


func _named(name: String) -> String:
	for d: String in _cols():
		if String(_cols()[d]["name"]) == name:
			return d
	return ""


func _pick(text: String) -> void:
	var q: Node = map.get("_q_menu")
	q.choose_text(text)
	await _frames(4)
	persist.refold()
	await _frames(1)


func _run() -> void:
	persist = map.get("_persist")
	level = map.get("level")

	# ---- K1 ----
	var obj_menu: Array = []
	for m: Array in TopBar.MENUS:
		if String(m[0]) == "Object":
			obj_menu = m[1]
	var want := ["builder.parent", "builder.clear_parent", "builder.join", "builder.separate",
		"builder.collection_new", "builder.collection_move", "builder.collection_link", "builder.collection_remove"]
	_ok("K1 the Object menu lists all eight", want.all(func(a: String) -> bool: return obj_menu.has(a)), obj_menu)

	var shape: String = persist.record("shape.create", {"type": "box", "gv": 1, "params": {},
		"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, Vector3(20, 0, 20))),
		"size": [2, 1, 2], "material_id": 2, "level_id": 0})
	persist.refold()

	# ---- K2 ----
	map.select_shape(shape)
	var q: Node = map.get("_q_menu")
	# Ctrl+G ASKS FOR THE NAME first (user 2026-10-18); Esc makes nothing.
	var n0 := _cols().size()
	await _run_action("builder.collection_new")
	_ok("K2 Ctrl+G asks for the name", q.name_open())
	q.cancel_name()
	await _frames(3)
	persist.refold()
	_ok("K2 Esc: nothing made", _cols().size() == n0, _cols().size() - n0)
	await _run_action("builder.collection_new")
	q.submit_name("A")
	await _frames(4)
	persist.refold()
	var a := _named("A")
	_ok("K2 Enter: a collection NAMED A holding the shape", a != "" and _cols()[a]["members"] == ["g:" + shape],
		_cols())
	var b := CollectionOps.create_empty(map, "B")
	persist.refold()

	# ---- K3 ----
	map.select_shape(shape)
	await _run_action("builder.collection_move")
	var texts: Array = q.choice_texts()
	_ok("K3 M opens the picker: + New Collection, then A and B", q.is_open() and texts == ["+ New Collection", "A", "B"], texts)
	await _pick("B")
	_ok("K3 picking B moves the shape there (out of A)", _cols()[b]["members"] == ["g:" + shape]
		and (_cols()[a]["members"] as Array).is_empty(), [_cols()[a]["members"], _cols()[b]["members"]])
	# + New Collection asks for the name too, then moves the selection into it.
	map.select_shape(shape)
	await _run_action("builder.collection_move")
	await _pick("+ New Collection")
	_ok("K3 + New Collection asks for the name", q.name_open())
	q.submit_name("N")
	await _frames(4)
	persist.refold()
	var nn := _named("N")
	_ok("K3 ... and moves the shape into the new collection N", nn != "" and _cols()[nn]["members"] == ["g:" + shape]
		and (_cols()[b]["members"] as Array).is_empty(), _cols().get(nn, {}))
	map.select_shape(shape)
	await _run_action("builder.collection_move")
	await _pick("B")                       # back to B for K4 / K5

	# ---- K4 ----
	map.select_shape(shape)
	await _run_action("builder.collection_link")
	await _pick("A")
	_ok("K4 Shift+M links it into A too (in both)", (_cols()[a]["members"] as Array).has("g:" + shape)
		and (_cols()[b]["members"] as Array).has("g:" + shape))

	# ---- K5 ----
	var c := CollectionOps.create_empty(map, "C")    # the shape is NOT in C
	persist.refold()
	map.select_shape(shape)
	await _run_action("builder.collection_remove")
	texts = q.choice_texts()
	_ok("K5 Ctrl+Alt+G lists only A and B (not C)", texts == ["A", "B"], texts)
	await _pick("A")
	_ok("K5 picking A takes it out of A only", not (_cols()[a]["members"] as Array).has("g:" + shape)
		and (_cols()[b]["members"] as Array).has("g:" + shape))
	_ok("K5 (C still exists, empty)", _cols().has(c))

	# ---- K6 ----
	var loose: Array = []
	for x: int in (level.get_live_brush_ids() as PackedInt32Array):
		if String(level.get_brush_group(x)) == "":
			loose.append(x)
	_ok("setup: two loose brushes", loose.size() >= 2)
	if loose.size() < 2:
		return
	var k0 := String(level.get_brush_key(int(loose[0])))
	var k1 := String(level.get_brush_key(int(loose[1])))
	map.select_brushes(PackedInt32Array([int(loose[0]), int(loose[1])]))
	await _run_action("builder.join")
	_ok("K6 Ctrl+J joins them into the active one's object",
		String(level.get_brush_group(int(level.brush_by_key(k0)))) == k1
		and String(level.get_brush_group(int(level.brush_by_key(k1)))) == k1)
	map.select_brushes(PackedInt32Array([int(level.brush_by_key(k0))]))
	await _run_action("builder.separate")
	var g0 := String(level.get_brush_group(int(level.brush_by_key(k0))))
	_ok("K6 P separates it into a new object", g0 != "" and g0 != k1, g0)

	# ---- K7 ----
	var panel: Node = map.get("_outliner")
	panel.refresh()
	var tree: Tree = panel.get("_tree")
	tree.deselect_all()
	((panel.get("_items") as Dictionary)["col:" + b] as TreeItem).select(0)
	Keymap.run("builder.rename")
	await _frames(3)
	var editing := false
	for ch: Node in tree.get_children(true):
		if ch is Popup and (ch as Popup).visible:
			editing = true
	_ok("K7 F2 opens the selected collection's name for editing", editing)

	# ---- K8 ----
	var p8: String = map.create_object("marker", Transform3D(Basis(), Vector3(60, 1, 0)))
	var c8: String = map.create_object("marker", Transform3D(Basis(), Vector3(61, 1, 0)))
	map.parent_objects([c8], p8)
	persist.refold()
	panel.refresh()
	map.select_object("scene:SpawnPoint1")
	await _run_action("builder.hide")
	_ok("K8 H hides the selected object", not (map.object_node("scene:SpawnPoint1") as Node3D).is_visible_in_tree())
	await _run_action("builder.unhide_all")
	_ok("K8 Alt+H shows it again", (map.object_node("scene:SpawnPoint1") as Node3D).is_visible_in_tree())
	map.select_object(c8)
	await _run_action("builder.hide_unselected")
	_ok("K8 Shift+H hides the other objects", not (map.object_node("scene:SpawnPoint2") as Node3D).is_visible_in_tree())
	_ok("K8 ... but not the selected child, nor its parent", (map.object_node(c8) as Node3D).is_visible_in_tree()
		and (map.object_node(p8) as Node3D).is_visible_in_tree())
	await _run_action("builder.unhide_all")
	_ok("K8 Alt+H shows everything", (map.object_node("scene:SpawnPoint2") as Node3D).is_visible_in_tree())
	panel.model.save(String(panel.get("_save_key")))       # leave the saved view as found