Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das HUD im Unity-Projekt (../luneraria-unity). - HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse, Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails, Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben - Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie - Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715; verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern - Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md - HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
5.3 KiB
GDScript
140 lines
5.3 KiB
GDScript
extends Node
|
|
## Interactive playtest driver for the real application (menu, match, HUD). It never touches the
|
|
## match state: every action is a real input event (mouse click, wheel, key) at window pixel
|
|
## coordinates, exactly as a player would give it. A tester writes command batches to files and
|
|
## gets a screenshot back after each batch:
|
|
## /tmp/lune_play/cmd-<n>.txt lines: click X Y · rclick X Y · move X Y · wheel X Y up|down ·
|
|
## key NAME · wait SECONDS · shot NAME
|
|
## /tmp/lune_play/out-<n>.png the screen after the batch; /tmp/lune_play/ok-<n> marks it done
|
|
## Hover (tooltips) follows the pointer of the batch: the control under it gets mouse_entered,
|
|
## because synthetic pointer moves do not move the OS cursor.
|
|
## A log of all commands with round and phase goes to docs/luneraria/logs/g3/playtest-log.txt.
|
|
## Run: godot --path . --resolution 1920x1080 res://tools/luneraria/g3_playtest.tscn
|
|
const DIR := "/tmp/lune_play/"
|
|
const LOG := "res://docs/luneraria/logs/g3/playtest-log.txt"
|
|
|
|
var main: Node
|
|
var next: int = 1
|
|
var busy: bool = false
|
|
var hovered: Control
|
|
var started: int = 0
|
|
var log_lines: PackedStringArray = []
|
|
|
|
func _ready() -> void:
|
|
DirAccess.make_dir_recursive_absolute(DIR)
|
|
for file: String in DirAccess.get_files_at(DIR):
|
|
DirAccess.remove_absolute(DIR + file)
|
|
main = load("res://scenes/luneraria.tscn").instantiate()
|
|
add_child(main)
|
|
started = Time.get_ticks_msec()
|
|
print("PLAYTEST_READY")
|
|
|
|
func _process(_delta: float) -> void:
|
|
if busy:
|
|
return
|
|
var path: String = DIR + "cmd-%d.txt" % next
|
|
if not FileAccess.file_exists(path):
|
|
return
|
|
busy = true
|
|
_run_batch(path, next)
|
|
|
|
func _run_batch(path: String, index: int) -> void:
|
|
await get_tree().process_frame
|
|
var text: String = FileAccess.get_file_as_string(path)
|
|
for raw: String in text.split("\n"):
|
|
var line: String = raw.strip_edges()
|
|
if line.is_empty() or line.begins_with("#"):
|
|
continue
|
|
_log(index, line)
|
|
await _command(line.split(" ", false))
|
|
await get_tree().create_timer(.35).timeout
|
|
await RenderingServer.frame_post_draw
|
|
get_viewport().get_texture().get_image().save_png(DIR + "out-%d.png" % index)
|
|
var marker := FileAccess.open(DIR + "ok-%d" % index, FileAccess.WRITE)
|
|
marker.store_string(_status())
|
|
marker.close()
|
|
next = index + 1
|
|
busy = false
|
|
|
|
func _status() -> String:
|
|
var game: Node = main.get("game") if main != null else null
|
|
if game == null or not is_instance_valid(game) or game.get("match_state") == null:
|
|
return "menu"
|
|
var state: LuneMatch = game.match_state
|
|
return "round=%d phase=%s" % [state.round_number, ["PLANUNG", "KAMPF", "ENDE"][state.phase]]
|
|
|
|
func _log(index: int, line: String) -> void:
|
|
var entry: String = "%7.1f s #%d [%s] %s" % [(Time.get_ticks_msec() - started) / 1000.0, index, _status(), line]
|
|
log_lines.append(entry)
|
|
var file := FileAccess.open(LOG, FileAccess.WRITE)
|
|
if file != null:
|
|
file.store_string("\n".join(log_lines) + "\n")
|
|
file.close()
|
|
|
|
func _command(parts: PackedStringArray) -> void:
|
|
match parts[0]:
|
|
"click", "rclick":
|
|
var at := Vector2(float(parts[1]), float(parts[2]))
|
|
await _move(at)
|
|
for pressed: bool in [true, false]:
|
|
_button(at, MOUSE_BUTTON_LEFT if parts[0] == "click" else MOUSE_BUTTON_RIGHT, pressed)
|
|
await get_tree().process_frame
|
|
"move":
|
|
await _move(Vector2(float(parts[1]), float(parts[2])))
|
|
"wheel":
|
|
var at := Vector2(float(parts[1]), float(parts[2]))
|
|
await _move(at)
|
|
for pressed: bool in [true, false]:
|
|
_button(at, MOUSE_BUTTON_WHEEL_UP if parts.size() < 4 or parts[3] == "up" else MOUSE_BUTTON_WHEEL_DOWN, pressed)
|
|
await get_tree().process_frame
|
|
"key":
|
|
var code: Key = OS.find_keycode_from_string(parts[1])
|
|
for pressed: bool in [true, false]:
|
|
var event := InputEventKey.new()
|
|
event.keycode = code
|
|
event.physical_keycode = code
|
|
event.pressed = pressed
|
|
Input.parse_input_event(event)
|
|
await get_tree().process_frame
|
|
"wait":
|
|
await get_tree().create_timer(float(parts[1])).timeout
|
|
"shot":
|
|
await RenderingServer.frame_post_draw
|
|
get_viewport().get_texture().get_image().save_png(DIR + parts[1] + ".png")
|
|
"quit":
|
|
get_tree().quit()
|
|
|
|
## Window pixels → the same path as a real mouse (Input), so GUI and game see one event.
|
|
func _button(at: Vector2, button: int, pressed: bool) -> void:
|
|
var event := InputEventMouseButton.new()
|
|
event.button_index = button
|
|
event.position = at
|
|
event.global_position = at
|
|
event.pressed = pressed
|
|
Input.parse_input_event(event)
|
|
|
|
func _move(at: Vector2) -> void:
|
|
var motion := InputEventMouseMotion.new()
|
|
motion.position = at
|
|
motion.global_position = at
|
|
Input.parse_input_event(motion)
|
|
await get_tree().process_frame
|
|
_hover(at)
|
|
|
|
## The topmost visible control under the pointer that takes mouse input gets mouse_entered.
|
|
func _hover(at: Vector2) -> void:
|
|
var canvas: Vector2 = get_viewport().get_screen_transform().affine_inverse() * at
|
|
var found: Control = null
|
|
for node: Node in get_tree().root.find_children("*", "Control", true, false):
|
|
var control := node as Control
|
|
if control.mouse_filter == Control.MOUSE_FILTER_IGNORE or not control.is_visible_in_tree():
|
|
continue
|
|
if control.get_global_rect().has_point(canvas):
|
|
found = control
|
|
if found == hovered:
|
|
return
|
|
if hovered != null and is_instance_valid(hovered):
|
|
hovered.mouse_exited.emit()
|
|
hovered = found
|
|
if found != null:
|
|
found.mouse_entered.emit()
|