|
| 1 | +from hammeraddons.bsp_transform import trans, Context |
| 2 | + |
| 3 | +from srctools import VMF, Entity, conv_int, Vec, Output |
| 4 | +import logging |
| 5 | + |
| 6 | +LOGGER = logging.getLogger("[Transform][Dynamic Priority]") |
| 7 | + |
| 8 | + |
| 9 | +@trans("Dynamic Priority") |
| 10 | +def dynamic_priority(ctx: Context): |
| 11 | + vmf = ctx.vmf |
| 12 | + |
| 13 | + light: Entity |
| 14 | + |
| 15 | + lights = set(vmf.by_class["light_rt_spot"]) | set(vmf.by_class["light_rt"]) |
| 16 | + |
| 17 | + added_logic = False |
| 18 | + |
| 19 | + all_lights = lights \ |
| 20 | + | set(vmf.by_class["light"]) \ |
| 21 | + | set(vmf.by_class["light_spot"]) \ |
| 22 | + | set(vmf.by_class["light_environment"]) \ |
| 23 | + | set(vmf.by_class["light_directional"]) |
| 24 | + |
| 25 | + # Each switchable light has a lightstyle assigned to it, we have to check how many of them are used and then use the unused ones |
| 26 | + # Check https://github.com/ValveSoftware/source-sdk-2013/blob/39f6dde8fbc238727c020d13b05ecadd31bda4c0/src/utils/vbsp/writebsp.cpp#L982-L1038 |
| 27 | + # for reference |
| 28 | + |
| 29 | + used_styles = set() |
| 30 | + |
| 31 | + for light in all_lights: |
| 32 | + used_styles.add(conv_int(light['style'], default=0)) |
| 33 | + |
| 34 | + |
| 35 | + available_styles = set(range(32, 65 + 32)) |
| 36 | + |
| 37 | + available_styles = list(available_styles - used_styles) |
| 38 | + |
| 39 | + lg0_static_style = available_styles[0] |
| 40 | + lg0_dynamic_style = available_styles[1] |
| 41 | + lg1_static_style = available_styles[2] |
| 42 | + lg1_dynamic_style = available_styles[3] |
| 43 | + |
| 44 | + for light in lights: |
| 45 | + |
| 46 | + if conv_int(light["_lightmode"], 2) != 2: # Only Baked Bounce makes sense to have this functionality |
| 47 | + continue |
| 48 | + |
| 49 | + if light["targetname", ""] != "": |
| 50 | + LOGGER.info(f"Lights with targetnames will be skipped! Light: {light['targetname', '']} at {light.get_origin()}") |
| 51 | + continue |
| 52 | + |
| 53 | + dynpr = conv_int(light["_dynamic_priority"], default=-1) |
| 54 | + |
| 55 | + if dynpr not in (0, 1, 2): |
| 56 | + LOGGER.warning(f"Invalid _dynamic_priority for light at {light.get_origin()}, skipping...") |
| 57 | + continue |
| 58 | + |
| 59 | + |
| 60 | + if dynpr == 2: # On High, don't change since the light is always dynamic |
| 61 | + continue |
| 62 | + |
| 63 | + LOGGER.info(f"Processing light at {light.get_origin()}") |
| 64 | + |
| 65 | + if not added_logic: |
| 66 | + added_logic = True |
| 67 | + LOGGER.info(f"Additionally, handler logic will be spawned at the position mentioned above!") |
| 68 | + AddLogic(vmf, light.get_origin()) |
| 69 | + |
| 70 | + |
| 71 | + light_copy = light.copy() |
| 72 | + light_bounce = light.copy() |
| 73 | + |
| 74 | + light_bounce["_lightmode"] = 2 # Ensure Bounce is created |
| 75 | + light_bounce["_removeaftercompile"] = 1 # Make VRAD remove this light after compilation |
| 76 | + # This trick allows us to create artificial bounce-only lights, because named lights don't get bounce lights |
| 77 | + |
| 78 | + # The thing is, even when switching the modes, bounce lights will remain on, because we're switching between groups and not on/off |
| 79 | + |
| 80 | + light["targetname"] = f"light_dynpr_dynamic_{dynpr}" |
| 81 | + |
| 82 | + if dynpr == 0: |
| 83 | + light["style"] = lg0_dynamic_style |
| 84 | + elif dynpr == 1: |
| 85 | + light["style"] = lg1_dynamic_style |
| 86 | + |
| 87 | + |
| 88 | + light_copy["targetname"] = f"light_dynpr_static_{dynpr}" |
| 89 | + |
| 90 | + if dynpr == 0: |
| 91 | + light_copy["style"] = lg0_static_style |
| 92 | + elif dynpr == 1: |
| 93 | + light_copy["style"] = lg1_static_style |
| 94 | + |
| 95 | + # Create a static copy |
| 96 | + light_copy["_lightmode"] = 0 # Fully static |
| 97 | + |
| 98 | + # We expect the mode to be medium by default, it also limits the amount of lights being switched at once when changing from this mode on map load |
| 99 | + if dynpr == 1: # Medium, set the static light to dark |
| 100 | + spawnflags = conv_int(light_copy["spawnflags", 0]) |
| 101 | + spawnflags |= 1 # Sets Initially Dark to True |
| 102 | + light_copy["spawnflags"] = spawnflags |
| 103 | + |
| 104 | + elif dynpr == 0: # Low, set the dynamic light to dark |
| 105 | + spawnflags = conv_int(light["spawnflags", 0]) |
| 106 | + spawnflags |= 1 # Sets Initially Dark to True |
| 107 | + light["spawnflags"] = spawnflags |
| 108 | + |
| 109 | + vmf.add_ents([light_copy, light_bounce]) |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | +def AddLogic(vmf: VMF, pos: Vec): |
| 114 | + """Add the necessary logic to load the saved state on every map load.""" |
| 115 | + |
| 116 | + logic_auto = vmf.create_ent( |
| 117 | + classname = 'logic_auto', |
| 118 | + angles = Vec(0, 0, 0), |
| 119 | + spawnflags = 0, |
| 120 | + origin = pos |
| 121 | + ) |
| 122 | + # Don't remove on fire, we can fire on every map load, even reloads. |
| 123 | + # If the game saves the state of the lights the script won't do anything |
| 124 | + # and if it doesn't, this will make sure that they are switched to a correct mode |
| 125 | + |
| 126 | + logic_script = vmf.create_ent( |
| 127 | + classname = 'logic_script', |
| 128 | + angles = Vec(0, 0, 0), |
| 129 | + targetname = '@PC_dynpr', |
| 130 | + vscripts = 'dynamic_priority.nut', |
| 131 | + origin = pos |
| 132 | + ) |
| 133 | + |
| 134 | + logic_auto.add_out( |
| 135 | + Output("OnMapSpawn", "@PC_dynpr", "RunScriptCode", "LoadFromMemory()") |
| 136 | + ) |
| 137 | + |
0 commit comments