extends ColorRect ## In-game close-up of the actual animated model; the match pauses underneath. signal closed var _previous_pause: bool = false var _view: SubViewport var _root: Node3D var _turntable: Node3D var _figure: Node3D var _camera: Camera3D var _time: float = 0 var _walking: bool = false var _attacking: bool = false var _rotate: bool = true var _close_button: Button var _caption: Label var _details: Label var _previous_focus: Control var _selector: OptionButton var _rotate_button: CheckButton var model_id: String = "" var _legacy_buttons: Array[Control] = [] var _clip_row: HBoxContainer var _clip_selector: OptionButton var _clip_time: float = 0.0 var _clip_paused: bool = false var _clip_slider: HSlider var _clip_readout: Label const CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Angriff links", "attack_b":"Angriff rechts", "hit":"Treffer", "ability":"Fähigkeit", "death":"Tod"} const WORKER_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Tragen", "stop":"Anhalten", "strike":"Werkzeugkontakt", "pickup":"Aufnehmen", "deposit":"Einfüllen", "return":"Rückkehr", "hit":"Treffer", "death":"Tod"} const RINGBORKE_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Schildstoß links", "attack_b":"Hieb rechts", "hit":"Treffer", "ability":"Heilung", "death":"Tod"} const BASALT_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Schlag links", "attack_b":"Schlag rechts", "hit":"Treffer", "ability":"Schutzhaltung", "death":"Tod"} const QUELL_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Ranke links", "attack_b":"Ranke rechts", "hit":"Treffer", "ability":"Mehrheilung", "death":"Tod"} const FUNKEN_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Freier Schuss", "attack_b":"Angelegter Schuss", "hit":"Treffer", "ability":"Durchschlagschuss", "death":"Tod"} const GLASS_CLIP_LABELS = {"idle":"Ruhe", "start":"Anlaufen", "walk":"Laufen", "stop":"Anhalten", "attack_a":"Freier Schuss", "attack_b":"Angelegter Schuss", "hit":"Treffer", "ability":"Panzerbrecherschuss", "death":"Tod"} func _ready() -> void: color = Color(.015, .025, .04, 1) set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) var margin := MarginContainer.new() margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) for side: String in ["left","right","top","bottom"]: margin.add_theme_constant_override("margin_"+side,32) add_child(margin) var layout := VBoxContainer.new() margin.add_child(layout) var header := HBoxContainer.new() layout.add_child(header) _caption = Label.new() _caption.add_theme_font_size_override("font_size",28) _caption.size_flags_horizontal = Control.SIZE_EXPAND_FILL header.add_child(_caption) _selector = OptionButton.new() for id: String in DataCatalog.UNITS: _selector.add_item(str(DataCatalog.UNITS[id].display_name)) _selector.set_item_metadata(_selector.item_count-1,id) _selector.add_item("Funkenleser") _selector.set_item_metadata(_selector.item_count-1,"worker") _selector.item_selected.connect(func(index: int): _select_model(str(_selector.get_item_metadata(index)))) header.add_child(_selector) var close := Button.new() _close_button = close close.text = "ZURÜCK [Esc / V]" close.pressed.connect(hide_model) header.add_child(close) _details = Label.new() layout.add_child(_details) var container := SubViewportContainer.new() container.stretch = true container.size_flags_vertical = Control.SIZE_EXPAND_FILL container.mouse_filter = Control.MOUSE_FILTER_STOP container.gui_input.connect(_drag) layout.add_child(container) _view = SubViewport.new() _view.own_world_3d = true _view.msaa_3d = Viewport.MSAA_4X _view.render_target_update_mode = SubViewport.UPDATE_DISABLED container.add_child(_view) _root = Node3D.new() _view.add_child(_root) var environment := WorldEnvironment.new() environment.environment = Environment.new() preload("res://scripts/presentation/lighting.gd").configure(environment.environment,true) _root.add_child(environment) for entry: Array in [[Vector3(-40,-30,0),Color("fff0dc"),1.35],[Vector3(-30,140,0),Color("bdcee8"),.42]]: var light := DirectionalLight3D.new() light.rotation_degrees = entry[0] light.light_color = entry[1] light.light_energy = entry[2] light.shadow_enabled = entry[2] > 1.0 light.light_angular_distance = 1.5 _root.add_child(light) _turntable = Node3D.new() _root.add_child(_turntable) var pedestal := MeshInstance3D.new() var disc := CylinderMesh.new() disc.top_radius = 1.12 disc.bottom_radius = 1.16 disc.height = .075 disc.radial_segments = 64 var stone := StandardMaterial3D.new() stone.albedo_color = Color("1a2430") stone.roughness = .55 stone.metallic = .25 disc.material = stone pedestal.mesh = disc pedestal.position.y = -.04 _root.add_child(pedestal) var rim := MeshInstance3D.new() var rim_mesh := TorusMesh.new() rim_mesh.inner_radius = 1.05 rim_mesh.outer_radius = 1.10 rim_mesh.rings = 48 rim_mesh.ring_segments = 4 var glow := StandardMaterial3D.new() glow.albedo_color = Color("7fe8c8") glow.emission_enabled = true glow.emission = Color("4ec9a8") glow.emission_energy_multiplier = 1.8 glow.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED rim.mesh = rim_mesh rim.material_override = glow rim.position.y = -.005 _root.add_child(rim) _camera = Camera3D.new() _camera.projection = Camera3D.PROJECTION_ORTHOGONAL _camera.size = 2.8 _camera.position = Vector3(3,2.0,4.8) _root.add_child(_camera) _camera.look_at(Vector3(0,.85,0),Vector3.UP) var row := HBoxContainer.new() row.alignment = BoxContainer.ALIGNMENT_CENTER layout.add_child(row) for title: String in ["Automatisch drehen","Gehen","Angreifen"]: var button := CheckButton.new() button.text = title row.add_child(button) if title == "Automatisch drehen": _rotate_button = button button.button_pressed = true button.toggled.connect(func(value: bool): _rotate = value) elif title == "Gehen": _legacy_buttons.append(button) button.toggled.connect(func(value: bool): _walking = value) else: _legacy_buttons.append(button) button.toggled.connect(func(value: bool): _attacking = value) _clip_row = HBoxContainer.new() _clip_row.alignment = BoxContainer.ALIGNMENT_CENTER layout.add_child(_clip_row) _clip_selector = OptionButton.new() for clip: String in CLIP_LABELS: _clip_selector.add_item(CLIP_LABELS[clip]) _clip_selector.set_item_metadata(_clip_selector.item_count-1,clip) _clip_selector.item_selected.connect(func(_index: int): _clip_time = 0.0) _clip_row.add_child(_clip_selector) var pause := CheckButton.new() pause.text = "Animation pausieren" pause.toggled.connect(func(value: bool): _clip_paused = value) _clip_row.add_child(pause) var restart := Button.new() restart.text = "Von vorn" restart.pressed.connect(func(): _clip_time = 0.0) _clip_row.add_child(restart) _clip_slider = HSlider.new() _clip_slider.custom_minimum_size.x = 180 _clip_slider.min_value = 0 _clip_slider.max_value = 1 _clip_slider.step = .001 _clip_slider.value_changed.connect(func(value: float): if is_instance_valid(_figure) and _figure.has_method("clip_length"): _clip_time = value*_figure.clip_length(str(_clip_selector.get_selected_metadata()))) _clip_row.add_child(_clip_slider) _clip_readout = Label.new() _clip_readout.custom_minimum_size.x = 120 _clip_row.add_child(_clip_readout) var hint := Label.new() hint.text = "Ziehen: drehen · Mausrad: vergrößern · Die laufende Partie ist pausiert." hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER layout.add_child(hint) hide() func show_model(id: String) -> void: if visible or not (DataCatalog.UNITS.has(id) or id == "worker"): return _previous_pause = GameState.paused _previous_focus = get_viewport().gui_get_focus_owner() GameState.paused = true Soundscape.stop_combat() _select_model(id) _view.render_target_update_mode = SubViewport.UPDATE_ALWAYS show() _close_button.grab_focus() func _select_model(id: String) -> void: model_id = id for index: int in range(_selector.item_count): if _selector.get_item_metadata(index) == id: _selector.select(index) if is_instance_valid(_figure): _figure.free() _figure = AssetLibrary.instantiate_fighter(id) _turntable.add_child(_figure) var bounds: AABB = _figure.get_aabb() var factor: float = 1.8/maxf(bounds.size.y,bounds.size.x*.80) _figure.scale = Vector3.ONE*factor _figure.position = -Vector3(bounds.get_center().x,bounds.position.y,bounds.get_center().z)*factor _turntable.rotation.y = 0 _camera.size = 2.8 _time = 0 _caption.text = "Funkenleser" if id == "worker" else str(DataCatalog.UNITS[id].display_name) var record: Dictionary = AssetLibrary._models[id] _details.text = "%s · %d Dreiecke · Tatsächliches Spielmodell" % [id.to_upper(),int(record.triangles)] var skeletal: bool = record.get("skeletal",false) _clip_row.visible = skeletal for button: Control in _legacy_buttons: button.visible = not skeletal _clip_time = 0.0 _clip_selector.clear() var labels: Dictionary = WORKER_CLIP_LABELS if id == "worker" else (RINGBORKE_CLIP_LABELS if id == "w04" else (BASALT_CLIP_LABELS if id == "g04" else (QUELL_CLIP_LABELS if id == "w10" else (FUNKEN_CLIP_LABELS if id == "g05" else (GLASS_CLIP_LABELS if id == "g06" else CLIP_LABELS))))) for clip: String in labels: _clip_selector.add_item(labels[clip]) _clip_selector.set_item_metadata(_clip_selector.item_count-1,clip) _clip_selector.select(0) if skeletal: var clip_count: int = int(record.get("clips", labels.keys()).size()) if record.has("clips") else labels.size() _details.text += " · %d Knochen · %d Bewegungen" % [int(record.bones), clip_count] func hide_model() -> void: if not visible: return hide() _view.render_target_update_mode = SubViewport.UPDATE_DISABLED GameState.paused = _previous_pause if is_instance_valid(_previous_focus): _previous_focus.grab_focus() closed.emit() func _process(delta: float) -> void: if not visible: return _time += delta if _rotate: _turntable.rotation.y += delta*.28 if _figure.has_method("preview"): var clip: String = str(_clip_selector.get_selected_metadata()) var duration: float = _figure.clip_length(clip) if not _clip_paused: _clip_time += delta if _clip_time > duration + .55: _clip_time = 0.0 _figure.preview(clip,minf(_clip_time,duration)) _clip_slider.set_value_no_signal(minf(_clip_time/duration,1.0)) _clip_readout.text = "%.2f / %.2f s" % [minf(_clip_time,duration),duration] elif _figure.has_method("pose"): _figure.pose(_time,_walking,maxf(0,.28-fmod(_time,1.25)) if _attacking else 0.0,0,0) func _drag(event: InputEvent) -> void: if event is InputEventMouseMotion and event.button_mask & MOUSE_BUTTON_MASK_LEFT: _turntable.rotation.y += event.relative.x*.012 if event is InputEventMouseButton and event.pressed: if event.button_index == MOUSE_BUTTON_WHEEL_UP: _camera.size = maxf(1.8,_camera.size-.15) elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: _camera.size = minf(4.2,_camera.size+.15)