Tooling
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""Puts the current sea material on a level that already exists, and saves it.
|
||||
|
||||
UnrealEditor-Cmd.exe <abs>/Salty.uproject -run=pythonscript \
|
||||
-script="<abs>/Scripts/Authoring/fix_sea_material.py --level /Game/Maps/L_World" \
|
||||
-AllowCommandletRendering -unattended -nopause -abslog=<abs>/Saved/Logs/sea.log
|
||||
|
||||
Why this is a script of its own rather than a flag on the world builders. `ensure_dressing` spawns the sea
|
||||
only when the level has none, which is the right rule - a rerun must not leave a second sun behind it - and
|
||||
the consequence is that changing `rocky_meadows.SEA_GREY` cannot reach a world that already has a sea. The
|
||||
alternative to this file is rebuilding ninety-eight landscapes to change one material reference, which is
|
||||
about forty minutes to avoid writing twenty lines.
|
||||
|
||||
It changes exactly one thing: the material on every actor labelled `World_Sea_Proto`. The plane's size and
|
||||
height are a product of the manifest and are already correct, so they are not touched, and neither is
|
||||
anything else in the level.
|
||||
|
||||
The arguments go *inside* the quoted `-script=` value. UPythonScriptCommandlet::Main reads `-Script=` as one
|
||||
string and hands it to the Python plugin, which splits it into a filename and arguments; anything put after
|
||||
it on the command line is parsed by the engine and never reaches Python, so a level named the wrong way round
|
||||
is silently ignored and the default is used instead.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import unreal
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
sys.path.insert(0, os.path.join(HERE, ".pylib"))
|
||||
import rocky_meadows # noqa: E402
|
||||
|
||||
DEFAULT_LEVELS = ("/Game/Maps/L_World",)
|
||||
|
||||
level_subsystem = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
||||
|
||||
|
||||
def parse_levels():
|
||||
"""`--level A --level B`, or every level in DEFAULT_LEVELS. Read out of sys.argv because the engine puts
|
||||
its own arguments there too."""
|
||||
argv = sys.argv[1:]
|
||||
levels = [argv[i + 1] for i, item in enumerate(argv) if item == "--level" and i + 1 < len(argv)]
|
||||
return levels or list(DEFAULT_LEVELS)
|
||||
|
||||
|
||||
def level_file(level_path):
|
||||
content = os.path.abspath(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_content_dir()))
|
||||
return os.path.join(content, level_path.replace("/Game/", "", 1) + ".umap")
|
||||
|
||||
|
||||
def require_writable(level_path):
|
||||
"""The same probe create_region_world.py makes, and for the same reason: the save is the last thing this
|
||||
does, an editor holding the level makes it fail with a sharing violation, and finding that out at the end
|
||||
is finding it out too late. `r+b` asks the operating system what a save asks and writes nothing."""
|
||||
path = level_file(level_path)
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError(f"{path} does not exist; build the level before repairing its sea")
|
||||
try:
|
||||
open(path, "r+b").close()
|
||||
except OSError as error:
|
||||
raise RuntimeError(
|
||||
f"{path} is locked by another process ({error.__class__.__name__}): an editor with {level_path} "
|
||||
f"open, or playing it, holds it. Load another level in the editor and run again."
|
||||
) from error
|
||||
|
||||
|
||||
def main():
|
||||
levels = parse_levels()
|
||||
unreal.log(f"sea material: {'grey placeholder' if rocky_meadows.SEA_GREY else 'the engine water material'}")
|
||||
for level_path in levels:
|
||||
require_writable(level_path)
|
||||
for level_path in levels:
|
||||
if not level_subsystem.load_level(level_path):
|
||||
raise RuntimeError(f"could not load {level_path}")
|
||||
world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
|
||||
if not world.get_path_name().startswith(level_path):
|
||||
raise RuntimeError(f"the editor world is {world.get_path_name()}, not {level_path}; "
|
||||
f"a save would go nowhere")
|
||||
changed = rocky_meadows.repair_sea(None)
|
||||
if not changed:
|
||||
continue
|
||||
if not level_subsystem.save_current_level():
|
||||
raise RuntimeError(f"saving {level_path} failed; see the log above")
|
||||
unreal.EditorLoadingAndSavingUtils.save_dirty_packages(True, True)
|
||||
unreal.log(f"{level_path} saved: {changed} sea plane(s) repainted")
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user