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)
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
@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
|
||||
Reference in New Issue
Block a user