420 | function BaleManager:consoleCommandAddBale(fillTypeName, isRoundbale, width, height, length, wrapState, modName) |
421 | local usage = "gsBaleAdd fillTypeName isRoundBale [width] [height/diameter] [length] [wrapState] [modName]" |
422 | |
423 | if not g_currentMission:getIsServer() then |
424 | Logging.error("Command only allowed on server!") |
425 | return |
426 | end |
427 | |
428 | fillTypeName = Utils.getNoNil(fillTypeName, "STRAW") |
429 | isRoundbale = Utils.stringToBoolean(isRoundbale) |
430 | |
431 | width = width ~= nil and tonumber(width) or nil |
432 | height = height ~= nil and tonumber(height) or nil |
433 | length = length ~= nil and tonumber(length) or nil |
434 | if wrapState ~= nil and tonumber(wrapState) == nil then |
435 | Logging.error("Invalid wrapState '%s'. Number expected", wrapState, usage) |
436 | return |
437 | end |
438 | wrapState = tonumber(wrapState or 0) |
439 | |
440 | local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName) |
441 | if fillTypeIndex == nil then |
442 | Logging.error("Invalid fillTypeName '%s' (e.g. STRAW). Use %s" ,fillTypeName, usage) |
443 | return |
444 | end |
445 | |
446 | local baleXMLFilename, _ = self:getBaleXMLFilename(fillTypeIndex, isRoundbale, width, height, length, height, modName) |
447 | if baleXMLFilename == nil then |
448 | Logging.error("Could not find bale for given size attributes! (%s)", usage) |
449 | self:consoleCommandListBales() |
450 | return |
451 | end |
452 | |
453 | local x, y, z = 0, 0, 0 |
454 | local dirX, dirZ, _ = 1, 0 |
455 | |
456 | if g_currentMission.controlPlayer then |
457 | local player = g_currentMission.player |
458 | if player ~= nil and player.isControlled and player.rootNode ~= nil and player.rootNode ~= 0 then |
459 | x, y, z = getWorldTranslation(player.rootNode) |
460 | dirX, dirZ = -math.sin(player.rotY), -math.cos(player.rotY) |
461 | end |
462 | elseif g_currentMission.controlledVehicle ~= nil then |
463 | x, y, z = getWorldTranslation(g_currentMission.controlledVehicle.rootNode) |
464 | dirX, _, dirZ = localDirectionToWorld(g_currentMission.controlledVehicle.rootNode, 0, 0, 1) |
465 | else |
466 | x, y, z = getWorldTranslation(getCamera()) |
467 | dirX, _, dirZ = localDirectionToWorld(getCamera(), 0, 0, -1) |
468 | end |
469 | |
470 | x, z = x + dirX * 4, z + dirZ * 4 |
471 | y = y + 5 |
472 | local ry = MathUtil.getYRotationFromDirection(dirX, dirZ) |
473 | |
474 | local farmId = g_currentMission:getFarmId() |
475 | farmId = ((farmId ~= FarmManager.SPECTATOR_FARM_ID) and farmId) or 1 -- don't spawn bales with spectator farm |
476 | |
477 | local baleObject = Bale.new(g_currentMission:getIsServer(), g_currentMission:getIsClient()) |
478 | if baleObject:loadFromConfigXML(baleXMLFilename, x, y, z, 0, ry, 0) then |
479 | baleObject:setFillType(fillTypeIndex, true) |
480 | baleObject:setWrappingState(wrapState) |
481 | baleObject:setOwnerFarmId(farmId, true) |
482 | baleObject:register() |
483 | end |
484 | |
485 | return string.format("Created bale at (%.2f, %.2f, %.2f). For specific bales use: %s", x, y, z, usage) |
486 | end |
490 | function BaleManager:consoleCommandListBales() |
491 | print("Available bale types:") |
492 | for _, bale in ipairs(self.bales) do |
493 | local attributes = {bale.xmlFilename} |
494 | table.insert(attributes, string.format("isRoundbale=%s", bale.isRoundbale)) |
495 | for _, sizeProperty in ipairs({"width", "height", "length", "diameter"}) do |
496 | if bale[sizeProperty] ~= nil and bale[sizeProperty] ~= 0 then |
497 | table.insert(attributes, string.format("%s=%s", sizeProperty, bale[sizeProperty])) |
498 | end |
499 | end |
500 | log(table.concat(attributes, " ")) |
501 | |
502 | local fillTypeNames = {} |
503 | for _, fillTypeData in ipairs(bale.fillTypes) do |
504 | table.insert(fillTypeNames, g_fillTypeManager:getFillTypeNameByIndex(fillTypeData.fillTypeIndex)) |
505 | end |
506 | log(" fillTypes: ", table.concat(fillTypeNames, " ")) |
507 | end |
508 | end |
302 | function BaleManager:getBaleIndex(fillTypeIndex, isRoundbale, width, height, length, diameter, customEnvironment) |
303 | -- for mods we search first in it's own custom environment for a matching bale |
304 | if customEnvironment ~= nil then |
305 | for baleIndex=1, #self.bales do |
306 | local bale = self.bales[baleIndex] |
307 | if bale.isAvailable then |
308 | if customEnvironment == bale.customEnvironment then |
309 | if self:getIsBaleMatching(bale, fillTypeIndex, isRoundbale, width, height, length, diameter) then |
310 | return baleIndex |
311 | end |
312 | end |
313 | end |
314 | end |
315 | end |
316 | |
317 | -- now search all bales without custom environment |
318 | for baleIndex=1, #self.bales do |
319 | local bale = self.bales[baleIndex] |
320 | if bale.isAvailable then |
321 | if bale.customEnvironment == nil then |
322 | if self:getIsBaleMatching(bale, fillTypeIndex, isRoundbale, width, height, length, diameter) then |
323 | return baleIndex |
324 | end |
325 | end |
326 | end |
327 | end |
328 | |
329 | return nil |
330 | end |
334 | function BaleManager:getIsBaleMatching(bale, fillTypeIndex, isRoundbale, width, height, length, diameter) |
335 | if bale.isRoundbale == isRoundbale then |
336 | local fillTypeMatch = false |
337 | for j=1, #bale.fillTypes do |
338 | if bale.fillTypes[j].fillTypeIndex == fillTypeIndex then |
339 | fillTypeMatch = true |
340 | break |
341 | end |
342 | end |
343 | |
344 | if fillTypeMatch then |
345 | local sizeMatch |
346 | if isRoundbale then |
347 | sizeMatch = (width == nil or MathUtil.round(width, 2) == bale.width) |
348 | and (diameter == nil or MathUtil.round(diameter, 2) == bale.diameter) |
349 | else |
350 | sizeMatch = (width == nil or MathUtil.round(width, 2) == bale.width) |
351 | and (height == nil or MathUtil.round(height, 2) == bale.height) |
352 | and (length == nil or MathUtil.round(length, 2) == bale.length) |
353 | end |
354 | |
355 | if sizeMatch then |
356 | return true |
357 | end |
358 | end |
359 | end |
360 | |
361 | return false |
362 | end |
177 | function BaleManager:loadBaleDataFromXML(bale, xmlFile, baseDirectory) |
178 | local i3dFilename = xmlFile:getValue("bale.filename") |
179 | if i3dFilename ~= nil then |
180 | bale.i3dFilename = Utils.getFilename(i3dFilename, baseDirectory) |
181 | if not fileExists(bale.i3dFilename) then |
182 | Logging.xmlError(xmlFile, "Bale i3d file could not be found '%s'", bale.i3dFilename) |
183 | return false |
184 | end |
185 | |
186 | bale.isRoundbale = xmlFile:getValue("bale.size#isRoundbale", true) |
187 | bale.width = MathUtil.round(xmlFile:getValue("bale.size#width", 0), 2) |
188 | bale.height = MathUtil.round(xmlFile:getValue("bale.size#height", 0), 2) |
189 | bale.length = MathUtil.round(xmlFile:getValue("bale.size#length", 0), 2) |
190 | bale.diameter = MathUtil.round(xmlFile:getValue("bale.size#diameter", 0), 2) |
191 | |
192 | if bale.isRoundbale and (bale.diameter == 0 or bale.width == 0) then |
193 | Logging.xmlError(xmlFile, "Missing size attributes for round bale. Requires width and diameter.") |
194 | return false |
195 | elseif not bale.isRoundbale and (bale.width == 0 or bale.height == 0 or bale.length == 0) then |
196 | Logging.xmlError(xmlFile, "Missing size attributes for square bale. Requires width, height and length.") |
197 | return false |
198 | end |
199 | |
200 | bale.fillTypes = {} |
201 | xmlFile:iterate("bale.fillTypes.fillType", function(index, key) |
202 | local fillTypeName = xmlFile:getValue(key .. "#name") |
203 | local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName) |
204 | if fillTypeIndex ~= nil then |
205 | local fillTypeData = {} |
206 | fillTypeData.fillTypeIndex = fillTypeIndex |
207 | fillTypeData.capacity = xmlFile:getValue(key .. "#capacity", 0) |
208 | table.insert(bale.fillTypes, fillTypeData) |
209 | else |
210 | Logging.xmlWarning(xmlFile, "Unknown fill type '%s' for bale in '%s'", fillTypeName, key) |
211 | end |
212 | end) |
213 | else |
214 | Logging.xmlError(xmlFile, "No i3D file defined in bale xml.") |
215 | return false |
216 | end |
217 | |
218 | return true |
219 | end |
120 | function BaleManager:loadBaleFromXML(xmlFile, key, baseDirectory) |
121 | if type(xmlFile) ~= "table" then |
122 | xmlFile = XMLFile.wrap(xmlFile) |
123 | end |
124 | |
125 | local xmlFilename = xmlFile:getString(key .. "#filename") |
126 | if xmlFilename ~= nil then |
127 | local bale = {} |
128 | bale.xmlFilename = Utils.getFilename(xmlFilename, baseDirectory) |
129 | bale.isAvailable = xmlFile:getBool(key .. "#isAvailable", true) |
130 | local baleXmlFile = XMLFile.load("TempBale", bale.xmlFilename, BaleManager.baleXMLSchema) |
131 | if baleXmlFile ~= nil then |
132 | local success = self:loadBaleDataFromXML(bale, baleXmlFile, baseDirectory) |
133 | baleXmlFile:delete() |
134 | if success then |
135 | table.insert(self.bales, bale) |
136 | return true |
137 | end |
138 | end |
139 | end |
140 | |
141 | Logging.xmlError(xmlFile, "Failed to load bale from xml '%s'", key) |
142 | |
143 | return false |
144 | end |
42 | function BaleManager:loadMapData(xmlFile, missionInfo, baseDirectory) |
43 | BaleManager:superClass().loadMapData(self) |
44 | |
45 | local filename = getXMLString(xmlFile, "map.bales#filename") |
46 | local xmlFilename = Utils.getFilename(filename, baseDirectory) |
47 | local balesXMLFile = XMLFile.load("TempBales", xmlFilename, BaleManager.mapBalesXMLSchema) |
48 | if balesXMLFile ~= nil then |
49 | self:loadBales(balesXMLFile, baseDirectory) |
50 | balesXMLFile:delete() |
51 | end |
52 | |
53 | for i=#self.modBalesToLoad, 1, -1 do |
54 | local bale = self.modBalesToLoad[i] |
55 | local baleXmlFile = XMLFile.load("TempBale", bale.xmlFilename, BaleManager.baleXMLSchema) |
56 | if baleXmlFile ~= nil then |
57 | if self:loadBaleDataFromXML(bale, baleXmlFile, bale.baseDirectory) then |
58 | table.insert(self.bales, bale) |
59 | end |
60 | baleXmlFile:delete() |
61 | end |
62 | table.remove(self.modBalesToLoad, i) |
63 | end |
64 | |
65 | for _, bale in ipairs(self.bales) do |
66 | bale.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(bale.i3dFilename, false, true, self.baleLoaded, self, bale) |
67 | end |
68 | |
69 | if g_addCheatCommands then |
70 | addConsoleCommand("gsBaleAdd", "Adds a bale", "consoleCommandAddBale", self) |
71 | addConsoleCommand("gsBaleList", "List available bale types", "consoleCommandListBales", self) |
72 | end |
73 | |
74 | return true |
75 | end |
148 | function BaleManager:loadModBaleFromXML(xmlFile, key, baseDirectory, customEnvironment) |
149 | if type(xmlFile) ~= "table" then |
150 | xmlFile = XMLFile.wrap(xmlFile) |
151 | end |
152 | |
153 | local xmlFilename = xmlFile:getString(key .. "#filename") |
154 | if xmlFilename ~= nil then |
155 | xmlFilename = Utils.getFilename(xmlFilename, baseDirectory) |
156 | |
157 | local bale = {} |
158 | bale.xmlFilename = xmlFilename |
159 | bale.baseDirectory = baseDirectory |
160 | bale.customEnvironment = customEnvironment |
161 | bale.isAvailable = xmlFile:getBool(key .. "#isAvailable", true) |
162 | |
163 | table.insert(self.modBalesToLoad, bale) |
164 | return true |
165 | end |
166 | |
167 | Logging.xmlError(xmlFile, "Failed to load bale from xml '%s'", key) |
168 | |
169 | return false |
170 | end |
512 | function BaleManager.registerBaleXMLPaths(schema) |
513 | schema:register(XMLValueType.STRING, "bale.filename", "Path to i3d file") |
514 | |
515 | schema:register(XMLValueType.BOOL, "bale.size#isRoundbale", "Bale is a roundbale", true) |
516 | schema:register(XMLValueType.FLOAT, "bale.size#width", "Bale Width", 0) |
517 | schema:register(XMLValueType.FLOAT, "bale.size#height", "Bale Height", 0) |
518 | schema:register(XMLValueType.FLOAT, "bale.size#length", "Bale Length", 0) |
519 | schema:register(XMLValueType.FLOAT, "bale.size#diameter", "Bale Diameter", 0) |
520 | |
521 | schema:register(XMLValueType.NODE_INDEX, "bale.mountableObject#triggerNode", "Trigger node") |
522 | schema:register(XMLValueType.FLOAT, "bale.mountableObject#forceAcceleration", "Acceleration force", 4) |
523 | schema:register(XMLValueType.FLOAT, "bale.mountableObject#forceLimitScale", "Force limit scale", 1) |
524 | schema:register(XMLValueType.BOOL, "bale.mountableObject#axisFreeY", "Joint is free in Y direction", false) |
525 | schema:register(XMLValueType.BOOL, "bale.mountableObject#axisFreeX", "Joint is free in X direction", false) |
526 | |
527 | schema:register(XMLValueType.STRING, "bale.uvId", "Specify that this bale model has a custom UV. This will result in baleWrapper to replace the bale if the UV is different to the defined one in the baleWrapper. So the baleWrapper will always use a bale with a UV that matches the wrapping texture.", "DEFAULT") |
528 | |
529 | schema:register(XMLValueType.NODE_INDEX, "bale.baleMeshes.baleMesh(?)#node", "Path to mesh node") |
530 | schema:register(XMLValueType.BOOL, "bale.baleMeshes.baleMesh(?)#supportsWrapping", "Defines if the mesh is hidden while wrapping or not") |
531 | schema:register(XMLValueType.STRING, "bale.baleMeshes.baleMesh(?)#fillTypes", "If defined this mesh is only visible if any of this fillTypes is set") |
532 | schema:register(XMLValueType.BOOL, "bale.baleMeshes.baleMesh(?)#isTensionBeltMesh", "Defines if this mesh is detected for tension belt calculation", false) |
533 | |
534 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?)#name", "Name of fill type") |
535 | schema:register(XMLValueType.FLOAT, "bale.fillTypes.fillType(?)#capacity", "Fill level of bale with this fill type") |
536 | schema:register(XMLValueType.FLOAT, "bale.fillTypes.fillType(?)#mass", "Mass of bale with this fill type", 500) |
537 | schema:register(XMLValueType.FLOAT, "bale.fillTypes.fillType(?)#forceAcceleration", "Force acceleration value of bale with this fill type", "bale.mountableObject#forceAcceleration") |
538 | schema:register(XMLValueType.BOOL, "bale.fillTypes.fillType(?)#supportsWrapping", "Wrapping is allowed while this type is used") |
539 | |
540 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?).diffuse#filename", "Diffuse texture to apply to all mesh nodes") |
541 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?).normal#filename", "Normal texture to apply to all mesh nodes") |
542 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?).specular#filename", "Specular texture to apply to all mesh nodes") |
543 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?).alpha#filename", "Alpha texture to apply to all mesh nodes") |
544 | |
545 | schema:register(XMLValueType.STRING, "bale.fillTypes.fillType(?).fermenting#outputFillType", "Output fill type after fermenting") |
546 | schema:register(XMLValueType.BOOL, "bale.fillTypes.fillType(?).fermenting#requiresWrapping", "Wrapping is required to start fermenting", true) |
547 | schema:register(XMLValueType.FLOAT, "bale.fillTypes.fillType(?).fermenting#time", "Fermenting time in ingame days which represent months", 1) |
548 | |
549 | -- packed bales |
550 | schema:register(XMLValueType.STRING, "bale.packedBale#singleBale", "Path to single bale xml filename") |
551 | schema:register(XMLValueType.NODE_INDEX, "bale.packedBale.singleBale(?)#node", "Single bale spawn node") |
552 | end |
224 | function BaleManager:update(dt) |
225 | if g_server ~= nil then |
226 | local numFermentations = #self.fermentations |
227 | if numFermentations > 0 then |
228 | local timeScale = g_currentMission:getEffectiveTimeScale() |
229 | |
230 | for i=numFermentations, 1, -1 do |
231 | local fermentation = self.fermentations[i] |
232 | |
233 | fermentation.time = fermentation.time + dt * timeScale |
234 | if fermentation.time >= fermentation.maxTime then |
235 | fermentation.bale:onFermentationEnd() |
236 | table.remove(self.fermentations, i) |
237 | end |
238 | |
239 | local percentage = fermentation.time / fermentation.maxTime |
240 | if math.floor(percentage * 100) ~= math.floor(fermentation.percentageSend * 100) then |
241 | fermentation.bale:onFermentationUpdate(percentage) |
242 | fermentation.percentageSend = percentage |
243 | end |
244 | end |
245 | end |
246 | end |
247 | end |