434 | function MixerWagon:addFillUnitFillLevel(superFunc, farmId, fillUnitIndex, fillLevelDelta, fillTypeIndex, toolType, fillPositionData) |
435 | local spec = self.spec_mixerWagon |
436 | |
437 | if fillUnitIndex ~= spec.fillUnitIndex then |
438 | return superFunc(self, farmId, fillUnitIndex, fillLevelDelta, fillTypeIndex, toolType, fillPositionData) |
439 | end |
440 | |
441 | local oldFillLevel = self:getFillUnitFillLevel(fillUnitIndex) |
442 | |
443 | local mixerWagonFillType = spec.fillTypeToMixerWagonFillType[fillTypeIndex] |
444 | |
445 | -- allow to put forage back to mixer. Split it up into valid forage mixing ratios |
446 | if fillTypeIndex == FillType.FORAGE and fillLevelDelta > 0 then |
447 | for _, entry in pairs(spec.mixerWagonFillTypes) do |
448 | local delta = fillLevelDelta * entry.ratio |
449 | self:addFillUnitFillLevel(farmId, fillUnitIndex, delta, next(entry.fillTypes), toolType, fillPositionData) |
450 | end |
451 | |
452 | return fillLevelDelta |
453 | end |
454 | |
455 | if mixerWagonFillType == nil then |
456 | -- used for discharge |
457 | if fillLevelDelta < 0 and oldFillLevel > 0 then |
458 | -- remove values from all fill types such that the ratio doesn't change |
459 | fillLevelDelta = math.max(fillLevelDelta, -oldFillLevel) |
460 | |
461 | local newFillLevel = 0 |
462 | for _, entry in pairs(spec.mixerWagonFillTypes) do |
463 | local entryDelta = fillLevelDelta * (entry.fillLevel / oldFillLevel) |
464 | entry.fillLevel = math.max(entry.fillLevel + entryDelta, 0) |
465 | newFillLevel = newFillLevel + entry.fillLevel |
466 | end |
467 | |
468 | if newFillLevel < 0.1 then |
469 | for _, entry in pairs(spec.mixerWagonFillTypes) do |
470 | entry.fillLevel = 0 |
471 | end |
472 | fillLevelDelta = -oldFillLevel |
473 | end |
474 | |
475 | self:raiseDirtyFlags(spec.dirtyFlag) |
476 | local ret = superFunc(self, farmId, fillUnitIndex, fillLevelDelta, fillTypeIndex, toolType, fillPositionData) |
477 | return ret |
478 | end |
479 | |
480 | return 0 |
481 | end |
482 | |
483 | local capacity = self:getFillUnitCapacity(fillUnitIndex) |
484 | local free = capacity - oldFillLevel |
485 | |
486 | if fillLevelDelta > 0 then |
487 | mixerWagonFillType.fillLevel = mixerWagonFillType.fillLevel + math.min(free, fillLevelDelta) |
488 | if self:getIsSynchronized() then |
489 | spec.activeTimer = spec.activeTimerMax |
490 | end |
491 | else |
492 | mixerWagonFillType.fillLevel = math.max(0, mixerWagonFillType.fillLevel + fillLevelDelta) |
493 | end |
494 | |
495 | local newFillLevel = 0 |
496 | for _, fillType in pairs(spec.mixerWagonFillTypes) do |
497 | newFillLevel = newFillLevel + fillType.fillLevel |
498 | end |
499 | newFillLevel = MathUtil.clamp(newFillLevel, 0, self:getFillUnitCapacity(fillUnitIndex)) |
500 | |
501 | local newFillType = FillType.UNKNOWN |
502 | local isSingleFilled = false |
503 | local isForageOk = false |
504 | |
505 | for _, fillType in pairs(spec.mixerWagonFillTypes) do |
506 | if newFillLevel == fillType.fillLevel then |
507 | isSingleFilled = true |
508 | newFillType = next(mixerWagonFillType.fillTypes) |
509 | break |
510 | end |
511 | end |
512 | |
513 | if not isSingleFilled then |
514 | isForageOk = true |
515 | for _, fillType in pairs(spec.mixerWagonFillTypes) do |
516 | if fillType.fillLevel < fillType.minPercentage * newFillLevel - 0.01 or fillType.fillLevel > fillType.maxPercentage * newFillLevel + 0.01 then |
517 | isForageOk = false |
518 | break |
519 | end |
520 | end |
521 | end |
522 | |
523 | if isForageOk then |
524 | newFillType = FillType.FORAGE |
525 | elseif not isSingleFilled then |
526 | newFillType = FillType.FORAGE_MIXING |
527 | end |
528 | |
529 | self:raiseDirtyFlags(spec.dirtyFlag) |
530 | |
531 | self:setFillUnitFillType(fillUnitIndex, newFillType) |
532 | |
533 | return superFunc(self, farmId, fillUnitIndex, newFillLevel-oldFillLevel, newFillType, toolType, fillPositionData) |
534 | end |
29 | function MixerWagon.initSpecialization() |
30 | local schema = Vehicle.xmlSchema |
31 | schema:setXMLSpecializationType("MixerWagon") |
32 | |
33 | AnimationManager.registerAnimationNodesXMLPaths(schema, "vehicle.mixerWagon.mixAnimationNodes") |
34 | AnimationManager.registerAnimationNodesXMLPaths(schema, "vehicle.mixerWagon.pickupAnimationNodes") |
35 | |
36 | schema:register(XMLValueType.NODE_INDEX, "vehicle.mixerWagon.baleTriggers.baleTrigger(?)#node", "Bale trigger node") |
37 | schema:register(XMLValueType.FLOAT, "vehicle.mixerWagon.baleTriggers.baleTrigger(?)#pickupSpeed", "Bale pickup speed in liter per second", 500) |
38 | schema:register(XMLValueType.BOOL, "vehicle.mixerWagon.baleTriggers.baleTrigger(?)#needsSetIsTurnedOn", "Vehicle needs to be turned on to pickup bales with this trigger", false) |
39 | schema:register(XMLValueType.BOOL, "vehicle.mixerWagon.baleTriggers.baleTrigger(?)#useEffect", "Filling effect is played while picking up a bale", false) |
40 | |
41 | schema:register(XMLValueType.TIME, "vehicle.mixerWagon#mixingTime", "Mixing time after the fill level was changed", 5) |
42 | schema:register(XMLValueType.INT, "vehicle.mixerWagon#fillUnitIndex", "Fill unit index", 1) |
43 | schema:register(XMLValueType.STRING, "vehicle.mixerWagon#recipe", "Recipe fill type name", "Forage") |
44 | |
45 | EffectManager.registerEffectXMLPaths(schema, "vehicle.mixerWagon.fillEffect") |
46 | |
47 | schema:setXMLSpecializationType() |
48 | |
49 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
50 | schemaSavegame:register(XMLValueType.FLOAT, "vehicles.vehicle(?).mixerWagon.fillType(?)#fillLevel", "Fill level", 0) |
51 | end |
400 | function MixerWagon:mixerWagonBaleTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId) |
401 | -- this happens if a compound child of a deleted compound is entering |
402 | if otherActorId ~= 0 then |
403 | local bale = g_currentMission:getNodeObject(otherActorId) |
404 | if bale ~= nil then |
405 | if bale:isa(Bale) then |
406 | local spec = self.spec_mixerWagon |
407 | |
408 | if self:getFillUnitSupportsFillType(spec.fillUnitIndex, bale:getFillType()) then |
409 | for i=1, #spec.baleTriggers do |
410 | local baleTrigger = spec.baleTriggers[i] |
411 | if baleTrigger.node == triggerId then |
412 | if onEnter then |
413 | baleTrigger.balesInTrigger[bale] = (baleTrigger.balesInTrigger[bale] or 0) + 1 |
414 | elseif onLeave then |
415 | baleTrigger.balesInTrigger[bale] = (baleTrigger.balesInTrigger[bale] or 1) - 1 |
416 | if baleTrigger.balesInTrigger[bale] == 0 then |
417 | baleTrigger.balesInTrigger[bale] = nil |
418 | end |
419 | end |
420 | end |
421 | end |
422 | else |
423 | if onEnter and otherActorId == bale.nodeId then |
424 | g_currentMission:broadcastEventToFarm(MixerWagonBaleNotAcceptedEvent.new(), self:getOwnerFarmId(), true) |
425 | end |
426 | end |
427 | end |
428 | end |
429 | end |
430 | end |
612 | function MixerWagon:onHUDTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId) |
613 | local object = g_currentMission.nodeToObject[otherActorId] |
614 | if object ~= nil and object:isa(Vehicle) and not SpecializationUtil.hasSpecialization(Enterable, object.specializations) then |
615 | -- only enterable vehicles are relevant |
616 | return |
617 | elseif g_currentMission.player ~= nil and otherActorId == g_currentMission.player.rootNode then |
618 | object = g_currentMission.player.rootNode |
619 | end |
620 | |
621 | if object ~= nil and object ~= self then -- exclude mixer wagon from triggering on himself |
622 | local spec = self.spec_mixerWagon |
623 | if onEnter then |
624 | if not g_currentMission.accessHandler:canPlayerAccess(self) then |
625 | return |
626 | end |
627 | -- count number of nodes per vehicle inside trigger |
628 | spec.vehicleToNodeCount[object] = (spec.vehicleToNodeCount[object] or 0) + 1 |
629 | elseif onLeave then |
630 | spec.vehicleToNodeCount[object] = (spec.vehicleToNodeCount[object] or 0) - 1 |
631 | end |
632 | |
633 | if spec.vehicleToNodeCount[object] == 1 then |
634 | -- only add once on inital node enter |
635 | g_currentMission.hud.inputHelp:addExtraExtensionVehicleNodeId(self.rootNode) |
636 | elseif spec.vehicleToNodeCount[object] <= 0 then |
637 | g_currentMission.hud.inputHelp:removeExtraExtensionVehicleNodeId(self.rootNode) |
638 | spec.vehicleToNodeCount[object] = nil |
639 | end |
640 | end |
641 | end |
88 | function MixerWagon:onLoad(savegame) |
89 | |
90 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonBaleTrigger#index", "vehicle.mixerWagon.baleTrigger#node") --FS17 to FS19 |
91 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagon.baleTrigger#index", "vehicle.mixerWagon.baleTrigger#node") --FS19 to FS19 |
92 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonPickupStartSound", "vehicle.turnOnVehicle.sounds.start") --FS17 to FS19 |
93 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonPickupStopSound", "vehicle.turnOnVehicle.sounds.stop") --FS17 to FS19 |
94 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonPickupSound", "vehicle.turnOnVehicle.sounds.work") --FS17 to FS19 |
95 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonRotatingParts.mixerWagonRotatingPart#type", "vehicle.mixerWagon.mixAnimationNodes.animationNode", "mixerWagonMix") --FS17 to FS19 |
96 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonRotatingParts.mixerWagonRotatingPart#type", "vehicle.mixerWagon.pickupAnimationNodes.animationNode", "mixerWagonPickup") --FS17 to FS19 |
97 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagonRotatingParts.mixerWagonScroller", "vehicle.mixerWagon.pickupAnimationNodes.pickupAnimationNode") --FS17 to FS19 |
98 | |
99 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.mixerWagon.baleTrigger#node", "vehicle.mixerWagon.baleTriggers.baleTrigger#node") --FS19 to FS22 |
100 | |
101 | local spec = self.spec_mixerWagon |
102 | |
103 | if self.isClient then |
104 | spec.mixAnimationNodes = g_animationManager:loadAnimations(self.xmlFile, "vehicle.mixerWagon.mixAnimationNodes", self.components, self, self.i3dMappings) |
105 | spec.pickupAnimationNodes = g_animationManager:loadAnimations(self.xmlFile, "vehicle.mixerWagon.pickupAnimationNodes", self.components, self, self.i3dMappings) |
106 | |
107 | spec.fillEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.mixerWagon.fillEffect", self.components, self, self.i3dMappings) |
108 | spec.fillEffectsFillType = FillType.UNKNOWN |
109 | spec.fillEffectsState = false |
110 | end |
111 | |
112 | if self.isServer then |
113 | spec.baleTriggers = {} |
114 | self.xmlFile:iterate("vehicle.mixerWagon.baleTriggers.baleTrigger", function(_, key) |
115 | local baleTrigger = {} |
116 | baleTrigger.node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
117 | if baleTrigger.node ~= nil then |
118 | addTrigger(baleTrigger.node, "mixerWagonBaleTriggerCallback", self) |
119 | |
120 | baleTrigger.pickupSpeed = self.xmlFile:getValue(key .. "#pickupSpeed", 500) / 1000 |
121 | baleTrigger.needsSetIsTurnedOn = self.xmlFile:getValue(key .. "#needsSetIsTurnedOn", false) |
122 | baleTrigger.useEffect = self.xmlFile:getValue(key .. "#useEffect", false) |
123 | baleTrigger.balesInTrigger = {} |
124 | table.insert(spec.baleTriggers, baleTrigger) |
125 | end |
126 | end) |
127 | end |
128 | |
129 | spec.activeTimerMax = self.xmlFile:getValue("vehicle.mixerWagon#mixingTime", 5) |
130 | spec.activeTimer = 0 |
131 | |
132 | spec.fillUnitIndex = self.xmlFile:getValue("vehicle.mixerWagon#fillUnitIndex", 1) |
133 | |
134 | -- remove grass_windrow from fillTypes, we do not want to support grass in mixer wagons becasue this would make hay "useless" |
135 | local fillUnit = self:getFillUnitByIndex(spec.fillUnitIndex) |
136 | if fillUnit ~= nil then |
137 | fillUnit.needsSaving = false |
138 | if fillUnit.supportedFillTypes[FillType.GRASS_WINDROW] then |
139 | fillUnit.supportedFillTypes[FillType.GRASS_WINDROW] = nil |
140 | end |
141 | end |
142 | |
143 | -- disable sync of fillLevels for fillUnit - will be handled by mixerWagon |
144 | fillUnit.synchronizeFillLevel = false |
145 | |
146 | spec.mixerWagonFillTypes = {} |
147 | spec.fillTypeToMixerWagonFillType = {} |
148 | |
149 | local recipeFillTypeName = self.xmlFile:getValue("vehicle.mixerWagon#recipe", "") |
150 | local recipeFillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(recipeFillTypeName) |
151 | if recipeFillTypeIndex == nil then |
152 | Logging.xmlError(self.xmlFile, "MixerWagon recipe '%s' not defined!", recipeFillTypeName) |
153 | end |
154 | |
155 | local recipe = g_currentMission.animalFoodSystem:getRecipeByFillTypeIndex(recipeFillTypeIndex) |
156 | if recipe == nil then |
157 | Logging.xmlWarning(self.xmlFile, "MixerWagon recipe '%s' not defined!", recipeFillTypeName) |
158 | end |
159 | |
160 | if recipe ~= nil then |
161 | for _, ingredient in ipairs(recipe.ingredients) do |
162 | local entry = {} |
163 | entry.fillLevel = 0 |
164 | entry.fillTypes = {} |
165 | entry.name = ingredient.name |
166 | entry.minPercentage = ingredient.minPercentage |
167 | entry.maxPercentage = ingredient.maxPercentage |
168 | entry.ratio = ingredient.ratio |
169 | |
170 | for _, fillTypeIndex in ipairs(ingredient.fillTypes) do |
171 | entry.fillTypes[fillTypeIndex] = true |
172 | spec.fillTypeToMixerWagonFillType[fillTypeIndex] = entry |
173 | end |
174 | |
175 | table.insert(spec.mixerWagonFillTypes, entry) |
176 | end |
177 | end |
178 | |
179 | -- load in a trigger for displaying mixer wagon HUDExtension if player is near |
180 | local hudTriggerI3d, hudTriggerSharedLoadRequestId = g_i3DManager:loadSharedI3DFile(MixerWagon.HUD_TRIGGER_I3D_FILENAME, false, false) |
181 | if hudTriggerI3d ~= 0 then |
182 | spec.hudTrigger = getChildAt(hudTriggerI3d, 0) |
183 | spec.hudTriggerSharedLoadRequestId = hudTriggerSharedLoadRequestId |
184 | link(self.rootNode, spec.hudTrigger) |
185 | addTrigger(spec.hudTrigger, "onHUDTriggerCallback", self) |
186 | delete(hudTriggerI3d) |
187 | spec.vehicleToNodeCount = {} |
188 | end |
189 | |
190 | spec.dirtyFlag = self:getNextDirtyFlag() |
191 | spec.effectDirtyFlag = self:getNextDirtyFlag() |
192 | end |
320 | function MixerWagon:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
321 | local spec = self.spec_mixerWagon |
322 | |
323 | local tipState = self:getTipState() |
324 | local isTurnedOn = self:getIsTurnedOn() |
325 | local isDischarging = tipState == Trailer.TIPSTATE_OPENING or tipState == Trailer.TIPSTATE_OPEN |
326 | |
327 | if self:getIsPowered() and (spec.activeTimer > 0 or isTurnedOn or isDischarging) then |
328 | spec.activeTimer = spec.activeTimer - dt |
329 | g_animationManager:startAnimations(spec.mixAnimationNodes) |
330 | else |
331 | g_animationManager:stopAnimations(spec.mixAnimationNodes) |
332 | end |
333 | |
334 | if self.isServer then |
335 | local fillEffectsFillType = FillType.UNKNOWN |
336 | if self:getFillUnitFreeCapacity(spec.fillUnitIndex) > 0 then |
337 | for i=1, #spec.baleTriggers do |
338 | local baleTrigger = spec.baleTriggers[i] |
339 | if not baleTrigger.needsSetIsTurnedOn or self:getIsTurnedOn() then |
340 | for bale, _ in pairs(baleTrigger.balesInTrigger) do |
341 | local baleFillLevel = bale:getFillLevel() |
342 | local deltaFillLevel = math.min(baleTrigger.pickupSpeed * dt, baleFillLevel) |
343 | local fillType = bale:getFillType() |
344 | |
345 | deltaFillLevel = self:addFillUnitFillLevel(self:getOwnerFarmId(), spec.fillUnitIndex, deltaFillLevel, fillType, ToolType.BALE, nil) |
346 | |
347 | baleFillLevel = baleFillLevel - deltaFillLevel |
348 | bale:setFillLevel(baleFillLevel) |
349 | if baleFillLevel < 0.01 then |
350 | bale:delete() |
351 | baleTrigger.balesInTrigger[bale] = nil |
352 | end |
353 | |
354 | if baleTrigger.useEffect then |
355 | fillEffectsFillType = fillType |
356 | end |
357 | end |
358 | end |
359 | end |
360 | end |
361 | |
362 | if fillEffectsFillType == FillType.UNKNOWN then |
363 | if self.getIsShovelEffectState ~= nil then |
364 | local state, fillType = self:getIsShovelEffectState() |
365 | if state then |
366 | fillEffectsFillType = fillType |
367 | end |
368 | end |
369 | end |
370 | |
371 | if spec.fillEffectsFillType ~= fillEffectsFillType then |
372 | spec.fillEffectsFillType = fillEffectsFillType |
373 | self:raiseDirtyFlags(spec.effectDirtyFlag) |
374 | end |
375 | end |
376 | |
377 | if self.isClient then |
378 | local state = spec.fillEffectsFillType ~= FillType.UNKNOWN |
379 | if state ~= spec.fillEffectsState then |
380 | if state then |
381 | g_effectManager:setFillType(spec.fillEffects, spec.fillEffectsFillType) |
382 | g_effectManager:startEffects(spec.fillEffects) |
383 | else |
384 | g_effectManager:stopEffects(spec.fillEffects) |
385 | end |
386 | |
387 | spec.fillEffectsState = state |
388 | end |
389 | end |
390 | end |