Files
tactical-shooter/client/map_template/template_map.gd
T
shawn e7299b17e9 Phase 7: netfox + godot-jolt stack upgrade
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)
2026-07-02 17:39:22 -04:00

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