Files

391 lines
15 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class_name GameUI
extends CanvasLayer
signal host_requested(settings: Dictionary)
signal join_requested(address: String)
signal leave_requested
signal preferences_changed
var game: Node3D
var root: Control
var panel: VBoxContainer
var status: Label
var page := ""
var fields := {}
var browser_rows: VBoxContainer
var refresh_clock := 0.0
const INK := Color("101e27")
const PAPER := Color("e6eee9")
const MINT := Color("a5dec8")
func _ready() -> void:
layer = 10
root = Control.new()
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
add_child(root)
var theme := Theme.new()
var font := SystemFont.new()
font.font_names = PackedStringArray(["Bahnschrift", "Segoe UI", "Arial"])
theme.default_font = font
theme.default_font_size = 20
for state in ["normal", "hover", "pressed", "focus"]:
var style := StyleBoxFlat.new()
style.bg_color = Color("233943") if state == "normal" else Color("3c625f")
style.border_width_bottom = 1
style.border_color = Color("52776f")
style.content_margin_left = 20
style.content_margin_right = 20
style.content_margin_top = 14
style.content_margin_bottom = 14
theme.set_stylebox(state, "Button", style)
theme.set_stylebox(state, "OptionButton", style)
theme.set_color("font_color", "Button", PAPER)
theme.set_color("font_color", "Label", PAPER)
root.theme = theme
main_menu()
func clear(title: String, subtitle: String) -> void:
root.show()
for child in root.get_children():
root.remove_child(child)
child.queue_free()
fields.clear()
browser_rows = null
var shade := ColorRect.new()
shade.color = Color(0.025, 0.07, 0.1, 0.985)
shade.position = Vector2.ZERO
shade.size = Vector2(630, 900)
root.add_child(shade)
var rail := ColorRect.new()
rail.color = MINT
rail.position = Vector2(630, 0)
rail.size = Vector2(3, 900)
root.add_child(rail)
panel = VBoxContainer.new()
panel.position = Vector2(60, 44)
panel.size = Vector2(510, 800)
panel.add_theme_constant_override("separation", 12)
root.add_child(panel)
label("B / L • LOCAL OPERATIONS", 16, MINT)
space(12)
label(title, 57)
label(subtitle, 17, Color("93aaa9"))
space(18)
status = Label.new()
status.position = Vector2(60, 825)
status.size = Vector2(515, 60)
status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
status.add_theme_font_size_override("font_size", 16)
status.add_theme_color_override("font_color", Color("f2c78e"))
root.add_child(status)
var caption_bg := ColorRect.new()
caption_bg.color = Color(0.025, 0.07, 0.1, 0.87)
caption_bg.position = Vector2(670, 732)
caption_bg.size = Vector2(760, 135)
root.add_child(caption_bg)
var caption := Label.new()
caption.position = Vector2(695, 744)
caption.text = "MAPS / QUARRY + DUST 2\nKARTE VOR DEM MATCH AUSWÄHLEN\n28 OPERATORS / LAN"
if title == "LOADOUT":
caption.text = "ARMORY / ORIGINAL WEAPON MODELS\nMAUS ZIEHEN: MODELL DREHEN\nMetall / Polymer / Optik / bewegliche Bauteile"
caption.add_theme_font_size_override("font_size", 24)
caption.add_theme_color_override("font_color", PAPER)
root.add_child(caption)
func label(text: String, size: int = 20, color: Color = PAPER) -> Label:
var l := Label.new()
l.text = text
l.add_theme_font_size_override("font_size", size)
l.add_theme_color_override("font_color", color)
panel.add_child(l)
return l
func space(height: float) -> void:
var c := Control.new()
c.custom_minimum_size.y = height
panel.add_child(c)
func button(text: String, action: Callable) -> Button:
var b := Button.new()
b.text = text
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
b.custom_minimum_size.y = 55
b.pressed.connect(action)
panel.add_child(b)
return b
func message(text: String) -> void:
if is_instance_valid(status):
status.text = text
func main_menu() -> void:
page = "main"
root.show()
for child in root.get_children():
root.remove_child(child)
child.queue_free()
fields.clear()
browser_rows = null
var front := FrontEnd.new()
front.ui = self
root.add_child(front)
func multiplayer_menu() -> void:
page = "multiplayer"
clear("MULTIPLAYER", "LAN / DEDICATED SERVER")
button("HOST GAME →", host_menu)
button("JOIN GAME →", browser_menu)
button("DIRECT CONNECT →", direct_menu)
space(15)
button("← BACK", main_menu)
func text_field(key: String, title: String, value: String) -> void:
label(title, 15, MINT)
var field := LineEdit.new()
field.text = value
field.custom_minimum_size.y = 42
field.max_length = 40
panel.add_child(field)
fields[key] = field
func option(key: String, title: String, items: Array, selected: int = 0) -> void:
label(title, 15, MINT)
var field := OptionButton.new()
for item in items:
field.add_item(str(item))
field.select(selected)
panel.add_child(field)
fields[key] = field
func play_menu() -> void:
page = "play"
clear("PLAY", "LOKALES MATCH / MIT BOTS")
option("map", "MAP AUSWÄHLEN", MapCatalog.NAMES, maxi(0, MapCatalog.IDS.find(game.config.get("map", "quarry"))))
option("difficulty", "BOT-SCHWIERIGKEIT", BotSkill.NAMES, int(game.config.get("difficulty", 0)))
button("START GAME →", func():
var settings: Dictionary = game.config.duplicate()
settings["map"] = MapCatalog.IDS[fields.map.selected]
settings["difficulty"] = fields.difficulty.selected
host_requested.emit(settings))
button("MATCH EINSTELLEN →", host_menu)
button("← BACK", main_menu)
func host_menu() -> void:
page = "host"
clear("HOST GAME", "MAP / MATCH / UDP %s" % game.port)
var scroll := ScrollContainer.new()
scroll.position = Vector2(60, 216)
scroll.size = Vector2(555, 590)
root.add_child(scroll)
var form := VBoxContainer.new()
form.custom_minimum_size.x = 510
form.add_theme_constant_override("separation", 4)
scroll.add_child(form)
panel = form
option("map", "MAP AUSWÄHLEN", MapCatalog.NAMES, maxi(0, MapCatalog.IDS.find(game.config.get("map", "quarry"))))
text_field("name", "SERVER NAME", game.config.server_name)
option("mode", "GAME MODE", ["Team Deathmatch", "Free For All", "Domination", "Kill Confirmed"], ["TDM", "FFA", "DOM", "KC"].find(game.config.mode))
option("slots", "MAXIMUM PLAYERS", [2, 4, 6, 8], 3)
option("bots", "BOTS · FREIE PLÄTZE AUFFÜLLEN", [0, 1, 3, 5, 7], 4)
option("difficulty", "BOT-SCHWIERIGKEIT", BotSkill.NAMES, int(game.config.get("difficulty", 0)))
option("score", "SCORE LIMIT", [25, 50, 75, 100], 1)
fields.mode.item_selected.connect(func(index): fields.score.select(0 if index == 1 else 1))
option("time", "TIME LIMIT", ["5 Minuten", "10 Minuten", "15 Minuten"], 1)
button("START GAME →", func():
host_requested.emit({"server_name": fields.name.text.left(30), "mode": ["TDM", "FFA", "DOM", "KC"][fields.mode.selected],
"max_players": [2, 4, 6, 8][fields.slots.selected], "bots": [0, 1, 3, 5, 7][fields.bots.selected],
"difficulty": fields.difficulty.selected, "map": MapCatalog.IDS[fields.map.selected],
"score_limit": [25, 50, 75, 100][fields.score.selected], "time_limit": [300, 600, 900][fields.time.selected]}))
button("← BACK", multiplayer_menu)
func browser_menu() -> void:
page = "browser"
clear("JOIN GAME", "AUTOMATISCHE LAN-SUCHE / MAP DES HOSTS")
browser_rows = VBoxContainer.new()
browser_rows.custom_minimum_size.y = 260
panel.add_child(browser_rows)
refresh_browser()
button("DIRECT CONNECT →", direct_menu)
button("← BACK", multiplayer_menu)
message("Server werden jede Sekunde aktualisiert. Discovery nutzt UDP 27841.")
func refresh_browser() -> void:
for child in browser_rows.get_children():
browser_rows.remove_child(child)
child.queue_free()
if game.discovery.servers.is_empty():
var empty := Label.new()
empty.text = "Suche nach lokalen Servern …\n\nNoch kein Host gefunden.\nDirect Connect ist ebenfalls verfügbar."
empty.add_theme_font_size_override("font_size", 18)
browser_rows.add_child(empty)
for address in game.discovery.servers:
var info: Dictionary = game.discovery.servers[address]
var b := Button.new()
var ping := "%s ms" % info.get("ping", -1) if info.get("ping", -1) >= 0 else "Ping …"
b.text = "%s · %s/%s · %s\n%s / %s JOIN →" % [str(info.get("name", "Server")).left(22), info.get("players", 0), info.get("max", 8), ping, str(info.get("mode", "TDM")) + " · " + MapCatalog.title(str(info.get("map", "quarry"))), address]
b.custom_minimum_size.y = 85
b.pressed.connect(func(): join_requested.emit(address))
browser_rows.add_child(b)
func direct_menu() -> void:
page = "direct"
clear("CONNECT", "DIREKT ZUM HOST / IPv4")
text_field("ip", "SERVER-ADRESSE · OPTIONAL :PORT", game.last_address)
button("CONNECT →", func(): join_requested.emit(fields.ip.text))
button("← BACK", multiplayer_menu)
space(15)
label("LAN- oder erreichbare Server-IP eingeben.\nDer Spielport muss per UDP erreichbar sein.\nFür Tests auf diesem PC: 127.0.0.1", 18, Color("93aaa9"))
func loadout_menu() -> void:
page = "loadout"
clear("KLASSENEDITOR", "DEINE WAFFE. DEIN AUFTRAG.")
for child in root.get_children():
root.remove_child(child)
child.queue_free()
var editor := ClassEditor.new()
editor.ui = self
root.add_child(editor)
func operator_menu() -> void:
page = "operator"
clear("OPERATOR", "STEVE / GAS MASK")
for child in root.get_children():
root.remove_child(child)
child.queue_free()
var gallery := OperatorGallery.new()
gallery.ui = self
root.add_child(gallery)
func settings_menu() -> void:
page = "settings"
clear("SETTINGS", "BEWEGUNG / SICHT / AUDIO")
button("STEUERUNG / TASTEN FREI BELEGEN →", controls_menu)
option("graphics", "GRAFIKQUALITÄT · %s VOLLBILD" % ControlBindings.hint("fullscreen"), GraphicsSettings.NAMES, game.graphics_quality)
fields.graphics.item_selected.connect(func(index):
GraphicsSettings.apply(game, index)
await ShaderPrewarm.run(game))
if not game.active or game.is_host:
option("difficulty", "BOT-SCHWIERIGKEIT · DIREKT AKTIV", BotSkill.NAMES, int(game.config.get("difficulty", 0)))
fields.difficulty.item_selected.connect(func(index): game.config["difficulty"] = index)
slider("MAUSEMPFINDLICHKEIT", 0.0005, 0.005, game.sensitivity, func(v): game.sensitivity = v)
slider("SICHTFELD", 70, 110, game.base_fov, func(v): game.base_fov = v)
slider("LAUTSTÄRKE", 0, 1, db_to_linear(AudioServer.get_bus_volume_db(0)), func(v): AudioServer.set_bus_volume_db(0, linear_to_db(maxf(0.0001, v))))
slider("MUSIK", 0, 1, MusicDirector.volume, func(v): MusicDirector.volume = v)
label("Musik: Luca Baradel · ASSAULT\nSowjetische Waffenmodelle: radint", 14, Color("93aaa9"))
space(14)
label("%s / %s Feuer / Zielen\n%s Nachladen %s Waffe wechseln\n%s Springen %s Ducken\n%s Interagieren %s Menü" % [ControlBindings.hint("fire"), ControlBindings.hint("aim"), ControlBindings.hint("reload"), ControlBindings.hint("switch"), ControlBindings.hint("jump"), ControlBindings.hint("crouch"), ControlBindings.hint("interact"), ControlBindings.hint("pause")], 18, Color("93aaa9"))
button("SAVE & BACK", func():
preferences_changed.emit()
if game.active: pause_menu()
else: main_menu())
func controls_menu() -> void:
page = "controls"
clear("STEUERUNG", "TASTATUR / MAUS")
for child in root.get_children():
root.remove_child(child)
child.queue_free()
var editor := ControlsEditor.new()
editor.ui = self
root.add_child(editor)
func slider(title: String, minimum: float, maximum: float, value: float, callback: Callable) -> void:
label(title, 15, MINT)
var control := HSlider.new()
control.min_value = minimum
control.max_value = maximum
control.step = (maximum - minimum) / 100
control.value = value
control.custom_minimum_size.y = 35
control.value_changed.connect(callback)
panel.add_child(control)
func pause_menu() -> void:
page = "pause"
clear("OPERATIONS", "MATCH LÄUFT WEITER")
button("RESUME →", func():
game.paused = false
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
hide_menu())
button("SETTINGS →", settings_menu)
button("LEAVE MATCH", func(): leave_requested.emit())
if game.is_host:
label("Host verlassen beendet das Match für alle.", 16, Color("93aaa9"))
var addresses := []
for address in IP.get_local_addresses():
if address.contains(".") and not address.begins_with("127."):
addresses.append(address)
label("HOST IP\n" + "\n".join(addresses), 18, MINT)
func hide_menu() -> void:
page = ""
root.hide()
func _process(dt: float) -> void:
refresh_clock += dt
if page == "browser" and refresh_clock > 1:
refresh_clock = 0
refresh_browser()
func account_placeholder() -> void:
var box := PanelContainer.new()
box.position = Vector2(1030, 38)
box.size = Vector2(490, 165)
root.add_child(box)
var content := VBoxContainer.new()
content.add_theme_constant_override("separation", 12)
box.add_child(content)
for words in ["ACCOUNT / BALD VERFÜGBAR", "Gastprofil · kein Konto verbunden", "GUTHABEN: — / SHOP: GESPERRT"]:
var line := Label.new()
line.text = words
line.add_theme_font_size_override("font_size", 20)
content.add_child(line)
var disabled := Button.new()
disabled.text = "ANMELDEN / REGISTRIEREN · PLATZHALTER"
disabled.disabled = true
content.add_child(disabled)
func skin_menu(weapon: int) -> void:
page = "skins"
clear("WEAPON LAB", "LOKALE DESIGNS / KEIN SHOP ERFORDERLICH")
var names := []
for entry: Dictionary in Arsenal.DATA: names.append(entry.name)
option("skin_weapon", "WAFFE", names, weapon)
fields.skin_weapon.item_selected.connect(skin_menu)
var showcase := WeaponShowcase.new()
root.add_child(showcase)
showcase.select_weapon(weapon)
var design := WeaponSkins.get_design(weapon)
var parts := WeaponSkins.LABELS.duplicate()
parts.append("Beschriftung")
option("skin_part", "BAUTEIL / FARBE", parts)
var picker := ColorPickerButton.new()
picker.custom_minimum_size.y = 42
picker.edit_alpha = false
picker.color = design.get("receiver", Color("8c8064"))
panel.add_child(picker)
fields.skin_part.item_selected.connect(func(index):
var key: String = WeaponSkins.PARTS[index] if index < 8 else "text_color"
picker.color = design.get(key, Color.WHITE))
picker.color_changed.connect(func(color):
var index: int = fields.skin_part.selected
var key: String = WeaponSkins.PARTS[index] if index < 8 else "text_color"
design[key] = color
WeaponSkins.apply(showcase.model, design))
text_field("skin_text", "EIGENER TEXT / BIS 32 ZEICHEN", design.get("text", ""))
fields.skin_text.max_length = 32
fields.skin_text.text_changed.connect(func(words):
design.text = words
WeaponSkins.apply(showcase.model, design))
label("Mit der Maus die Vorschau drehen.\nFarben ändern keine Waffenwerte.", 16, Color("93aaa9"))
button("DESIGN SPEICHERN", func():
WeaponSkins.set_design(weapon, design)
if PlayerSettings.save(game) == OK:
message("Design lokal gespeichert. Für diese Waffe aktiv.")
else:
message("Design aktiv, konnte aber nicht dauerhaft gespeichert werden."))
button("WERKSDESIGN WIEDERHERSTELLEN", func():
WeaponSkins.set_design(weapon, {})
preferences_changed.emit()
skin_menu(weapon))
button("← ZURÜCK / UNGESPEICHERTES VERWERFEN", loadout_menu)