OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / test / gate_duplicate_linked.gd

205 lines · gd ref:
extends Node
## GATE — ALT+D, DUPLICATE LINKED (Blender's linked data; ShapeFold._link_data).
##   L1  Alt+D (a real key) on a staircase: a new staircase LINKED to it; Move picks it up
##   L2  steps set on the COPY: the source changes too (the pieces of both)
##   L3  steps set on the SOURCE: the copy follows
##   L4  moving the copy leaves the source where it is (the frame is the object's own)
##   L5  resizing the copy leaves the source's size (Blender's object scale)
##   L6  a modifier on the copy is the copy's only
##   L7  Shift+D's copy is NOT linked: an edit on it stays its own
##   L8  Alt+D of a link links to the ROOT
##   L9  the root deleted: its links still share their data
##   L10 Alt+D on a plain brush: refused, nothing written
##   L11 two arrival orders give the same shared params
##   L12 a flatten keeps the set linked
##
## RUN AS A SCENE: CI=true "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_duplicate_linked.tscn

const ShapeFold := preload("res://scenes/maps/shapes/param/shape_fold.gd")

var fails := 0
var checks := 0
var map: Node3D
var persist: Node
var level: Node
var stack: 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 < 15:
		print("  **FAIL** only %d checks ran -- setup died before the assertions" % checks)
		fails += 1
	print("\nGATE duplicate_linked: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
	get_tree().quit(0 if fails == 0 else 1)


## A real key press (Input, not a call): the path the player's key takes.
func _key(k: Key, shift := false, alt := false) -> void:
	var e := InputEventKey.new()
	e.keycode = k
	e.physical_keycode = k
	e.shift_pressed = shift
	e.alt_pressed = alt
	e.pressed = true
	Input.parse_input_event(e)
	await get_tree().process_frame
	var u := e.duplicate()
	u.pressed = false
	Input.parse_input_event(u)
	await get_tree().process_frame


func _stairs(at: Vector3) -> String:
	var d: String = persist.record("shape.create", {"type": "stairs", "gv": 1, "params": {"steps": 4},
		"frame": SceneObjects.xform_to(Transform3D(Basis.IDENTITY, at)),
		"size": [3.0, 2.0, 1.5], "material_id": 2, "level_id": 0})
	persist.refold()
	return d


func _steps(d: String) -> int:
	return int((persist.shape_state(d)["params"] as Dictionary).get("steps", -1))


func _set_params(d: String, ps: Dictionary) -> void:
	persist.begin_gesture("Edit shape")
	persist.record("shape.set", {"target": d, "params": ps})
	persist.end_gesture()
	persist.refold()


func _n(d: String) -> int:
	return (level.get_group_brush_ids(d) as PackedInt32Array).size()


## Alt+D on `d` (selected as the shape), the Move cancelled: the copy's dot.
func _alt_d(d: String) -> String:
	map.select_shape(d)
	await _key(KEY_D, false, true)
	var made := String(map.get("_sel_shape"))
	if stack.get("active") != null:
		stack.cancel()
	persist.refold()
	return made if made != d else ""


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

	# ---- L1 ----
	var a := _stairs(Vector3(40, 0, 40))
	map.select_shape(a)
	await _key(KEY_D, false, true)
	var b := String(map.get("_sel_shape"))
	_ok("L1 Alt+D: a new staircase", b != "" and b != a and String(persist.shape_state(b).get("type", "")) == "stairs", b)
	_ok("L1 ... linked to the source", String(persist.shape_state(b).get("link", "")) == a, persist.shape_state(b).get("link"))
	_ok("L1 ... and Move picks it up", stack.get("active") != null)
	if stack.get("active") != null:
		stack.cancel()
	persist.refold()

	# ---- L2 / L3 ----
	var n4 := _n(a)
	_set_params(b, {"steps": 7})
	_ok("L2 steps 7 on the COPY: the source has 7 too", _steps(a) == 7 and _steps(b) == 7, [_steps(a), _steps(b)])
	_ok("L2 ... the pieces of both changed", _n(a) == _n(b) and _n(a) != n4, [n4, _n(a), _n(b)])
	_set_params(a, {"steps": 5})
	_ok("L3 steps 5 on the SOURCE: the copy follows", _steps(b) == 5, _steps(b))

	# ---- L4 / L5 ----
	persist.begin_gesture("Move")
	map.translate_units([{"kind": "shape", "shape": b, "w": 1.0}], Vector3(4, 0, 0))
	persist.end_gesture()
	persist.refold()
	_ok("L4 the copy moved, the source did not", (persist.shape_state(a)["frame"] as Transform3D).origin.is_equal_approx(Vector3(40, 0, 40))
		and (persist.shape_state(b)["frame"] as Transform3D).origin.is_equal_approx(Vector3(44, 0, 40)))
	persist.begin_gesture("Size")
	persist.record("shape.size", {"target": b, "size": [6.0, 2.0, 1.5]})
	persist.end_gesture()
	persist.refold()
	_ok("L5 the copy resized, the source kept its size", (persist.shape_state(a)["size"] as Vector3).is_equal_approx(Vector3(3, 2, 1.5))
		and (persist.shape_state(b)["size"] as Vector3).is_equal_approx(Vector3(6, 2, 1.5)))

	# ---- L6 ----
	var na := _n(a)
	var md: String = map.add_modifier("mirror", b)
	_ok("L6 a mirror on the copy: the source has no copies", md != "" and _n(a) == na and _n(b) > na, [na, _n(a), _n(b)])
	map.remove_modifier(md)

	# ---- L7 ----
	map.select_shape(a)
	map.duplicate_selection()
	var c := String(map.get("_sel_shape"))
	stack.cancel()
	persist.refold()
	_set_params(c, {"steps": 9})
	_ok("L7 Shift+D's copy is its own: steps 9 on it, the source keeps 5", _steps(c) == 9 and _steps(a) == 5,
		[_steps(c), _steps(a)])

	# ---- L8 ----
	var b2 := await _alt_d(b)
	_ok("L8 Alt+D of a link links to the ROOT", b2 != "" and String(persist.shape_state(b2).get("link", "")) == a,
		persist.shape_state(b2).get("link") if b2 != "" else "no copy")

	# ---- L11 ----
	var recs: Array = persist.records(false).filter(func(x: Dictionary) -> bool: return ShapeFold.handles(String(x["kind"])))
	var rev := recs.duplicate()
	rev.reverse()
	var s1 := ShapeFold.resolve_states(recs)
	var s2 := ShapeFold.resolve_states(rev)
	_ok("L11 another arrival order, the same shared steps", int(s1[b2]["params"]["steps"]) == int(s2[b2]["params"]["steps"])
		and int(s1[b2]["params"]["steps"]) == 5)

	# ---- L9 ----
	persist.begin_gesture("Delete")
	persist.record("shape.remove", {"target": a})
	persist.end_gesture()
	persist.refold()
	_set_params(b, {"steps": 3})
	_ok("L9 the root deleted: an edit on one link reaches the other", _steps(b2) == 3 and _n(b2) > 0, [_steps(b2), _n(b2)])

	# ---- L12 ----
	var r12: Dictionary = persist.flatten_map()
	var links: Array = []
	for d: String in (persist.live_shapes() as Dictionary):
		if String(persist.shape_state(d)["type"]) == "stairs" and _steps(d) == 3:
			links.append(d)
	_ok("L12 a flatten keeps both links", not r12.has("error") and links.size() == 2, [links, r12.get("error", "")])
	if links.size() == 2:
		_set_params(links[0], {"steps": 6})
		_ok("L12 ... still linked: an edit on one reaches the other", _steps(links[1]) == 6, _steps(links[1]))

	# ---- L10 ----
	var plain := -1
	for bb: int in (level.get_live_brush_ids() as PackedInt32Array):
		if map.shape_of_brush(bb) == "":
			plain = bb
			break
	var n10: int = (persist.records(false) as Array).size()
	map.select_brushes(PackedInt32Array([plain]))
	await _key(KEY_D, false, true)
	_ok("L10 Alt+D on a plain brush: refused, nothing written", (persist.records(false) as Array).size() == n10
		and stack.get("active") == null, (persist.records(false) as Array).size() - n10)