Skip to content

Commit b2ff09b

Browse files
authored
Dynamic Light Bridge Lights Local Addon (#10)
* Added light bridge lights local addon. * Fixed thumbnail. * Various bug fixes. - Fixed a bug where any reduction in bridge lights for one bridge would apply to all bridges in the level until refreshed. - Fixed a bug where lights could extend beyond where a bridge stops in specific cases. - Fixed a bug where all bridge lights would flicker for a tick when a bridge is removed. - Fixed a bug where dynamic props could block the bridge's lights. - Removed need for `sv_alternateticks 0`.
1 parent f85f2c2 commit b2ff09b

File tree

4 files changed

+223
-0
lines changed

4 files changed

+223
-0
lines changed
371 KB
Loading

light_bridge_lights/addon.kv3

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!-- kv3 encoding:text:version{e21c7f3c-8a33-41c5-9977-a76d3a32aa0d} format:generic:version{7412167c-06e9-4698-aff2-e63eb59037e7} -->
2+
{
3+
mod = "Dynamic Light Bridge Lights (★)"
4+
description = "Adds dynamic lighting to light bridges, similar in style to our Clustered Renderer showcase video.\n\nWARNING: This addon can be very performance-heavy on lower-end hardware due to the number of lights it creates.\n\nThis addon is a stand-in solution until Area Lights have been implemented.\n\nVersion 1.0.0"
5+
type = ""
6+
id = 0
7+
thumbnail = ".assets/thumb.jpg"
8+
authors =
9+
[
10+
"P2:CE Team",
11+
]
12+
dependencies = [ ]
13+
tags = [ ]
14+
ignore = [ ]
15+
metadata =
16+
{
17+
}
18+
assets =
19+
{
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// table containing useful dev functions
2+
if(!("Dev" in getroottable())) {
3+
::Dev <- {
4+
function msg(msg) {
5+
printl("[LIGHT BRIDGE LIGHTS] " + msg)
6+
}
7+
8+
function msgDeveloper(msg) {
9+
if(GetDeveloperLevel() > 0) printl("[LIGHT BRIDGE LIGHTS - DEV] " + msg)
10+
}
11+
12+
function EntFireByHandleCompressed(ent, input, param = "", delay = 0.0, activator = null, caller = null) {
13+
if(ent != null) EntFireByHandle(ent, input, param, delay, activator, caller)
14+
else Dev.msgDeveloper("Tried to fire null entity!")
15+
}
16+
17+
function distance(vec1, vec2) {
18+
return (vec1 - vec2).Length()
19+
}
20+
}
21+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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

Comments
 (0)