extends SceneTree ## The greengrocer: do the pyramids build and SETTLE, does produce burst instead of ## shattering, does a collapse crush its own bottom layer, does the floor get slippery, ## do the scales hang and swing, can you carry and throw fruit, and is the bag actually ## impossible to open from the wrong end. ## Godot --headless --path game --script dev/probe_grocer.gd func _initialize() -> void: var main: Node = (load("res://main.tscn") as PackedScene).instantiate() get_root().add_child(main) await process_frame main._load_level("grocer") for i in 10: await physics_frame var splat = main.get("_splat") var produce := _count("produce") print("site : ", main.get("_plan").level_name()) print("produce : %d glass: %d total smashables: %d" % [ produce, _count("glass"), get_nodes_in_group("smashable").size()]) # --- do the stacks hold? --- for i in 120: await physics_frame var moving := 0 var total := 0.0 for n in get_nodes_in_group("smashable"): var b := n as RigidBody3D if b == null or b.freeze: continue var v := b.linear_velocity.length() total += v if v > 0.15: moving += 1 print("after 2s : %d still moving, total %.2f m/s (stacks should be at rest)" % [ moving, total]) print("survived : %d produce (spawn crush would show up as losses)" % _count("produce")) # --- squash one: does it burst rather than shard? --- var before_l: float = splat.litres var debris_before := get_nodes_in_group("debris").size() var victim: Smashable = null for n in get_nodes_in_group("smashable"): if (n as Smashable).kind == "produce": victim = n break var at := victim.global_position victim.shatter(Vector3.UP) await process_frame print("squash one: litres %.2f -> %.2f debris %d -> %d (must NOT rise: fruit has no shards)" % [ before_l, splat.litres, debris_before, get_nodes_in_group("debris").size()]) # --- drop a melon on a pile: does the collapse crush? --- var n0 := _count("produce") for n in get_nodes_in_group("smashable"): var b := n as Smashable if b != null and b.kind == "produce": b.apply_central_impulse(Vector3(randf_range(-3, 3), 0, randf_range(-3, 3))) for i in 150: await physics_frame print("shoved all: %d -> %d produce %.1f litres from the collapse" % [ n0, _count("produce"), splat.litres]) # --- slippery? --- var player = main.get("_player") player.global_position = Vector3(at.x, 0.0, at.z) await process_frame print("slip here : %.2f (0 = dry floor)" % splat.slip_under_player()) print("hud : ", splat.hud_line()) # --- the hanging scales: do they hang, and do they swing when hit? --- var pans: Array = [] for n in get_nodes_in_group("smashable"): if String(n.name).begins_with("scale-pan"): pans.append(n) var joints := 0 for n in main.get("_world").get_children(): if n is PinJoint3D: joints += 1 print("scales : %d pans, %d pin joints" % [pans.size(), joints]) if not pans.is_empty(): var pan: RigidBody3D = pans[0] var anchor := pan.global_position # the ORIGIN is the pin: it must not move var bob := func() -> Vector3: return pan.global_transform * Vector3(0.0, -0.25, 0.0) var rest: Vector3 = bob.call() pan.apply_central_impulse(Vector3(4.0, 0.0, 0.0)) var peak := 0.0 for i in 180: await physics_frame peak = maxf(peak, ((bob.call() as Vector3) - rest).length()) print("swing : dish swings %.2f m, pin drifts %.4f m, still moving at 3s: %.2f" % [ peak, (pan.global_position - anchor).length(), ((bob.call() as Vector3) - rest).length()]) # --- carry and throw --- var grab = player.get("grab") var apple: Smashable = null for n in get_nodes_in_group("smashable"): var sm := n as Smashable if sm != null and sm.kind == "produce" and not sm.freeze and not sm.is_broken(): apple = sm break if apple != null and grab != null: var cam = main.get("_cam") # stand the player next to it and look at it player.global_position = Vector3(apple.global_position.x, 0.0, apple.global_position.z + 0.9) await physics_frame cam.look_at(apple.global_position, Vector3.UP) await physics_frame grab.try_grab() print("carry : holding=%s carrying=%s" % [grab.is_holding(), grab.is_carrying()]) for i in 4: await physics_frame # let it ride up to the hand first var l0: float = splat.litres grab.primary() # throw it var thrown := apple.linear_velocity.length() if is_instance_valid(apple) else -1.0 print("throw : leaves the hand at %.1f m/s (brittle threshold is 1.8)" % thrown) for i in 60: await physics_frame print("landed : litres %.2f -> %.2f (a thrown apple must burst)" % [l0, splat.litres]) # --- the bag --- var tasks = main.get("_tasks") tasks.phase = Tasks.Phase.IDLE tasks._begin_bags() var open_end: int = tasks.get("_bag_open_end") tasks.handle_key(KEY_SPACE) # tear one off print("bag : torn off, stage=%d" % tasks.get("_bag_stage")) # rub the WRONG end for twenty strokes tasks._bag_end = 1 - open_end for i in 20: tasks.handle_key(KEY_LEFT if i % 2 == 0 else KEY_RIGHT) print("wrong end : grip %.2f after 20 strokes (must be 0.00)" % tasks.get("_grip")) # mash ONE key on the right end: also nothing, because that is not rubbing tasks._bag_end = open_end for i in 20: tasks.handle_key(KEY_LEFT) print("mashing : grip %.2f after 20 identical keys (must be ~0.08: one stroke)" % tasks.get("_grip")) # now do it properly var strokes := 0 while tasks.phase == Tasks.Phase.BAGS and strokes < 60: tasks.handle_key(KEY_LEFT if strokes % 2 == 0 else KEY_RIGHT) strokes += 1 print("right end : opened in %d alternating strokes, phase now %d" % [ strokes, tasks.phase]) # --- and none of it leaks to the office --- main._load_level("scranton") for i in 6: await physics_frame print("at office : litres %.1f (reset) produce %d" % [splat.litres, _count("produce")]) quit() func _count(kind: String) -> int: var n := 0 for s in get_nodes_in_group("smashable"): if (s as Smashable) != null and (s as Smashable).kind == kind: n += 1 return n