Files

109 lines
4.6 KiB
GDScript
Raw Permalink Normal View History

2026-09-12 11:01:37 +02:00
class_name WeaponSkins
extends RefCounted
const PARTS := ["receiver", "handguard", "stock", "barrel", "grip", "optic", "magazine", "bolt"]
const LABELS := ["Gehäuse", "Handschutz", "Schaft", "Lauf", "Griff", "Visierung", "Magazin", "Verschluss"]
static var designs: Dictionary = {}
static var revision := 0
static func clean(value: Variant) -> Dictionary:
if not value is Dictionary: return {}
var result := {}
result.soviet_variant = 1 if value.get("soviet_variant",0) == 1 else 0
for key in PARTS + ["text_color"]:
if value.get(key) is Color:
var c: Color = value[key]
if is_finite(c.r) and is_finite(c.g) and is_finite(c.b): result[key] = Color(clampf(c.r, 0, 1), clampf(c.g, 0, 1), clampf(c.b, 0, 1), 1)
var words := str(value.get("text", "")).replace("\n", " ").replace("\r", " ").replace("\t", " ").left(32)
result.text = words
result.attachments = WeaponAttachments.clean(value.get("attachments", {}))
result.stickers = WeaponStickers.clean(value.get("stickers", []))
return result
static func set_design(weapon: int, design: Dictionary) -> void:
if weapon < 0 or weapon >= Arsenal.DATA.size(): return
designs[str(weapon)] = clean(design)
revision += 1
static func get_design(weapon: int) -> Dictionary:
return clean(designs.get(str(weapon), {}))
static func split_frame(frame: Node3D) -> void:
# Preserve semantic parts before batching so customization never recolors an entire weapon.
var nodes := {}
for part in PARTS.slice(0, 6):
var group := Node3D.new()
group.name = part
frame.add_child(group)
nodes[part] = group
for child in frame.get_children():
if not child is MeshInstance3D: continue
var at: Vector3 = child.transform * child.mesh.get_aabb().get_center()
var part := "receiver"
if at.z > 0.10: part = "stock"
elif at.z < -0.53: part = "barrel"
elif at.z < -0.23: part = "handguard"
elif at.y > 0.065: part = "optic"
elif at.y < -0.055: part = "grip"
frame.remove_child(child)
nodes[part].add_child(child)
for part in nodes:
WeaponGeometry.batch(nodes[part])
nodes[part].set_meta("skin_part", part)
static func apply(model: Node3D, design: Dictionary) -> void:
design = clean(design)
if model.has_meta("soviet_model"): SovietWeapons.variant(model,design)
WeaponAttachments.apply(model, design)
WeaponStickers.apply(model, design.get("stickers", []))
for part in PARTS:
var group := model.get_node_or_null("Frame/" + part)
if part == "magazine": group = model.get_node_or_null("Magazine")
if part == "bolt": group = model.get_node_or_null("Bolt")
if group == null: continue
var meshes := group.get_children()
if part == "optic" and model.has_node("Frame/FactoryOptic"):
meshes.append_array(model.get_node("Frame/FactoryOptic").get_children())
for child in meshes:
if not child is MeshInstance3D: continue
if not child.has_meta("factory_material"): child.set_meta("factory_material", child.material_override)
var factory: StandardMaterial3D = child.get_meta("factory_material")
child.material_override = factory
if factory and design.has(part) and not factory.emission_enabled:
var finish := factory.duplicate() as StandardMaterial3D
finish.albedo_color = design[part]
child.material_override = finish
var lettering := model.get_node_or_null("CustomLettering")
var pistol := int(model.get_meta("weapon_index", 0)) == 4
if lettering == null: lettering = model.get_node_or_null("Bolt/CustomLettering")
if lettering == null:
lettering = Node3D.new()
lettering.name = "CustomLettering"
if pistol: model.get_node("Bolt").add_child(lettering)
else: model.add_child(lettering)
for side in [-1, 1]:
var label := Label3D.new()
label.name = "Text" + str(side)
var surface_x := 0.0193 if pistol else (0.0466 if int(model.get_meta("weapon_index", 0)) == 7 else 0.036)
label.position = Vector3(side * surface_x, 0.045 if pistol else 0.018, -0.07)
label.rotation.y = side * PI / 2
label.pixel_size = 0.00035
label.font_size = 48
label.outline_size = 0
label.no_depth_test = false
lettering.add_child(label)
for label: Label3D in lettering.get_children():
label.text = design.get("text", "")
label.pixel_size = minf(0.00018 if pistol else 0.00035, (0.09 if pistol else 0.15) / maxf(1, label.text.length() * 28))
label.modulate = design.get("text_color", Color.WHITE)
static func loadout_designs(primary: int) -> Dictionary:
return clean_loadout(designs, primary)
static func clean_loadout(value: Variant, primary: int) -> Dictionary:
var result := {}
if value is Dictionary:
for index in [Arsenal.primary_id(primary), 4]:
result[str(index)] = clean(value.get(str(index), {}))
return result