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>
223 lines
8.5 KiB
GDScript
223 lines
8.5 KiB
GDScript
extends Node
|
|
## G3 HUD captures from the real game at the running window size (1920x1080, 2560x1440,
|
|
## 1280x720, ultrawide). Every view is a real LuneGame with the real HUD; fixtures only set
|
|
## public match state before or between phases (gold, round, building HP, boss claim), exactly
|
|
## like the G1/G2 views. HUD interactions go through the HUD's own public methods when the
|
|
## build has them, so the same script photographs the G2 HUD ("vorher") and the G3 HUD.
|
|
## Run: godot --path . --resolution 1920x1080 res://tools/luneraria/g3_views.tscn -- --out=res://docs/luneraria/screenshots/g3/nachher-1920x1080 [--only=01,05]
|
|
const Game = preload("res://scripts/luneraria/lune_game.gd")
|
|
|
|
var out: String = "res://docs/luneraria/screenshots/g3/nachher/"
|
|
var only: Array[String] = []
|
|
var shots: Array[String] = []
|
|
var game: Node3D
|
|
|
|
func _ready() -> void:
|
|
for arg: String in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--out="):
|
|
out = arg.substr(6)
|
|
if not out.ends_with("/"):
|
|
out += "/"
|
|
elif arg.begins_with("--only="):
|
|
only.assign(arg.substr(7).split(","))
|
|
call_deferred("run")
|
|
|
|
func wanted(label: String) -> bool:
|
|
return only.is_empty() or only.any(func(prefix: String) -> bool: return label.begins_with(prefix))
|
|
|
|
func shot(label: String, frames: int = 12) -> void:
|
|
for i: int in frames:
|
|
await get_tree().process_frame
|
|
# G3: wait until the portrait cache has rendered every requested model.
|
|
var cache: Node = get_tree().root.get_node_or_null("LunePortraits")
|
|
var guard: int = 0
|
|
while cache != null and cache.has_method("pending") and cache.pending() > 0 and guard < 600:
|
|
await get_tree().process_frame
|
|
guard += 1
|
|
for i: int in 3:
|
|
await get_tree().process_frame
|
|
await RenderingServer.frame_post_draw
|
|
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(out))
|
|
get_viewport().get_texture().get_image().save_png(out + label + ".png")
|
|
shots.append(label)
|
|
print("G3_VIEW ", label, " ", get_viewport().get_visible_rect().size)
|
|
|
|
func wait(seconds: float) -> void:
|
|
await get_tree().create_timer(seconds).timeout
|
|
|
|
func fresh(seed: int = 4, slot: int = 1) -> LuneMatch:
|
|
if game != null and is_instance_valid(game):
|
|
game.queue_free()
|
|
await get_tree().process_frame
|
|
await get_tree().process_frame
|
|
game = Game.new()
|
|
add_child(game)
|
|
game.start({"seed": seed, "human_slot": slot, "bunds": ["wurzelbund", "wurzelbund", "glutpakt", "glutpakt", "wurzelbund", "glutpakt"]})
|
|
await get_tree().process_frame
|
|
return game.match_state
|
|
|
|
## Calls a HUD method if this build has it (G3); returns false otherwise (G2).
|
|
func hud_call(method: String, args: Array = []) -> bool:
|
|
if game.hud.has_method(method):
|
|
game.hud.callv(method, args)
|
|
return true
|
|
return false
|
|
|
|
func command(command_value: Dictionary) -> Dictionary:
|
|
return game.match_state.submit(game.human_slot, command_value)
|
|
|
|
## Runs the combat clock quickly (more fixed steps per frame, rules unchanged), then holds it.
|
|
func advance_combat(seconds: float, hold: bool = true) -> void:
|
|
var state: LuneMatch = game.match_state
|
|
game.paused = false
|
|
while state.phase == LuneMatch.Phase.COMBAT and state.combat.elapsed < seconds:
|
|
for i: int in range(20):
|
|
if state.phase != LuneMatch.Phase.COMBAT or state.combat.elapsed >= seconds:
|
|
break
|
|
state.step_combat()
|
|
await get_tree().process_frame
|
|
game.paused = hold
|
|
|
|
func armies(state: LuneMatch, slots: Array, round_number: int) -> void:
|
|
state.round_number = round_number
|
|
for slot: int in range(6):
|
|
state.players[slot].draft.roster.clear()
|
|
state.players[slot].draft.gold = 100000 if slots.has(slot) else 0
|
|
if slots.has(slot):
|
|
LuneBot.plan(state, slot, "ausgewogen")
|
|
for entry: Dictionary in state.players[slot].draft.roster:
|
|
if entry.lane == LuneMatch.GROTTO:
|
|
entry.lane = state.players[slot].lane
|
|
var hero: Dictionary = state.players[slot].draft.hero
|
|
if not hero.is_empty() and hero.lane == LuneMatch.GROTTO:
|
|
hero.lane = state.players[slot].lane
|
|
# The fixture gold only buys the armies; the human keeps an ordinary amount afterwards.
|
|
state.players[game.human_slot].draft.gold = 185
|
|
|
|
func run() -> void:
|
|
Soundscape.enabled = false
|
|
await planning_views()
|
|
await boss_views()
|
|
await combat_views()
|
|
await ancient_views()
|
|
if game != null and is_instance_valid(game):
|
|
game.queue_free()
|
|
await get_tree().process_frame
|
|
print("G3_VIEWS_DONE ", shots.size())
|
|
get_tree().quit()
|
|
|
|
# ---------------------------------------------------------------- planning
|
|
func planning_views() -> void:
|
|
if not (wanted("01") or wanted("02") or wanted("03") or wanted("04") or wanted("05")):
|
|
return
|
|
var state: LuneMatch = await fresh()
|
|
# A normal planning round: round 3, starter army bought, some gold left.
|
|
state.round_number = 3
|
|
state.players[game.human_slot].draft.gold = 420
|
|
game._starter()
|
|
await wait(.4)
|
|
if wanted("01"):
|
|
await shot("01-planung")
|
|
# Hero hired and shown.
|
|
state.players[game.human_slot].draft.gold = 900
|
|
command({"type": "hero_buy", "hero_id": "held_ringborke"})
|
|
game._rebuild_preview()
|
|
if not hud_call("open_hero"):
|
|
hud_call("show_page", [1])
|
|
await wait(.4)
|
|
if wanted("02"):
|
|
await shot("02-held-ausgewaehlt")
|
|
# Item choice with one item equipped.
|
|
var hero_uid: String = str(state.players[game.human_slot].draft.hero.get("uid", ""))
|
|
command({"type": "item_buy", "item_id": "glutklinge", "uid": hero_uid})
|
|
hud_call("open_items")
|
|
await wait(.3)
|
|
if wanted("03"):
|
|
await shot("03-itemauswahl")
|
|
hud_call("close_modal")
|
|
# A normal unit selected.
|
|
var roster: Array = state.players[game.human_slot].draft.roster
|
|
if not roster.is_empty():
|
|
if not hud_call("select_unit", [str(roster[0].uid)]):
|
|
hud_call("show_page", [0])
|
|
await wait(.3)
|
|
if wanted("04"):
|
|
await shot("04-einheit-ausgewaehlt")
|
|
hud_call("clear_selection")
|
|
# Attack champions: one booked for the next round.
|
|
command({"type": "champion_book", "champion_id": "ch_belagerung", "lane": "mid"})
|
|
if not hud_call("open_champions"):
|
|
hud_call("show_page", [2])
|
|
await wait(.3)
|
|
if wanted("05"):
|
|
await shot("05-angriffschampion")
|
|
|
|
# ---------------------------------------------------------------- boss
|
|
func boss_views() -> void:
|
|
if not (wanted("06") or wanted("07")):
|
|
return
|
|
var state: LuneMatch = await fresh()
|
|
state.round_number = 10
|
|
state.boss.next_available_round = 10
|
|
game.focus_grotto(true)
|
|
await wait(1.0)
|
|
if wanted("06"):
|
|
await shot("06-boss-verfuegbar")
|
|
# Own team conquered the boss in round 11: deployment in round 12 on Mid, planning of 12.
|
|
state.round_number = 12
|
|
state.boss.claim = {"team": 0, "won_round": 11, "deploy_round": 12, "chooser_slot": 1, "lane": "mid"}
|
|
state.boss.next_available_round = 15
|
|
game.focus_lane("mid", true)
|
|
await wait(1.0)
|
|
if wanted("07"):
|
|
await shot("07-boss-angekuendigt")
|
|
|
|
# ---------------------------------------------------------------- combat
|
|
func combat_views() -> void:
|
|
if not (wanted("08") or wanted("09") or wanted("10") or wanted("13") or wanted("14")):
|
|
return
|
|
var state: LuneMatch = await fresh()
|
|
armies(state, [0, 1, 2, 3, 4, 5], 14)
|
|
state.building_hp["rot_top_outer"] = 0.0
|
|
state.building_hp["blau_bot_outer"] = 0.0
|
|
game._on_ready()
|
|
await get_tree().process_frame
|
|
await advance_combat(22.0)
|
|
for spec: Array in [["08-kampf-top", "top"], ["09-kampf-mid", "mid"], ["10-kampf-bottom", "bot"]]:
|
|
if wanted(str(spec[0]).left(2)):
|
|
game.focus_lane(spec[1], true)
|
|
await wait(.5)
|
|
await shot(spec[0], 4)
|
|
if wanted("13"):
|
|
var ruin: Vector2 = state.map.building_def("blau_bot_outer").position
|
|
game.camera.look_at_point(Vector3(ruin.x, 0, ruin.y), 22.0, true)
|
|
await wait(.5)
|
|
await shot("13-turm-zerstoert", 4)
|
|
if wanted("14"):
|
|
game.camera.look_at_point(Vector3(0, 0, 0), 46.0, true)
|
|
await wait(.5)
|
|
await shot("14-grosse-kampfansicht", 4)
|
|
|
|
# ---------------------------------------------------------------- Ancients
|
|
func ancient_views() -> void:
|
|
if not (wanted("11") or wanted("12")):
|
|
return
|
|
var state: LuneMatch = await fresh()
|
|
state.round_number = 21
|
|
for id: String in ["blau_mid_outer", "blau_mid_inner", "blau_base_1", "blau_base_2"]:
|
|
state.building_hp[id] = 0.0
|
|
state.building_hp["blau_ancient"] = float(state.map.building_def("blau_ancient").max_hp) * .22
|
|
game.focus_base(true)
|
|
await wait(1.2)
|
|
if wanted("11"):
|
|
await shot("11-eigener-ancient-kritisch")
|
|
state = await fresh()
|
|
state.round_number = 137
|
|
for id: String in ["rot_top_outer", "rot_top_inner", "rot_base_1", "rot_base_2"]:
|
|
state.building_hp[id] = 0.0
|
|
state.building_hp["rot_ancient"] = float(state.map.building_def("rot_ancient").max_hp) * .18
|
|
var red: Vector2 = state.map.core_position(1)
|
|
game.camera.look_at_point(Vector3(red.x, 0, red.y), 26.0, true)
|
|
await wait(1.2)
|
|
if wanted("12"):
|
|
await shot("12-gegnerischer-ancient-kritisch")
|