392 | function PlaceableSystem:consoleCommandDeleteAllPlaceables(includePreplaced) |
393 | local usage = "Usage: gsPlaceablesDeleteAll [includePreplaced]" |
394 | local numDeleted = 0 |
395 | |
396 | for i=#self.placeables, 1, -1 do |
397 | local placeable = self.placeables[i] |
398 | if not placeable:isMapBound() or includePreplaced then |
399 | placeable:delete() |
400 | numDeleted = numDeleted + 1 |
401 | end |
402 | end |
403 | |
404 | if includePreplaced then |
405 | return string.format("Deleted all %i placeables! Included preplaced ones!", numDeleted) |
406 | end |
407 | |
408 | return string.format("Deleted %i placeables! Excluded preplaced ones.\n%s", numDeleted, usage) |
409 | end |
478 | function PlaceableSystem:consoleCommandLoadAllPlaceables() |
479 | if self.isLoadAllRunning then |
480 | return "Cannot start loading all placeables. Another loading is currently running" |
481 | end |
482 | if g_currentMission:getIsServer() and not g_currentMission.missionDynamicInfo.isMultiplayer then |
483 | g_i3DManager:clearEntireSharedI3DFileCache(false) |
484 | |
485 | local placeablesToLoad = {} |
486 | for _, storeItem in ipairs(g_storeManager:getItems()) do |
487 | if storeItem.brush ~= nil and storeItem.brush.type ~= "" then |
488 | -- handle fences |
489 | if storeItem.brush.type == "fence" then |
490 | local singletonFilename = storeItem.brush.parameters[1] |
491 | if singletonFilename ~= nil then |
492 | table.insert(placeablesToLoad, singletonFilename) |
493 | else |
494 | Logging.error("No fence singleton filename found for '%s'", storeItem.xmlFilename) |
495 | end |
496 | else |
497 | table.insert(placeablesToLoad, storeItem.xmlFilename) |
498 | end |
499 | end |
500 | end |
501 | |
502 | if #placeablesToLoad > 0 then |
503 | self.isLoadAllRunning = true |
504 | Logging.info("Start loading all placeables...") |
505 | |
506 | local x, z = 0, 0 |
507 | local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) |
508 | local position = {x=x, y=y, z=z} |
509 | local rotation = {x=0, y=0, z=0} |
510 | |
511 | |
512 | local callback |
513 | callback = function(_, placeable, loadingState, args) |
514 | |
515 | if loadingState == Placeable.LOADING_STATE_ERROR then |
516 | Logging.error("Could not load placeable '%s'", placeablesToLoad[1]) |
517 | else |
518 | Logging.info("Loaded placeable '%s'", placeable.configFileName) |
519 | placeable:finalizePlacement() |
520 | end |
521 | |
522 | if placeable ~= nil then |
523 | placeable:delete() |
524 | end |
525 | table.remove(placeablesToLoad, 1) |
526 | |
527 | if #placeablesToLoad == 0 then |
528 | Logging.info("Finished loading placeables") |
529 | self.isLoadAllRunning = false |
530 | else |
531 | PlaceableUtil.loadPlaceable(placeablesToLoad[1], position, rotation, AccessHandler.EVERYONE, nil, callback, nil, {}) |
532 | end |
533 | end |
534 | |
535 | PlaceableUtil.loadPlaceable(placeablesToLoad[1], position, rotation, AccessHandler.EVERYONE, nil, callback, nil, {}) |
536 | else |
537 | Logging.info("No placeables found") |
538 | end |
539 | end |
540 | end |
544 | function PlaceableSystem:consoleCommandPlaceableTestAreas() |
545 | self.isTestAreaRenderingActive = not self.isTestAreaRenderingActive |
546 | |
547 | for _, placeable in ipairs(self.placeables) do |
548 | local spec = placeable.spec_placement |
549 | if spec ~= nil then |
550 | for _, area in ipairs(spec.testAreas) do |
551 | if self.isTestAreaRenderingActive then |
552 | area.debugTestBox:createWithStartEnd(area.startNode, area.endNode) |
553 | area.debugStartNode:createWithNode(area.startNode, getName(area.startNode), false, nil) |
554 | area.debugEndNode:createWithNode(area.endNode, getName(area.endNode), false, nil) |
555 | area.debugArea:createWithStartEnd(area.startNode, area.endNode) |
556 | |
557 | g_debugManager:addPermanentElement(area.debugTestBox) |
558 | g_debugManager:addPermanentElement(area.debugStartNode) |
559 | g_debugManager:addPermanentElement(area.debugEndNode) |
560 | g_debugManager:addPermanentElement(area.debugArea) |
561 | else |
562 | g_debugManager:removePermanentElement(area.debugTestBox) |
563 | g_debugManager:removePermanentElement(area.debugStartNode) |
564 | g_debugManager:removePermanentElement(area.debugEndNode) |
565 | g_debugManager:removePermanentElement(area.debugArea) |
566 | end |
567 | end |
568 | end |
569 | end |
570 | end |
413 | function PlaceableSystem:consoleCommandReloadAllPlaceables() |
414 | if self.isReloadRunning then |
415 | return "Cannot start reloading. Another reloading is currently running" |
416 | end |
417 | if g_currentMission:getIsServer() and not g_currentMission.missionDynamicInfo.isMultiplayer then |
418 | g_i3DManager:clearEntireSharedI3DFileCache(false) |
419 | |
420 | local placeablesToReload = {} |
421 | for _, placeable in ipairs(self.placeables) do |
422 | table.insert(placeablesToReload, placeable) |
423 | end |
424 | self:setSaveIds() |
425 | |
426 | if #placeablesToReload > 0 then |
427 | self.isReloadRunning = true |
428 | Logging.info("Start reloading placeables...") |
429 | local callback = function(_, placeable, loadingState, args) |
430 | local oldPlaceable = args.placeable |
431 | local xmlFile = args.xmlFile |
432 | |
433 | xmlFile:delete() |
434 | |
435 | table.removeElement(placeablesToReload, oldPlaceable) |
436 | |
437 | if loadingState == Placeable.LOADING_STATE_ERROR then |
438 | if placeable ~= nil then |
439 | placeable:delete() |
440 | end |
441 | Logging.error("Could not reload placeable '%s'. (%d left)", (placeable and placeable.configFileName) or "unknown", #placeablesToReload) |
442 | else |
443 | Logging.info("Reloaded placeable '%s'. (%d left)", placeable.configFileName, #placeablesToReload) |
444 | oldPlaceable.isReloading = true -- flag to skip actions in onDelete() such as resetting of indoor areas (old placeable is deleted after new one was loaded) |
445 | oldPlaceable:delete() |
446 | placeable:register() |
447 | end |
448 | |
449 | if #placeablesToReload == 0 then |
450 | Logging.info("Finished reloading placeables") |
451 | self.isReloadRunning = false |
452 | end |
453 | end |
454 | |
455 | for k, placeable in ipairs(placeablesToReload) do |
456 | local usedModNames = {} |
457 | local xmlFile = XMLFile.create("placeableXMLFile", "", "placeables", Placeable.xmlSchemaSavegame) |
458 | self:savePlaceableToXML(placeable, xmlFile, 0, 1, usedModNames) |
459 | |
460 | local key = "placeables.placeable(0)" |
461 | local missionInfo = g_currentMission.missionInfo |
462 | local missionDynamicInfo = g_currentMission.missionDynamicInfo |
463 | |
464 | local arguments = { |
465 | placeable = placeable, |
466 | xmlFile = xmlFile |
467 | } |
468 | self:loadPlaceableFromXML(xmlFile, key, missionInfo, missionDynamicInfo, false, callback, nil, arguments) |
469 | end |
470 | else |
471 | Logging.info("No placeables found") |
472 | end |
473 | end |
474 | end |
44 | function PlaceableSystem:delete() |
45 | for i=#self.placeables, 1, -1 do |
46 | local placeable = self.placeables[i] |
47 | placeable:delete() |
48 | end |
49 | |
50 | self.mission = nil |
51 | self.placeables = {} |
52 | self.savegameIdToPlaceable = {} |
53 | self.weatherStations = {} |
54 | self.farmhouses = {} |
55 | self.bunkerSilos = {} |
56 | |
57 | removeConsoleCommand("gsPlaceablesDeleteAll") |
58 | removeConsoleCommand("gsPlaceablesReloadAll") |
59 | removeConsoleCommand("gsPlaceablesLoadAll") |
60 | removeConsoleCommand("gsPlaceablesShowTestAreas") |
61 | end |
230 | function PlaceableSystem:load(xmlFilename, defaultXMLFilename, missionInfo, missionDynamicInfo, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) |
231 | local xmlFile = XMLFile.load("placeablesXML", xmlFilename, Placeable.xmlSchemaSavegame) |
232 | |
233 | -- Savegame version is used by the default list to determine if an upgrade has to be made |
234 | self.version = xmlFile:getValue("placeables#version", 1) |
235 | |
236 | local loadingData = {} |
237 | loadingData.xmlFile = xmlFile |
238 | loadingData.xmlFilename = xmlFilename |
239 | |
240 | if xmlFilename ~= defaultXMLFilename then |
241 | -- Upgrading |
242 | loadingData.defaultXMLFilename = defaultXMLFilename |
243 | end |
244 | |
245 | loadingData.missionInfo = missionInfo |
246 | loadingData.missionDynamicInfo = missionDynamicInfo |
247 | loadingData.placeablesById = {} |
248 | loadingData.index = 0 |
249 | loadingData.asyncCallbackFunction = asyncCallbackFunction |
250 | loadingData.asyncCallbackObject = asyncCallbackObject |
251 | loadingData.asyncCallbackArguments = asyncCallbackArguments |
252 | |
253 | if not self:loadNextPlaceableFromXML(loadingData) then |
254 | self:loadFinished(loadingData) |
255 | end |
256 | end |
324 | function PlaceableSystem:loadFinished(loadingData) |
325 | -- Do a second pass over the base-data to load any placeable upgrades |
326 | if loadingData.defaultXMLFilename ~= nil then |
327 | loadingData.xmlFile:delete() |
328 | |
329 | loadingData.xmlFile = XMLFile.load("placeablesXML", loadingData.defaultXMLFilename, Placeable.xmlSchemaSavegame) |
330 | loadingData.defaultXMLFilename = nil |
331 | loadingData.upgradeOnly = true |
332 | loadingData.index = 0 |
333 | |
334 | local savegameVersion = self.version |
335 | self.version = loadingData.xmlFile:getValue("placeables#version", 1) |
336 | |
337 | -- No version change so no upgrades needed |
338 | if self.version <= savegameVersion then |
339 | return self:loadFinished(loadingData) |
340 | end |
341 | |
342 | if not self:loadNextPlaceableFromXML(loadingData) then |
343 | self:loadFinished(loadingData) |
344 | end |
345 | else |
346 | g_asyncTaskManager:addTask(function() |
347 | loadingData.xmlFile:delete() |
348 | if loadingData.asyncCallbackFunction ~= nil then |
349 | loadingData.asyncCallbackFunction(loadingData.asyncCallbackObject, loadingData.asyncCallbackArguments) |
350 | end |
351 | end) |
352 | end |
353 | end |
260 | function PlaceableSystem:loadNextPlaceableFromXML(loadingData) |
261 | if g_currentMission.cancelLoading then |
262 | return false |
263 | end |
264 | |
265 | local xmlFile = loadingData.xmlFile |
266 | local missionInfo = loadingData.missionInfo |
267 | local missionDynamicInfo = loadingData.missionDynamicInfo |
268 | local defaultItemsToSPFarm = xmlFile:getValue("placeables#loadAnyFarmInSingleplayer", false) |
269 | |
270 | -- loop until we can load a placeable or we are at the end of the file |
271 | while true do |
272 | local index = loadingData.index |
273 | loadingData.index = loadingData.index + 1 |
274 | |
275 | local key = string.format("placeables.placeable(%d)", index) |
276 | if not xmlFile:hasProperty(key) then |
277 | return false |
278 | end |
279 | |
280 | local shouldLoad = true |
281 | if loadingData.upgradeOnly then |
282 | local sinceVersion = xmlFile:getValue(key .. "#sinceVersion") |
283 | |
284 | -- Only upgrade with an item that appeared newly since the last save of the savegame |
285 | shouldLoad = sinceVersion ~= nil and sinceVersion >= self.version |
286 | end |
287 | |
288 | if shouldLoad then |
289 | if self:loadPlaceableFromXML(xmlFile, key, missionInfo, missionDynamicInfo, defaultItemsToSPFarm, self.loadNextPlaceableFromXMLFinished, self, loadingData) then |
290 | return true |
291 | end |
292 | end |
293 | end |
294 | end |
298 | function PlaceableSystem:loadNextPlaceableFromXMLFinished(placeable, loadingState, loadingData) |
299 | if g_currentMission.cancelLoading then |
300 | self:loadFinished(loadingData) |
301 | return |
302 | end |
303 | |
304 | if placeable ~= nil then |
305 | if loadingState == Placeable.LOADING_STATE_ERROR then |
306 | Logging.warning("Corrupt savegame, placeable '%s' could not be loaded", placeable.configFileName) |
307 | g_currentMission.placeableSystem:removePlaceable(placeable) |
308 | placeable:delete() |
309 | else |
310 | if placeable.currentSavegameId ~= nil then |
311 | self.savegameIdToPlaceable[placeable.currentSavegameId] = placeable |
312 | end |
313 | placeable:register() |
314 | end |
315 | end |
316 | |
317 | if not self:loadNextPlaceableFromXML(loadingData) then |
318 | self:loadFinished(loadingData) |
319 | end |
320 | end |
357 | function PlaceableSystem:loadPlaceableFromXML(xmlFile, key, missionInfo, missionDynamicInfo, defaultItemsToSPFarm, callback, target, args) |
358 | local filename = xmlFile:getValue(key.."#filename") |
359 | local defaultProperty = xmlFile:getValue(key .. "#defaultFarmProperty", false) |
360 | local farmId = xmlFile:getValue(key .. "#farmId") |
361 | |
362 | -- Always load all placeables from a savegame. Load any non-farm placeables from the default game. Only load non-farm placeables |
363 | -- when that option is set. |
364 | local loadForCompetitive = defaultProperty and missionInfo.isCompetitiveMultiplayer and g_farmManager:getFarmById(farmId) ~= nil |
365 | local loadDefaultProperty = defaultProperty and (missionInfo.loadDefaultFarm and not missionDynamicInfo.isMultiplayer) and (farmId == FarmManager.SINGLEPLAYER_FARM_ID or defaultItemsToSPFarm) |
366 | local allowedToLoad = missionInfo.isValid or not defaultProperty or loadDefaultProperty or loadForCompetitive |
367 | |
368 | if allowedToLoad then |
369 | filename = NetworkUtil.convertFromNetworkFilename(filename) |
370 | local savegame = {xmlFile=xmlFile, key=key, ignoreFarmId=false} |
371 | |
372 | if loadDefaultProperty and defaultItemsToSPFarm and farmId ~= FarmManager.SINGLEPLAYER_FARM_ID then |
373 | farmId = FarmManager.SINGLEPLAYER_FARM_ID |
374 | savegame.ignoreFarmId = true |
375 | end |
376 | |
377 | local posX, posY, posZ = xmlFile:getValue(key .. "#position") |
378 | local rotX, rotY, rotZ = xmlFile:getValue(key .. "#rotation") |
379 | |
380 | local position = {x=posX, y=posY, z=posZ} |
381 | local rotation = {x=rotX, y=rotY, z=rotZ} |
382 | |
383 | PlaceableUtil.loadPlaceable(filename, position, rotation, farmId, savegame, callback, target, args) |
384 | return true |
385 | else |
386 | Logging.xmlInfo(xmlFile, "Placeable '%s' is not allowed to be loaded", filename) |
387 | end |
388 | end |
15 | function PlaceableSystem.new(mission, customMt) |
16 | local self = setmetatable({}, customMt or PlaceableSystem_mt) |
17 | |
18 | self.mission = mission |
19 | self.placeables = {} |
20 | self.savegameIdToPlaceable = {} |
21 | |
22 | self.weatherStations = {} |
23 | self.farmhouses = {} |
24 | self.bunkerSilos = {} |
25 | |
26 | self.version = 1 |
27 | |
28 | self.isReloadRunning = false |
29 | |
30 | if self.mission:getIsServer() then |
31 | if g_addTestCommands then |
32 | addConsoleCommand("gsPlaceablesDeleteAll", "Deletes all placeables", "consoleCommandDeleteAllPlaceables", self) |
33 | addConsoleCommand("gsPlaceablesReloadAll", "Reloads all placeables", "consoleCommandReloadAllPlaceables", self) |
34 | addConsoleCommand("gsPlaceablesLoadAll", "Loads all placeables", "consoleCommandLoadAllPlaceables", self) |
35 | addConsoleCommand("gsPlaceablesShowTestAreas", "Show test areas of all placeables", "consoleCommandPlaceableTestAreas", self) |
36 | end |
37 | end |
38 | |
39 | return self |
40 | end |
212 | function PlaceableSystem:savePlaceableToXML(placeable, xmlFile, index, i, usedModNames) |
213 | local placeableKey = string.format("placeables.placeable(%d)", index) |
214 | |
215 | local modName = placeable.customEnvironment |
216 | if modName ~= nil then |
217 | if usedModNames ~= nil then |
218 | usedModNames[modName] = modName |
219 | end |
220 | xmlFile:setValue(placeableKey.."#modName", modName) |
221 | end |
222 | |
223 | xmlFile:setValue(placeableKey.."#filename", HTMLUtil.encodeToHTML(NetworkUtil.convertToNetworkFilename(placeable.configFileName))) |
224 | |
225 | placeable:saveToXMLFile(xmlFile, placeableKey, usedModNames) |
226 | end |