saumwacht-godot/scripts/presentation/asset_library.gd
Martin Stickelbroeck faf0fbdef0 M3: Balance, Kartenprops, sieben Volksrepräsentanten und Abnahme
Bots planen Items/Champions vor der Armee und helfen deterministisch,
damit Spiegelmatches fair bleiben. Boss und Profile neu abgestimmt;
Balance-, Stress- und Volks-Werkzeuge sowie Kartenprops ergänzt.
2026-09-28 00:41:05 +02:00

273 lines
12 KiB
GDScript

extends Node
## Resolve IDs once; keep source GLBs editable while batching static parts at runtime.
var _models: Dictionary = {}
var _icons: Dictionary = {}
var _meshes: Dictionary = {}
var _textures: Dictionary = {}
var _rigs: Dictionary = {}
var _surface_materials: Dictionary = {}
const LivingMaterial = preload("res://scripts/presentation/living_material.gdshader")
const ForgedMaterial = preload("res://scripts/presentation/forged_material.gdshader")
const PrismaticCore = preload("res://scripts/presentation/prismatic_core.gdshader")
const AnimatedFigure = preload("res://scripts/presentation/animated_figure.gd")
const SkeletalFigure = preload("res://scripts/presentation/skeletal_figure.gd")
const WorkerFigure = preload("res://scripts/presentation/worker_figure.gd")
const RingborkeMaterial = preload("res://scripts/presentation/ringborke.gdshader")
const ArbeiterMaterial = preload("res://scripts/presentation/arbeiter.gdshader")
var missing: Array[String] = []
func _ready() -> void:
for entry: Dictionary in _read_manifest("res://assets/phase4_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase11_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase12_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase15_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase16_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase18_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase19_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/luneraria/peoples_manifest.json"):
_models[str(entry.id)] = entry
for entry: Dictionary in _read_manifest("res://assets/phase3_manifest.json"):
_icons[str(entry.id)] = str(entry.resource_path)
_icons["worker"] = _icons.get("worker_funkenleser", "")
func _read_manifest(path: String) -> Array:
if not FileAccess.file_exists(path):
return []
var value: Variant = JSON.parse_string(FileAccess.get_file_as_string(path))
return value.get("assets", []) if value is Dictionary else []
func icon(id: String) -> Texture2D:
if not _textures.has(id):
var path: String = _icons.get(id, "")
_textures[id] = load(path) as Texture2D if not path.is_empty() and ResourceLoader.exists(path) else null
return _textures[id]
func model_height(id: String) -> float:
var record: Dictionary = _models.get(id, {})
var bounds: Dictionary = record.get("bounds_godot", {})
return float(bounds.get("max", [0, 1.6, 0])[1])
func instantiate_model(id: String) -> Node3D:
if not _models.has(id):
return null
if not _meshes.has(id):
var path: String = str(_models[id].resource_path)
if not ResourceLoader.exists(path):
if not missing.has(id):
missing.append(id)
push_warning("Missing visual asset: " + id)
return null
var packed: PackedScene = load(path) as PackedScene
if packed == null:
return null
var source: Node3D = packed.instantiate() as Node3D
var mesh: ArrayMesh = bake_mesh(source)
source.free()
_meshes[id] = mesh
var instance := MeshInstance3D.new()
instance.name = "Model_" + id
instance.mesh = _meshes[id]
instance.set_meta("asset_id", id)
return instance
func bake_mesh(source: Node3D) -> ArrayMesh:
var surfaces: Dictionary = {}
_collect(source, Transform3D.IDENTITY, surfaces)
var mesh := ArrayMesh.new()
for material: Material in surfaces:
var builder: SurfaceTool = surfaces[material]
builder.set_material(material)
builder.commit(mesh)
return mesh
func instantiate_fighter(id: String) -> Node3D:
if _models.get(id, {}).get("skeletal", false):
var scene := load(str(_models[id].resource_path)) as PackedScene
var source: Node3D = scene.instantiate()
decorate_model(source)
var info: Dictionary = _models[id].bounds_godot
var low := Vector3(info.min[0],info.min[1],info.min[2])
var high := Vector3(info.max[0],info.max[1],info.max[2])
var box := AABB(low,high-low)
if id == "worker":
var worker := WorkerFigure.new()
worker.setup(source, box)
return worker
var figure := SkeletalFigure.new()
figure.setup(source,box)
if _models[id].has("attack_cycle"):
figure.attack_cycle.assign(_models[id].attack_cycle)
return figure
if not (id.begins_with("g") or id.begins_with("e") or id.begins_with("w") or id.begins_with("core_team")) or not _models.has(id):
return instantiate_model(id)
if not _rigs.has(id):
var packed := load(str(_models[id].resource_path)) as PackedScene
if packed == null:
return instantiate_model(id)
var source := packed.instantiate() as Node3D
var groups: Dictionary = {}
_collect_rig(source, Transform3D.IDENTITY, id, groups)
var meshes: Dictionary = {}
var bounds := AABB()
var first: bool = true
for key: String in groups:
var mesh := ArrayMesh.new()
for material: Material in groups[key]:
var builder: SurfaceTool = groups[key][material]
builder.set_material(material)
builder.commit(mesh)
var box: AABB = mesh.get_aabb()
bounds = box if first else bounds.merge(box)
first = false
# Meshes are collected in model space. Move their vertices around joints.
var pivot: Vector3 = box.get_center()
if key.begins_with("Leg") or key.begins_with("Arm"):
pivot.y = box.end.y - box.size.y * .10
elif key == "Flame":
pivot.y = box.position.y
var centered := ArrayMesh.new()
for surface: int in range(mesh.get_surface_count()):
var builder := SurfaceTool.new()
builder.begin(Mesh.PRIMITIVE_TRIANGLES)
builder.append_from(mesh, surface, Transform3D(Basis.IDENTITY, -pivot))
builder.set_material(mesh.surface_get_material(surface))
builder.commit(centered)
meshes[key] = {"mesh": centered, "pivot": pivot}
source.free()
_rigs[id] = {"groups": meshes, "bounds": bounds}
var figure := AnimatedFigure.new()
figure.setup(id, _rigs[id].groups, _rigs[id].bounds)
return figure
func _collect_rig(node: Node3D, parent: Transform3D, id: String, groups: Dictionary) -> void:
var transform: Transform3D = parent * node.transform
if node is MeshInstance3D and node.mesh != null:
var part_name: String = str(node.name)
var key: String = "Body"
for prefix: String in ["LegL", "LegR", "ArmL", "ArmR", "Weapon"]:
if part_name.begins_with(prefix):
key = prefix
if part_name.begins_with("Leg"):
key = part_name.get_slice("_", 0)
if part_name.begins_with("WeaponL_"):
key = "ArmL"
elif part_name.begins_with("WeaponR_"):
key = "ArmR"
if part_name.begins_with("Core_Crystal"):
key = "Crystal"
if id.begins_with("w") and part_name.begins_with("Head_"):
key = "Weapon"
if id == "g05" and key.begins_with("Arm"):
key = "Weapon"
if id in ["g07", "g08"] and (part_name.begins_with("Body_Mortar") or part_name.begins_with("Body_Rim") or key == "Weapon"):
key = "Mortar"
if part_name.begins_with("Weapon_Flame"):
key = "Flame"
if not groups.has(key):
groups[key] = {}
for index: int in range(node.mesh.get_surface_count()):
var material: Material = presentation_material(node.get_active_material(index))
if not groups[key].has(material):
var builder := SurfaceTool.new()
builder.begin(Mesh.PRIMITIVE_TRIANGLES)
groups[key][material] = builder
(groups[key][material] as SurfaceTool).append_from(node.mesh, index, transform)
for child: Node in node.get_children():
if child is Node3D:
_collect_rig(child, transform, id, groups)
func _collect(node: Node3D, parent_transform: Transform3D, surfaces: Dictionary) -> void:
var transform: Transform3D = parent_transform * node.transform
if node is MeshInstance3D and node.mesh != null:
for index: int in range(node.mesh.get_surface_count()):
var material: Material = presentation_material(node.get_active_material(index))
if not surfaces.has(material):
var builder := SurfaceTool.new()
builder.begin(Mesh.PRIMITIVE_TRIANGLES)
surfaces[material] = builder
(surfaces[material] as SurfaceTool).append_from(node.mesh, index, transform)
for child: Node in node.get_children():
if child is Node3D:
_collect(child, transform, surfaces)
func presentation_material(source: Material) -> Material:
if source is StandardMaterial3D and (source.resource_name.begins_with("AB_") or source.resource_name.begins_with("BB_") or source.resource_name.begins_with("QG_") or source.resource_name.begins_with("FL_") or source.resource_name.begins_with("GB_") or source.resource_name.begins_with("LP_")):
var ab_key: String = source.resource_name
if not _surface_materials.has(ab_key):
if source.emission_enabled or ab_key in ["AB_Lamp", "AB_Spark", "AB_Glass", "AB_Lens", "BB_Magma", "BB_Ember", "QG_Spring", "FL_Ember", "FL_Crystal", "GB_Crystal", "GB_Ember"]:
_surface_materials[ab_key] = source.duplicate()
else:
var ab := ShaderMaterial.new()
ab.shader = ArbeiterMaterial
ab.set_shader_parameter("roughness_value", source.roughness)
ab.set_shader_parameter("metallic_value", source.metallic)
var surface: int = 5 if source.metallic > .5 else 0
if "Leather" in ab_key or "Boot" in ab_key or "Glove" in ab_key or ab_key.begins_with("LP_") and ab_key.ends_with("_Skin"):
surface = 1
elif "Cloth" in ab_key or "Hood" in ab_key or "LP_" in ab_key and ("Fur" in ab_key or "Hair" in ab_key):
surface = 2
elif "Bark" in ab_key or "Wood" in ab_key or "Vine" in ab_key:
surface = 3
elif "Leaf" in ab_key or "Moss" in ab_key or "Petal" in ab_key:
surface = 4
ab.set_shader_parameter("surface_kind", surface)
ab.set_shader_parameter("grain_amount", .12 if surface in [0, 3] else .065)
ab.set_shader_parameter("grain_scale", 72.0 if surface in [0, 3] else 140.0)
ab.set_shader_parameter("relief_depth", .0009 if surface == 0 else (.0006 if surface == 3 else .00015))
ab.resource_name = ab_key
_surface_materials[ab_key] = ab
return _surface_materials[ab_key]
if source is StandardMaterial3D and source.resource_name.begins_with("RB_") and source.resource_name != "RB_Cutwood" and not source.emission_enabled:
var key: String = source.resource_name
if not _surface_materials.has(key):
var material := ShaderMaterial.new()
material.shader = RingborkeMaterial
material.set_shader_parameter("wood_height",preload("res://assets/materials/phase10/oak-height-4k.png"))
material.set_shader_parameter("bark",key in ["RB_Bark","RB_Heartwood"])
material.set_shader_parameter("endgrain",key == "RB_Cutwood")
material.set_shader_parameter("leaf",key in ["RB_Leaf","RB_Moss","RB_Petal"])
_surface_materials[key] = material
return _surface_materials[key]
if source is StandardMaterial3D and source.resource_name.begins_with("SW_HD_Core_"):
var crystal_key: String = source.resource_name
if not _surface_materials.has(crystal_key):
var crystal := ShaderMaterial.new()
crystal.shader = PrismaticCore
crystal.set_shader_parameter("base_color", source.albedo_color)
_surface_materials[crystal_key] = crystal
return _surface_materials[crystal_key]
if not source is StandardMaterial3D or not source.resource_name.begins_with("SW_HD_") or source.emission_enabled:
return source
var key: String = source.resource_name
if not _surface_materials.has(key):
var material := ShaderMaterial.new()
material.shader = LivingMaterial if key.begins_with("SW_HD_W_") else ForgedMaterial
if key.begins_with("SW_HD_W_"):
material.set_shader_parameter("bark", "bark" in key or "wood" in key or "groove" in key)
material.set_shader_parameter("endgrain", "endgrain" in key)
material.set_shader_parameter("moss", "moss" in key)
material.set_shader_parameter("oak_color", load("res://assets/materials/phase10/oak-albedo-4k.png"))
material.set_shader_parameter("oak_height", load("res://assets/materials/phase10/oak-height-4k.png"))
material.set_shader_parameter("rock_map", preload("res://assets/materials/phase10/limestone-albedo.png"))
material.resource_name = key
material.set_shader_parameter("base_color", source.albedo_color)
material.set_shader_parameter("base_roughness", source.roughness)
material.set_shader_parameter("metalness", source.metallic)
material.set_shader_parameter("grain_amount", .17 if "basalt" in key or "edge" in key else .07)
_surface_materials[key] = material
return _surface_materials[key]
func decorate_model(root: Node) -> void:
if root is MeshInstance3D and root.mesh != null:
for index: int in range(root.mesh.get_surface_count()):
root.set_surface_override_material(index, presentation_material(root.get_active_material(index)))
for child: Node in root.get_children():
decorate_model(child)