147 lines
7.4 KiB
GDScript
147 lines
7.4 KiB
GDScript
extends SceneTree
|
|
|
|
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
|
|
const PROP_STAGE_ROLES := preload("res://scripts/props/prop_stage_roles.gd")
|
|
const CAPTURE_DIR := "res://tests/phase20b_captures/set_down_surface_highlights"
|
|
|
|
var _stage: SandboxStage
|
|
var _failures: int = 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
root.gui_embed_subwindows = true
|
|
var make_dir_error: Error = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(CAPTURE_DIR))
|
|
_check(make_dir_error == OK or make_dir_error == ERR_ALREADY_EXISTS, "capture folder is available")
|
|
_stage = SANDBOX_STAGE.instantiate() as SandboxStage
|
|
root.add_child(_stage)
|
|
await _wait_frames(10)
|
|
var recovery: ConfirmationDialog = _stage.find_child("AutosaveRecoveryDialog", true, false) as ConfirmationDialog
|
|
if recovery != null:
|
|
recovery.hide()
|
|
_stage.call("_on_grid_toggled", false)
|
|
var spawner: StageSpawner = _stage.get("_spawner") as StageSpawner
|
|
var actor: StickmanRig = spawner.spawn_stickman_asset("res://content/stickmen/break.stk", Vector2(-160.0, -190.0))
|
|
var supported_prop: PropBlock = spawner.spawn_prop_asset("crate", Vector2(110.0, -10.0))
|
|
var unsupported_prop: PropBlock = spawner.spawn_prop_asset("crate", Vector2(300.0, -10.0))
|
|
_check(actor != null and supported_prop != null and unsupported_prop != null, "stage fixtures spawn")
|
|
if actor == null or supported_prop == null or unsupported_prop == null:
|
|
await _finish()
|
|
quit(1)
|
|
return
|
|
_stage.call("_finalize_placed_node", actor)
|
|
_stage.call("_finalize_placed_node", supported_prop)
|
|
_stage.call("_finalize_placed_node", unsupported_prop)
|
|
var supported_roles: Dictionary = PROP_STAGE_ROLES.new_placement(supported_prop.has_usable_grip())
|
|
supported_roles["holds_other_props"] = true
|
|
supported_prop.set_stage_roles(supported_roles)
|
|
var unsupported_roles: Dictionary = PROP_STAGE_ROLES.new_placement(unsupported_prop.has_usable_grip())
|
|
unsupported_roles["holds_other_props"] = false
|
|
unsupported_prop.set_stage_roles(unsupported_roles)
|
|
var set_down_action: Dictionary = {
|
|
"action_id": "set-down-highlight-step-one",
|
|
"type": "set_down_prop",
|
|
"target_position": Vector2(110.0, -10.0),
|
|
}
|
|
_check(actor.set_action_plan([{"actions": [set_down_action]}]), "Step 1 contains the Set Down Prop action")
|
|
_stage.call("set_mode", SandboxStage.StageMode.DIRECT)
|
|
var selection: StageSelection = _stage.get("_selection") as StageSelection
|
|
selection.select_only(actor)
|
|
_stage.call("_refresh_character_settings_card")
|
|
await _wait_frames(8)
|
|
var overlay: StagePlacementOverlay = _stage.get("_placement_overlay") as StagePlacementOverlay
|
|
_check(overlay != null and overlay.surface_world == _stage.get_node("World"), "set-down overlay reads the live stage world")
|
|
var support_points: PackedVector2Array = overlay.call("_prop_collision_world_points", supported_prop)
|
|
var excluded_points: PackedVector2Array = overlay.call("_prop_collision_world_points", unsupported_prop)
|
|
var support_edges: Array[PackedVector2Array] = overlay.call("_top_facing_edges", support_points)
|
|
var excluded_edges: Array[PackedVector2Array] = overlay.call("_top_facing_edges", excluded_points)
|
|
_check(not support_edges.is_empty() and not excluded_edges.is_empty(), "both props expose a top edge for role filtering")
|
|
if support_edges.is_empty() or excluded_edges.is_empty():
|
|
await _finish()
|
|
quit(1)
|
|
return
|
|
var target_world: Vector2 = (support_edges[0][0] + support_edges[0][1]) * 0.5
|
|
var screen_target: Vector2 = _stage.get_viewport().get_canvas_transform() * target_world
|
|
await _send_mouse_motion(screen_target)
|
|
_stage.call("_begin_set_down_position_capture", actor, "Click where to let go", Callable(), Callable())
|
|
await _wait_frames(6)
|
|
var capture_overlay: StageInspectorCaptureOverlay = _stage.get("_inspector_capture_overlay") as StageInspectorCaptureOverlay
|
|
var capture_prompt: Label = capture_overlay.find_child("CapturePrompt", true, false) as Label if capture_overlay != null else null
|
|
var cancel_button: Button = capture_overlay.find_child("Capture_cancel_capture", true, false) as Button if capture_overlay != null else null
|
|
_check(capture_overlay != null and capture_overlay.has_capture(), "Set Down capture shows the inspector capture row")
|
|
_check(capture_prompt != null and capture_prompt.text == "Set Down Prop", "inspector row names the action without a long hint")
|
|
_check(cancel_button != null and cancel_button.visible, "the inspector Cancel button stays visible")
|
|
var action_tooltip: Control = _stage.find_child("ActionTooltip", true, false) as Control
|
|
_check(action_tooltip == null or not action_tooltip.visible, "set-down capture has no extra floating instruction text")
|
|
_check(overlay.action_visible and overlay.set_down_preview_visible and not overlay.action_show_trajectory, "set-down capture shows surfaces without the old trajectory")
|
|
var capture_path: String = CAPTURE_DIR.path_join("set_down_support_surface_filter.png")
|
|
await RenderingServer.frame_post_draw
|
|
var image: Image = root.get_texture().get_image()
|
|
var save_error: Error = image.save_png(ProjectSettings.globalize_path(capture_path)) if image != null and not image.is_empty() else ERR_CANT_CREATE
|
|
_check(save_error == OK, "focused Godot run saves the set-down surface capture")
|
|
var canvas_transform: Transform2D = _stage.get_viewport().get_canvas_transform()
|
|
var supported_mid: Vector2 = canvas_transform * ((support_edges[0][0] + support_edges[0][1]) * 0.5)
|
|
var unsupported_mid: Vector2 = canvas_transform * ((excluded_edges[0][0] + excluded_edges[0][1]) * 0.5)
|
|
_check(_has_cyan_near(image, supported_mid), "Props can sit on top surface receives a cyan highlight")
|
|
_check(not _has_cyan_near(image, unsupported_mid), "prop with Props can sit on top off receives no cyan highlight")
|
|
print("SET_DOWN_SURFACE_CAPTURE path=%s size=%s supported=%s unsupported=%s" % [capture_path, str(image.get_size()) if image != null else "missing", str(supported_mid), str(unsupported_mid)])
|
|
await _send_escape()
|
|
_check(not capture_overlay.has_capture(), "Escape cancels set-down capture and removes the inspector strip")
|
|
print("PHASE20B_SET_DOWN_SURFACE_RESULT failures=%d" % _failures)
|
|
await _finish()
|
|
quit(1 if _failures > 0 else 0)
|
|
|
|
|
|
func _has_cyan_near(image: Image, position: Vector2) -> bool:
|
|
if image == null or image.is_empty():
|
|
return false
|
|
var center: Vector2i = Vector2i(roundi(position.x), roundi(position.y))
|
|
for y: int in range(center.y - 3, center.y + 4):
|
|
for x: int in range(center.x - 3, center.x + 4):
|
|
if x < 0 or y < 0 or x >= image.get_width() or y >= image.get_height():
|
|
continue
|
|
var pixel: Color = image.get_pixel(x, y)
|
|
if pixel.g > pixel.r + 0.08 and pixel.b > pixel.r + 0.05:
|
|
return true
|
|
return false
|
|
|
|
|
|
func _send_mouse_motion(position: Vector2) -> void:
|
|
var motion: InputEventMouseMotion = InputEventMouseMotion.new()
|
|
motion.position = position
|
|
motion.global_position = position
|
|
Input.parse_input_event(motion)
|
|
await _wait_frames(2)
|
|
|
|
|
|
func _send_escape() -> void:
|
|
var escape: InputEventKey = InputEventKey.new()
|
|
escape.keycode = KEY_ESCAPE
|
|
escape.pressed = true
|
|
Input.parse_input_event(escape)
|
|
await _wait_frames(3)
|
|
|
|
|
|
func _wait_frames(count: int) -> void:
|
|
for _index: int in count:
|
|
await process_frame
|
|
await RenderingServer.frame_post_draw
|
|
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if condition:
|
|
print("PASS: %s" % message)
|
|
else:
|
|
_failures += 1
|
|
push_error("FAIL: " + message)
|
|
|
|
|
|
func _finish() -> void:
|
|
if _stage != null and is_instance_valid(_stage):
|
|
_stage.queue_free()
|
|
await process_frame
|