e7299b17e9
Stack installed: - netfox v1.35.3 (core + extras + noray + internals) - godot-jolt v0.16.0-stable Architecture: - Server: ENet transport (works headless, no netfox deps) - Client/Editor: netfox rollback (RollbackSynchronizer, TickInterpolator) New/modified: - docs/migration-netfox-plan.md — migration architecture - scripts/network/network_manager.gd — netfox-aware ENet fallback - scripts/network/player.gd — clean base player - client/characters/player_netfox.gd — rollback player w/ WeaponManager - client/characters/input/player_net_input.gd — BaseNetInput subclass - client/characters/character/fps_character_controller.gd — netfox input feed - client/weapons/ — weapon data, registry, TacticalWeaponHitscan, WeaponManager - client/scripts/round_replicator.gd — client-side round state bridge - server/scripts/round_manager.gd — improved state machine - server/scripts/plugin_api/plugin_manager.gd — refined plugin system - config: enemy_tag, ally_tag for meatball targeting Removed: old C++ SimulationServer GDExtension (replaced by netfox rollback)
203 lines
5.8 KiB
GDScript
203 lines
5.8 KiB
GDScript
@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
|