extends RefCounted ## Shared material library of Champions of Luneraria (first set in G2; meant for G4/G5 too). ## GLBs from the G2 Blender generators name their surfaces LN_*; `dress` swaps them for one ## shared ShaderMaterial per name. Everything that differs per building or per team (team ## colours, energy, damage, occlusion fade, rune family, banner silhouette) is a per-instance ## shader parameter with a fixed slot (see the instance_index hints in the shaders), so all ## buildings share the same few materials. const StoneShader = preload("res://scripts/luneraria/view/materials/lune_stone.gdshader") const MetalShader = preload("res://scripts/luneraria/view/materials/lune_metal.gdshader") const CrystalShader = preload("res://scripts/luneraria/view/materials/lune_crystal.gdshader") const RuneShader = preload("res://scripts/luneraria/view/materials/lune_rune.gdshader") const ClothShader = preload("res://scripts/luneraria/view/materials/lune_cloth.gdshader") const WaterGlowShader = preload("res://scripts/luneraria/view/materials/lune_water_glow.gdshader") const HideShader = preload("res://scripts/luneraria/view/materials/lune_hide.gdshader") const Limestone = preload("res://assets/materials/phase10/limestone-albedo.png") const NEUTRAL: int = 2 ## Team identity beyond colour: cool team (Blau) and warm team (Rot) differ in crystal and ## rune hue, metal (silver-steel / bronze), rune family, banner silhouette and emblem. ## Index 2 is the neutral grotto. const TEAM_STYLE: Array[Dictionary] = [ {"crystal": Color("78c6ff"), "rune": Color("86d2ff"), "metal": Color(.80, .85, .92), "cloth": Color("2a5aa8"), "emblem": Color("e8eef8"), "light": Color("8cc8ff"), "pattern": 0.0, "shape": 0.0, "name": "kühl · Mondsichel · Silber"}, {"crystal": Color("ff7d45"), "rune": Color("ffae5a"), "metal": Color(.92, .64, .34), "cloth": Color("9a2c20"), "emblem": Color("f4d58c"), "light": Color("ffae72"), "pattern": 1.0, "shape": 1.0, "name": "warm · Sonnenscheibe · Bronze"}, {"crystal": Color("b784ff"), "rune": Color("c29cff"), "metal": Color(.70, .62, .50), "cloth": Color("45305f"), "emblem": Color("d9c8f0"), "light": Color("b37cff"), "pattern": 2.0, "shape": 0.0, "name": "neutral · Grotte"}, ] static var _cache: Dictionary = {} static func _shader_material(shader: Shader, params: Dictionary) -> ShaderMaterial: var material := ShaderMaterial.new() material.shader = shader for key: String in params: material.set_shader_parameter(key, params[key]) return material ## The shared material for an LN_* surface name, or null for foreign materials. static func get_material(key: String) -> Material: if _cache.has(key): return _cache[key] var material: Material = null match key: "LN_Stone_Pale": material = _shader_material(StoneShader, {"base_color": Color(.72, .68, .61), "stone_map": Limestone, "lichen": .22}) "LN_Stone_Dark": material = _shader_material(StoneShader, {"base_color": Color(.30, .30, .33), "stone_map": Limestone, "lichen": .12, "course_height": .34, "block_length": .66, "roughness_base": .9}) "LN_Stone_Grotto": material = _shader_material(StoneShader, {"base_color": Color(.43, .43, .46), "stone_map": Limestone, "lichen": .3, "course_height": .5, "block_length": .9, "tint_variation": .07}) "LN_Rock": material = _shader_material(StoneShader, {"base_color": Color(.38, .38, .40), "stone_map": Limestone, "lichen": .45, "ashlar": false, "texture_scale": .55, "tint_variation": .05, "roughness_base": .93}) "LN_Metal_Team": material = _shader_material(MetalShader, {"base_color": Color(.92, .92, .92), "roughness_base": .32, "wear": .6}) "LN_Metal_Iron": material = _shader_material(MetalShader, {"base_color": Color(.24, .24, .26), "roughness_base": .52, "metallic_value": .8, "wear": .8}) "LN_Metal_Bronze": material = _shader_material(MetalShader, {"base_color": Color(.66, .47, .24), "roughness_base": .42, "patina": .7}) "LN_Crystal_Team", "LN_Crystal_Grotto": material = _shader_material(CrystalShader, {}) "LN_Rune_Team", "LN_Rune_Grotto": material = _shader_material(RuneShader, {}) "LN_Cloth_Team": material = _shader_material(ClothShader, {}) "LN_Water_Glow": material = _shader_material(WaterGlowShader, {}) # The Schlundkrone is skinned: its surfaces read their patterns from rest positions. "LN_Boss_Hide": material = _shader_material(HideShader, {}) "LN_Boss_Bone": material = _shader_material(HideShader, {"base_color": Color(.74, .71, .64), "plate_color": Color(.83, .80, .72), "vein_amount": .25, "roughness_base": .62}) "LN_Boss_Bronze": material = _shader_material(HideShader, {"base_color": Color(.42, .31, .18), "plate_color": Color(.52, .38, .21), "vein_amount": 0.0, "roughness_base": .42, "metallic_value": .85, "patina": .75}) "LN_Crystal_Boss": material = _shader_material(CrystalShader, {"heart_radius": .02, "depth_scale": .3, "vein_scale": 3.6}) "LN_Wood": var wood := StandardMaterial3D.new() wood.albedo_color = Color(.33, .23, .15) wood.roughness = .82 wood.vertex_color_use_as_albedo = true material = wood "LN_Cave": var cave := StandardMaterial3D.new() cave.albedo_color = Color(.02, .015, .03) cave.roughness = 1.0 material = cave if material != null: material.resource_name = key _cache[key] = material return material ## Swaps LN_* surface materials below root for the shared ones. Returns every geometry ## instance that carries at least one LN_* surface (the ones that take instance parameters). static func dress(root: Node) -> Array[GeometryInstance3D]: var result: Array[GeometryInstance3D] = [] _dress(root, result) return result static func _dress(node: Node, result: Array[GeometryInstance3D]) -> void: if node is MeshInstance3D and (node as MeshInstance3D).mesh != null: var instance := node as MeshInstance3D var dressed: bool = false for index: int in range(instance.mesh.get_surface_count()): var source: Material = instance.mesh.surface_get_material(index) if source == null or not source.resource_name.begins_with("LN_"): continue var shared: Material = get_material(source.resource_name) if shared != null: instance.set_surface_override_material(index, shared) dressed = true if dressed: result.append(instance) for child: Node in node.get_children(): _dress(child, result) ## Team colours and families on a set of instances. seed decorrelates buildings. static func apply_team(instances: Array, team: int, seed_value: float) -> void: var style: Dictionary = TEAM_STYLE[clampi(team, 0, 2)] for instance: GeometryInstance3D in instances: instance.set_instance_shader_parameter("tint", style.crystal) instance.set_instance_shader_parameter("metal_tint", style.metal) instance.set_instance_shader_parameter("pattern", style.pattern) instance.set_instance_shader_parameter("cloth_color", style.cloth) instance.set_instance_shader_parameter("emblem_color", style.emblem) instance.set_instance_shader_parameter("shape", style.shape) instance.set_instance_shader_parameter("seed", seed_value) ## Rune surfaces use the rune hue; crystals the crystal hue. Both share the `tint` slot, so a ## mesh that only carries runes gets the rune hue. static func apply_rune_tint(instances: Array, team: int) -> void: var style: Dictionary = TEAM_STYLE[clampi(team, 0, 2)] for instance: GeometryInstance3D in instances: instance.set_instance_shader_parameter("tint", style.rune) static func set_param(instances: Array, parameter: String, value: Variant) -> void: for instance: GeometryInstance3D in instances: instance.set_instance_shader_parameter(parameter, value) static func team_color(team: int, key: String) -> Color: return TEAM_STYLE[clampi(team, 0, 2)][key]