@tool extends Node3D # Validates template map structure in the editor output panel. # # Run this in the Godot editor: # 1. Open template_map.tscn # 2. Look at the Output panel (bottom dock) for validation results # # Checks: # - Required gameplay node groups are present # - Spawn points exist for both teams # - Bomb sites are defined # - Buy zones are present # - LightmapGI is configured # # Groups used by map nodes: # ct_spawn — Counter-Terrorist spawn positions # t_spawn — Terrorist spawn positions # buy_zone — Purchase zone areas # bomb_site — Bomb plant site areas (+ bombsite_a / bombsite_b for ID) # cubemap_origin — Reflection cubemap capture origin # map_bounds — Out-of-bounds kill walls # # Game logic discovers these at runtime by scanning for nodes with # the corresponding group, not by node name. func _ready(): if not Engine.is_editor_hint(): return print("") print("=== Map Template: Validate Scene ===") print("") var checks = [] # --- Required node types by group --- var groups_to_check = { "ct_spawn": "CT Spawn points", "t_spawn": "T Spawn points", "buy_zone": "Buy zones", "bomb_site": "Bomb sites", "cubemap_origin": "Cubemap capture origins", "map_bounds": "Map boundary walls", } for group in groups_to_check: var nodes = get_tree().get_nodes_in_group(group) var label = groups_to_check[group] if nodes.size() > 0: checks.append([str(" + ", label, " (", nodes.size(), " found)"), true]) else: checks.append([str(" - ", label, " — NONE FOUND"), false]) # --- CSG floor check --- var floor_nodes = _find_csg_floor() checks.append([str(" + CSG Floor geometry (", floor_nodes.size(), " nodes)"), floor_nodes.size() > 0]) # --- Wall check --- var wall_count = _find_csg_walls() checks.append([str(" + CSG Wall geometry (", wall_count, " walls)"), wall_count >= 3]) # --- LightmapGI check --- var lightmap = _find_lightmap_gi() if lightmap: checks.append([" + LightmapGI configured ✓", true]) if lightmap.light_data != null: checks.append([" + LightmapGI data: BAKED ✓", true]) else: checks.append([" - LightmapGI: NOT YET BAKED", false]) checks.append([str(" + LightmapGI.bounces = ", lightmap.bounces), lightmap.bounces >= 2]) checks.append([str(" + LightmapGI.texel_scale = ", lightmap.texel_scale), lightmap.texel_scale <= 2.0]) else: checks.append([" - LightmapGI — NOT FOUND", false]) # --- ReflectionProbe check --- var probe = _find_reflection_probe() checks.append([" + ReflectionProbe present", probe != null]) # --- WorldEnvironment check --- var env = _find_world_env() checks.append([" + WorldEnvironment present", env != null]) # --- SunLight check --- var sun = _find_dir_light() checks.append([" + DirectionalLight3D (sun) present", sun != null]) # --- Map scale / playable area estimate --- var extents = _estimate_floor_extents(floor_nodes) if extents: checks.append([str(" + Playable area: ~", extents[0], "x", extents[1], " units"), true]) # Print results print(" ─── Validation Results ───") var passed = 0 var failed = 0 for check in checks: if check[1]: passed += 1 else: failed += 1 print(check[0]) print("") print(" Passed: ", passed, " Failed: ", failed) print("") if failed > 0: print(" NOTE: ", failed, " check(s) did not pass.") print(" See warnings above for details — these are advisory,") print(" the map will still function but may be missing critical nodes.") else: print(" All checks passed. Map template is ready!") print("") print(" === Validation complete ===") print("") func _find_nodes_by_group(group_name: String) -> Array: return get_tree().get_nodes_in_group(group_name) func _find_csg_floor() -> Array: """Return all CSG nodes with floor-like Y position and flat orientation.""" var results = [] for child in get_children(): if child is CSGBox3D or child is CSGCombiner3D: if abs(child.transform.origin.y) < 0.5: var s = child if s is CSGBox3D and s.size.y < 0.3: results.append(child) elif s is CSGCombiner3D: results.append(child) return results func _find_csg_walls() -> int: """Count CSG box nodes with vertical wall-like dimensions.""" var count = 0 for child in get_children(): if child is CSGBox3D: var s: CSGBox3D = child # Wall-like: one thin dimension, tall Y var dims = [s.size.x, s.size.y, s.size.z] dims.sort() if dims[0] < 0.5 and dims[1] > 1.5 and dims[2] > 0.5: if child.name.begins_with("Wall") or child.name.begins_with("Divider") or child.name.begins_with("Mid"): count += 1 return count func _find_lightmap_gi() -> LightmapGI: for child in get_children(): if child is LightmapGI: return child return null func _find_reflection_probe() -> ReflectionProbe: for child in get_children(): if child is ReflectionProbe: return child return null func _find_world_env() -> WorldEnvironment: for child in get_children(): if child is WorldEnvironment: return child return null func _find_dir_light() -> DirectionalLight3D: for child in get_children(): if child is DirectionalLight3D: return child return null func _estimate_floor_extents(floor_nodes: Array) -> Array: """Estimate playable area width and depth from flat CSGBox3D nodes.""" var min_x = INF var max_x = -INF var min_z = INF var max_z = -INF for node in floor_nodes: if node is CSGBox3D: var p = node.transform.origin var s = node.size # Approximate bounding box using position + half-extents min_x = min(min_x, p.x - s.x * 0.5) max_x = max(max_x, p.x + s.x * 0.5) min_z = min(min_z, p.z - s.z * 0.5) max_z = max(max_z, p.z + s.z * 0.5) if min_x != INF and max_x != INF and min_z != INF and max_z != INF: return [int(max_x - min_x), int(max_z - min_z)] return null