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)
184 lines
5.8 KiB
GDScript
184 lines
5.8 KiB
GDScript
@tool
|
||
extends Node
|
||
|
||
# Map Validator — Command-line entry point.
|
||
#
|
||
# Usage:
|
||
# godot --path client --script tools/validate_map.gd -- res://maps/mymap.tscn
|
||
#
|
||
# The scene path must come after `--` so Godot passes it as a user argument.
|
||
#
|
||
# Exit codes:
|
||
# 0 — All checks passed
|
||
# 1 — Warnings only (light suggestions, no blocked items)
|
||
# 2 — Errors found (must-fix items)
|
||
#
|
||
# Runs four validation modules in order:
|
||
# 1. Scene structure — node hierarchy, required groups, LightmapGI, WorldEnvironment
|
||
# 2. Polygon count — total mesh triangles ≤ 50K
|
||
# 3. Texture size — all material textures ≤ 1024×1024
|
||
# 4. Light count — dynamic (non-baked) lights ≤ 4, lightmap bake status
|
||
#
|
||
# Each module returns a dictionary {pass: bool, errors: [str], warnings: [str]}.
|
||
|
||
const VALIDATOR_DIR := "res://tools/validate_map/"
|
||
|
||
enum ExitCode {
|
||
PASS = 0,
|
||
WARNINGS = 1,
|
||
ERRORS = 2,
|
||
}
|
||
|
||
|
||
func _ready() -> void:
|
||
var args := OS.get_cmdline_user_args()
|
||
if args.is_empty():
|
||
_print_usage()
|
||
get_tree().quit(ExitCode.ERRORS)
|
||
return
|
||
|
||
var scene_path := args[0]
|
||
|
||
# Validate path prefix
|
||
if not scene_path.begins_with("res://"):
|
||
push_error("Scene path must be a Godot resource path (res://) — got: ", scene_path)
|
||
get_tree().quit(ExitCode.ERRORS)
|
||
return
|
||
|
||
# Load the scene
|
||
var scene := ResourceLoader.load(scene_path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE)
|
||
if scene == null:
|
||
push_error("Failed to load scene: ", scene_path)
|
||
get_tree().quit(ExitCode.ERRORS)
|
||
return
|
||
|
||
print("═══════════════════════════════════════════")
|
||
print(" MAP VALIDATOR")
|
||
print(" Scene: ", scene_path)
|
||
print("═══════════════════════════════════════════")
|
||
print("")
|
||
|
||
# Instantiate the scene (in @tool mode this runs in editor)
|
||
var instance_data: Array = _try_instantiate(scene, scene_path)
|
||
var instance: Node = instance_data[0] as Node
|
||
var instantiate_error: String = instance_data[1] as String
|
||
if instance == null:
|
||
push_error(instantiate_error)
|
||
get_tree().quit(ExitCode.ERRORS)
|
||
return
|
||
|
||
add_child(instance)
|
||
|
||
# Run all validators
|
||
var results: Array[Dictionary] = []
|
||
results.append(_run_module("validate_scene", instance, scene_path))
|
||
results.append(_run_module("validate_polycount", instance, scene_path))
|
||
results.append(_run_module("validate_textures", instance, scene_path))
|
||
results.append(_run_module("validate_lights", instance, scene_path))
|
||
|
||
# Summary
|
||
print("")
|
||
print("═══════════════════════════════════════════")
|
||
print(" SUMMARY")
|
||
print("═══════════════════════════════════════════")
|
||
|
||
var total_errors := 0
|
||
var total_warnings := 0
|
||
|
||
for result in results:
|
||
var label: String = "PASS" if result["pass"] else result["status"]
|
||
print(" [", label, "] ", result["name"])
|
||
total_errors += result["errors"].size()
|
||
total_warnings += result["warnings"].size()
|
||
|
||
print("")
|
||
print(" Errors: ", total_errors)
|
||
print(" Warnings: ", total_warnings)
|
||
|
||
if total_errors > 0:
|
||
print(" Result: FAIL")
|
||
print("")
|
||
get_tree().quit(ExitCode.ERRORS)
|
||
elif total_warnings > 0:
|
||
print(" Result: PASS (with warnings)")
|
||
print("")
|
||
get_tree().quit(ExitCode.WARNINGS)
|
||
else:
|
||
print(" Result: PASS")
|
||
print("")
|
||
get_tree().quit(ExitCode.PASS)
|
||
|
||
|
||
# Attempts to instantiate a PackedScene, catching errors gracefully.
|
||
func _try_instantiate(scene: PackedScene, path: String) -> Array:
|
||
var err_test := scene.can_instantiate()
|
||
if not err_test:
|
||
return [null, "Scene cannot be instantiated (may depend on other resources): " + path]
|
||
|
||
var inst := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
|
||
if inst == null:
|
||
return [null, "Scene instantiate returned null: " + path]
|
||
return [inst, ""]
|
||
|
||
|
||
# Loads a validator script from VALIDATOR_DIR and runs its checks.
|
||
func _run_module(name: String, scene_root: Node, scene_path: String) -> Dictionary:
|
||
var script_path := VALIDATOR_DIR.path_join(name + ".gd")
|
||
var script := ResourceLoader.load(script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE)
|
||
if script == null:
|
||
push_error("Failed to load validator module: ", script_path)
|
||
return {
|
||
"name": name,
|
||
"pass": false,
|
||
"status": "ERROR",
|
||
"errors": ["Validator script not found: " + script_path],
|
||
"warnings": [],
|
||
}
|
||
|
||
# Instantiate the validator module directly (scripts extend RefCounted)
|
||
if not script.has_method("validate"):
|
||
push_error("Validator module missing validate() method: ", script_path)
|
||
return {
|
||
"name": name,
|
||
"pass": false,
|
||
"status": "ERROR",
|
||
"errors": ["Missing validate() method in " + script_path],
|
||
"warnings": [],
|
||
}
|
||
|
||
print("── [", name, "] ────────────────────────────")
|
||
|
||
var validator = script.new()
|
||
var result: Dictionary = validator.validate(scene_root, scene_path)
|
||
|
||
# Print inline results
|
||
for w in result.get("warnings", []):
|
||
print(" ⚠ ", w)
|
||
for e in result.get("errors", []):
|
||
print(" ✖ ", e)
|
||
|
||
var ok: bool = result.get("pass", false)
|
||
if ok:
|
||
print(" ✓ pass")
|
||
else:
|
||
print(" ✖ FAIL")
|
||
|
||
print("")
|
||
return result
|
||
|
||
|
||
func _print_usage() -> void:
|
||
print("Map Validator — usage:")
|
||
print("")
|
||
print(" godot --path <project> --script tools/validate_map.gd -- <scene_path>")
|
||
print("")
|
||
print("Examples:")
|
||
print(" godot --path client --script tools/validate_map.gd -- res://maps/de_dust2.tscn")
|
||
print(" godot --path client --script tools/validate_map.gd -- res://maps/my_map.tscn")
|
||
print("")
|
||
print("Exit codes:")
|
||
print(" 0 — All checks passed")
|
||
print(" 1 — Warnings (non-blocking suggestions)")
|
||
print(" 2 — Errors (must-fix items)")
|
||
print("")
|