420 | function PowerConsumer:consoleSetPowerConsumer(neededMinPtoPower, neededMaxPtoPower, forceFactor, maxForce, forceDir, ptoRpm) |
421 | if neededMinPtoPower == nil then |
422 | return "No arguments given! Usage: gsPowerConsumerSet <neededMinPtoPower> <neededMaxPtoPower> <forceFactor> <maxForce> <forceDir> <ptoRpm>" |
423 | end |
424 | local object |
425 | if g_currentMission ~= nil and g_currentMission.controlledVehicle ~= nil then |
426 | if g_currentMission.controlledVehicle:getSelectedImplement() ~= nil and g_currentMission.controlledVehicle:getSelectedImplement().object.spec_powerConsumer ~= nil then |
427 | object = g_currentMission.controlledVehicle:getSelectedImplement().object |
428 | end |
429 | end |
430 | if object ~= nil then |
431 | object.spec_powerConsumer.neededMinPtoPower = Utils.getNoNil(neededMinPtoPower, object.spec_powerConsumer.neededMinPtoPower) |
432 | object.spec_powerConsumer.neededMaxPtoPower = Utils.getNoNil(neededMaxPtoPower, object.spec_powerConsumer.neededMaxPtoPower) |
433 | object.spec_powerConsumer.forceFactor = Utils.getNoNil(forceFactor, object.spec_powerConsumer.forceFactor) |
434 | object.spec_powerConsumer.maxForce = Utils.getNoNil(maxForce, object.spec_powerConsumer.maxForce) |
435 | object.spec_powerConsumer.forceDir = Utils.getNoNil(forceDir, object.spec_powerConsumer.forceDir) |
436 | object.spec_powerConsumer.ptoRpm = Utils.getNoNil(ptoRpm, object.spec_powerConsumer.ptoRpm) |
437 | for _, veh in pairs(g_currentMission.vehicles) do |
438 | if veh.configFileName == object.configFileName then |
439 | veh.spec_powerConsumer.neededMinPtoPower = object.spec_powerConsumer.neededMinPtoPower |
440 | veh.spec_powerConsumer.neededMaxPtoPower = object.spec_powerConsumer.neededMaxPtoPower |
441 | veh.spec_powerConsumer.forceFactor = object.spec_powerConsumer.forceFactor |
442 | veh.spec_powerConsumer.maxForce = object.spec_powerConsumer.maxForce |
443 | veh.spec_powerConsumer.forceDir = object.spec_powerConsumer.forceDir |
444 | veh.spec_powerConsumer.ptoRpm = object.spec_powerConsumer.ptoRpm |
445 | end |
446 | end |
447 | else |
448 | return "No vehicle with powerConsumer specialization selected" |
449 | end |
450 | end |
265 | function PowerConsumer:getCanBeTurnedOn(superFunc) |
266 | local rootVehicle = self.rootVehicle |
267 | if rootVehicle ~= nil and rootVehicle.getMotor ~= nil then |
268 | local rootMotor = rootVehicle:getMotor() |
269 | |
270 | local torqueRequested, _ = self:getConsumedPtoTorque(true) |
271 | local totalTorque, _ = PowerConsumer.getTotalConsumedPtoTorque(rootVehicle, self) |
272 | torqueRequested = torqueRequested + totalTorque |
273 | torqueRequested = torqueRequested / rootMotor:getPtoMotorRpmRatio() |
274 | |
275 | -- 90% of motor torque should be more than requested torque, because we need some torque to accelerate the vehicle |
276 | if torqueRequested > 0 and torqueRequested > 0.9 * rootMotor:getPeakTorque() then |
277 | if not self:getIsTurnedOn() then |
278 | return false, true |
279 | end |
280 | end |
281 | end |
282 | |
283 | if superFunc ~= nil then |
284 | return superFunc(self) |
285 | else |
286 | return true, false |
287 | end |
288 | end |
293 | function PowerConsumer:getCanBeTurnedOnAll(superFunc) |
294 | if not superFunc(self) then |
295 | return false |
296 | end |
297 | |
298 | local rootVehicle = self.rootVehicle |
299 | if rootVehicle ~= nil and rootVehicle.getMotor ~= nil then |
300 | local rootMotor = rootVehicle:getMotor() |
301 | |
302 | local torqueRequested, _ = PowerConsumer.getTotalConsumedPtoTorque(rootVehicle, nil, true) |
303 | torqueRequested = torqueRequested / rootMotor:getPtoMotorRpmRatio() |
304 | |
305 | -- 90% of motor torque should be more than requested torque, because we need some torque to accelerate the vehicle |
306 | if torqueRequested > 0 and torqueRequested > 0.9 * rootMotor:getPeakTorque() then |
307 | if not self:getIsTurnedOn() then |
308 | return false, self.spec_powerConsumer.turnOnNotAllowedWarning |
309 | end |
310 | end |
311 | end |
312 | |
313 | return true, false |
314 | end |
230 | function PowerConsumer:getConsumedPtoTorque(expected, ignoreTurnOnPeak) |
231 | if self:getDoConsumePtoPower() or (expected ~= nil and expected) then |
232 | local spec = self.spec_powerConsumer |
233 | |
234 | local rpm = spec.ptoRpm |
235 | if rpm > 0.001 then |
236 | local consumingLoad, count = self:getConsumingLoad() |
237 | if count > 0 then |
238 | consumingLoad = consumingLoad / count |
239 | else |
240 | consumingLoad = 1 |
241 | end |
242 | |
243 | local turnOnPeakPowerMultiplier = math.max(math.max(math.min(spec.turnOnPeakPowerTimer / spec.turnOnPeakPowerDuration, 1), 0) * spec.turnOnPeakPowerMultiplier, 1) |
244 | if ignoreTurnOnPeak == true then |
245 | turnOnPeakPowerMultiplier = 1 |
246 | end |
247 | |
248 | local neededPtoPower = spec.neededMinPtoPower + (consumingLoad * (spec.neededMaxPtoPower - spec.neededMinPtoPower)) |
249 | return neededPtoPower / (rpm*math.pi/30), spec.virtualPowerMultiplicator * turnOnPeakPowerMultiplier |
250 | end |
251 | end |
252 | |
253 | return 0, 1 |
254 | end |
487 | function PowerConsumer.getSpecValueNeededPower(storeItem, realItem, configurations, saleItem, returnValues, returnRange) |
488 | if storeItem.specs.neededPower ~= nil then |
489 | local minPower = storeItem.specs.neededPower.base or 0 |
490 | for _, value in pairs(storeItem.specs.neededPower.config) do |
491 | minPower = math.max(minPower, value) |
492 | end |
493 | |
494 | if minPower == 0 then |
495 | return nil |
496 | end |
497 | |
498 | local hp, kw = g_i18n:getPower(minPower) |
499 | return string.format(g_i18n:getText("shop_neededPowerValue"), MathUtil.round(kw), MathUtil.round(hp)) |
500 | end |
501 | return nil |
502 | end |
371 | function PowerConsumer.getTotalConsumedPtoTorque(self, excludeVehicle, expected, ignoreTurnOnPeak) |
372 | local torque, virtualMultiplicator = 0, 1 |
373 | if self ~= excludeVehicle then |
374 | if self.getConsumedPtoTorque ~= nil then |
375 | torque, virtualMultiplicator = self:getConsumedPtoTorque(expected, ignoreTurnOnPeak) |
376 | end |
377 | end |
378 | |
379 | if self.getAttachedImplements ~= nil then |
380 | local attachedImplements = self:getAttachedImplements() |
381 | for _, implement in pairs(attachedImplements) do |
382 | local implementTorque, implementMultiplicator = PowerConsumer.getTotalConsumedPtoTorque(implement.object, excludeVehicle, expected, ignoreTurnOnPeak) |
383 | torque = torque + implementTorque |
384 | |
385 | if torque == 0 then |
386 | virtualMultiplicator = implementMultiplicator |
387 | else |
388 | local ratio = implementTorque / torque |
389 | virtualMultiplicator = virtualMultiplicator * (1 - ratio) + implementMultiplicator * ratio |
390 | end |
391 | end |
392 | end |
393 | |
394 | return torque, virtualMultiplicator |
395 | end |
15 | function PowerConsumer.initSpecialization() |
16 | g_configurationManager:addConfigurationType("powerConsumer", g_i18n:getText("configuration_powerConsumer"), "powerConsumer", nil, nil, nil, ConfigurationUtil.SELECTOR_MULTIOPTION) |
17 | |
18 | g_storeManager:addSpecType("neededPower", "shopListAttributeIconPowerReq", PowerConsumer.loadSpecValueNeededPower, PowerConsumer.getSpecValueNeededPower, "vehicle") |
19 | |
20 | local schema = Vehicle.xmlSchema |
21 | schema:setXMLSpecializationType("PowerConsumer") |
22 | |
23 | PowerConsumer.registerPowerConsumerXMLPaths(schema, "vehicle.powerConsumer") |
24 | PowerConsumer.registerPowerConsumerXMLPaths(schema, "vehicle.powerConsumer.powerConsumerConfigurations.powerConsumerConfiguration(?)") |
25 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, "vehicle.powerConsumer.powerConsumerConfigurations.powerConsumerConfiguration(?)") |
26 | |
27 | schema:register(XMLValueType.INT, "vehicle.storeData.specs.neededPower", "Needed power") |
28 | schema:register(XMLValueType.INT, "vehicle.powerConsumer.powerConsumerConfigurations.powerConsumerConfiguration(?)#neededPower", "Needed power") |
29 | |
30 | schema:setXMLSpecializationType() |
31 | end |
186 | function PowerConsumer:loadPowerSetup(xmlFile, baseKey) |
187 | local spec = self.spec_powerConsumer |
188 | |
189 | XMLUtil.checkDeprecatedXMLElements(xmlFile, baseKey .. "#neededPtoPower", string.format("%s#neededMinPtoPower and %s#neededMaxPtoPower", baseKey, baseKey)) |
190 | |
191 | spec.neededMaxPtoPower = xmlFile:getValue(baseKey .. "#neededMaxPtoPower", 0) |
192 | spec.neededMinPtoPower = xmlFile:getValue(baseKey .. "#neededMinPtoPower", spec.neededMaxPtoPower) -- in kW at ptoRpm |
193 | if spec.neededMaxPtoPower < spec.neededMinPtoPower then |
194 | Logging.xmlWarning(self.xmlFile, "'%s#neededMaxPtoPower' is smaller than '%s#neededMinPtoPower'", baseKey, baseKey) |
195 | end |
196 | |
197 | spec.ptoRpm = xmlFile:getValue(baseKey .. "#ptoRpm", 0) |
198 | spec.virtualPowerMultiplicator = xmlFile:getValue(baseKey .. "#virtualPowerMultiplicator", 1) |
199 | end |
101 | function PowerConsumer:onLoad(savegame) |
102 | local spec = self.spec_powerConsumer |
103 | |
104 | local foldingConfigurationId = Utils.getNoNil(self.configurations["powerConsumer"], 1) |
105 | local configKey = string.format("vehicle.powerConsumer.powerConsumerConfigurations.powerConsumerConfiguration(%d)", foldingConfigurationId - 1) |
106 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.powerConsumer.powerConsumerConfigurations.powerConsumerConfiguration", foldingConfigurationId, self.components, self) |
107 | |
108 | if not self.xmlFile:hasProperty(configKey) then |
109 | configKey = "vehicle.powerConsumer" |
110 | end |
111 | |
112 | spec.forceNode = self.xmlFile:getValue(configKey .. "#forceNode", nil, self.components, self.i3dMappings) |
113 | spec.forceDirNode = self.xmlFile:getValue(configKey .. "#forceDirNode", spec.forceNode, self.components, self.i3dMappings) |
114 | spec.forceFactor = self.xmlFile:getValue(configKey .. "#forceFactor", 1.0) |
115 | |
116 | spec.maxForce = self.xmlFile:getValue(configKey .. "#maxForce", 0) -- kN |
117 | spec.forceDir = self.xmlFile:getValue(configKey .. "#forceDir", 1) |
118 | |
119 | spec.useTurnOnState = self.xmlFile:getValue(configKey .. "#useTurnOnState", true) |
120 | |
121 | spec.turnOnNotAllowedWarning = string.format(self.xmlFile:getValue(configKey .. "#turnOnNotAllowedWarning", "warning_insufficientPowerOutput", self.customEnvironment), self.typeDesc) |
122 | |
123 | self:loadPowerSetup(self.xmlFile, configKey) |
124 | |
125 | spec.speedLimitModifier = {} |
126 | spec.sourceMotorPeakPower = math.huge |
127 | |
128 | spec.turnOnPeakPowerMultiplier = self.xmlFile:getValue(configKey .. "#turnOnPeakPowerMultiplier", 3) |
129 | spec.turnOnPeakPowerDuration = self.xmlFile:getValue(configKey .. "#turnOnPeakPowerDuration", 2.5) |
130 | spec.turnOnPeakPowerTimer = -1 |
131 | |
132 | self.xmlFile:iterate(configKey .. ".speedLimitModifier", function(index, key) |
133 | local entry = {} |
134 | entry.offset = self.xmlFile:getValue(key .. "#offset") |
135 | |
136 | if entry.offset ~= nil then |
137 | entry.minPowerKw = self.xmlFile:getValue(key .. "#minPowerHp", 0) * 0.735499 |
138 | entry.maxPowerKw = self.xmlFile:getValue(key .. "#maxPowerHp", 0) * 0.735499 |
139 | table.insert(spec.speedLimitModifier, entry) |
140 | else |
141 | Logging.xmlWarning(self.xmlFile, "Invalid offset found for '%s'", key) |
142 | end |
143 | end) |
144 | |
145 | if #spec.speedLimitModifier == 0 then |
146 | SpecializationUtil.removeEventListener(self, "onPostDetach", PowerConsumer) |
147 | end |
148 | end |
155 | function PowerConsumer:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
156 | if self.isActive then |
157 | local spec = self.spec_powerConsumer |
158 | |
159 | if spec.forceNode ~= nil and self.movingDirection == spec.forceDir then |
160 | local multiplier = self:getPowerMultiplier() |
161 | if multiplier ~= 0 then |
162 | local frictionForce = spec.forceFactor * self.lastSpeedReal * 1000 * self:getTotalMass(false) / (dt/1000) |
163 | |
164 | local force = -math.min(frictionForce, spec.maxForce) * self.movingDirection * multiplier |
165 | local dx,dy,dz = localDirectionToWorld(spec.forceDirNode, 0, 0, force) |
166 | local px,py,pz = getCenterOfMass(spec.forceNode) |
167 | |
168 | addForce(spec.forceNode, dx,dy,dz, px,py,pz, true) |
169 | |
170 | if (VehicleDebug.state == VehicleDebug.DEBUG_PHYSICS or VehicleDebug.state == VehicleDebug.DEBUG_TUNING) and self.isActiveForInputIgnoreSelectionIgnoreAI then |
171 | local str = string.format("frictionForce=%.2f maxForce=%.2f -> force=%.2f", frictionForce, spec.maxForce, force) |
172 | renderText(0.7, 0.85, getCorrectTextSize(0.02), str) |
173 | end |
174 | end |
175 | end |
176 | |
177 | if spec.turnOnPeakPowerTimer > 0 then |
178 | spec.turnOnPeakPowerTimer = spec.turnOnPeakPowerTimer - dt |
179 | end |
180 | end |
181 | end |
71 | function PowerConsumer.registerFunctions(vehicleType) |
72 | SpecializationUtil.registerFunction(vehicleType, "loadPowerSetup", PowerConsumer.loadPowerSetup) |
73 | SpecializationUtil.registerFunction(vehicleType, "getPtoRpm", PowerConsumer.getPtoRpm) |
74 | SpecializationUtil.registerFunction(vehicleType, "getDoConsumePtoPower", PowerConsumer.getDoConsumePtoPower) |
75 | SpecializationUtil.registerFunction(vehicleType, "getPowerMultiplier", PowerConsumer.getPowerMultiplier) |
76 | SpecializationUtil.registerFunction(vehicleType, "getConsumedPtoTorque", PowerConsumer.getConsumedPtoTorque) |
77 | SpecializationUtil.registerFunction(vehicleType, "getConsumingLoad", PowerConsumer.getConsumingLoad) |
78 | end |
35 | function PowerConsumer.registerPowerConsumerXMLPaths(schema, basePath) |
36 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#forceNode", "Force node") |
37 | schema:register(XMLValueType.NODE_INDEX, basePath .. "#forceDirNode", "Force node", "Force node") |
38 | schema:register(XMLValueType.FLOAT, basePath .. "#forceFactor", "Force factor", 1) |
39 | |
40 | schema:register(XMLValueType.FLOAT, basePath .. "#maxForce", "Max. force (kN)", 0) |
41 | schema:register(XMLValueType.FLOAT, basePath .. "#forceDir", "Force direction", 1) |
42 | |
43 | schema:register(XMLValueType.BOOL, basePath .. "#useTurnOnState", "While vehicle is turned on the vehicle consumes the pto power", true) |
44 | |
45 | schema:register(XMLValueType.FLOAT, basePath .. "#turnOnPeakPowerMultiplier", "While turning the tool on a short peak power with this multiplier is consumed", 3) |
46 | schema:register(XMLValueType.TIME, basePath .. "#turnOnPeakPowerDuration", "Duration for peak power while turning on (sec)", 2) |
47 | |
48 | schema:register(XMLValueType.L10N_STRING, basePath .. "#turnOnNotAllowedWarning", "Turn on not allowed text", "warning_insufficientPowerOutput") |
49 | |
50 | schema:register(XMLValueType.FLOAT, basePath .. "#neededMaxPtoPower", "Needed max. pto power", 0) |
51 | schema:register(XMLValueType.FLOAT, basePath .. "#neededMinPtoPower", "Needed min. pto power", "neededMaxPtoPower") |
52 | schema:register(XMLValueType.FLOAT, basePath .. "#ptoRpm", "Pto rpm", 0) |
53 | schema:register(XMLValueType.FLOAT, basePath .. "#virtualPowerMultiplicator", "Virtual multiplicator for pto power to increased the motor load without reducing the available power for driving", 1) |
54 | |
55 | schema:register(XMLValueType.FLOAT, basePath .. ".speedLimitModifier(?)#offset", "Speed limit offset to apply") |
56 | schema:register(XMLValueType.FLOAT, basePath .. ".speedLimitModifier(?)#minPowerHp", "Min. power in HP of root motor", 0) |
57 | schema:register(XMLValueType.FLOAT, basePath .. ".speedLimitModifier(?)#maxPowerHp", "Max. power in HP of root motor", 0) |
58 | end |