1+ IncludeScript (" light_bridge_lights/helper.nut" )
2+
3+ // sv_init.nut runs before entities have spawned + on every game load. wait until entities are ready before running
4+ function scriptStart () {
5+ local initTimer = CreateEntityByName (" logic_timer" , { // check every tick if entities have actually spawned in yet
6+ targetname = " lightbridgelights_canstarttimer"
7+ RefireTime = 0.01
8+ })
9+
10+ initTimer. ConnectOutput (" OnTimer" , " scriptInit" )
11+ EntFire (" lightbridgelights_canstarttimer" , " Enable" )
12+ }
13+ function scriptInit () { // if player exists, other entities exist
14+ local player = GetPlayer ()
15+ if (player != null ) {
16+ EntFire (" lightbridgelights_canstarttimer" , " Kill" ) // prevent further inits
17+
18+ Setup ()
19+ if (GetDeveloperLevel () > 0 ) Dev. msgDeveloper (" Script initialised." )
20+ }
21+ }
22+
23+ ::LIGHT_COLOUR <- Vector(63 , 196 , 252 ) // RGB colour values - light blue
24+ const LIGHT_BRIGHTNESS = 40
25+ const LIGHT_D50 = 50
26+ const LIGHT_D0 = 300
27+ const LIGHT_SHADOWSIZE = - 1 // DONT CHANGE THIS FROM -1, it just instantly overwhelms the shadow atlas (-1 = no shadows)
28+
29+ const LIGHT_SPACING = 20 // distance between lights in units, less spacing means more lights but a higher performance cost
30+
31+ bridgesCached <- [] // stores bridge handles and their entindexes
32+ bridgeLightCountPrevious <- {}
33+ bridgesCacheMarkedForReset <- false
34+ const CACHE_REFRESH_TIME = 0.01
35+
36+ // traces used for calculating bridge length
37+ const TRACE_DISTANCE = 8192
38+ TRACE_MASK <- MASK_SOLID | MASK_WATER | MASK_BLOCKLOS
39+ TRACE_COLLISION_GROUP <- COLLISION_GROUP_NONE
40+ TRACE_BOUNDS_MIN <- Vector(- 12 , - 12 , - 12 )
41+ TRACE_BOUNDS_MAX <- Vector(12 , 12 , 12 )
42+
43+ function Setup () {
44+ Dev. msgDeveloper (" Creating cache refresh timer..." )
45+ local loopTimer = CreateEntityByName (" logic_timer" , { // cache refresh timer
46+ RefireTime = CACHE_REFRESH_TIME
47+ })
48+ loopTimer. ConnectOutput (" OnTimer" , " bridgeCacheRefresh" )
49+ bridgeCacheRefresh () // initial cache
50+ }
51+
52+ function bridgeCacheRefresh () {
53+ if (bridgesCacheMarkedForReset) {
54+ foreach (idx, data in bridgesCached) {
55+ local bridgeIndex = data. index
56+ lightRemoveAtBridge (bridgeIndex) // prevent duplicate lights
57+
58+ bridgesCached. remove (idx) // dont attempt to remove lights at this bridge again
59+ }
60+ bridgeCacheReset ()
61+ bridgesCacheMarkedForReset = false
62+ }
63+
64+ foreach (data in bridgesCached) {
65+ local bridge = data. bridge
66+ local bridgeIndex = data. index
67+
68+ // reset the cache if any cached bridges are invalid
69+ // one small problem with this is it causes all lights to respawn (hence a small flicker), but it's better than crashing
70+
71+ if (! bridge. IsValid ()) {
72+ lightRemoveAtBridge (bridgeIndex)
73+ bridgesCacheMarkedForReset = true // wait until next tick to prevent crashes
74+ break
75+ }
76+
77+ local lightCountNew = bridgeGetLightCount (bridge)
78+
79+ // get old light count, or use new light count if not found
80+ local lightCountOld = (bridgeIndex in bridgeLightCountPrevious) ? bridgeLightCountPrevious[bridgeIndex] : lightCountNew
81+
82+ // check if light count has changed
83+ if (lightCountNew != lightCountOld) {
84+ if (lightCountNew < lightCountOld) lightRemoveAtBridge (bridgeIndex, lightCountNew) // remove lights if bridge has shrunk
85+ if (lightCountNew > lightCountOld) lightSpawnAtBridge (bridge, lightCountOld) // spawn additional lights
86+ bridgeLightCountPrevious[bridgeIndex] <- lightCountNew
87+ }
88+ }
89+
90+ for (local bridge = null ; bridge = Entities. FindByClassname (bridge, " projected_wall_entity" );) {
91+ if (bridgeIsCached (bridge) || ! bridge. IsValid ()) continue // skip cached bridges or invalid bridges
92+
93+ // update cache with new bridge
94+
95+ local bridgeIndex = bridge. entindex ()
96+
97+ bridgesCached. append ({
98+ bridge = bridge,
99+ index = bridgeIndex
100+ })
101+
102+ local bridgeLightCount = bridgeGetLightCount (bridge)
103+ bridgeLightCountPrevious[bridgeIndex] <- bridgeLightCount // store initial lightcount in a table based on entindex() for comparison later
104+
105+ lightSpawnAtBridge (bridge)
106+ }
107+ }
108+
109+ function bridgeIsCached (bridge) {
110+ foreach (data in bridgesCached) {
111+ if (data. bridge == bridge) return true
112+ }
113+ return false
114+ }
115+
116+ function bridgeCacheReset () {
117+ Dev. msgDeveloper (" Resetting bridge cache..." )
118+ bridgesCached <- []
119+ bridgeLightCountPrevious <- {}
120+ }
121+
122+ function bridgeCalculateLength (bridge) {
123+ local pos = bridge. GetOrigin ()
124+ local forward = bridge. GetForwardVector ()
125+ local ray = TraceHull (pos, pos + (forward * TRACE_DISTANCE), TRACE_BOUNDS_MIN, TRACE_BOUNDS_MAX, TRACE_MASK, bridge, TRACE_COLLISION_GROUP)
126+
127+ return Dev. distance (pos, ray. GetEndPos ())
128+ }
129+
130+ function bridgeGetLightCount (bridge) {
131+ local bridgeLength = bridgeCalculateLength (bridge)
132+
133+ return floor (bridgeLength / LIGHT_SPACING) + 1
134+ }
135+
136+ function lightSpawnAtBridge (bridge, currentLightCount = 0 ) {
137+ local bridgeLength = bridgeCalculateLength (bridge)
138+ local lightCount = bridgeGetLightCount (bridge)
139+ local bridgeIndex = bridge. entindex ()
140+
141+ Dev. msgDeveloper (" Spawning " + (lightCount - currentLightCount) + " lights." )
142+
143+ for (local i = currentLightCount; i < lightCount; i++ ) { // spawn lights from currentLightCount onwards
144+ local distance = (i * LIGHT_SPACING < bridgeLength) ? i * LIGHT_SPACING : bridgeLength // prevent overshoot on last light
145+ local light = lightCreate (bridge. GetOrigin () + (bridge. GetForwardVector () * distance))
146+
147+ light. SetParent (bridge)
148+ light. __KeyValueFromString (" targetname" , bridgeIndex + " _light" + i)
149+ }
150+ }
151+
152+ function lightCreate (pos) { // returns light handle
153+ local light = null
154+
155+ light = CreateEntityByName (" light_rt" , {
156+ _lightmode = 3 ,
157+ spawnflags = 2
158+ })
159+ light. SetLightColor (LIGHT_COLOUR, LIGHT_BRIGHTNESS)
160+ light. SetLightFalloffD50D0 (LIGHT_D50, LIGHT_D0)
161+ light. SetShadowSize (LIGHT_SHADOWSIZE)
162+
163+ light. Spawn ()
164+ light. SetOrigin (pos)
165+
166+ return light
167+ }
168+
169+ function lightRemoveAtBridge (bridgeIndex, numLightsToKeep = 0 ) {
170+ Dev. msgDeveloper (" Removing lights from bridgeIndex " + bridgeIndex + " starting at light index " + numLightsToKeep + " ." )
171+
172+ // how many lights could ever exist on this bridge given the current settings
173+ local numLightsPotential = (TRACE_DISTANCE / LIGHT_SPACING)
174+
175+ for (local i = numLightsToKeep; i < numLightsToKeep + numLightsPotential; i++ ) { // remove all lights from index numLightsToKeep onwards
176+ local light = Entities. FindByName (null , bridgeIndex + " _light" + i)
177+ if (light != null ) Dev. EntFireByHandleCompressed (light, " Kill" )
178+ }
179+ }
180+
181+ scriptStart ()
0 commit comments