Files

233 lines
13 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 ClassEditor
extends PanelContainer
var ui: GameUI
var slot := 0
var weapon := 0
var secondary := false
var draft: Dictionary
var showcase: WeaponShowcase
var options_panel: VBoxContainer
var list_panel: VBoxContainer
var title_label: Label
var stats_label: Label
var notice: Label
var class_name_edit: LineEdit
var tabs: OptionButton
var selected_sticker := 0
var overview: LoadoutOverview
var workshop: Control
func _ready() -> void:
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
var style := StyleBoxFlat.new()
style.bg_color = Color("050707")
style.content_margin_left = 28; style.content_margin_right = 28
style.content_margin_top = 20; style.content_margin_bottom = 18
add_theme_stylebox_override("panel", style)
var theme_copy := ui.root.theme.duplicate() as Theme
theme_copy.default_font_size = 17
for state in ["normal", "hover", "pressed", "focus"]:
var b := StyleBoxFlat.new()
b.bg_color = Color("111518") if state == "normal" else Color("233740")
b.border_width_bottom = 2; b.border_color = Color("86bfd0") if state != "normal" else Color("3a474c")
b.content_margin_left = 12; b.content_margin_right = 12
b.content_margin_top = 9; b.content_margin_bottom = 9
theme_copy.set_stylebox(state, "Button", b)
theme_copy.set_stylebox(state, "OptionButton", b)
theme = theme_copy
var column := VBoxContainer.new(); workshop = column; column.add_theme_constant_override("separation", 14); add_child(column)
var heading := Label.new(); heading.text = "ARSENAL / KLASSENEDITOR"; heading.add_theme_font_size_override("font_size", 32); heading.modulate = Color("86bfd0"); column.add_child(heading)
var row := HBoxContainer.new(); row.size_flags_vertical = Control.SIZE_EXPAND_FILL; row.add_theme_constant_override("separation", 22); column.add_child(row)
var left := VBoxContainer.new(); left.custom_minimum_size.x = 225; row.add_child(left)
add_label(left, "01 / EIGENE KLASSEN")
var scroll := ScrollContainer.new(); scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL; left.add_child(scroll)
list_panel = VBoxContainer.new(); list_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL; scroll.add_child(list_panel)
add_label(left, "VORLAGE IN DIESEN SLOT KOPIEREN")
var preset := OptionButton.new()
for name in ClassLoadouts.PRESETS: preset.add_item(name)
left.add_child(preset)
add_button(left, "VORLAGE ÜBERNEHMEN", func(): draft = ClassLoadouts.template(preset.selected).duplicate(true); refresh())
add_button(left, "KLASSE DUPLIZIEREN", duplicate_class)
var center := VBoxContainer.new(); center.size_flags_horizontal = Control.SIZE_EXPAND_FILL; row.add_child(center)
title_label = add_label(center, ""); title_label.add_theme_font_size_override("font_size", 26)
class_name_edit = LineEdit.new(); class_name_edit.max_length = 24; center.add_child(class_name_edit)
class_name_edit.text_changed.connect(func(value): draft.name = value)
var switch_row := HBoxContainer.new(); center.add_child(switch_row)
add_button(switch_row, "PRIMÄRWAFFE", func(): secondary = false; refresh())
add_button(switch_row, "P12 / SEKUNDÄR", func(): secondary = true; refresh())
showcase = WeaponShowcase.new(); center.add_child(showcase)
showcase.position = Vector2.ZERO
showcase.custom_minimum_size = Vector2(300, 240)
showcase.size_flags_vertical = Control.SIZE_EXPAND_FILL
showcase.stretch = true
add_label(center, "MAUS ZIEHEN: DREHEN • MAUSRAD: ZOOM")
stats_label = add_label(center, ""); stats_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
add_label(center, "AUSRÜSTUNG 2 × FRAG / 1 × FLASH / ENERGIESCHILD")
var right := VBoxContainer.new(); right.custom_minimum_size.x = 340; row.add_child(right)
tabs = OptionButton.new()
for name in ["02 / WAFFE & AUFSÄTZE", "03 / LACKIERUNG", "04 / BILDSTICKER"]: tabs.add_item(name)
right.add_child(tabs); tabs.item_selected.connect(func(_i): rebuild_options())
var option_scroll := ScrollContainer.new(); option_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL; right.add_child(option_scroll)
options_panel = VBoxContainer.new(); options_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL; options_panel.add_theme_constant_override("separation", 10); option_scroll.add_child(options_panel)
notice = add_label(column, "Änderungen gelten für den nächsten Spawn. Klassen bleiben lokal gespeichert.")
notice.modulate = Color("86bfd0")
var footer := HBoxContainer.new(); footer.add_theme_constant_override("separation", 16); column.add_child(footer)
add_button(footer, "SPEICHERN & AUSRÜSTEN", save_class)
add_button(footer, "← ZURÜCK", ui.main_menu)
var player := LineEdit.new(); player.text = ui.game.nickname; player.max_length = 20; player.custom_minimum_size.x = 190; player.placeholder_text = "Operatorname"; footer.add_child(player)
player.text_changed.connect(func(value): ui.game.nickname = value.strip_edges())
slot = ClassLoadouts.selected
draft = ClassLoadouts.entries[slot].duplicate(true)
refresh()
overview = LoadoutOverview.new()
overview.editor = self
add_child(overview)
workshop.hide()
add_button(footer, "← KLASSENÜBERSICHT", func(): workshop.hide(); overview.show(); overview.refresh())
func show_workshop() -> void:
overview.hide()
workshop.show()
func add_label(parent: Node, value: String) -> Label:
var label := Label.new(); label.text = value; label.add_theme_font_size_override("font_size", 16); parent.add_child(label); return label
func add_button(parent: Node, value: String, callback: Callable) -> Button:
var button := Button.new(); button.text = value; button.pressed.connect(callback); parent.add_child(button); return button
func clear_children(parent: Node) -> void:
for child in parent.get_children(): parent.remove_child(child); child.queue_free()
func refresh() -> void:
weapon = 4 if secondary else int(draft.primary)
class_name_edit.text = draft.name
title_label.text = Arsenal.DATA[weapon].name
clear_children(list_panel)
for i in ClassLoadouts.entries.size():
var label := "%02d %s" % [i + 1, ClassLoadouts.entries[i].name]
if i == ClassLoadouts.selected: label = "● " + label
var button := add_button(list_panel, label, func():
ClassLoadouts.entries[slot] = ClassLoadouts.clean(draft)
slot = i; draft = ClassLoadouts.entries[i].duplicate(true); refresh())
button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
button.custom_minimum_size.x = 220
button.modulate = Color("86bfd0") if i == slot else Color.WHITE
showcase.select_weapon(weapon)
showcase.camera.size = 0.48 if weapon == 4 else (0.95 if weapon in [3, 5, 6] else 0.75)
update_model()
rebuild_options()
if is_instance_valid(overview): overview.refresh()
func design() -> Dictionary:
var key := str(weapon)
if not draft.skins.has(key): draft.skins[key] = WeaponSkins.clean({})
return draft.skins[key]
func update_model() -> void:
WeaponSkins.apply(showcase.model, design())
var w := WeaponAttachments.stats(weapon, design())
stats_label.text = "SCHADEN %s • MAGAZIN %d • REICHWEITE %.0f m\nNACHLADEN %.2f s • ZIELZEIT %.2f s • TEMPO %.0f %%" % [str(w.damage), w.mag, w.range, w.reload, w.ads, w.speed * 100]
func rebuild_options() -> void:
clear_children(options_panel)
var d := design()
if tabs.selected == 0:
add_label(options_panel, "WAFFENAUSWAHL")
var select := OptionButton.new()
var ids: Array = [4] if secondary else Arsenal.PRIMARY_IDS
for id in ids: select.add_item(Arsenal.DATA[id].name)
select.selected = ids.find(weapon); options_panel.add_child(select)
select.item_selected.connect(func(i): draft.primary = ids[i]; refresh())
if weapon == 4:
add_label(options_panel,"SOWJETISCHES MODELL")
var variant := OptionButton.new()
variant.add_item("Makarov PM")
variant.add_item("Tokarev TT-33")
variant.selected = d.get("soviet_variant",0)
options_panel.add_child(variant)
variant.item_selected.connect(func(value): d.soviet_variant = value; update_model())
if not d.has("attachments"): d.attachments = {}
for i in WeaponAttachments.SLOTS.size():
var key: String = WeaponAttachments.SLOTS[i]
add_label(options_panel, WeaponAttachments.LABELS[i].to_upper())
var choice := OptionButton.new()
for name in WeaponAttachments.OPTIONS[key]: choice.add_item(name)
choice.selected = d.attachments.get(key, 0); options_panel.add_child(choice)
if weapon == 4 and key == "stock": choice.selected = 0; choice.disabled = true
var hint := add_label(options_panel, WeaponAttachments.HELP[key][choice.selected]); hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; hint.custom_minimum_size.x = 310; hint.modulate = Color("97b6af")
if weapon == 4 and key == "stock": hint.text = "P12 besitzt keinen austauschbaren Schaft."
choice.item_selected.connect(func(value): d.attachments[key] = value; hint.text = WeaponAttachments.HELP[key][value]; update_model())
elif tabs.selected == 1:
for i in WeaponSkins.PARTS.size():
var key: String = WeaponSkins.PARTS[i]
var row := HBoxContainer.new(); options_panel.add_child(row)
var label := add_label(row, WeaponSkins.LABELS[i]); label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var picker := ColorPickerButton.new(); picker.custom_minimum_size = Vector2(96, 28); picker.edit_alpha = false; picker.color = d.get(key, Color("586267")); row.add_child(picker)
picker.color_changed.connect(func(value): d[key] = value; update_model())
add_label(options_panel, "BESCHRIFTUNG")
var words := LineEdit.new(); words.max_length = 32; words.text = d.get("text", ""); options_panel.add_child(words)
words.text_changed.connect(func(value): d.text = value; update_model())
add_label(options_panel, "TEXTFARBE")
var text_color := ColorPickerButton.new()
text_color.custom_minimum_size.y = 28
text_color.edit_alpha = false
text_color.color = d.get("text_color", Color.WHITE)
options_panel.add_child(text_color)
text_color.color_changed.connect(func(value): d.text_color = value; update_model())
add_button(options_panel, "LACKIERUNG ZURÜCKSETZEN", func():
for key in WeaponSkins.PARTS + ["text", "text_color"]: d.erase(key)
update_model(); rebuild_options())
else:
add_label(options_panel, "3 STICKERPLÄTZE / BEIDE WAFFENSEITEN")
var positions := OptionButton.new()
for name in ["Gehäuse hinten", "Gehäuse Mitte", "Gehäuse vorne"]: positions.add_item(name)
positions.selected = selected_sticker; options_panel.add_child(positions)
positions.item_selected.connect(func(i): selected_sticker = i; rebuild_options())
d.stickers = WeaponStickers.clean(d.get("stickers", []))
var preview := TextureRect.new(); preview.custom_minimum_size = Vector2(256, 180); preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE; preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED; preview.texture = WeaponStickers.texture(d.stickers[selected_sticker]); options_panel.add_child(preview)
add_button(options_panel, "BILD IMPORTIEREN …", import_sticker)
add_button(options_panel, "STICKER ENTFERNEN", func(): d.stickers[selected_sticker] = ""; update_model(); rebuild_options())
var hint := add_label(options_panel, "PNG / JPG / WebP · maximal 8 MB\nTransparente PNGs empfohlen.\nImport wird auf 256 Pixel verkleinert."); hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
add_label(options_panel, "GESPEICHERTE STICKER")
var gallery := GridContainer.new(); gallery.columns = 4; options_panel.add_child(gallery)
var folder := DirAccess.open(WeaponStickers.directory)
if folder:
for file in folder.get_files():
var id := file.get_basename()
if not WeaponStickers.valid(id) or gallery.get_child_count() >= 32: continue
var tile := TextureButton.new()
tile.texture_normal = WeaponStickers.texture(id)
tile.ignore_texture_size = true
tile.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT_CENTERED
tile.custom_minimum_size = Vector2(68, 68)
tile.tooltip_text = "Sticker auf ausgewählten Platz setzen"
gallery.add_child(tile)
tile.pressed.connect(func(): d.stickers[selected_sticker] = id; update_model(); rebuild_options())
func import_sticker() -> void:
var dialog := FileDialog.new(); dialog.access = FileDialog.ACCESS_FILESYSTEM; dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
dialog.filters = PackedStringArray(["*.png,*.jpg,*.jpeg,*.webp ; Bilder"])
add_child(dialog); dialog.popup_centered_ratio(0.75)
dialog.file_selected.connect(func(path):
var id := WeaponStickers.import_file(path)
if id.is_empty(): notice.text = WeaponStickers.error
else: design().stickers[selected_sticker] = id; update_model(); rebuild_options(); notice.text = "Sticker importiert. Zum Behalten Klasse speichern."
dialog.queue_free())
dialog.canceled.connect(dialog.queue_free)
func save_class() -> void:
ClassLoadouts.entries[slot] = ClassLoadouts.clean(draft)
ClassLoadouts.selected = slot
if ClassLoadouts.save() != OK: notice.text = "Speichern fehlgeschlagen."; return
ClassLoadouts.activate(ui.game, slot)
notice.text = "Klasse gespeichert und ausgerüstet. Wird beim nächsten Spawn verwendet."
refresh()
func duplicate_class() -> void:
var next := (slot + 1) % 10
ClassLoadouts.entries[slot] = ClassLoadouts.clean(draft)
slot = next; draft = draft.duplicate(true); draft.name = str(draft.name).left(17) + " Kopie"
notice.text = "Kopie in Slot %d vorbereitet. Speichern bestätigt das Ersetzen." % (slot + 1)
refresh()