187 lines
6.0 KiB
Python
187 lines
6.0 KiB
Python
"""
|
|
FixTerraceLighting.py
|
|
=====================
|
|
For the Terrace level:
|
|
1. Positions the RuinedCrypt_01_P Level Instance relative to PlayerStart
|
|
2. Sets lighting to match RuinedCrypt's cool atmospheric tone
|
|
3. Disables shadow casting on excess movable lights (fixes overlap warning)
|
|
|
|
How to run:
|
|
File -> Execute Python Script -> select this file
|
|
Terrace.umap must be open (tab should show "Terrace")
|
|
"""
|
|
|
|
import unreal
|
|
|
|
USE_OPEN_LEVEL = True # Run against the currently open map
|
|
|
|
# How many units BELOW PlayerStart to place the RuinedCrypt ground
|
|
# (keeps terrain hidden beneath the tower platform)
|
|
RUIN_Z_OFFSET = -350.0
|
|
|
|
# RuinedCrypt scale — 0.7 = 70% of original size
|
|
RUIN_SCALE = 0.7
|
|
|
|
# Atmospheric light color: cool blue-green to match RuinedCrypt's soft tone
|
|
SUN_COLOR = unreal.LinearColor(0.65, 0.80, 0.90, 1.0) # cool / blue-white
|
|
SUN_LUX = 8000.0 # low intensity for dusk/evening mood
|
|
|
|
SKY_INTENSITY = 1.2
|
|
|
|
FOG_DENSITY = 0.04 # subtle atmospheric fog
|
|
FOG_FALLOFF = 0.25
|
|
|
|
|
|
# ------------------------------------------------------------------ helpers --
|
|
|
|
def _log(msg):
|
|
unreal.log(f"[FixTerrace] {msg}")
|
|
|
|
def _warn(msg):
|
|
unreal.log_warning(f"[FixTerrace] {msg}")
|
|
|
|
def _actors():
|
|
sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
return list(sub.get_all_level_actors())
|
|
|
|
def _of_class(cls):
|
|
return [a for a in _actors() if a.get_class() == cls]
|
|
|
|
|
|
# --------------------------------------------------------------- functions ---
|
|
|
|
def fix_ruinedcrypt_position():
|
|
"""Aligns the RuinedCrypt Level Instance to the PlayerStart location."""
|
|
all_actors = _actors()
|
|
|
|
# Find PlayerStart
|
|
ps = next((a for a in all_actors if a.get_class() == unreal.PlayerStart), None)
|
|
if ps is None:
|
|
_warn("PlayerStart not found — skipping RuinedCrypt positioning.")
|
|
return
|
|
|
|
ps_loc = ps.get_actor_location()
|
|
_log(f"PlayerStart location: X={ps_loc.x:.0f} Y={ps_loc.y:.0f} Z={ps_loc.z:.0f}")
|
|
|
|
# Find RuinedCrypt Level Instance by label or class name
|
|
ruin = None
|
|
for a in all_actors:
|
|
label = a.get_actor_label().lower()
|
|
cls = a.get_class().get_name().lower()
|
|
if "ruinedcrypt" in label or "ruined_crypt" in label or \
|
|
("levelinstance" in cls and "ruined" in label):
|
|
ruin = a
|
|
break
|
|
|
|
if ruin is None:
|
|
_warn("RuinedCrypt_01_P Level Instance not found. "
|
|
"Check the actor label in the Outliner.")
|
|
return
|
|
|
|
# New position: same X/Y as PlayerStart, Z offset downward
|
|
new_loc = unreal.Vector(ps_loc.x, ps_loc.y, ps_loc.z + RUIN_Z_OFFSET)
|
|
ruin.set_actor_location(new_loc, sweep=False, teleport=True)
|
|
ruin.set_actor_scale3d(unreal.Vector(RUIN_SCALE, RUIN_SCALE, RUIN_SCALE))
|
|
ruin.set_actor_rotation(unreal.Rotator(0.0, 0.0, 0.0), teleport_physics=True)
|
|
|
|
_log(f"RuinedCrypt positioned at Z={new_loc.z:.0f} Scale={RUIN_SCALE}")
|
|
|
|
|
|
def fix_directional_light():
|
|
"""Sets the DirectionalLight to a cool atmospheric color."""
|
|
lights = _of_class(unreal.DirectionalLight)
|
|
if not lights:
|
|
_warn("DirectionalLight not found.")
|
|
return
|
|
|
|
# Keep only the first one, remove duplicates
|
|
keep = lights[0]
|
|
for extra in lights[1:]:
|
|
unreal.get_editor_subsystem(unreal.EditorActorSubsystem).destroy_actor(extra)
|
|
_log(f"Removed {len(lights)-1} duplicate DirectionalLight(s).")
|
|
|
|
comp = keep.get_component_by_class(unreal.DirectionalLightComponent)
|
|
if comp:
|
|
comp.set_intensity(SUN_LUX)
|
|
comp.set_light_color(SUN_COLOR)
|
|
comp.set_atmosphere_sun_light(True)
|
|
comp.set_cast_shadows(True)
|
|
comp.set_mobility(unreal.ComponentMobility.STATIONARY)
|
|
_log("DirectionalLight updated.")
|
|
|
|
|
|
def fix_sky_light():
|
|
"""Sets SkyLight intensity and removes duplicates."""
|
|
skies = _of_class(unreal.SkyLight)
|
|
if not skies:
|
|
return
|
|
keep = skies[0]
|
|
for extra in skies[1:]:
|
|
unreal.get_editor_subsystem(unreal.EditorActorSubsystem).destroy_actor(extra)
|
|
|
|
comp = keep.get_component_by_class(unreal.SkyLightComponent)
|
|
if comp:
|
|
comp.set_intensity(SKY_INTENSITY)
|
|
comp.set_mobility(unreal.ComponentMobility.STATIONARY)
|
|
_log("SkyLight updated.")
|
|
|
|
|
|
def fix_fog():
|
|
"""Configures subtle atmospheric height fog."""
|
|
fogs = _of_class(unreal.ExponentialHeightFog)
|
|
if not fogs:
|
|
_warn("ExponentialHeightFog not found — skipping fog setup.")
|
|
return
|
|
comp = fogs[0].get_component_by_class(unreal.ExponentialHeightFogComponent)
|
|
if comp:
|
|
comp.set_fog_density(FOG_DENSITY)
|
|
comp.set_fog_height_falloff(FOG_FALLOFF)
|
|
comp.set_volumetric_fog(False)
|
|
_log("Fog updated.")
|
|
|
|
|
|
def disable_excess_movable_shadows():
|
|
"""Disables shadow casting on movable PointLights and SpotLights
|
|
to fix the TOO MANY OVERLAPPING SHADOWED MOVABLE LIGHTS warning."""
|
|
all_actors = _actors()
|
|
fixed = 0
|
|
for a in all_actors:
|
|
cls = a.get_class()
|
|
if cls not in (unreal.PointLight, unreal.SpotLight):
|
|
continue
|
|
for comp_cls in (unreal.PointLightComponent, unreal.SpotLightComponent):
|
|
comp = a.get_component_by_class(comp_cls)
|
|
if comp and comp.get_mobility() == unreal.ComponentMobility.MOVABLE:
|
|
comp.set_cast_shadows(False)
|
|
fixed += 1
|
|
if fixed:
|
|
_log(f"Disabled shadow casting on {fixed} movable PointLight/SpotLight(s).")
|
|
|
|
|
|
def save():
|
|
world = unreal.get_editor_subsystem(
|
|
unreal.UnrealEditorSubsystem).get_editor_world()
|
|
if world:
|
|
try:
|
|
unreal.EditorLoadingAndSavingUtils.save_dirty_packages_with_confirmation(False)
|
|
_log("Saved.")
|
|
except Exception as e:
|
|
_warn(f"Save error: {e}")
|
|
|
|
|
|
# ------------------------------------------------------------------- main --
|
|
|
|
def run():
|
|
_log("Starting...")
|
|
fix_ruinedcrypt_position()
|
|
fix_directional_light()
|
|
fix_sky_light()
|
|
fix_fog()
|
|
disable_excess_movable_shadows()
|
|
save()
|
|
_log("Done. Move the camera in the Viewport to see the changes.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|