365 | function Shovel:getShovelNodeIsActive(shovelNode) |
366 | local isActive = true |
367 | |
368 | if shovelNode.needsMovement then |
369 | local x,y,z = getWorldTranslation(shovelNode.node) |
370 | local _,_,dz = worldToLocal(shovelNode.node, shovelNode.lastPosition[1], shovelNode.lastPosition[2], shovelNode.lastPosition[3]) |
371 | isActive = isActive and dz < 0 |
372 | |
373 | shovelNode.lastPosition[1] = x |
374 | shovelNode.lastPosition[2] = y |
375 | shovelNode.lastPosition[3] = z |
376 | end |
377 | |
378 | if shovelNode.maxPickupAngle ~= nil then |
379 | local _,dy,_ = localDirectionToWorld(shovelNode.node, 0, 0, 1) |
380 | local angle = math.acos(dy) |
381 | if angle > shovelNode.maxPickupAngle then |
382 | return false |
383 | end |
384 | end |
385 | |
386 | if shovelNode.needsAttacherVehicle then |
387 | if self.getAttacherVehicle ~= nil and self:getAttacherVehicle() == nil then |
388 | return false |
389 | end |
390 | end |
391 | |
392 | return isActive |
393 | end |
449 | function Shovel:handleDischargeRaycast(superFunc, dischargeNode, hitObject, hitShape, hitDistance, hitFillUnitIndex, hitTerrain) |
450 | local spec = self.spec_shovel |
451 | if spec.shovelDischargeInfo.node ~= nil and spec.shovelDischargeInfo.dischargeNodeIndex == dischargeNode.index then |
452 | if hitObject ~= nil then |
453 | local fillType = self:getDischargeFillType(dischargeNode) |
454 | local allowFillType = hitObject:getFillUnitAllowsFillType(hitFillUnitIndex, fillType) |
455 | if allowFillType and hitObject:getFillUnitFreeCapacity(hitFillUnitIndex, fillType, self:getOwnerFarmId()) > 0 then |
456 | self:setDischargeState(Dischargeable.DISCHARGE_STATE_OBJECT, true) |
457 | else |
458 | if self:getDischargeState() == Dischargeable.DISCHARGE_STATE_OBJECT then |
459 | self:setDischargeState(Dischargeable.DISCHARGE_STATE_OFF, true) |
460 | end |
461 | end |
462 | else |
463 | local fillLevel = self:getFillUnitFillLevel(dischargeNode.fillUnitIndex) |
464 | if fillLevel > 0 and self:getShovelTipFactor() > 0 then |
465 | if self:getCanDischargeToGround(dischargeNode) then |
466 | self:setDischargeState(Dischargeable.DISCHARGE_STATE_GROUND, true) |
467 | else |
468 | if self:getIsActiveForInput(true) then |
469 | if not self:getCanDischargeToLand(dischargeNode) then |
470 | g_currentMission:showBlinkingWarning(g_i18n:getText("warning_youDontHaveAccessToThisLand"), 5000) |
471 | elseif not self:getCanDischargeAtPosition(dischargeNode) then |
472 | g_currentMission:showBlinkingWarning(g_i18n:getText("warning_actionNotAllowedHere"), 5000) |
473 | end |
474 | end |
475 | end |
476 | end |
477 | end |
478 | else |
479 | superFunc(self, dischargeNode, hitObject, hitShape, hitDistance, hitFillUnitIndex, hitTerrain) |
480 | end |
481 | end |
23 | function Shovel.initSpecialization() |
24 | local schema = Vehicle.xmlSchema |
25 | schema:setXMLSpecializationType("Shovel") |
26 | |
27 | schema:register(XMLValueType.BOOL, "vehicle.shovel#ignoreFillUnitFillType", "Ignore fill unit fill type", false) |
28 | schema:register(XMLValueType.BOOL, "vehicle.shovel#useSpeedLimit", "Use speed limit while shovel is turned on", false) |
29 | |
30 | schema:register(XMLValueType.NODE_INDEX, Shovel.SHOVEL_NODE_XML_KEY .. "#node", "Shovel node") |
31 | schema:register(XMLValueType.INT, Shovel.SHOVEL_NODE_XML_KEY .. "#fillUnitIndex", "Fill unit index", 1) |
32 | schema:register(XMLValueType.INT, Shovel.SHOVEL_NODE_XML_KEY .. "#loadInfoIndex", "Load info index", 1) |
33 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. "#width", "Shovel node width", 1) |
34 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. "#length", "Shovel node length", 0.5) |
35 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. "#yOffset", "Shovel node y offset", 0) |
36 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. "#zOffset", "Shovel node z offset", 0) |
37 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. "#needsMovement", "Needs movement", true) |
38 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. "#fillLitersPerSecond", "Fill liters per second", "inf.") |
39 | schema:register(XMLValueType.ANGLE, Shovel.SHOVEL_NODE_XML_KEY .. "#maxPickupAngle", "Max. pickup angle") |
40 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. "#needsAttacherVehicle", "Needs attacher vehicle connected", true) |
41 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. "#resetFillLevel", "Reset fill level to zero while the shovel node is not active", false) |
42 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. "#ignoreFillLevel", "Ignore fill level of the fill unit while filling", false) |
43 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. "#ignoreFarmlandState", "Ignore farmland state for pickup", false) |
44 | schema:register(XMLValueType.BOOL, Shovel.SHOVEL_NODE_XML_KEY .. ".smoothing#allowed", "Leveler smoothes while driving backward", false) |
45 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. ".smoothing#radius", "Smooth ground radius", 0.5) |
46 | schema:register(XMLValueType.FLOAT, Shovel.SHOVEL_NODE_XML_KEY .. ".smoothing#overlap", "Radius overlap", 1.7) |
47 | |
48 | schema:register(XMLValueType.INT, "vehicle.shovel.dischargeInfo#dischargeNodeIndex", "Discharge node index", 1) |
49 | schema:register(XMLValueType.NODE_INDEX, "vehicle.shovel.dischargeInfo#node", "Discharge info node") |
50 | |
51 | schema:register(XMLValueType.ANGLE, "vehicle.shovel.dischargeInfo#minSpeedAngle", "Discharge info min. speed angle") |
52 | schema:register(XMLValueType.ANGLE, "vehicle.shovel.dischargeInfo#maxSpeedAngle", "Discharge info max. speed angle") |
53 | |
54 | EffectManager.registerEffectXMLPaths(schema, "vehicle.shovel.fillEffect") |
55 | |
56 | schema:setXMLSpecializationType() |
57 | end |
329 | function Shovel:loadShovelNode(xmlFile, key, shovelNode) |
330 | shovelNode.node = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
331 | if shovelNode.node == nil then |
332 | Logging.xmlWarning(self.xmlFile, "Missing 'node' for shovelNode '%s'!", key) |
333 | return false |
334 | end |
335 | |
336 | shovelNode.fillUnitIndex = xmlFile:getValue(key .. "#fillUnitIndex", 1) |
337 | shovelNode.loadInfoIndex = xmlFile:getValue(key .. "#loadInfoIndex", 1) |
338 | |
339 | shovelNode.width = xmlFile:getValue(key .. "#width", 1) |
340 | shovelNode.length = xmlFile:getValue(key .. "#length", 0.5) |
341 | shovelNode.yOffset = xmlFile:getValue(key .. "#yOffset", 0) |
342 | shovelNode.zOffset = xmlFile:getValue(key .. "#zOffset", 0) |
343 | |
344 | shovelNode.needsMovement = xmlFile:getValue(key .. "#needsMovement", true) |
345 | shovelNode.lastPosition = {0, 0, 0} |
346 | |
347 | shovelNode.fillLitersPerSecond = xmlFile:getValue(key .. "#fillLitersPerSecond", math.huge) / 1000 |
348 | shovelNode.maxPickupAngle = xmlFile:getValue(key .. "#maxPickupAngle") |
349 | |
350 | shovelNode.needsAttacherVehicle = xmlFile:getValue(key .. "#needsAttacherVehicle", true) |
351 | |
352 | shovelNode.resetFillLevel = xmlFile:getValue(key .. "#resetFillLevel", false) |
353 | shovelNode.ignoreFillLevel = xmlFile:getValue(key .. "#ignoreFillLevel", false) |
354 | shovelNode.ignoreFarmlandState = xmlFile:getValue(key .. "#ignoreFarmlandState", false) |
355 | |
356 | shovelNode.allowsSmoothing = xmlFile:getValue(key .. ".smoothing#allowed", false) |
357 | shovelNode.smoothGroundRadius = xmlFile:getValue(key .. ".smoothing#radius", 0.5) |
358 | shovelNode.smoothOverlap = xmlFile:getValue(key .. ".smoothing#overlap", 1.7) |
359 | |
360 | return true |
361 | end |
98 | function Shovel:onLoad(savegame) |
99 | local spec = self.spec_shovel |
100 | |
101 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpNode", "vehicle.shovel.shovelNode#node") --FS17 to FS19 |
102 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpWidth", "vehicle.shovel.shovelNode#width") --FS17 to FS19 |
103 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpLength", "vehicle.shovel.shovelNode#length") --FS17 to FS19 |
104 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpYOffset") --FS17 to FS19 |
105 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpRequiresMovement", "vehicle.shovel.shovelNode#needsMovement") --FS17 to FS19 |
106 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.shovel#pickUpNeedsToBeTurnedOn", "vehicle.shovel.shovelNode#needsActivation") --FS17 to FS19 |
107 | |
108 | spec.ignoreFillUnitFillType = self.xmlFile:getValue("vehicle.shovel#ignoreFillUnitFillType", false) |
109 | spec.useSpeedLimit = self.xmlFile:getValue("vehicle.shovel#useSpeedLimit", false) |
110 | |
111 | spec.shovelNodes = {} |
112 | local i = 0 |
113 | while true do |
114 | local key = string.format("vehicle.shovel.shovelNode(%d)", i) |
115 | if not self.xmlFile:hasProperty(key) then |
116 | break |
117 | end |
118 | |
119 | local shovelNode = {} |
120 | |
121 | if self:loadShovelNode(self.xmlFile, key, shovelNode) then |
122 | table.insert(spec.shovelNodes, shovelNode) |
123 | end |
124 | i = i + 1 |
125 | end |
126 | |
127 | spec.shovelDischargeInfo = {} |
128 | spec.shovelDischargeInfo.dischargeNodeIndex = self.xmlFile:getValue("vehicle.shovel.dischargeInfo#dischargeNodeIndex", 1) |
129 | spec.shovelDischargeInfo.node = self.xmlFile:getValue("vehicle.shovel.dischargeInfo#node", nil, self.components, self.i3dMappings) |
130 | |
131 | if spec.shovelDischargeInfo.node ~= nil then |
132 | local minSpeedAngle = self.xmlFile:getValue("vehicle.shovel.dischargeInfo#minSpeedAngle") |
133 | local maxSpeedAngle = self.xmlFile:getValue("vehicle.shovel.dischargeInfo#maxSpeedAngle") |
134 | if minSpeedAngle == nil or maxSpeedAngle == nil then |
135 | Logging.xmlWarning(self.xmlFile, "Missing 'minSpeedAngle' or 'maxSpeedAngle' for dischargeNode 'vehicle.shovel.dischargeInfo'") |
136 | return false |
137 | end |
138 | spec.shovelDischargeInfo.minSpeedAngle = minSpeedAngle |
139 | spec.shovelDischargeInfo.maxSpeedAngle = maxSpeedAngle |
140 | end |
141 | |
142 | if self.isClient then |
143 | spec.fillEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.shovel.fillEffect", self.components, self, self.i3dMappings) |
144 | spec.fillEffectTime = 0 |
145 | end |
146 | |
147 | spec.effectDirtyFlag = self:getNextDirtyFlag() |
148 | spec.loadingFillType = FillType.UNKNOWN |
149 | spec.lastValidFillType = FillType.UNKNOWN |
150 | spec.smoothAccumulation = 0 |
151 | |
152 | if #spec.shovelNodes == 0 then |
153 | SpecializationUtil.removeEventListener(self, "onReadStream", Shovel) |
154 | SpecializationUtil.removeEventListener(self, "onWriteStream", Shovel) |
155 | SpecializationUtil.removeEventListener(self, "onReadUpdateStream", Shovel) |
156 | SpecializationUtil.removeEventListener(self, "onWriteUpdateStream", Shovel) |
157 | SpecializationUtil.removeEventListener(self, "onUpdateTick", Shovel) |
158 | SpecializationUtil.removeEventListener(self, "onFillUnitFillLevelChanged", Shovel) |
159 | end |
160 | end |
209 | function Shovel:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
210 | local spec = self.spec_shovel |
211 | |
212 | if self.isServer then |
213 | local validPickupFillType = FillType.UNKNOWN |
214 | for _, shovelNode in pairs(spec.shovelNodes) do |
215 | if self:getShovelNodeIsActive(shovelNode) then |
216 | local fillLevel = self:getFillUnitFillLevel(shovelNode.fillUnitIndex) |
217 | local capacity = self:getFillUnitCapacity(shovelNode.fillUnitIndex) |
218 | if fillLevel < capacity or shovelNode.ignoreFillLevel then |
219 | local pickupFillType = self:getFillUnitFillType(shovelNode.fillUnitIndex) |
220 | if fillLevel / capacity < self:getFillTypeChangeThreshold() then |
221 | pickupFillType = FillType.UNKNOWN |
222 | end |
223 | local minValidLiter = g_densityMapHeightManager:getMinValidLiterValue(pickupFillType) or 0 |
224 | local freeCapacity = math.min(shovelNode.ignoreFillLevel and math.huge or capacity - fillLevel, shovelNode.fillLitersPerSecond * dt) |
225 | local sx,sy,sz = localToWorld(shovelNode.node, -shovelNode.width * 0.5, shovelNode.yOffset, shovelNode.zOffset) |
226 | local ex,ey,ez = localToWorld(shovelNode.node, shovelNode.width * 0.5, shovelNode.yOffset, shovelNode.zOffset) |
227 | local innerRadius = shovelNode.length |
228 | local radius = nil |
229 | |
230 | if self:getCanShovelAtPosition(shovelNode) then |
231 | if pickupFillType == FillType.UNKNOWN or spec.ignoreFillUnitFillType then |
232 | pickupFillType = DensityMapHeightUtil.getFillTypeAtLine(sx,sy,sz, ex,ey,ez, innerRadius) |
233 | end |
234 | |
235 | if pickupFillType ~= FillType.UNKNOWN and self:getFillUnitSupportsFillType(shovelNode.fillUnitIndex, pickupFillType) and self:getFillUnitAllowsFillType(shovelNode.fillUnitIndex, pickupFillType) then |
236 | local fillDelta, lineOffset = DensityMapHeightUtil.tipToGroundAroundLine(self, -freeCapacity - minValidLiter, pickupFillType, sx,sy,sz, ex,ey,ez, innerRadius, radius, shovelNode.lineOffset, true, nil) |
237 | shovelNode.lineOffset = lineOffset |
238 | |
239 | if not shovelNode.ignoreFillLevel and -fillDelta > freeCapacity then |
240 | self:setFillUnitCapacity(shovelNode.fillUnitIndex, fillLevel - fillDelta) |
241 | shovelNode.capacityChanged = true |
242 | end |
243 | |
244 | if fillDelta < 0 then |
245 | local loadInfo = self:getFillVolumeLoadInfo(shovelNode.loadInfoIndex) |
246 | self:addFillUnitFillLevel(self:getOwnerFarmId(), shovelNode.fillUnitIndex, -fillDelta, pickupFillType, ToolType.UNDEFINED, loadInfo) |
247 | validPickupFillType = pickupFillType |
248 | |
249 | -- call fill level changed callack to inform bunker silo about change |
250 | self:notifiyBunkerSilo(fillDelta, pickupFillType, (sx+ex) * 0.5, (sy+ey) * 0.5, (sz+ez) * 0.5) |
251 | end |
252 | end |
253 | end |
254 | end |
255 | elseif shovelNode.resetFillLevel then |
256 | local fillLevel = self:getFillUnitFillLevel(shovelNode.fillUnitIndex) |
257 | if fillLevel > 0 then |
258 | self:addFillUnitFillLevel(self:getOwnerFarmId(), shovelNode.fillUnitIndex, -fillLevel, self:getFillUnitFillType(shovelNode.fillUnitIndex), ToolType.UNDEFINED) |
259 | end |
260 | end |
261 | |
262 | if shovelNode.allowsSmoothing then |
263 | local _,dy,_ = localDirectionToWorld(shovelNode.node, 0, 0, 1) |
264 | local angle = math.acos(dy) |
265 | if angle > shovelNode.maxPickupAngle then |
266 | local smoothAmount = 0 |
267 | if self.lastSpeedReal > 0.0002 then -- start smoothing if driving faster than 0.7km/h |
268 | smoothAmount = spec.smoothAccumulation + math.max(self.lastMovedDistance * 0.5, 0.0003*dt) -- smooth 1.2m per meter driving or at least 0.3m/s |
269 | local rounded = DensityMapHeightUtil.getRoundedHeightValue(smoothAmount) |
270 | spec.smoothAccumulation = smoothAmount - rounded |
271 | else |
272 | spec.smoothAccumulation = 0 |
273 | end |
274 | |
275 | if smoothAmount > 0 then |
276 | DensityMapHeightUtil.smoothAroundLine(shovelNode.node, shovelNode.width, shovelNode.smoothGroundRadius, shovelNode.smoothOverlap, smoothAmount) |
277 | end |
278 | end |
279 | end |
280 | end |
281 | |
282 | if validPickupFillType == FillType.UNKNOWN then |
283 | spec.fillEffectTime = spec.fillEffectTime - dt |
284 | if spec.fillEffectTime > 0 then |
285 | validPickupFillType = spec.loadingFillType |
286 | end |
287 | else |
288 | spec.fillEffectTime = 500 |
289 | end |
290 | |
291 | if spec.loadingFillType ~= validPickupFillType then |
292 | spec.loadingFillType = validPickupFillType |
293 | self:raiseDirtyFlags(spec.effectDirtyFlag) |
294 | end |
295 | end |
296 | |
297 | if self.isClient then |
298 | if spec.loadingFillType ~= FillType.UNKNOWN then |
299 | g_effectManager:setFillType(spec.fillEffects, spec.loadingFillType) |
300 | g_effectManager:startEffects(spec.fillEffects) |
301 | else |
302 | g_effectManager:stopEffects(spec.fillEffects) |
303 | end |
304 | end |
305 | end |
574 | function Shovel:updateDebugValues(values) |
575 | local spec = self.spec_shovel |
576 | |
577 | local info = spec.shovelDischargeInfo |
578 | if info.node ~= nil then |
579 | local _,dy,_ = localDirectionToWorld(info.node, 0,0,1) |
580 | local angle = math.acos(dy) |
581 | table.insert(values, {name="angle", value=math.deg(angle)}) |
582 | table.insert(values, {name="minSpeedAngle", value=math.deg(info.minSpeedAngle)}) |
583 | table.insert(values, {name="maxSpeedAngle", value=math.deg(info.maxSpeedAngle)}) |
584 | |
585 | if angle > info.minSpeedAngle then |
586 | local factor = math.max(0, math.min(1.0, (angle - info.minSpeedAngle) / (info.maxSpeedAngle - info.minSpeedAngle))) |
587 | table.insert(values, {name="factor", value=factor}) |
588 | else |
589 | table.insert(values, {name="factor", value="Out of Range - 0"}) |
590 | end |
591 | end |
592 | end |