extends "res://src/ui/panels/editor_panel.gd" ## Loadout-Bereich im Map-Tab (Karte „Loadout pro Map"): pro Map wählbar, ob die ## Spieler ihr eigenes Lobby-Loadout mitbringen oder die Map ein festes Loadout ## (Primär-/Sekundärwaffe + Aufsätze) vorschreibt. Gespeichert als optionaler ## Block doc["loadout"] (Regeln: src/svmap/loadout_katalog.gd, V18). Der Server ## setzt ein festes Loadout durch, der Client zeigt es an. In beiden Modi lässt ## sich zusätzlich der Granaten-/Minen-Vorrat pro Leben festlegen (auch für Bots). ## ## Jede Änderung ist ein Undo-Schritt (History-Block "loadout"; fehlt der Block, ## ist das Spieler-Loadout gemeint). const KEINE := "" var mode_option: OptionButton var primary_option: OptionButton var secondary_option: OptionButton var fixed_box: VBoxContainer var attach_box: VBoxContainer var hint_label: Label var vorrat_check: CheckBox var vorrat_box: VBoxContainer var granaten_spins: Dictionary = {} # Granaten-ID -> SpinBox var minen_spin: SpinBox var _loading := false var _ids: Array[String] = [] static func spec() -> Dictionary: return {"titel": "Loadout", "gruppe": "map", "order": 25} func panel_title() -> String: return "Loadout" func build() -> void: add_child(_header_label("Loadout")) add_child(_muted_label("Legt fest, mit welchen Waffen die Spieler diese Karte spielen.")) mode_option = OptionButton.new() mode_option.tooltip_text = "Eigenes Loadout: Spieler nehmen ihre Auswahl aus der Lobby mit. Festes Loadout: alle bekommen genau diese Waffen, der Server setzt das durch." mode_option.add_item("Eigenes Loadout der Spieler (Lobby)") mode_option.add_item("Festes Map-Loadout") add_child(mode_option) fixed_box = VBoxContainer.new() fixed_box.add_theme_constant_override("separation", UIScale.px(8)) add_child(fixed_box) fixed_box.add_child(_field_label("Primärwaffe (Rücken)")) primary_option = OptionButton.new() fixed_box.add_child(primary_option) fixed_box.add_child(_field_label("Sekundärwaffe (Hüfte)")) secondary_option = OptionButton.new() fixed_box.add_child(secondary_option) _ids = LoadoutKatalog.waffen_ids() for id in _ids: primary_option.add_item(LoadoutKatalog.anzeigename(id)) secondary_option.add_item(LoadoutKatalog.anzeigename(id)) secondary_option.add_item("— keine —") attach_box = VBoxContainer.new() attach_box.add_theme_constant_override("separation", UIScale.px(6)) fixed_box.add_child(attach_box) hint_label = _muted_label("Messer bleiben unverändert.") fixed_box.add_child(hint_label) add_child(_header_label("Granaten und Minen")) vorrat_check = CheckBox.new() vorrat_check.text = "Anzahl für diese Karte festlegen" vorrat_check.tooltip_text = "Aus: es gilt der Standard des Servers. An: Spieler und Bots bekommen pro Leben genau diese Anzahl." vorrat_check.toggled.connect(func(an: bool) -> void: if not _loading: set_vorrat_aktiv(an)) add_child(vorrat_check) vorrat_box = VBoxContainer.new() vorrat_box.add_theme_constant_override("separation", UIScale.px(6)) add_child(vorrat_box) for gid in LoadoutKatalog.GRANATEN_PANEL: var spin := _anzahl_zeile(str(LoadoutKatalog.GRANATEN[gid]), LoadoutKatalog.MAX_GRANATEN_JE_ART) spin.value_changed.connect(func(v: float) -> void: if not _loading: set_granaten(gid, int(v))) granaten_spins[gid] = spin minen_spin = _anzahl_zeile("Minen", LoadoutKatalog.MAX_MINEN) minen_spin.value_changed.connect(func(v: float) -> void: if not _loading: set_minen(int(v))) mode_option.item_selected.connect(func(i: int) -> void: set_fixed(i == 1)) primary_option.item_selected.connect(func(i: int) -> void: set_weapons(_ids[i], _secondary_id())) secondary_option.item_selected.connect(func(_i: int) -> void: set_weapons(_primary_id(), _secondary_id())) refresh_values() func _anzahl_zeile(titel: String, max_n: int) -> SpinBox: var row := HBoxContainer.new() var lab := _field_label(titel) lab.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(lab) var spin := SpinBox.new() spin.min_value = 0 spin.max_value = max_n spin.step = 1 row.add_child(spin) vorrat_box.add_child(row) return spin # ------------------------------------------------------------- Lesen ------- func _primary_id() -> String: return _ids[primary_option.selected] if primary_option.selected >= 0 else "" func _secondary_id() -> String: var i := secondary_option.selected return _ids[i] if i >= 0 and i < _ids.size() else KEINE func current() -> Dictionary: return main.doc.get("loadout", {}) if main != null and not main.doc.is_empty() else {} func is_fixed() -> bool: return str(current().get("mode", LoadoutKatalog.MODE_PLAYER)) == LoadoutKatalog.MODE_FIXED # ------------------------------------------------------------- Ändern ------ ## Modus wechseln. Auf „fest" ohne vorherige Wahl: M40 + Pistole 1 als Start. func set_fixed(fixed: bool) -> void: if main.doc.is_empty(): return if not fixed: _commit(_nur_vorrat(current()), "Loadout: Spieler-Loadout") return var cur := current() if cur.get("mode", "") == LoadoutKatalog.MODE_FIXED: return var prim := "m40" if LoadoutKatalog.hat_waffe("m40") else (_ids[0] if not _ids.is_empty() else "") var sec := "pistol1" if LoadoutKatalog.hat_waffe("pistol1") else KEINE var neu := {"mode": LoadoutKatalog.MODE_FIXED, "primary": prim, "secondary": sec, "attachments": {}} var vorrat: Variant = _nur_vorrat(cur) if vorrat != null: neu.merge(vorrat as Dictionary, false) _commit(neu, "Loadout: festes Map-Loadout") ## Spieler-Loadout mit Vorrat bleibt als Block erhalten; ohne Vorrat kein Block. func _nur_vorrat(lo: Dictionary) -> Variant: if not lo.has("grenades") and not lo.has("mines"): return null var out := {"mode": LoadoutKatalog.MODE_PLAYER} for key in ["grenades", "mines"]: if lo.has(key): out[key] = lo[key] return out func hat_vorrat() -> bool: return current().has("grenades") or current().has("mines") ## Vorrat festlegen (Start = Server-Standard) oder wieder dem Server überlassen. func set_vorrat_aktiv(an: bool) -> void: if main.doc.is_empty() or an == hat_vorrat(): return var lo := current().duplicate(true) if an: if lo.is_empty(): lo["mode"] = LoadoutKatalog.MODE_PLAYER lo["grenades"] = {"he1": 1, "blend1": 1, "smoke1": 1} lo["mines"] = LoadoutKatalog.STANDARD_MINEN _commit(lo, "Loadout: Granaten und Minen festgelegt") return lo.erase("grenades") lo.erase("mines") var leer := str(lo.get("mode", LoadoutKatalog.MODE_PLAYER)) == LoadoutKatalog.MODE_PLAYER _commit(null if leer else lo, "Loadout: Granaten und Minen wie Server") func set_granaten(gid: String, anzahl: int) -> void: if main.doc.is_empty() or not hat_vorrat() or not LoadoutKatalog.GRANATEN.has(gid): return var lo := current().duplicate(true) var g: Dictionary = lo.get("grenades", {}) if anzahl > 0: g[gid] = clampi(anzahl, 0, LoadoutKatalog.MAX_GRANATEN_JE_ART) else: g.erase(gid) lo["grenades"] = g _commit(lo, "Loadout: %s" % str(LoadoutKatalog.GRANATEN[gid])) func set_minen(anzahl: int) -> void: if main.doc.is_empty() or not hat_vorrat(): return var lo := current().duplicate(true) lo["mines"] = clampi(anzahl, 0, LoadoutKatalog.MAX_MINEN) _commit(lo, "Loadout: Minen") ## Waffen setzen. Aufsätze von Waffen, die nicht mehr im Loadout sind, fallen weg. func set_weapons(primary: String, secondary: String) -> void: if main.doc.is_empty() or not is_fixed(): return if not LoadoutKatalog.hat_waffe(primary) or (secondary != KEINE and not LoadoutKatalog.hat_waffe(secondary)): return var lo := current().duplicate(true) lo["primary"] = primary lo["secondary"] = secondary var att: Dictionary = lo.get("attachments", {}) for w in att.keys(): if w != primary and w != secondary: att.erase(w) lo["attachments"] = att _commit(lo, "Loadout: %s / %s" % [LoadoutKatalog.anzeigename(primary), LoadoutKatalog.anzeigename(secondary) if secondary != KEINE else "keine"]) ## Ein Aufsatz-Slot einer Loadout-Waffe setzen (part = Teil-ID, "" = nichts). func set_part(weapon: String, slot: String, part: String) -> void: if main.doc.is_empty() or not is_fixed(): return var lo := current().duplicate(true) if weapon != str(lo.get("primary", "")) and weapon != str(lo.get("secondary", "")): return var att: Dictionary = lo.get("attachments", {}) var sel := LoadoutKatalog.auswahl(weapon, str(att.get(weapon, ""))) if not sel.has(slot): return sel[slot] = part var wire := LoadoutKatalog.encode(weapon, sel) if LoadoutKatalog.pruefe_aufsaetze(weapon, wire) != "": return if wire.is_empty(): att.erase(weapon) else: att[weapon] = wire lo["attachments"] = att _commit(lo, "Loadout: Aufsatz %s" % slot) func _commit(neu: Variant, text: String) -> void: var alt: Variant = main.doc.get("loadout", null) if alt != null: alt = (alt as Dictionary).duplicate(true) if neu == null: main.doc.erase("loadout") else: main.doc["loadout"] = (neu as Dictionary).duplicate(true) var danach: Variant = main.doc.get("loadout", null) if danach != null: danach = (danach as Dictionary).duplicate(true) if alt == danach: return main.history.commit(text, {"loadout": alt}, {"loadout": danach}) main.dirty = true main.call("_refresh_file_label") main.call("_refresh_history_buttons") refresh_values() main.call("_set_status", "%s." % text) # ------------------------------------------------------------ Anzeige ------ ## UI aus dem doc auffüllen (Laden, Undo/Redo) — löst keinen Schreibzugriff aus. func refresh_values() -> void: if mode_option == null or main == null: return _loading = true var lo := current() var fixed := is_fixed() mode_option.select(1 if fixed else 0) fixed_box.visible = fixed if fixed: var prim := str(lo.get("primary", "")) var sec := str(lo.get("secondary", "")) primary_option.select(maxi(0, _ids.find(prim))) secondary_option.select(_ids.find(sec) if sec != KEINE and _ids.has(sec) else _ids.size()) _rebuild_attachments(prim, sec, lo.get("attachments", {})) var vorrat := hat_vorrat() vorrat_check.button_pressed = vorrat vorrat_box.visible = vorrat for gid in granaten_spins: (granaten_spins[gid] as SpinBox).value = int((lo.get("grenades", {}) as Dictionary).get(gid, 0)) minen_spin.value = int(lo.get("mines", LoadoutKatalog.STANDARD_MINEN)) _loading = false func _rebuild_attachments(prim: String, sec: String, att: Dictionary) -> void: for c in attach_box.get_children(): attach_box.remove_child(c) c.queue_free() for w in [prim, sec]: if w == "" or LoadoutKatalog.slots(w).is_empty(): continue attach_box.add_child(_header_label("Aufsätze: %s" % LoadoutKatalog.anzeigename(w))) var sel := LoadoutKatalog.auswahl(w, str(att.get(w, ""))) var sl := LoadoutKatalog.slots(w) var slot_names: Array = sl.keys() slot_names.sort() for s in slot_names: var sd: Dictionary = sl[s] var row := HBoxContainer.new() var lab := _field_label(str(s).replace("_", " ")) lab.custom_minimum_size = UIScale.v2(90, 0) row.add_child(lab) var ob := OptionButton.new() ob.name = "Teil_%s_%s" % [w, s] ob.size_flags_horizontal = Control.SIZE_EXPAND_FILL var parts: Array = [] if bool(sd.get("optional", false)): parts.append("") parts.append_array(sd.get("options", [])) for p in parts: ob.add_item(LoadoutKatalog.teil_name(str(p))) ob.select(maxi(0, parts.find(sel.get(s, "")))) ob.item_selected.connect(func(i: int) -> void: if not _loading: set_part(w, str(s), str(parts[i]))) row.add_child(ob) attach_box.add_child(row)