404 | function Washable:addToLocalWashableNode(node, updateFunc, customIndex, extraParams) |
405 | local spec = self.spec_washable |
406 | |
407 | local nodeData = {} |
408 | |
409 | --if washableNode already exists we add node to existing washableNode |
410 | if customIndex ~= nil then |
411 | if spec.washableNodesByIndex[customIndex] ~= nil then |
412 | table.insert(spec.washableNodesByIndex[customIndex].nodes, node) |
413 | return |
414 | else |
415 | spec.washableNodesByIndex[customIndex] = nodeData |
416 | end |
417 | end |
418 | |
419 | --if washableNode doesn't exists we create a new one |
420 | nodeData.nodes = {node} |
421 | nodeData.updateFunc = updateFunc |
422 | nodeData.dirtAmount = 0 |
423 | nodeData.dirtAmountSent = 0 |
424 | |
425 | nodeData.colorChanged = false |
426 | local defaultColor, _ = g_currentMission.environment:getDirtColors() |
427 | nodeData.color = {defaultColor[1], defaultColor[2], defaultColor[3]} |
428 | nodeData.defaultColor = {defaultColor[1], defaultColor[2], defaultColor[3]} |
429 | |
430 | if extraParams ~= nil then |
431 | for i, v in pairs(extraParams) do |
432 | nodeData[i] = v |
433 | end |
434 | end |
435 | |
436 | table.insert(spec.washableNodes, nodeData) |
437 | end |
30 | function Washable.initSpecialization() |
31 | local schema = Vehicle.xmlSchema |
32 | schema:setXMLSpecializationType("Washable") |
33 | schema:register(XMLValueType.FLOAT, "vehicle.washable#dirtDuration", "Duration until fully dirty (minutes)", 90) |
34 | schema:register(XMLValueType.FLOAT, "vehicle.washable#washDuration", "Duration until fully clean (minutes)", 1) |
35 | schema:register(XMLValueType.FLOAT, "vehicle.washable#workMultiplier", "Multiplier while working", 4) |
36 | schema:register(XMLValueType.FLOAT, "vehicle.washable#fieldMultiplier", "Multiplier while on field", 2) |
37 | schema:register(XMLValueType.STRING, "vehicle.washable#blockedWashTypes", "Block specific ways to clean vehicle (HIGH_PRESSURE_WASHER, RAIN, TRIGGER)") |
38 | schema:setXMLSpecializationType() |
39 | |
40 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
41 | schemaSavegame:register(XMLValueType.FLOAT, "vehicles.vehicle(?).washable.dirtNode(?)#amount", "Dirt amount") |
42 | schemaSavegame:register(XMLValueType.FLOAT, "vehicles.vehicle(?).washable.dirtNode(?)#snowScale", "Snow scale") |
43 | end |
82 | function Washable:onLoad(savegame) |
83 | local spec = self.spec_washable |
84 | |
85 | spec.washableNodes = {} |
86 | spec.washableNodesByIndex = {} |
87 | self:addToLocalWashableNode(nil, Washable.updateDirtAmount, nil, nil) -- create global / default washableNode |
88 | spec.globalWashableNode = spec.washableNodes[1] |
89 | |
90 | spec.dirtDuration = self.xmlFile:getValue("vehicle.washable#dirtDuration", 90) * 60 * 1000 |
91 | if spec.dirtDuration ~= 0 then |
92 | spec.dirtDuration = 1 / spec.dirtDuration |
93 | end |
94 | |
95 | spec.washDuration = math.max(self.xmlFile:getValue("vehicle.washable#washDuration", 1) * 60 * 1000, 0.00001) -- washDuration == 0 washes vehicle immediately |
96 | |
97 | spec.workMultiplier = self.xmlFile:getValue("vehicle.washable#workMultiplier", 4) |
98 | spec.fieldMultiplier = self.xmlFile:getValue("vehicle.washable#fieldMultiplier", 2) |
99 | |
100 | spec.blockedWashTypes = {} |
101 | local blockedWashTypesStr = self.xmlFile:getValue("vehicle.washable#blockedWashTypes") |
102 | if blockedWashTypesStr ~= nil then |
103 | local blockedWashTypes = blockedWashTypesStr:split(" ") |
104 | for _, typeStr in pairs(blockedWashTypes) do |
105 | typeStr = "WASHTYPE_" .. typeStr |
106 | if Washable[typeStr] ~= nil then |
107 | spec.blockedWashTypes[Washable[typeStr]] = true |
108 | else |
109 | Logging.xmlWarning(self.xmlFile, "Unknown wash type '%s' in '%s'", typeStr, "vehicle.washable#blockedWashTypes") |
110 | end |
111 | end |
112 | end |
113 | |
114 | spec.lastDirtMultiplier = 0 |
115 | |
116 | spec.dirtyFlag = self:getNextDirtyFlag() |
117 | end |
121 | function Washable:onLoadFinished(savegame) |
122 | local spec = self.spec_washable |
123 | |
124 | -- getting als washable nodes in postLoad to make sure also linked nodes are washable |
125 | for _, component in pairs(self.components) do |
126 | self:addAllSubWashableNodes(component.node) |
127 | end |
128 | |
129 | if savegame ~= nil and Washable.getIntervalMultiplier() ~= 0 then |
130 | for i=1, #spec.washableNodes do |
131 | local nodeData = spec.washableNodes[i] |
132 | local nodeKey = string.format("%s.washable.dirtNode(%d)", savegame.key, i-1) |
133 | local amount = savegame.xmlFile:getValue(nodeKey.."#amount", 0) |
134 | self:setNodeDirtAmount(nodeData, amount, true) |
135 | |
136 | if nodeData.loadFromSavegameFunc ~= nil then |
137 | nodeData.loadFromSavegameFunc(savegame.xmlFile, nodeKey) |
138 | end |
139 | end |
140 | else |
141 | for i=1, #spec.washableNodes do |
142 | local nodeData = spec.washableNodes[i] |
143 | self:setNodeDirtAmount(nodeData, 0, true) |
144 | end |
145 | end |
146 | end |
239 | function Washable:onUpdateTick(dt, isActive, isActiveForInput, isSelected) |
240 | if self.isServer then |
241 | local spec = self.spec_washable |
242 | spec.lastDirtMultiplier = self:getDirtMultiplier() * Washable.getIntervalMultiplier() * Platform.gameplay.dirtDurationScale |
243 | |
244 | local allowsWashingByRain = self:getAllowsWashingByType(Washable.WASHTYPE_RAIN) |
245 | local rainScale, timeSinceLastRain, temperature = 0, 0, 0 |
246 | if allowsWashingByRain then |
247 | local weather = g_currentMission.environment.weather |
248 | rainScale = weather:getRainFallScale() |
249 | timeSinceLastRain = weather:getTimeSinceLastRain() |
250 | temperature = weather:getCurrentTemperature() |
251 | end |
252 | |
253 | for i=1, #spec.washableNodes do |
254 | local nodeData = spec.washableNodes[i] |
255 | local changedAmount = nodeData.updateFunc(self, nodeData, dt, allowsWashingByRain, rainScale, timeSinceLastRain, temperature) |
256 | if changedAmount ~= 0 then |
257 | self:setNodeDirtAmount(nodeData, nodeData.dirtAmount + changedAmount) |
258 | end |
259 | end |
260 | end |
261 | end |
202 | function Washable.readWashableNodeData(self, streamId, connection) |
203 | local spec = self.spec_washable |
204 | for i=1, #spec.washableNodes do |
205 | local nodeData = spec.washableNodes[i] |
206 | local dirtAmount = streamReadUIntN(streamId, Washable.SEND_NUM_BITS) / Washable.SEND_MAX_VALUE |
207 | self:setNodeDirtAmount(nodeData, dirtAmount, true) |
208 | |
209 | if streamReadBool(streamId) then |
210 | local r = streamReadUIntN(streamId, Washable.SEND_NUM_BITS) / Washable.SEND_MAX_VALUE |
211 | local g = streamReadUIntN(streamId, Washable.SEND_NUM_BITS) / Washable.SEND_MAX_VALUE |
212 | local b = streamReadUIntN(streamId, Washable.SEND_NUM_BITS) / Washable.SEND_MAX_VALUE |
213 | |
214 | self:setNodeDirtColor(nodeData, r, g, b, true) |
215 | end |
216 | end |
217 | end |
70 | function Washable.registerEventListeners(vehicleType) |
71 | SpecializationUtil.registerEventListener(vehicleType, "onLoad", Washable) |
72 | SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", Washable) |
73 | SpecializationUtil.registerEventListener(vehicleType, "onReadStream", Washable) |
74 | SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", Washable) |
75 | SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", Washable) |
76 | SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", Washable) |
77 | SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", Washable) |
78 | end |
47 | function Washable.registerFunctions(vehicleType) |
48 | SpecializationUtil.registerFunction(vehicleType, "updateDirtAmount", Washable.updateDirtAmount) |
49 | SpecializationUtil.registerFunction(vehicleType, "addDirtAmount", Washable.addDirtAmount) |
50 | SpecializationUtil.registerFunction(vehicleType, "getDirtAmount", Washable.getDirtAmount) |
51 | SpecializationUtil.registerFunction(vehicleType, "setNodeDirtAmount", Washable.setNodeDirtAmount) |
52 | SpecializationUtil.registerFunction(vehicleType, "getNodeDirtAmount", Washable.getNodeDirtAmount) |
53 | SpecializationUtil.registerFunction(vehicleType, "setNodeDirtColor", Washable.setNodeDirtColor) |
54 | SpecializationUtil.registerFunction(vehicleType, "addAllSubWashableNodes", Washable.addAllSubWashableNodes) |
55 | SpecializationUtil.registerFunction(vehicleType, "addWashableNodes", Washable.addWashableNodes) |
56 | SpecializationUtil.registerFunction(vehicleType, "validateWashableNode", Washable.validateWashableNode) |
57 | SpecializationUtil.registerFunction(vehicleType, "addToGlobalWashableNode", Washable.addToGlobalWashableNode) |
58 | SpecializationUtil.registerFunction(vehicleType, "getWashableNodeByCustomIndex", Washable.getWashableNodeByCustomIndex) |
59 | SpecializationUtil.registerFunction(vehicleType, "addToLocalWashableNode", Washable.addToLocalWashableNode) |
60 | SpecializationUtil.registerFunction(vehicleType, "removeAllSubWashableNodes", Washable.removeAllSubWashableNodes) |
61 | SpecializationUtil.registerFunction(vehicleType, "removeWashableNode", Washable.removeWashableNode) |
62 | SpecializationUtil.registerFunction(vehicleType, "getDirtMultiplier", Washable.getDirtMultiplier) |
63 | SpecializationUtil.registerFunction(vehicleType, "getWorkDirtMultiplier", Washable.getWorkDirtMultiplier) |
64 | SpecializationUtil.registerFunction(vehicleType, "getWashDuration", Washable.getWashDuration) |
65 | SpecializationUtil.registerFunction(vehicleType, "getAllowsWashingByType", Washable.getAllowsWashingByType) |
66 | end |
150 | function Washable:saveToXMLFile(xmlFile, key, usedModNames) |
151 | local spec = self.spec_washable |
152 | |
153 | for i=1, #spec.washableNodes do |
154 | local nodeData = spec.washableNodes[i] |
155 | local nodeKey = string.format("%s.dirtNode(%d)", key, i-1) |
156 | xmlFile:setValue(nodeKey.."#amount", nodeData.dirtAmount) |
157 | |
158 | if nodeData.saveToSavegameFunc ~= nil then |
159 | nodeData.saveToSavegameFunc(xmlFile, nodeKey) |
160 | end |
161 | end |
162 | end |
310 | function Washable:setNodeDirtAmount(nodeData, dirtAmount, force) |
311 | local spec = self.spec_washable |
312 | nodeData.dirtAmount = MathUtil.clamp(dirtAmount, 0, 1) |
313 | |
314 | local diff = nodeData.dirtAmountSent - nodeData.dirtAmount |
315 | if math.abs(diff) > Washable.SEND_THRESHOLD or force then |
316 | for i=1, #nodeData.nodes do |
317 | local node = nodeData.nodes[i] |
318 | local x, _, z, w = getShaderParameter(node, "RDT") |
319 | setShaderParameter(node, "RDT", x, nodeData.dirtAmount, 0, w, false) |
320 | end |
321 | |
322 | if self.isServer then |
323 | self:raiseDirtyFlags(spec.dirtyFlag) |
324 | nodeData.dirtAmountSent = nodeData.dirtAmount |
325 | end |
326 | end |
327 | end |
337 | function Washable:setNodeDirtColor(nodeData, r, g, b, force) |
338 | local spec = self.spec_washable |
339 | local cr, cg, cb = nodeData.color[1], nodeData.color[2], nodeData.color[3] |
340 | if (math.abs(r-cr) > Washable.SEND_THRESHOLD or math.abs(g-cg) > Washable.SEND_THRESHOLD or math.abs(b-cb) > Washable.SEND_THRESHOLD) or force then |
341 | for _, node in pairs(nodeData.nodes) do |
342 | local _, _, _, w = getShaderParameter(node, "dirtColor") |
343 | setShaderParameter(node, "dirtColor", r, g, b, w, false) |
344 | end |
345 | |
346 | nodeData.color[1], nodeData.color[2], nodeData.color[3] = r, g, b |
347 | |
348 | if self.isServer then |
349 | self:raiseDirtyFlags(spec.dirtyFlag) |
350 | nodeData.colorChanged = true |
351 | end |
352 | end |
353 | end |
525 | function Washable:updateDebugValues(values) |
526 | local spec = self.spec_washable |
527 | if spec.washableNodes ~= nil then |
528 | local allowsWashingByRain = self:getAllowsWashingByType(Washable.WASHTYPE_RAIN) |
529 | local rainScale, timeSinceLastRain, temperature = 0, 0, 0 |
530 | if allowsWashingByRain then |
531 | local weather = g_currentMission.environment.weather |
532 | rainScale = weather:getRainFallScale() |
533 | timeSinceLastRain = weather:getTimeSinceLastRain() |
534 | temperature = weather:getCurrentTemperature() |
535 | end |
536 | |
537 | for i, nodeData in ipairs(spec.washableNodes) do |
538 | local changedAmount = nodeData.updateFunc(self, nodeData, 3600000, allowsWashingByRain, rainScale, timeSinceLastRain, temperature) |
539 | table.insert(values, {name="WashableNode"..i, value=string.format("%.4f a/h (%.2f) (color %.2f %.2f %.2f)", changedAmount, spec.washableNodes[i].dirtAmount, nodeData.color[1], nodeData.color[2], nodeData.color[3])}) |
540 | end |
541 | end |
542 | end |
265 | function Washable:updateDirtAmount(nodeData, dt, allowsWashingByRain, rainScale, timeSinceLastRain, temperature) |
266 | local spec = self.spec_washable |
267 | local change = 0 |
268 | |
269 | if allowsWashingByRain then |
270 | if rainScale > 0.1 and timeSinceLastRain < 30 and temperature > 0 then |
271 | if nodeData.dirtAmount > 0.5 then |
272 | change = -(dt / spec.washDuration) |
273 | end |
274 | end |
275 | end |
276 | |
277 | local dirtMultiplier = spec.lastDirtMultiplier |
278 | if dirtMultiplier ~= 0 then |
279 | change = dt * spec.dirtDuration * dirtMultiplier |
280 | end |
281 | |
282 | return change |
283 | end |
221 | function Washable.writeWashableNodeData(self, streamId, connection) |
222 | local spec = self.spec_washable |
223 | for i=1, #spec.washableNodes do |
224 | local nodeData = spec.washableNodes[i] |
225 | streamWriteUIntN(streamId, math.floor(nodeData.dirtAmount * Washable.SEND_MAX_VALUE + 0.5), Washable.SEND_NUM_BITS) |
226 | |
227 | streamWriteBool(streamId, nodeData.colorChanged) |
228 | if nodeData.colorChanged then |
229 | streamWriteUIntN(streamId, math.floor(nodeData.color[1] * Washable.SEND_MAX_VALUE + 0.5), Washable.SEND_NUM_BITS) |
230 | streamWriteUIntN(streamId, math.floor(nodeData.color[2] * Washable.SEND_MAX_VALUE + 0.5), Washable.SEND_NUM_BITS) |
231 | streamWriteUIntN(streamId, math.floor(nodeData.color[3] * Washable.SEND_MAX_VALUE + 0.5), Washable.SEND_NUM_BITS) |
232 | nodeData.colorChanged = false |
233 | end |
234 | end |
235 | end |