class_name LoadoutOverview extends Control const CYAN := Color("86bfd0") var editor: ClassEditor var layout := Control.new() var classes := VBoxContainer.new() var primary: Button var pistol: Button var bench: WeaponShowcase var thumbnails: Array[WeaponShowcase] = [] var weapon_names: Array[Label] = [] var attachment_labels: Array[Label] = [] var feedback: Label var sidearm: Node3D func text(value: String, at: Vector2, font_size: int, color: Color = Color("a7a8a4")) -> Label: var label := Label.new() label.text = value label.position = at label.add_theme_font_size_override("font_size", font_size) label.add_theme_color_override("font_color", color) layout.add_child(label) return label func tile(value: String, at: Vector2, dimensions: Vector2, callback: Callable) -> Button: var button := Button.new() button.text = value button.position = at button.size = dimensions button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.add_theme_font_size_override("font_size", 19) button.add_theme_color_override("font_color", Color("969b9b")) button.add_theme_color_override("font_hover_color", CYAN) button.add_theme_color_override("font_focus_color", CYAN) for state: String in ["normal", "hover", "pressed", "focus"]: var style := StyleBoxFlat.new() style.bg_color = Color(0.065, 0.066, 0.068, 0.92) if state == "normal" else Color(0.09, 0.13, 0.145, 0.97) style.border_width_top = 0 if state == "normal" else 1 style.border_color = CYAN style.content_margin_left = 18 button.add_theme_stylebox_override(state, style) button.pressed.connect(callback) layout.add_child(button) return button func _ready() -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(layout) layout.mouse_filter = Control.MOUSE_FILTER_IGNORE resized.connect(func(): layout.scale = size / Vector2(1600, 900)) layout.scale = size / Vector2(1600, 900) bench = WeaponShowcase.new() layout.add_child(bench) bench.position = Vector2(680, 85) bench.size = Vector2(920, 750) bench.stretch = true bench.mouse_filter = Control.MOUSE_FILTER_IGNORE var environment := bench.stage.get_child(0) as WorldEnvironment environment.environment.background_color = Color("050707") environment.environment.ambient_light_energy = 0.18 for child in bench.stage.get_children(): if child is OmniLight3D: child.light_energy = 1.1 child.shadow_enabled = true make_bench() text("EDIT LOADOUTS", Vector2(77, 33), 38, Color("6e959f")) text(editor.ui.game.nickname + " / LOCAL OPERATOR", Vector2(1150, 47), 16) var scroll := ScrollContainer.new() scroll.position = Vector2(80, 142) scroll.size = Vector2(308, 280) layout.add_child(scroll) classes.size_flags_horizontal = Control.SIZE_EXPAND_FILL classes.add_theme_constant_override("separation", 7) scroll.add_child(classes) primary = tile("", Vector2(445, 142), Vector2(337, 158), func(): open_weapon(false)) pistol = tile("", Vector2(445, 310), Vector2(337, 158), func(): open_weapon(true)) for index in 2: var card: Button = primary if index == 0 else pistol var heading := text("Primary Weapon" if index == 0 else "Secondary Weapon", Vector2.ZERO, 18) heading.reparent(card, false) heading.position = Vector2(18, 8) heading.mouse_filter = Control.MOUSE_FILTER_IGNORE var weapon_name := text("", Vector2.ZERO, 13) weapon_name.reparent(card, false) weapon_name.position = Vector2(18, 33) weapon_names.append(weapon_name) var dots := text("", Vector2.ZERO, 16, Color("9f7639")) dots.reparent(card, false) dots.position = Vector2(235, 10) attachment_labels.append(dots) var thumbnail := WeaponShowcase.new() card.add_child(thumbnail) thumbnail.position = Vector2(62, 52) thumbnail.size = Vector2(220, 77) thumbnail.stretch = true thumbnail.mouse_filter = Control.MOUSE_FILTER_IGNORE var env := thumbnail.stage.get_child(0) as WorldEnvironment env.environment.background_color = Color("101112") thumbnails.append(thumbnail) var caption := text("ANPASSEN / WAFFENSCHMIED", Vector2.ZERO, 11, Color("626969")) caption.reparent(card, false) caption.position = Vector2(18, 139) tile("Ausrüstung\n\n◈ ENERGIESCHILD ✦ CLAYMORE", Vector2(445, 480), Vector2(337, 100), func(): editor.notice.text = "Ausrüstung ist für alle Klassen festgelegt.") tile("Lethal\n\nFrag-Granate × 2", Vector2(445, 590), Vector2(337, 73), func(): editor.notice.text = "Zwei Frag-Granaten pro Leben.") tile("Tactical\n\nFlashbang × 1", Vector2(445, 675), Vector2(337, 73), func(): editor.notice.text = "Eine Flashbang pro Leben.") tile("KLASSENOPTIONEN / VORLAGEN", Vector2(80, 690), Vector2(308, 40), func(): editor.show_workshop()) tile("SPEICHERN & AUSRÜSTEN", Vector2(80, 741), Vector2(308, 40), editor.save_class) tile("← BACK", Vector2(80, 843), Vector2(160, 36), editor.ui.main_menu) text("AUSWÄHLEN / WAFFE ANPASSEN", Vector2(270, 853), 14) feedback = text("Änderungen gelten ab dem nächsten Spawn.", Vector2(820, 853), 13) feedback.size = Vector2(700, 35) feedback.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS refresh() func box(at: Vector3, dimensions: Vector3, color: Color) -> MeshInstance3D: var node := MeshInstance3D.new() var mesh := BoxMesh.new() mesh.size = dimensions node.mesh = mesh var material := StandardMaterial3D.new() material.albedo_color = color material.roughness = 0.84 node.material_override = material node.position = at bench.stage.add_child(node) return node func make_bench() -> void: var tabletop := box(Vector3(0, -0.19, -0.1), Vector3(1.3, 0.09, 1.7), Color("242a2b")) var surface := tabletop.material_override as StandardMaterial3D surface.albedo_texture = load("res://assets/materials/concrete_color.jpg") surface.normal_enabled = true surface.normal_texture = load("res://assets/materials/concrete_normal.jpg") surface.uv1_scale = Vector3(2, 2, 2) var pad := box(Vector3(0, -0.13, -0.15), Vector3(0.54, 0.06, 0.91), Color("121616")) pad.rotation.y = -0.2 for index in 10: var seam := box(Vector3(-0.23 + index * 0.05, -0.097, -0.15), Vector3(0.003, 0.005, 0.84), Color("252a28")) seam.rotation.y = -0.2 sidearm = WeaponModels.build(4) bench.stage.add_child(sidearm) sidearm.position = Vector3(0.39, -0.06, 0.43) sidearm.rotation = Vector3(0, -0.65, PI / 2) for index in 18: var shell := box(Vector3(sin(index * 4.7) * 0.5, -0.125, cos(index * 2.8) * 0.7), Vector3(0.009, 0.009, 0.035), Color("9e8c55")) shell.rotation.y = index * 1.7 for index in 3: var bottle := MeshInstance3D.new() var cylinder := CylinderMesh.new() cylinder.top_radius = 0.033 cylinder.bottom_radius = 0.038 cylinder.height = 0.19 bottle.mesh = cylinder bottle.position = Vector3(0.44, -0.025, -0.6 + index * 0.17) var mat := StandardMaterial3D.new() mat.albedo_color = Color("303d30") mat.metallic = 0.45 mat.roughness = 0.45 bottle.material_override = mat bench.stage.add_child(bottle) box(bottle.position + Vector3(0, 0.1, 0), Vector3(0.025, 0.02, 0.025), Color("8e918c")) box(Vector3(-0.4, -0.075, 0.3), Vector3(0.2, 0.15, 0.24), Color("625741")) box(Vector3(-0.4, 0.003, 0.3), Vector3(0.08, 0.005, 0.035), Color("bfbeb2")) box(Vector3(-0.4, 0.004, 0.3), Vector3(0.035, 0.005, 0.08), Color("bfbeb2")) func refresh() -> void: if not is_instance_valid(primary): return for child in classes.get_children(): classes.remove_child(child) child.queue_free() for index in ClassLoadouts.entries.size(): var button := tile(str(ClassLoadouts.entries[index].name) + (" ★" if index == ClassLoadouts.selected else ""), Vector2.ZERO, Vector2(308, 50), func(): ClassLoadouts.entries[editor.slot] = ClassLoadouts.clean(editor.draft) editor.slot = index editor.draft = ClassLoadouts.entries[index].duplicate(true) editor.refresh()) button.reparent(classes) button.custom_minimum_size = Vector2(300, 50) button.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS button.add_theme_color_override("font_color", CYAN if index == editor.slot else Color("55747e")) if index == editor.slot: button.grab_focus() var id := int(editor.draft.primary) for index in 2: var weapon_id := id if index == 0 else 4 weapon_names[index].text = Arsenal.DATA[weapon_id].name var design: Dictionary = editor.draft.skins.get(str(weapon_id), {}) var attachments: Dictionary = design.get("attachments", {}) var count := 0 for value: Variant in attachments.values(): if int(value) != 0: count += 1 attachment_labels[index].text = "●".repeat(count) + "○".repeat(maxi(0, 5 - count)) thumbnails[index].select_weapon(weapon_id) WeaponSkins.apply(thumbnails[index].model, design) thumbnails[index].camera.position = Vector3(1.4, 0, -0.15) thumbnails[index].camera.look_at(Vector3(0, -0.025, -0.15)) thumbnails[index].camera.size = 0.25 if weapon_id == 4 else 0.42 WeaponSkins.apply(sidearm, editor.draft.skins.get("4", {})) bench.select_weapon(id) WeaponSkins.apply(bench.model, editor.draft.skins.get(str(id), {})) bench.model.rotation = Vector3(0, -0.2, PI / 2) bench.model.position = Vector3(0, -0.025, 0.1) bench.camera.position = Vector3(1.15, 1.7, 1.1) bench.camera.look_at(Vector3(0, -0.1, -0.1)) bench.camera.size = 1.75 func open_weapon(is_secondary: bool) -> void: editor.secondary = is_secondary editor.refresh() editor.show_workshop() func _process(_dt: float) -> void: if is_instance_valid(feedback): feedback.text = editor.notice.text