big
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
extends SceneTree
|
||||
|
||||
## Loads every menu-selectable level through an active single-player match,
|
||||
## waits for its player/viewmodel to initialize, then advances. This is the
|
||||
## release gate for scene-load, shader, and teardown errors.
|
||||
|
||||
## The map list is DISCOVERED, not written down.
|
||||
##
|
||||
## It used to be a hardcoded array, and a newly added level (Sakura Crossing)
|
||||
## loaded, shipped and never once went through this gate — the run still said
|
||||
## "PASS: all 5 levels" while there were six. A release gate that has to be
|
||||
## edited by hand every time it gains something to test is a gate that will be
|
||||
## wrong exactly when it matters.
|
||||
##
|
||||
## `scenes/maps/*/map_meta.cfg` is already the definition of "menu-selectable",
|
||||
## since that is what the main menu and map_preview_capture both scan.
|
||||
const MAPS_DIR := "res://scenes/maps/"
|
||||
|
||||
var MAPS: Array[String] = []
|
||||
|
||||
|
||||
func _discover_maps() -> Array[String]:
|
||||
var out: Array[String] = []
|
||||
var dir := DirAccess.open(MAPS_DIR)
|
||||
if dir == null:
|
||||
return out
|
||||
dir.list_dir_begin()
|
||||
var folder := dir.get_next()
|
||||
while folder != "":
|
||||
if dir.current_is_dir() and not folder.begins_with("."):
|
||||
var cfg := ConfigFile.new()
|
||||
if cfg.load(MAPS_DIR + folder + "/map_meta.cfg") == OK:
|
||||
var scene_path: String = cfg.get_value("map", "scene_path", "")
|
||||
if scene_path != "" and ResourceLoader.exists(scene_path):
|
||||
out.append(scene_path)
|
||||
folder = dir.get_next()
|
||||
out.sort()
|
||||
return out
|
||||
|
||||
|
||||
var _mode := "boot"
|
||||
var _frames := 0
|
||||
var _map_index := -1
|
||||
var _failures: Array[String] = []
|
||||
var _loaded_maps := 0
|
||||
var _skin_override := ""
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if not args.is_empty():
|
||||
_skin_override = args[0]
|
||||
MAPS = _discover_maps()
|
||||
print("MAP_SMOKE discovered %d levels: %s" % [MAPS.size(), ", ".join(MAPS)])
|
||||
change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
||||
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
_frames += 1
|
||||
if _mode == "boot":
|
||||
if _frames == 45:
|
||||
if not _skin_override.is_empty():
|
||||
var skin_manager = root.get_node_or_null("SkinManager")
|
||||
if skin_manager and skin_manager.skins.has(_skin_override):
|
||||
# Test-only selection: direct assignment avoids overwriting
|
||||
# the player's saved preference in user://.
|
||||
skin_manager.active_skin_id = _skin_override
|
||||
print("MAP_SMOKE skin override: ", _skin_override)
|
||||
else:
|
||||
_fail("unknown skin override: " + _skin_override)
|
||||
var network = root.get_node_or_null("NetworkManager")
|
||||
if network and network.has_method("start_singleplayer_match"):
|
||||
network.start_singleplayer_match(GameMode.DEATHMATCH)
|
||||
_load_next()
|
||||
return false
|
||||
|
||||
if _mode == "map":
|
||||
if _frames == 150:
|
||||
var players := root.find_children("*", "CharacterBody3D", true, false)
|
||||
var local_player = null
|
||||
for player in players:
|
||||
if player.has_method("get_visual_model") \
|
||||
and player.is_multiplayer_authority():
|
||||
local_player = player
|
||||
break
|
||||
if local_player == null:
|
||||
_fail("missing local player: " + MAPS[_map_index])
|
||||
else:
|
||||
var managers: Array[Node] = local_player.find_children(
|
||||
"WeaponManager", "WeaponManager", true, false)
|
||||
if managers.is_empty():
|
||||
_fail("missing WeaponManager: " + MAPS[_map_index])
|
||||
else:
|
||||
print("MAP_SMOKE loaded ", MAPS[_map_index])
|
||||
_loaded_maps += 1
|
||||
_audit_null_materials()
|
||||
elif _frames >= 180:
|
||||
_load_next()
|
||||
return false
|
||||
|
||||
if _mode == "drain":
|
||||
if _frames >= 180:
|
||||
if _failures.is_empty() and _loaded_maps == MAPS.size():
|
||||
print("MAP_SMOKE PASS: all %d levels loaded cleanly" % MAPS.size())
|
||||
quit(0)
|
||||
else:
|
||||
for failure in _failures:
|
||||
push_error("MAP_SMOKE FAIL: " + failure)
|
||||
quit(1)
|
||||
return true
|
||||
return false
|
||||
return false
|
||||
|
||||
|
||||
func _load_next() -> void:
|
||||
_map_index += 1
|
||||
_frames = 0
|
||||
if _map_index >= MAPS.size():
|
||||
_mode = "drain"
|
||||
unload_current_scene()
|
||||
return
|
||||
_mode = "map"
|
||||
var error := change_scene_to_file(MAPS[_map_index])
|
||||
if error != OK:
|
||||
_fail("could not load %s (error %d)" % [MAPS[_map_index], error])
|
||||
|
||||
|
||||
func _fail(message: String) -> void:
|
||||
_failures.append(message)
|
||||
printerr("MAP_SMOKE ", message)
|
||||
|
||||
|
||||
func _audit_null_materials() -> void:
|
||||
var null_surfaces: Array[String] = []
|
||||
for candidate in root.find_children("*", "MeshInstance3D", true, false):
|
||||
var mesh_instance := candidate as MeshInstance3D
|
||||
if mesh_instance.mesh == null:
|
||||
continue
|
||||
for surface in mesh_instance.mesh.get_surface_count():
|
||||
if mesh_instance.get_active_material(surface) == null:
|
||||
null_surfaces.append(
|
||||
"%s[%d]" % [mesh_instance.get_path(), surface])
|
||||
if null_surfaces.is_empty():
|
||||
print("MAP_SMOKE material audit: 0 null surfaces")
|
||||
else:
|
||||
_fail("%d null mesh materials in %s: %s" % [
|
||||
null_surfaces.size(),
|
||||
MAPS[_map_index],
|
||||
", ".join(null_surfaces.slice(0, 12)),
|
||||
])
|
||||
Reference in New Issue
Block a user