86 | function PlaceableLights:onLoad(savegame) |
87 | local spec = self.spec_lights |
88 | local xmlFile = self.xmlFile |
89 | |
90 | local environmentMaskSystem = g_currentMission.environment.environmentMaskSystem |
91 | |
92 | spec.sharedLights = {} |
93 | spec.groups = {} |
94 | spec.triggerToGroup = {} |
95 | |
96 | spec.activatable = PlaceableLightsActivatable.new(self) |
97 | |
98 | g_messageCenter:subscribe(MessageType.SETTING_CHANGED["lightsProfile"], self.lightSetupChanged, self) |
99 | |
100 | xmlFile:iterate("placeable.lights.group", function (lightIndex, lightGroupKey) |
101 | local group = {} |
102 | group.triggerNode = xmlFile:getValue(lightGroupKey .. "#triggerNode", nil, self.components, self.i3dMappings) |
103 | if group.triggerNode ~= nil then |
104 | addTrigger(group.triggerNode, "lightsTriggerCallback", self) |
105 | spec.triggerToGroup[group.triggerNode] = group |
106 | end |
107 | |
108 | local inputActionName = xmlFile:getValue(lightGroupKey .. "#inputAction", "INTERACT") |
109 | group.inputAction = InputAction[inputActionName] or InputAction.INTERACT |
110 | |
111 | group.name = xmlFile:getValue(lightGroupKey .. "#name", "action_placeableLightShed", self.customEnvironment) |
112 | group.activateText = xmlFile:getValue(lightGroupKey .. "#activateText", "action_placeableLightPos", self.customEnvironment) |
113 | group.deactivateText = xmlFile:getValue(lightGroupKey .. "#deactivateText", "action_placeableLightNeg", self.customEnvironment) |
114 | |
115 | local activateTimeStr = xmlFile:getValue(lightGroupKey .. "#activateTime", nil) |
116 | if activateTimeStr ~= nil then |
117 | group.activateMinute = Utils.getMinuteOfDayFromTime(activateTimeStr) |
118 | if group.activateMinute == nil then |
119 | Logging.xmlWarning(xmlFile, "Invalid activateTime string '%s' given for group '%s'. Use 'hh:mm' format", activateTimeStr, lightGroupKey) |
120 | else |
121 | group.activateMinute = math.max(1, group.activateMinute) -- if a time is set, it has to be at least 1 since 0 == disabled |
122 | end |
123 | end |
124 | |
125 | local deactivateTimeStr = xmlFile:getValue(lightGroupKey .. "#deactivateTime", nil) |
126 | if deactivateTimeStr ~= nil then |
127 | group.deactivateMinute = Utils.getMinuteOfDayFromTime(deactivateTimeStr) |
128 | if group.deactivateMinute == nil then |
129 | Logging.xmlWarning(xmlFile, "Invalid deactivateTime string '%s' given for group '%s'. Use 'hh:mm' format", deactivateTimeStr, lightGroupKey) |
130 | else |
131 | group.deactivateMinute = math.max(1, group.deactivateMinute) -- if a time is set, it has to be at least 1 since 0 == disabled |
132 | end |
133 | end |
134 | |
135 | group.weatherRequiredMask = environmentMaskSystem:getWeatherMaskFromFlagNames(xmlFile:getValue(lightGroupKey .. "#weatherRequiredFlags", nil)) |
136 | group.weatherPreventMask = environmentMaskSystem:getWeatherMaskFromFlagNames(xmlFile:getValue(lightGroupKey .. "#weatherPreventFlags", nil)) |
137 | |
138 | if self.isClient then |
139 | group.samples = {} |
140 | group.samples.toggle = g_soundManager:loadSampleFromXML(xmlFile, lightGroupKey .. ".sounds", "toggle", self.baseDirectory, self.components, 1, AudioGroup.ENVIRONMENT, self.i3dMappings, nil) |
141 | end |
142 | |
143 | if (group.activateMinute ~= nil and group.deactivateMinute) == nil or (group.deactivateMinute == nil and group.activateMinute ~= nil) then |
144 | Logging.xmlWarning(xmlFile, "Incomplete automatic toggle time in '%s'", lightGroupKey) |
145 | else |
146 | group.hasManualLights = group.triggerNode ~= nil |
147 | |
148 | group.isActive = false |
149 | group.playerInRange = false |
150 | |
151 | if #spec.groups < PlaceableLights.MAX_NUM_GROUPS then |
152 | table.insert(spec.groups, group) |
153 | group.index = #spec.groups |
154 | else |
155 | Logging.xmlWarning(xmlFile, "Too many light groups registered. Max. %d are allowed", PlaceableLights.MAX_NUM_GROUPS) |
156 | end |
157 | end |
158 | end) |
159 | |
160 | xmlFile:iterate("placeable.lights.sharedLight", function (lightIndex, lightKey) |
161 | local sharedLight = {} |
162 | |
163 | local xmlFilename = xmlFile:getValue(lightKey .. "#filename") |
164 | if xmlFilename ~= nil then |
165 | sharedLight.xmlFilename = Utils.getFilename(xmlFilename, self.baseDirectory) |
166 | sharedLight.groupIndex = xmlFile:getValue(lightKey .. "#groupIndex", 1) |
167 | sharedLight.color = xmlFile:getValue(lightKey .. "#color", nil, true) |
168 | sharedLight.linkNode = xmlFile:getValue(lightKey .. "#linkNode", "0>", self.components, self.i3dMappings) |
169 | |
170 | local group = spec.groups[sharedLight.groupIndex] |
171 | if group == nil then |
172 | Logging.xmlError("Group index '%d' in '%s' does not exist", sharedLight.groupIndex, lightKey) |
173 | else |
174 | if sharedLight.linkNode ~= nil then |
175 | sharedLight.rotations = {} |
176 | xmlFile:iterate(lightKey .. ".rotationNode", function (rotIndex, rotKey) |
177 | local name = xmlFile:getValue(rotKey.."#name") |
178 | local rotation = xmlFile:getValue(rotKey.."#rotation", nil, true) |
179 | if name ~= nil then |
180 | sharedLight.rotations[name] = rotation |
181 | end |
182 | end) |
183 | |
184 | local lightXMLFile = XMLFile.load("sharedLight", sharedLight.xmlFilename, Lights.sharedLightXMLSchema) |
185 | if lightXMLFile ~= nil then |
186 | local filename = lightXMLFile:getValue("light.filename") |
187 | if filename ~= nil then |
188 | local loadingTask = self:createLoadingTask(spec) |
189 | filename = Utils.getFilename(filename, self.baseDirectory) |
190 | |
191 | local arguments = { |
192 | sharedLight = sharedLight, |
193 | lightXMLFile = lightXMLFile, |
194 | loadingTask = loadingTask, |
195 | group = group, |
196 | filename = filename |
197 | } |
198 | sharedLight.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(filename, false, false, self.sharedLightLoaded, self, arguments) |
199 | table.insert(spec.sharedLights, sharedLight) |
200 | else |
201 | Logging.xmlWarning(lightXMLFile, "Missing light i3d filename!") |
202 | lightXMLFile:delete() |
203 | end |
204 | end |
205 | end |
206 | end |
207 | end |
208 | end) |
209 | |
210 | |
211 | spec.lightShapes = {} -- self illum faces within the placeable, only shader will be applied, always stay visible |
212 | |
213 | local getNodeShaderLightIntensity = function(node) |
214 | if getHasShaderParameter(node, "lightControl") then |
215 | local x = getShaderParameter(node, "lightControl") |
216 | return x |
217 | end |
218 | return 1 |
219 | end |
220 | |
221 | xmlFile:iterate("placeable.lights.lightShape", function (lightIndex, lightKey) |
222 | local lightShape = {} |
223 | |
224 | lightShape.groupIndex = xmlFile:getValue(lightKey .. "#groupIndex", 1) |
225 | lightShape.node = xmlFile:getValue(lightKey .. "#node", "0>", self.components, self.i3dMappings) |
226 | if lightShape.node ~= nil then |
227 | lightShape.intensity = xmlFile:getValue(lightKey .. "#intensity", getNodeShaderLightIntensity(lightShape.node)) |
228 | |
229 | local group = spec.groups[lightShape.groupIndex] |
230 | if group == nil then |
231 | Logging.xmlError(xmlFile, "Group index '%d' in '%s' does not exist", lightShape.groupIndex, lightKey) |
232 | else |
233 | if not group.hasManualLights then |
234 | if group.activateMinute ~= nil then |
235 | setVisibilityConditionMinuteOfDay(lightShape.node, group.activateMinute, group.deactivateMinute) |
236 | end |
237 | if group.weatherRequiredMask ~= nil or group.weatherPreventMask ~= nil then |
238 | setVisibilityConditionWeatherMask(lightShape.node, group.weatherRequiredMask or 0, group.weatherPreventMask or 0) |
239 | end |
240 | |
241 | setVisibilityConditionRenderInvisible(lightShape.node, true) -- still render self illum faces when group is not visible, only apply shader |
242 | setVisibilityConditionVisibleShaderParameter(lightShape.node, lightShape.intensity) |
243 | end |
244 | end |
245 | table.insert(spec.lightShapes, lightShape) |
246 | end |
247 | end) |
248 | |
249 | |
250 | spec.realLights = { |
251 | low={}, |
252 | high={}, |
253 | } |
254 | local loadRealLight = function(realLightKey, insertTable) |
255 | local realLight = {} |
256 | realLight.node = xmlFile:getValue(realLightKey .. "#node", nil, self.components, self.i3dMappings) |
257 | if realLight.node ~= nil then |
258 | realLight.groupIndex = xmlFile:getValue(realLightKey .. "#groupIndex", 1) |
259 | |
260 | local group = spec.groups[realLight.groupIndex] |
261 | if group == nil then |
262 | Logging.xmlError("Group index '%d' in '%s' does not exist", realLight.groupIndex, realLightKey) |
263 | else |
264 | if group.activateMinute ~= nil then |
265 | setVisibilityConditionMinuteOfDay(realLight.node, group.activateMinute, group.deactivateMinute) |
266 | end |
267 | if group.weatherRequiredMask ~= nil or group.weatherPreventMask ~= nil then |
268 | setVisibilityConditionWeatherMask(realLight.node, group.weatherRequiredMask or 0, group.weatherPreventMask or 0) |
269 | end |
270 | |
271 | table.insert(insertTable, realLight) |
272 | end |
273 | end |
274 | end |
275 | |
276 | xmlFile:iterate("placeable.lights.realLights.low.light", function (lightIndex, lightKey) |
277 | loadRealLight(lightKey, spec.realLights.low) |
278 | end) |
279 | |
280 | xmlFile:iterate("placeable.lights.realLights.high.light", function (lightIndex, lightKey) |
281 | loadRealLight(lightKey, spec.realLights.high) |
282 | end) |
283 | end |
49 | function PlaceableLights.registerXMLPaths(schema, basePath) |
50 | schema:setXMLSpecializationType("Lights") |
51 | schema:register(XMLValueType.STRING, basePath .. ".lights.sharedLight(?)#filename", "Path to shared light xml file") |
52 | schema:register(XMLValueType.INT, basePath .. ".lights.sharedLight(?)#groupIndex", "Parent group", 1) |
53 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".lights.sharedLight(?)#linkNode", "Link node") |
54 | schema:register(XMLValueType.COLOR, basePath .. ".lights.sharedLight(?)#color", "Light color") |
55 | schema:register(XMLValueType.STRING, basePath .. ".lights.sharedLight(?).rotationNode(?)#name", "Rotation node name") |
56 | schema:register(XMLValueType.VECTOR_ROT, basePath .. ".lights.sharedLight(?).rotationNode(?)#rotation", "Rotation to set") |
57 | |
58 | schema:register(XMLValueType.INT, basePath .. ".lights.lightShape(?)#groupIndex", "Parent group", 1) |
59 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".lights.lightShape(?)#node", "Light shape / self-illum-mesh node. Always visible, only shader is set") |
60 | schema:register(XMLValueType.FLOAT, basePath .. ".lights.lightShape(?)#intensity", "Intensity for the shader if active", 25) |
61 | |
62 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".lights.realLights.low.light(?)#node", "Real light node used on low performance profile. Visibility is toggled based on settings") |
63 | schema:register(XMLValueType.INT, basePath .. ".lights.realLights.low.light(?)#groupIndex", "Parent group", 1) |
64 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".lights.realLights.high.light(?)#node", "Real light node used on high performance profile. Visibility is toggled based on settings") |
65 | schema:register(XMLValueType.INT, basePath .. ".lights.realLights.high.light(?)#groupIndex", "Parent group", 1) |
66 | |
67 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".lights.group(?)#triggerNode", "Activation Trigger for manual control") |
68 | schema:register(XMLValueType.STRING, basePath .. ".lights.group(?)#inputAction", "Input Action name", "INTERACT") |
69 | schema:register(XMLValueType.L10N_STRING, basePath .. ".lights.group(?)#name", "Group name for display", "action_placeableLightShed") |
70 | schema:register(XMLValueType.L10N_STRING, basePath .. ".lights.group(?)#activateText", "Activate text to display in help menu", "action_placeableLightPos") |
71 | schema:register(XMLValueType.L10N_STRING, basePath .. ".lights.group(?)#deactivateText", "Deactivate text to display in help menu", "action_placeableLightNeg") |
72 | |
73 | schema:register(XMLValueType.STRING, basePath .. ".lights.group(?)#activateTime", "If defined, light will be turned on at this time of day. Format hh:mm") |
74 | schema:register(XMLValueType.STRING, basePath .. ".lights.group(?)#deactivateTime", "If defined, light will be turned off at this time of day. Format hh:mm") |
75 | schema:register(XMLValueType.STRING, basePath .. ".lights.group(?)#weatherRequiredFlags", "Space separeted list of environment flag names to be used as required mask") |
76 | schema:register(XMLValueType.STRING, basePath .. ".lights.group(?)#weatherPreventFlags", "Space separeted list of environment flag names to be used as prevent mask") |
77 | |
78 | SoundManager.registerSampleXMLPaths(schema, basePath .. ".lights.group(?).sounds", "toggle") |
79 | |
80 | schema:setXMLSpecializationType() |
81 | end |
293 | function PlaceableLights:sharedLightLoaded(i3dNode, failedReason, args) |
294 | local spec = self.spec_lights |
295 | |
296 | local sharedLight = args.sharedLight |
297 | local lightXMLFile = args.lightXMLFile |
298 | local loadingTask = args.loadingTask |
299 | local lightGroup = args.group |
300 | local i3dFilename = args.filename |
301 | |
302 | if i3dNode ~= nil and i3dNode ~= 0 then |
303 | if self.loadingState == Placeable.LOADING_STATE_OK then |
304 | sharedLight.node = lightXMLFile:getValue("light.rootNode#node", "0", i3dNode) |
305 | sharedLight.i3dFilename = i3dFilename |
306 | |
307 | sharedLight.lightShapes = {} |
308 | lightXMLFile:iterate("light.defaultLight", function (lightIndex, lightKey) |
309 | local lightShape = {} |
310 | lightShape.node = lightXMLFile:getValue(lightKey.."#node", nil, i3dNode) |
311 | if lightShape.node ~= nil then |
312 | if getHasShaderParameter(lightShape.node, "lightControl") then |
313 | lightShape.intensity = lightXMLFile:getValue(lightKey.."#intensity", 25) |
314 | |
315 | if lightGroup.hasManualLights then |
316 | setShaderParameter(lightShape.node, "lightControl", 0, 0, 0, 0, false) |
317 | else |
318 | if lightGroup.activateMinute ~= nil then |
319 | setVisibilityConditionMinuteOfDay(lightShape.node, lightGroup.activateMinute, lightGroup.deactivateMinute) |
320 | end |
321 | if lightGroup.weatherRequiredMask ~= nil or lightGroup.weatherPreventMask ~= nil then |
322 | setVisibilityConditionWeatherMask(lightShape.node, lightGroup.weatherRequiredMask or 0, lightGroup.weatherPreventMask or 0) |
323 | end |
324 | setVisibilityConditionRenderInvisible(lightShape.node, true) -- still render self illum faces when group is not visible, only apply shader |
325 | setVisibilityConditionVisibleShaderParameter(lightShape.node, lightShape.intensity) |
326 | end |
327 | |
328 | table.insert(sharedLight.lightShapes, lightShape) |
329 | else |
330 | Logging.xmlWarning(lightXMLFile, "Node '%s' has no shaderparameter 'lightControl'. Ignoring node!", getName(lightShape.node)) |
331 | end |
332 | |
333 | if sharedLight.color ~= nil and getHasShaderParameter(lightShape.node, "colorScale") then |
334 | setShaderParameter(lightShape.node, "colorScale", sharedLight.color[1], sharedLight.color[2], sharedLight.color[3], 0, false) |
335 | end |
336 | else |
337 | Logging.xmlWarning(lightXMLFile, "Could not find node for '%s'!", lightKey) |
338 | end |
339 | end) |
340 | |
341 | lightXMLFile:iterate("light.rotationNode", function (rotIndex, rotKey) |
342 | local name = lightXMLFile:getValue(rotKey .."#name") |
343 | if name ~= nil then |
344 | local node = lightXMLFile:getValue(rotKey .. "#node", nil, i3dNode) |
345 | if sharedLight.rotations[name] ~= nil then |
346 | setRotation(node, unpack(sharedLight.rotations[name])) |
347 | end |
348 | end |
349 | end) |
350 | sharedLight.rotations = nil |
351 | |
352 | link(sharedLight.linkNode, sharedLight.node) |
353 | end |
354 | |
355 | delete(i3dNode) |
356 | end |
357 | |
358 | lightXMLFile:delete() |
359 | |
360 | self:finishLoadingTask(loadingTask) |
361 | end |
449 | function PlaceableLights:updateLightState(groupIndex, isActive) |
450 | local spec = self.spec_lights |
451 | local group = spec.groups[groupIndex] |
452 | |
453 | if group.hasManualLights then |
454 | -- visibility condition shapes have their intensity set once on load |
455 | for _, sharedLight in ipairs(spec.sharedLights) do |
456 | if sharedLight.groupIndex == groupIndex then |
457 | for j=1, #sharedLight.lightShapes do |
458 | local lightShape = sharedLight.lightShapes[j] |
459 | setShaderParameter(lightShape.node, "lightControl", isActive and lightShape.intensity or 0, 0, 0, 0, false) |
460 | end |
461 | end |
462 | end |
463 | |
464 | for _, lightShape in ipairs(spec.lightShapes) do |
465 | if lightShape.groupIndex == groupIndex then |
466 | setShaderParameter(lightShape.node, "lightControl", isActive and lightShape.intensity or 0, 0, 0, 0, false) |
467 | end |
468 | end |
469 | end |
470 | |
471 | local activeLightSetup = spec.realLights.low |
472 | local inactiveLightSetup = spec.realLights.high |
473 | if self:getUseHighProfile() then |
474 | activeLightSetup = spec.realLights.high |
475 | inactiveLightSetup = spec.realLights.low |
476 | end |
477 | |
478 | for _, realLight in ipairs(activeLightSetup) do |
479 | if realLight.groupIndex == groupIndex then |
480 | setVisibility(realLight.node, not group.hasManualLights or isActive) -- lights are always active for groups which cannot be toggled manually (visiblity condition) |
481 | end |
482 | end |
483 | |
484 | for _, realLight in ipairs(inactiveLightSetup) do |
485 | if realLight.groupIndex == groupIndex then |
486 | setVisibility(realLight.node, false) |
487 | end |
488 | end |
489 | end |