""" The default map (module of the import runner, see import_assets.py). /Game/Maps/Descent_Main holds ONLY a PlayerStart (and the engine's own world actors). Everything else is spawned by code when play begins (Core/DescentBoot.h, DESIGN_UE.md section 1.5): fog, exposure, the sun, the terrain, the carrier. A second height fog or post-process volume placed in the map would fight the environment actor, and pressing Play in the editor, PlayGame.bat and the scenario runner must all give the same world. The map's World Settings name ADescentGameMode as the game mode (Config/DefaultEngine.ini does the same project-wide). Safe to re-run: the map is rebuilt only if it does not exist, if it still holds anything beyond the PlayerStart (an older version of this module put fog, a post-process volume and the showcase rig in it), or with --force-map. The Kazah showcase scenarios spawn their own rig, lights, fog and exposure (Debug/Scenarios_Kazah.cpp). """ import unreal MAP_DIR = "/Game/Maps" MAP_NAME = "Descent_Main" MAP_PATH = MAP_DIR + "/" + MAP_NAME STATE_NAME = "map" MAP_SCHEMA = 2 # bump when what the map must contain changes # The actors a "new level" always has (or the editor insists on) and that this module must not destroy: # destroying engine-owned actors logs errors. Anything else found in the level is removed. KEEP_CLASSES = ("WorldSettings", "Brush", "DefaultPhysicsVolume", "PlayerStart", "LevelBounds") # PlayerStart in uu (cm): 14 m behind the origin, 1.5 m up. A fallback for the editor's Play button; the boot # sequence places the character itself. PLAYER_START_LOCATION = (-1400.0, 0.0, 150.0) def run(ctx): eal = ctx.eal state = ctx.load_state(STATE_NAME) exists = eal.does_asset_exist(MAP_PATH) les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem) eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem) eal.make_directory(MAP_DIR) if exists: les.load_level(MAP_PATH) extras = [a.get_class().get_name() for a in eas.get_all_level_actors() if unreal.SystemLibrary.is_valid(a) and a.get_class().get_name() not in KEEP_CLASSES] if state.get("schema") == MAP_SCHEMA and not extras and not ctx.force_map: ctx.log("map %s is current (schema %d, only a PlayerStart); leaving it alone (use -ForceMap to rebuild)" % (MAP_PATH, MAP_SCHEMA)) return if extras: ctx.log("map %s holds more than a PlayerStart (%s); cleaning it" % (MAP_PATH, ", ".join(sorted(set(extras))))) elif not les.new_level(MAP_PATH): raise RuntimeError("could not create the new level " + MAP_PATH) # Strip the level down to what it may hold; list what was there so a surprise shows up in the log. removed = [] for a in list(eas.get_all_level_actors()): # An actor destroyed along with its parent (the showcase rig's child Tender) is a stale reference by now; # destroying it again logs an error, which makes the commandlet exit non-zero. if not unreal.SystemLibrary.is_valid(a): continue cls = a.get_class().get_name() if cls not in KEEP_CLASSES: removed.append(cls) eas.destroy_actor(a) if removed: ctx.log("map: removed %d actor(s): %s" % (len(removed), ", ".join(sorted(set(removed))))) # Exactly one PlayerStart. starts = [a for a in eas.get_all_level_actors() if a.get_class().get_name() == "PlayerStart"] for extra in starts[1:]: eas.destroy_actor(extra) if starts: start = starts[0] start.set_actor_location(unreal.Vector(*PLAYER_START_LOCATION), False, True) start.set_actor_rotation(unreal.Rotator(roll=0.0, pitch=0.0, yaw=0.0), True) else: start = eas.spawn_actor_from_class(unreal.PlayerStart, unreal.Vector(*PLAYER_START_LOCATION), unreal.Rotator(roll=0.0, pitch=0.0, yaw=0.0)) start.set_actor_label("PlayerStart") world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world() ws = world.get_world_settings() ctx.try_set(ws, "default_game_mode", unreal.DescentGameMode.static_class()) if not les.save_current_level(): raise RuntimeError("could not save " + MAP_PATH) left = sorted(set(a.get_class().get_name() for a in eas.get_all_level_actors())) ctx.log("map %s saved: %d actors (%s)" % (MAP_PATH, len(eas.get_all_level_actors()), ", ".join(left))) ctx.save_state(STATE_NAME, {"schema": MAP_SCHEMA})