OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_assign_generator.gd

153 lines · gd ref:
extends Node
## GATE — ASSIGN A GENERATOR (#120 F2, #109 "cube -> stairs, the cube stays as the passage").
##   G1  box -> stairs: the shape generates stairs, AND the box comes out as passage
##       (a clearance brush with the box's corners)
##   G2  params persist across a switch: stairs steps = 7, remove the generator (back to
##       the box), assign stairs again -> still 7
##   G3  each switch is one gesture; Ctrl+Z goes back to the previous type
##   G4  a HULL made from a wedge -> stairs -> Remove generator: the same wedge corners
##   G5  the picker lists the create-tool types, and "Remove generator" once assigned
##   G6  Shift+D on stairs-over-a-box: the copy keeps the box passage
##   G7  a flatten keeps it too
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_assign_generator.tscn

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 < 14:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE assign_generator: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


func _same_corners(a: PackedVector3Array, b: PackedVector3Array) -> bool:
	if a.size() != b.size():
		return false
	for v: Vector3 in a:
		if not Array(b).any(func(w: Vector3) -> bool: return v.distance_to(w) < 1.0e-3):
			return false
	return true


func _pieces(dot: String, clearance: bool) -> Array:
	var out: Array = []
	for b: int in (level.get_group_brush_ids(dot) as PackedInt32Array):
		if bool((level.get_brush(b) as Dictionary).get("clearance", false)) == clearance:
			out.append(b)
	return out


func _new_brush(planes: Array) -> int:
	persist.begin_gesture("Add")
	var dot: String = persist.record("brush.create", {"planes": planes, "proj": [], "material_id": 1,
		"level_id": 0, "sky": false})
	persist.end_gesture()
	persist.refold()
	return int(level.brush_by_key(dot))


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

	# A box brush made parametric: a `box` shape 4 x 2 x 2.
	var b := _new_brush(Array(ShapeKit.box_piece(Vector3(40, 0, 40), Vector3(44, 2, 42))["planes"]))
	var box_corners := FaceKit.corners(level.get_brush(b)["planes"])
	map.select_brushes(PackedInt32Array([b]))
	var s := String(map.make_parametric()[0])
	_ok("setup: a box shape", String(persist.shape_state(s).get("type", "")) == "box")

	# ---- G1 ----
	var g0: int = (persist.gestures() as Array).size()
	_ok("G1 assign stairs", map.assign_generator(s, "stairs"))
	_ok("G1 ... the shape is stairs", String(persist.shape_state(s).get("type", "")) == "stairs")
	_ok("G1 ... with several step pieces", _pieces(s, false).size() >= 3, _pieces(s, false).size())
	var box_passage := false
	for c: int in _pieces(s, true):
		if _same_corners(FaceKit.corners(level.get_brush(c)["planes"]), box_corners):
			box_passage = true
	_ok("G1 ... and the box is a passage (clearance)", box_passage)
	_ok("G3 one gesture", (persist.gestures() as Array).size() == g0 + 1)

	# ---- G2 ----
	persist.begin_gesture("Steps")
	persist.record("shape.set", {"target": s, "params": {"steps": 7}})
	persist.end_gesture()
	persist.refold()
	map.select_shape(s)
	var rows: Array = map.generator_choices()
	_ok("G5 the picker offers Remove generator first", not rows.is_empty() and String(rows[0][0]).begins_with("Remove generator"),
		rows.map(func(r: Array) -> String: return String(r[0])))
	_ok("G5 ... and the create-tool types (not hull, not stairs itself)", rows.any(func(r: Array) -> bool: return String(r[0]) == "Tunnel")
		and not rows.any(func(r: Array) -> bool: return String(r[0]) in ["Hull", "Stairs"]))
	map.assign_generator(s, "box")
	_ok("G2 remove: back to the box, no passage", String(persist.shape_state(s)["type"]) == "box" and _pieces(s, true).is_empty()
		and _same_corners(FaceKit.corners(level.get_brush(int(_pieces(s, false)[0]))["planes"]), box_corners))
	map.assign_generator(s, "stairs")
	_ok("G2 stairs again: steps is still 7", int(persist.shape_state(s)["params"].get("steps", 0)) == 7,
		persist.shape_state(s)["params"])

	# ---- G3 ----
	persist.undo_last()
	persist.refold()
	_ok("G3 Ctrl+Z goes back to the box", String(persist.shape_state(s)["type"]) == "box")

	# ---- G4 ----
	var wedge: Array = Array(ShapeKit.box_piece(Vector3(50, 0, 50), Vector3(54, 3, 52))["planes"])
	var sn := Vector3(1, 1, 0).normalized()
	wedge.append_array([sn.x, sn.y, sn.z, sn.dot(Vector3(53, 3, 50))])
	var w := _new_brush(wedge)
	var wc := FaceKit.corners(level.get_brush(w)["planes"])
	map.select_brushes(PackedInt32Array([w]))
	var h := String(map.make_parametric()[0])
	map.assign_generator(h, "stairs")
	map.assign_generator(h, "hull")
	_ok("G4 hull -> stairs -> remove: the same wedge", String(persist.shape_state(h)["type"]) == "hull"
		and _pieces(h, false).size() == 1 and _same_corners(FaceKit.corners(level.get_brush(int(_pieces(h, false)[0]))["planes"]), wc))

	# ---- G6 ---- (s is a box again after G3's undo: make it stairs over the box)
	map.assign_generator(s, "stairs")
	map.select_shape(s)
	map.duplicate_selection()
	(map.call("_ensure_ops") as Node).cancel()
	persist.refold()
	var cp := String(map.selected_shape())
	_ok("G6 the copy is stairs with the box passage", cp != s and String(persist.shape_state(cp).get("type", "")) == "stairs"
		and String((persist.shape_state(cp).get("input", {}) as Dictionary).get("type", "")) == "box", persist.shape_state(cp).get("input"))

	# ---- G7 ----
	persist.flatten_map()
	var kept := 0
	for d: String in (persist.live_shapes() as Dictionary):
		var st: Dictionary = persist.shape_state(d)
		if String(st.get("type", "")) == "stairs" and String((st.get("input", {}) as Dictionary).get("type", "")) == "box":
			kept += 1
	_ok("G7 after a flatten both stairs still have their box passage", kept == 2, kept)