LUADOC - Farming Simulator 19

Placeable

Description
Base Class for placeables Note about terrain modification on placement: If terrain modification is enabled using the placeable.leveling#requireLeveling attribute, the configuration needs at least one leveling area to be defined (ramps are optional). These areas represent parallelograms which are passed to terrain modification functions. They are defined by start, width and height nodes which in this case should be set up to form rectangular shapes. For the best results, create a new transform group for each area at the starting positions and add separate nodes for the three defining points in the placeable object I3D file.
Parent
Object
XML Configuration Parameters
placeable.filenamestring File name of associated I3D file
placeable.placement#sizeXfloat Nominal local space X size
placeable.placement#sizeZfloat Nominal local space Z size
placeable.placement#testSizeXfloat Local space X overlap testing size
placeable.placement#testSizeZfloat Local space Z overlap testing size
placeable.placement#useRandomYRotationbool If true, Y rotation is set randomly on placement
placeable.placement#useManualYRotationbool If true, Y rotation is set manually by the player
placeable.placement#alignToWorldYbool If true, the placeable is aligned with the ground on placement
placeable.placement#pos1Nodestring Required if alignToWorldY is false. Auxiliary positioning node.
placeable.placement#pos2Nodestring Required if alignToWorldY is false. Auxiliary positioning node.
placeable.placement#pos3Nodestring Required if alignToWorldY is false. Auxiliary positioning node.
placeable.incomePerHour1float Income per hour on easy difficulty
placeable.incomePerHour2float Income per hour on normal difficulty
placeable.incomePerHour3float Income per hour on hard difficulty
placeable.dayNightObjects.dayNightObject#indexstring Node index
placeable.dayNightObjects.dayNightObject#visibleDaybool If true, the node is visible during the day
placeable.dayNightObjects.dayNightObject#visibleNightbool If true, the node is visible during the night
placeable.clearAreas.clearArea.startstring Clear area start node index
placeable.clearAreas.clearArea.widthstring Clear area width node index
placeable.clearAreas.clearArea.heightstring Clear area height node index
placeable.leveling#requireLevelingbool [optional, default=false] If true, the ground around the placeable is leveled and all other leveling properties are used
placeable.leveling#maxSmoothDistancefloat [optional] Radius around leveling areas where terrain will be smoothed towards the placeable
placeable.leveling#maxSlopefloat [optional] Maximum slope of terrain created by outside smoothing expressed as an angle in degrees [0, 90]
placeable.leveling#maxEdgeAnglefloat [optional] Maximum angle between polygons in smoothed areas expressed as an angle in degrees [0, 90]
placeable.leveling#smoothingGroundTypeint [optional] Ground type used to paint the smoothed ground from leveling areas up to the radius of "maxSmoothDistance" (one of the ground types defined in groundTypes.xml)
placeable.leveling.levelAreas.levelArea.groundTypestring [optional] Ground type used to paint the ground directly under area (one of the ground types defined in groundTypes.xml)
placeable.leveling.levelAreas.levelArea.startstring [optional] Leveling area start node index
placeable.leveling.levelAreas.levelArea.widthstring [optional] Leveling area width node index
placeable.leveling.levelAreas.levelArea.heightstring [optional] Leveling area height node index
placeable.leveling.rampAreas.rampArea.groundTypestring [optional, deprecated] Ground type used to paint the ground directly under area (one of the ground types defined in groundTypes.xml)
placeable.leveling.rampAreas.rampArea.maxSlopefloat [optional, deprecated] Maximum slope of this ramp inclination expressed as an angle in degrees [0, 90]
placeable.leveling.rampAreas.rampArea.rootstring [optional, deprecated] Ramp area transform root node index
placeable.leveling.rampAreas.rampArea.startstring [optional, deprecated] Ramp area start node index, must be a child of root
placeable.leveling.rampAreas.rampArea.widthstring [optional, deprecated] Ramp area width node index, must be a sibling of start
placeable.leveling.rampAreas.rampArea.heightstring [optional, deprecated] Ramp area height node index, must be a sibling of start
placeable.tipOcclusionUpdateArea#sizeXfloat
placeable.tipOcclusionUpdateArea#sizeZfloat
placeable.tipOcclusionUpdateArea#centerXfloat
placeable.tipOcclusionUpdateArea#centerZfloat
placeable.animatedObjectsAnimated objects configurations
placable.soundsnode [optional] Sound samples, for sound properties see SoundManager
placable.storedata.maxItemCount[optional] maximum number of instances of this placeable which can be placed

Functions

alignToTerrain

Description
Align placeable to terrain
Definition
alignToTerrain()
Code
868function Placeable:alignToTerrain()
869 -- only (re-)align if the property is set and we're on the server, clients will get the correct transform in sync.
870 -- This also avoids wrong positioning of placeables on modified terrain.
871 if not self.alignToWorldY and self.isServer then
872 local x1,y1,z1 = getWorldTranslation(self.nodeId)
873 y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1,y1,z1)
874 setTranslation(self.nodeId, x1,y1,z1)
875 local x2,y2,z2 = getWorldTranslation(self.pos1Node)
876 y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2,y2,z2)
877 local x3,y3,z3 = getWorldTranslation(self.pos2Node)
878 y3 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x3,y3,z3)
879 local x4,y4,z4 = getWorldTranslation(self.pos3Node)
880 y4 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x4,y4,z4)
881 local dirX = x2 - x1
882 local dirY = y2 - y1
883 local dirZ = z2 - z1
884 local dir2X = x3 - x4
885 local dir2Y = y3 - y4
886 local dir2Z = z3 - z4
887 local upX,upY,upZ = MathUtil.crossProduct(dir2X, dir2Y, dir2Z, dirX, dirY, dirZ)
888 setDirection(self.nodeId, dirX, dirY, dirZ, upX,upY,upZ)
889 end
890end

canBuy

Description
Returns true if we can place a building checking item count
Definition
canBuy()
Return Values
bool
Code
1038function Placeable:canBuy()
1039 local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
1040 local enoughItems = storeItem.maxItemCount == nil or (storeItem.maxItemCount ~= nil and g_currentMission:getNumOfItems(storeItem, g_currentMission:getFarmId()) < storeItem.maxItemCount)
1041 return enoughItems
1042end

clearFoliageAndTipAreas

Description
Clear foliage and tipAny from clearAreas
Definition
clearFoliageAndTipAreas()
Code
894function Placeable:clearFoliageAndTipAreas()
895 if self.isServer then
896 for _, areas in pairs{self.clearAreas, self.levelAreas} do
897 for _, area in pairs(areas) do
898 local x,_,z = getWorldTranslation(area.start)
899 local x1,_,z1 = getWorldTranslation(area.width)
900 local x2,_,z2 = getWorldTranslation(area.height)
901 -- clear foliage
902 FSDensityMapUtil.removeFieldArea(x, z, x1, z1, x2, z2)
903 FSDensityMapUtil.removeWeedArea(x, z, x1, z1, x2, z2)
904 FSDensityMapUtil.eraseTireTrack(x, z, x1, z1, x2, z2)
905 -- clear tipAny
906 DensityMapHeightUtil.clearArea(x, z, x1, z1, x2, z2)
907 end
908 end
909
910 -- Add foliage again
911 for _, area in pairs(self.foliageAreas) do
912 FieldUtil.setAreaFruit(area.fieldDimensions, area.fruitType, area.fruitState)
913 end
914 end
915end

collectPickObjects

Description
Collect pick objects
Definition
collectPickObjects(integer node)
Arguments
integernodenode id
Code
934function Placeable:collectPickObjects(node)
935 if getRigidBodyType(node) ~= "NoRigidBody" then
936 table.insert(self.pickObjects, node)
937 end
938 local numChildren = getNumOfChildren(node)
939 for i=1, numChildren do
940 self:collectPickObjects(getChildAt(node, i-1))
941 end
942end

createNode

Description
Create node
Definition
createNode(string i3dFilename)
Arguments
stringi3dFilenamei3d file name
Return Values
booleansuccesssuccess
Code
378function Placeable:createNode(i3dFilename)
379 self.i3dFilename = i3dFilename
380 local nodeRoot = g_i3DManager:loadSharedI3DFile(i3dFilename, nil, false, false)
381 if nodeRoot == 0 then
382 return false
383 end
384
385 if self.useMultiRootNode then
386 link(getRootNode(), nodeRoot)
387 self.nodeId = nodeRoot
388 else
389 local nodeId = getChildAt(nodeRoot, 0)
390 if nodeId == 0 then
391 delete(nodeRoot)
392 return false
393 end
394 link(getRootNode(), nodeId)
395 delete(nodeRoot)
396 self.nodeId = nodeId
397 end
398
399 return true
400end

dayChanged

Description
Called if day changed
Definition
dayChanged()
Code
1122function Placeable:dayChanged()
1123 self.age = self.age + 1
1124end

delete

Description
Deleting placeable
Definition
delete()
Code
153function Placeable:delete()
154 self.isDeleted = true
155 -- Remove from placeables to delete list, so that base mission doesn't try to double delete
156 g_currentMission:removePlaceableToDelete(self)
157 if self.i3dFilename ~= nil then
158 g_i3DManager:releaseSharedI3DFile(self.i3dFilename, nil, true)
159 end
160
161 for _, node in ipairs(self.triggerMarkers) do
162 g_currentMission:removeTriggerMarker(node)
163 end
164
165 if self.writtenBlockedAreas then
166 -- unblock areas in the blocked area map
167 local deform = self:createDeformationObject(g_currentMission.terrainRootNode, true, false)
168 deform:unblockAreas()
169 end
170
171 for _, animatedObject in ipairs(self.animatedObjects) do
172 animatedObject:delete()
173 end
174
175 for _, hotspot in ipairs(self.mapHotspots) do
176 g_currentMission.hud:removeMapHotspot(hotspot)
177 hotspot:delete()
178 end
179
180 if g_currentMission ~= nil and g_currentMission.environment ~= nil then
181 g_currentMission.environment:removeDayChangeListener(self)
182 g_currentMission.environment:removeWeatherChangeListener(self)
183 g_currentMission.environment:removeHourChangeListener(self)
184 end
185
186 unregisterObjectClassName(self)
187 g_currentMission:removeItemToSave(self)
188 g_currentMission:removePlaceable(self)
189 for _, node in pairs(self.pickObjects) do
190 g_currentMission:removeNodeObject(node)
191 end
192
193 if self.isClient then
194 g_soundManager:deleteSamples(self.samples)
195 end
196
197 if self.boughtWithFarmland and self.isServer then
198 g_farmlandManager:removeStateChangeListener(self)
199 end
200
201 g_currentMission:removeOwnedItem(self)
202
203 if self.nodeId ~= 0 and entityExists(self.nodeId) then
204 delete(self.nodeId)
205 self.nodeId = 0
206 end
207
208 Placeable:superClass().delete(self)
209end

finalizePlacement

Description
Called if placeable is placed
Definition
finalizePlacement()
Code
795function Placeable:finalizePlacement()
796 if self.isInPreviewMode then
797 print("Error: Can't finalize placement of preview placeables")
798 end
799 if not self.isolated then
800
801 self:alignToTerrain()
802
803 -- block areas in the blocked area map to prevent placeable overlaps (bug 23748)
804 local deform = self:createDeformationObject(g_currentMission.terrainRootNode, true, false)
805 deform:blockAreas()
806 self.writtenBlockedAreas = true
807
808 addToPhysics(self.nodeId)
809 g_currentMission:addPlaceable(self)
810 g_currentMission:addItemToSave(self)
811 g_currentMission:addOwnedItem(self)
812 self:collectPickObjects(self.nodeId)
813
814 for _, node in pairs(self.pickObjects) do
815 g_currentMission:addNodeObject(node, self)
816 end
817
818 local missionInfo = g_currentMission.missionInfo
819 if self.isServer then
820 if not self.isLoadedFromSavegame or
821 (missionInfo.isValid and not (missionInfo:getIsTipCollisionValid(g_currentMission) and missionInfo:getIsPlacementCollisionValid(g_currentMission))) then
822 self:setTipOcclusionAreaDirty()
823 end
824 end
825 end
826
827 if self.isClient then
828 g_soundManager:playSample(self.samples.idle)
829 end
830
831 -- initially update dayNightObjects
832 self:weatherChanged()
833 g_currentMission.environment:addHourChangeListener(self)
834
835 local x,_,z = getWorldTranslation(self.nodeId)
836 self.farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(x, z)
837
838 if self.boughtWithFarmland then
839 if self.isServer then
840 self:updateOwnership(true)
841 end
842
843 g_farmlandManager:addStateChangeListener(self)
844 end
845
846 g_messageCenter:publish(MessageType.FARM_PROPERTY_CHANGED, {self:getOwnerFarmId()})
847end

getDailyUpkeep

Description
Returns daily up keep
Definition
getDailyUpkeep()
Return Values
integerdailyUpkeepdaily up keep
Code
1070function Placeable:getDailyUpkeep()
1071 local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
1072 local multiplier = 1
1073 if storeItem.lifetime ~= nil and storeItem.lifetime ~= 0 then
1074 local ageMultiplier = math.min(self.age/storeItem.lifetime, 1)
1075 multiplier = 1 + EconomyManager.MAX_DAILYUPKEEP_MULTIPLIER * ageMultiplier
1076 end
1077 return StoreItemUtil.getDailyUpkeep(storeItem, nil) * multiplier
1078end

getIsPlayerInRange

Description
Returns true if player is in range
Definition
getIsPlayerInRange(float distance, table player)
Arguments
floatdistancedistance
tableplayerplayer
Return Values
booleanisInRangeis in range
Code
229function Placeable:getIsPlayerInRange(distance, player)
230 if self.nodeId ~= 0 then
231 distance = Utils.getNoNil(distance, 10)
232 if player == nil then
233 for _, player in pairs(g_currentMission.players) do
234 if self:isInActionDistance(player, self.nodeId, distance) then
235 return true, player
236 end
237 end
238 else
239 return self:isInActionDistance(player, self.nodeId, distance), player
240 end
241 end
242 return false, nil
243end

getPrice

Description
Returns price
Definition
getPrice()
Return Values
integerpriceprice
Code
1030function Placeable:getPrice()
1031 return self.price
1032end

getSellPrice

Description
Returns sell price
Definition
getSellPrice()
Return Values
integersellPricesell price
Code
1091function Placeable:getSellPrice()
1092 local priceMultiplier = 0.5
1093 local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
1094 local maxVehicleAge = storeItem.lifetime
1095
1096 if maxVehicleAge ~= nil and maxVehicleAge ~= 0 then
1097 priceMultiplier = priceMultiplier * math.exp(-3.5 * math.min(self.age/maxVehicleAge, 1))
1098 end
1099
1100 return math.floor(self.price * math.max(priceMultiplier, 0.05))
1101end

getSpecValueIncomePerHour

Description
Returns value of income per hour
Definition
getSpecValueIncomePerHour(table storeItem, table realItem)
Arguments
tablestoreItemstore item
tablerealItemreal item
Return Values
integerincomePerHourincome per hour
Code
1339function Placeable.getSpecValueIncomePerHour(storeItem, realItem)
1340 if storeItem.specs.incomePerHour == nil then
1341 return nil
1342 end
1343 return string.format(g_i18n:getText("shop_incomeValue"), g_i18n:formatMoney(storeItem.specs.incomePerHour[g_currentMission.missionInfo.economicDifficulty]))
1344end

hourChanged

Description
Called if hour changed
Definition
hourChanged()
Code
1112function Placeable:hourChanged()
1113 if self.isServer then
1114 if self.incomePerHour ~= 0 then
1115 g_currentMission:addMoney(self.incomePerHour, self:getOwnerFarmId(), MoneyType.PROPERTY_INCOME, true)
1116 end
1117 end
1118end

initPose

Description
Initialize pose
Definition
initPose(float x, float y, float z, float rx, float ry, float rz, boolean initRandom)
Arguments
floatxx world position
floatyy world position
floatzz world position
floatrxrx world rotation
floatryry world rotation
floatrzrz world rotation
booleaninitRandominitialize random
Code
926function Placeable:initPose(x,y,z, rx,ry,rz, initRandom)
927 setTranslation(self.nodeId, x, y, z)
928 setRotation(self.nodeId, rx, ry, rz)
929end

isInActionDistance

Description
Returns true if player is in range
Definition
isInActionDistance(table player, integer refNode, float distance)
Arguments
tableplayerplayer
integerrefNodeid of reference node
floatdistancedistance
Return Values
booleanisInRangeis in range
Code
251function Placeable:isInActionDistance(player, refNode, distance)
252 local x,_,z = getWorldTranslation(refNode)
253 local px,_,pz = getWorldTranslation(player.rootNode)
254 local dx,dz = px-x, pz-z
255 if dx*dx + dz*dz < distance*distance then
256 return true
257 end
258
259 return false
260end

load

Description
Load placeable
Definition
load(string xmlFilename, float x, float y, float z, float rx, float ry, float rz, boolean initRandom)
Arguments
stringxmlFilenamexml file name
floatxx world position
floatyz world position
floatzz world position
floatrxrx world rotation
floatryry world rotation
floatrzrz world rotation
booleaninitRandominitialize random
Return Values
booleansuccesssuccess
Code
450function Placeable:load(xmlFilename, x,y,z, rx,ry,rz, initRandom)
451 self.configFileName = xmlFilename
452 self.customEnvironment, self.baseDirectory = Utils.getModNameAndBaseDirectory(xmlFilename)
453
454 local xmlFile = loadXMLFile("TempXML", xmlFilename)
455 if xmlFile == 0 then
456 return false
457 end
458 local i3dFilename = getXMLString(xmlFile, "placeable.filename")
459 self.placementSizeX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeX"), self.placementSizeX)
460 self.placementSizeZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeZ"), self.placementSizeZ)
461 self.placementSizeOffsetX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeOffsetX"), self.placementSizeOffsetX)
462 self.placementSizeOffsetZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeOffsetZ"), self.placementSizeOffsetZ)
463 self.placementTestSizeX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeX"), self.placementSizeX)
464 self.placementTestSizeZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeZ"), self.placementSizeZ)
465 self.placementTestSizeOffsetX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeOffsetX"), self.placementTestSizeOffsetX)
466 self.placementTestSizeOffsetZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeOffsetZ"), self.placementTestSizeOffsetZ)
467 self.useRandomYRotation = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#useRandomYRotation"), self.useRandomYRotation)
468 self.useManualYRotation = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#useManualYRotation"), self.useManualYRotation)
469 self.alignToWorldY = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#alignToWorldY"), true)
470 self.placementPositionSnapSize = math.abs(Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#placementPositionSnapSize"), 0.0))
471 self.placementPositionSnapOffset = math.abs(Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#placementPositionSnapOffset"), 0.0))
472 self.placementRotationSnapAngle = math.rad(math.abs(Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#placementRotationSnapAngle"), 0.0)))
473
474 self.boughtWithFarmland = Utils.getNoNil(getXMLBool(xmlFile, "placeable.boughtWithFarmland"), false)
475
476 self.incomePerHour = getXMLFloat(xmlFile, "placeable.incomePerHour" .. g_currentMission.missionInfo.economicDifficulty)
477 -- fallback for old single value format
478 if self.incomePerHour == nil then
479 self.incomePerHour = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.incomePerHour"), 0)
480 if g_currentMission.missionInfo.economicDifficulty == 1 then
481 self.incomePerHour = self.incomePerHour * 1.5
482 elseif g_currentMission.missionInfo.economicDifficulty == 3 then
483 self.incomePerHour = self.incomePerHour / 1.5
484 end
485 end
486
487 local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
488 if storeItem ~= nil then
489 if self.price == 0 or self.price == nil then
490 self.price = StoreItemUtil.getDefaultPrice(storeItem)
491 end
492
493 if g_currentMission ~= nil and storeItem.canBeSold then
494 g_currentMission.environment:addDayChangeListener(self)
495 end
496 end
497
498 if i3dFilename == nil then
499 delete(xmlFile)
500 return false
501 end
502 self.i3dFilename = Utils.getFilename(i3dFilename, self.baseDirectory)
503
504 if not self:createNode(self.i3dFilename) then
505 delete(xmlFile)
506 return false
507 end
508 self:initPose(x,y,z, rx,ry,rz, initRandom)
509
510 if hasXMLProperty(xmlFile, "placeable.dayNightObjects") then
511 local i = 0
512 while true do
513 local key = string.format("placeable.dayNightObjects.dayNightObject(%d)", i)
514 if not hasXMLProperty(xmlFile, key) then
515 break
516 end
517
518 local node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key.."#node"))
519 if node ~= nil then
520 if self.dayNightObjects == nil then
521 self.dayNightObjects = {}
522 g_currentMission.environment:addWeatherChangeListener(self)
523 end
524
525 local visibleDay = getXMLBool(xmlFile, key.."#visibleDay")
526 local visibleNight = getXMLBool(xmlFile, key.."#visibleNight")
527 local intensityDay = getXMLFloat(xmlFile, key.."#intensityDay")
528 local intensityNight = getXMLFloat(xmlFile, key.."#intensityNight")
529
530 table.insert(self.dayNightObjects, {node=node, visibleDay=visibleDay, visibleNight=visibleNight, intensityDay=intensityDay, intensityNight=intensityNight})
531 end
532 i = i + 1
533 end
534 end
535
536 -- load leveling properties
537 self.requireLeveling = Utils.getNoNil(getXMLBool(xmlFile, "placeable.leveling#requireLeveling"), self.requireLeveling)
538 if self.requireLeveling then
539 self.maxSmoothDistance = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.leveling#maxSmoothDistance"), 3)
540 self.maxSlope = MathUtil.degToRad(Utils.getNoNil(getXMLFloat(xmlFile, "placeable.leveling#maxSlope"), 45))
541 self.maxEdgeAngle = MathUtil.degToRad(Utils.getNoNil(getXMLFloat(xmlFile, "placeable.leveling#maxEdgeAngle"), 45))
542 self.smoothingGroundType = getXMLString(xmlFile, "placeable.leveling#smoothingGroundType")
543 end
544
545 self:loadAreasFromXML(self.clearAreas, xmlFile, "placeable.clearAreas.clearArea(%d)", false, false)
546 self:loadAreasFromXML(self.levelAreas, xmlFile, "placeable.leveling.levelAreas.levelArea(%d)", false, true) -- load leveling info
547 -- ramps loaded only for backwards compatibility (removing blocking area) or mods:
548 self:loadAreasFromXML(self.rampAreas, xmlFile, "placeable.leveling.rampAreas.rampArea(%d)", true, true) -- load ramps and leveling info
549 self:loadAreasFromXML(self.foliageAreas, xmlFile, "placeable.foliageAreas.foliageArea(%d)", false, false, true) -- load ramps and leveling info
550
551 if hasXMLProperty(xmlFile, "placeable.tipOcclusionUpdateArea") then
552 local sizeX = getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#sizeX")
553 local sizeZ = getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#sizeZ")
554
555 if sizeX ~= nil and sizeZ ~= nil then
556 local centerX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#centerX"), 0)
557 local centerZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#centerZ"), 0)
558 self.tipOcclusionUpdateArea = {centerX, centerZ, sizeX, sizeZ}
559 end
560 end
561
562 if not self.alignToWorldY then
563 self.pos1Node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos1Node"))
564 self.pos2Node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos2Node"))
565 self.pos3Node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos3Node"))
566 if self.pos1Node == nil or self.pos2Node == nil or self.pos3Node == nil then
567 self.alignToWorldY = true
568 print("Warning: Pos1Node, Pos2Node and Pos3Node has to be set when alignToWorldY is false!")
569 end
570 end
571
572 if hasXMLProperty(xmlFile, "placeable.animatedObjects") then
573 local i = 0
574 while true do
575 local animationKey = string.format("placeable.animatedObjects.animatedObject(%d)", i)
576 if not hasXMLProperty(xmlFile, animationKey) then
577 break
578 end
579
580 local animatedObject = AnimatedObject:new(self.isServer, self.isClient)
581 animatedObject:setOwnerFarmId(self:getOwnerFarmId(), false)
582 if not animatedObject:load(self.nodeId, xmlFile, animationKey, self.configFileName) then
583 print("Error: Failed to load animated object " .. tostring(i))
584 else
585 animatedObject:register(true)
586 table.insert(self.animatedObjects, animatedObject)
587 end
588
589 i = i + 1
590 end
591 end
592
593 local i = 0
594 while true do
595 local triggerMarkerKey = string.format("placeable.triggerMarkers.triggerMarker(%d)", i)
596 if not hasXMLProperty(xmlFile, triggerMarkerKey) then
597 break
598 end
599
600 local node = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, triggerMarkerKey.."#node"))
601 if node ~= nil then
602 table.insert(self.triggerMarkers, node)
603 g_currentMission:addTriggerMarker(node)
604 end
605
606 i = i + 1
607 end
608
609 if hasXMLProperty(xmlFile, "placeable.hotspots") then
610 local i = 0
611 while true do
612 local hotspotKey = string.format("placeable.hotspots.hotspot(%d)", i)
613 if not hasXMLProperty(xmlFile, hotspotKey) then
614 break
615 end
616
617 local hotspot = self:loadHotspotFromXML(xmlFile, hotspotKey)
618 hotspot:setOwnerFarmId(self:getOwnerFarmId(), false)
619 if hotspot ~= nil then
620 g_currentMission:addMapHotspot(hotspot)
621 table.insert(self.mapHotspots, hotspot)
622 end
623
624 i = i + 1
625 end
626 end
627
628 if self.isClient then
629 self.samples.idle = g_soundManager:loadSampleFromXML(xmlFile, "placeable.sounds", "idle", self.baseDirectory, self.nodeId, 1, AudioGroup.ENVIRONMENT, nil, nil)
630 end
631
632 delete(xmlFile)
633
634 return true
635end

loadAreaFromXML

Description
Load a single area definition from XML. Areas are defined by three nodes: start, width and height. The start node denotes the origin of the area, while width and height provide the dimensions of the spanned parallelogram. Usage of areas include clear areas (foliage is cleared) and leveling areas (terrain is leveled) around placeable objects.
Definition
loadAreaFromXML(table area, integer xmlFile, string key, bool isRamp)
Arguments
tableareaEmpty area definition table which receives the loaded node IDs.
integerxmlFileID of the XML file
stringkeyString Key to the XML element
boolisRampIf true, the targeted areas are ramps
Return Values
booleansuccesssuccess
Code
675function Placeable:loadAreaFromXML(area, xmlFile, key, isRamp, isLeveling)
676 local start = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#startNode"))
677 local width = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#widthNode"))
678 local height = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#heightNode"))
679
680 if start ~= nil and width ~= nil and height ~= nil then
681 area.root = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#rootNode"))
682 area.start = start
683 area.width = width
684 area.height = height
685 area.texture = getXMLString(xmlFile, key .. "#texture")
686
687 if isRamp then
688 local rx, ry, rz = getRotation(area.root)
689 area.baseRotation = {rx, ry, rz} -- store base rotation for resetting during preview
690 local rampSlope = getXMLFloat(xmlFile, key .. "#maxSlope")
691 area.maxSlope = rampSlope and MathUtil.degToRad(rampSlope) or self.maxSlope
692 end
693
694 if isLeveling then
695 area.groundType = getXMLString(xmlFile, key .. "#groundType")
696 end
697
698 return true
699 end
700
701 return false
702end

loadFoliageAreaFromXML

Description
Load foliage definitons from XML.
Definition
loadFoliageAreaFromXML()
Code
706function Placeable:loadFoliageAreaFromXML(area, xmlFile, key)
707 local rootNode = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#rootNode"))
708 local fruitType = getXMLString(xmlFile, key .. "#fruitType")
709 local fruitTypeDesc = g_fruitTypeManager:getFruitTypeByName(fruitType)
710 local state = getXMLInt(xmlFile, key .. "#state")
711
712 if rootNode ~= nil and fruitTypeDesc ~= nil then
713 area.fruitType = fruitTypeDesc.index
714 area.fieldDimensions = rootNode
715 area.fruitState = Utils.getNoNil(state, fruitTypeDesc.maxHarvestingGrowthState - 1)
716
717 return true
718 end
719
720 return false
721end

loadFromXMLFile

Description
Loading from attributes and nodes
Definition
loadFromXMLFile(integer xmlFile, string key, boolean resetVehicles)
Arguments
integerxmlFileid of xml object
stringkeykey
booleanresetVehiclesreset vehicles
Return Values
booleansuccesssuccess
Code
950function Placeable:loadFromXMLFile(xmlFile, key, resetVehicles)
951 local x,y,z = StringUtil.getVectorFromString(getXMLString(xmlFile, key.."#position"))
952 local xRot,yRot,zRot = StringUtil.getVectorFromString(getXMLString(xmlFile, key.."#rotation"))
953 if x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil then
954 return false
955 end
956
957 xRot = math.rad(xRot)
958 yRot = math.rad(yRot)
959 zRot = math.rad(zRot)
960
961 local xmlFilename = getXMLString(xmlFile, key.."#filename")
962 if xmlFilename == nil then
963 return false
964 end
965 xmlFilename = NetworkUtil.convertFromNetworkFilename(xmlFilename)
966
967 if self:load(xmlFilename, x,y,z, xRot, yRot, zRot, false, false) then
968 self.age = Utils.getNoNil(getXMLFloat(xmlFile, key.."#age"), 0)
969 self.price = Utils.getNoNil(getXMLInt(xmlFile, key.."#price"), self.price)
970
971 -- Use a call so any sub-objects of the placeable can be updated
972 self:setOwnerFarmId(Utils.getNoNil(getXMLInt(xmlFile, key .. "#farmId"), AccessHandler.EVERYONE), true)
973
974 self.mapBoundId = Utils.getNoNil(getXMLString(xmlFile, key .. "#mapBoundId"), self.mapBoundId)
975 self.isLoadedFromSavegame = true
976 self:finalizePlacement()
977
978 for i, animatedObject in ipairs(self.animatedObjects) do
979 animatedObject:loadFromXMLFile(xmlFile, string.format("%s.animatedObjects.animatedObject(%d)", key, i - 1))
980 end
981
982 return true
983 else
984 return false
985 end
986end

loadHotspotFromXML

Description
Load hotspot from XML definitons
Definition
loadHotspotFromXML()
Code
725function Placeable:loadHotspotFromXML(xmlFile, key)
726 local name = Utils.getNoNil(getXMLString(xmlFile, key.."#name"), "")
727
728 local category = Utils.getNoNil(getXMLString(xmlFile, key.."#category"), "CATEGORY_TRIGGER")
729 if MapHotspot[category] ~= nil then
730 category = MapHotspot[category]
731 else
732 category = MapHotspot.CATEGORY_DEFAULT
733 end
734
735 local hotspot = MapHotspot:new(name, category)
736
737 local text = g_i18n:convertText(Utils.getNoNil(getXMLString(xmlFile, key.."#fullName"), ""))
738 if text ~= "" then
739 local showName = Utils.getNoNil(getXMLBool(xmlFile, key.."#showName"), false)
740
741 hotspot:setText(text, not showName)
742 end
743
744 local imageFilename = getXMLString(xmlFile, key.."#imageFilename")
745 if imageFilename ~= nil then
746 imageFilename = Utils.getFilename(imageFilename, self.baseDirectory)
747 end
748
749 local imageUVs = StringUtil.getVectorNFromString(getXMLString(xmlFile, key.."#imageUVs"), 4)
750 local imageName = getXMLString(xmlFile, key.."#imageName")
751 if imageName ~= nil and MapHotspot.UV[imageName] ~= nil then
752 imageUVs = MapHotspot.UV[imageName]
753 end
754 if imageUVs ~= nil then
755 imageUVs = getNormalizedUVs(imageUVs)
756 end
757 if imageUVs ~= nil then
758 local baseColor = StringUtil.getVectorNFromString(getXMLString(xmlFile, key.."#baseColor"), 4)
759 hotspot:setBorderedImage(imageFilename, imageUVs, baseColor)
760 end
761
762 local linkNode = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#linkNode"))
763 if linkNode == nil then
764 linkNode = self.nodeId
765 end
766 hotspot:setLinkedNode(linkNode)
767
768 local width = getXMLFloat(xmlFile, key.."#width")
769 local height = getXMLFloat(xmlFile, key.."#height")
770 if width ~= nil and height ~= nil then
771 hotspot:setSize(width, height)
772 end
773
774 hotspot:setBlinking(Utils.getNoNil(getXMLBool(xmlFile, key.."#blinking"), false))
775 hotspot:setPersistent(Utils.getNoNil(getXMLBool(xmlFile, key.."#persistent"), false))
776 hotspot:setRenderLast(Utils.getNoNil(getXMLBool(xmlFile, key.."#renderLast"), false))
777
778 local textSize = getXMLInt(xmlFile, key.."#textSize")
779 if textSize ~= nil then
780 _, textSize = getNormalizedScreenValues(0, textSize)
781 hotspot:setTextOptions(textSize)
782 end
783
784 local hotspotTextOffset = Utils.getNoNil(getXMLString(xmlFile, key .. "#hotspotTextOffset"), "0px 0px")
785 hotspot:setRawTextOffset(hotspotTextOffset)
786
787 local textColor = StringUtil.getVectorNFromString(getXMLString(xmlFile, key.."#textColor"), 4)
788 hotspot:setTextOptions(nil, nil, nil, textColor)
789
790 return hotspot
791end

loadSpecValueIncomePerHour

Description
Loads capacity spec value
Definition
loadSpecValueIncomePerHour(integer xmlFile, string customEnvironment)
Arguments
integerxmlFileid of xml object
stringcustomEnvironmentcustom environment
Return Values
tablecapacityAndUnitcapacity and unit
Code
1322function Placeable.loadSpecValueIncomePerHour(xmlFile, customEnvironment)
1323 if not hasXMLProperty(xmlFile, "placeable.incomePerHour1") then
1324 return nil
1325 end
1326
1327 local incomePerHour = {}
1328 incomePerHour[1] = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.incomePerHour1"), 0)
1329 incomePerHour[2] = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.incomePerHour2"), 0)
1330 incomePerHour[3] = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.incomePerHour3"), 0)
1331 return incomePerHour
1332end

new

Description
Creating placeable
Definition
new(boolean isServer, boolean isClient, table customMt)
Arguments
booleanisServeris server
booleanisClientis client
tablecustomMtcustom metatable
Return Values
tableinstanceInstance of object
Code
104function Placeable:new(isServer, isClient, customMt)
105 local self = Object:new(isServer, isClient, customMt or Placeable_mt)
106 self.nodeId = 0
107
108 self.useRandomYRotation = false
109 self.useManualYRotation = false
110 self.placementSizeX = 1
111 self.placementSizeZ = 1
112 self.placementSizeOffsetX = 0
113 self.placementSizeOffsetZ = 0
114 self.placementTestSizeX = 1
115 self.placementTestSizeZ = 1
116 self.placementTestSizeOffsetX = 0
117 self.placementTestSizeOffsetZ = 0
118 self.requireLeveling = false
119 self.maxSmoothDistance = 3
120 self.maxSlope = MathUtil.degToRad(45)
121 self.maxEdgeAngle = MathUtil.degToRad(45)
122 self.smoothingGroundType = nil
123
124 self.triggerMarkers = {}
125 self.clearAreas = {}
126 self.levelAreas = {}
127 self.rampAreas = {}
128 self.foliageAreas = {}
129 self.samples = {}
130 self.pickObjects = {}
131 self.animatedObjects = {}
132 self.triggerMarkers = {}
133 self.mapHotspots = {}
134 self.isolated = false
135 self.isDeleted = false
136 self.useMultiRootNode = false
137 self.price = 0
138 self.age = 0
139 self.isInPreviewMode = nil
140 self.placementPositionSnapSize = 0
141 self.placementPositionSnapOffset = 0
142 self.placementRotationSnapAngle = 0
143
144 -- defines that a placeable is placed on a map directly, and with a unique ID
145 self.mapBoundId = nil
146
147 registerObjectClassName(self, "Placeable")
148 return self
149end

onBuy

Description
Called on buy
Definition
onBuy()
Code
1054function Placeable:onBuy()
1055end

onCreateGlowMaterial

Description
On create glow material
Definition
onCreateGlowMaterial(empty empty, integer id)
Arguments
emptyemptyempty
integeridid of node
Code
88function Placeable.onCreateGlowMaterial(_, id)
89 if getHasShaderParameter(id, "colorScale") then
90 Placeable.GLOW_MATERIAL = getMaterial(id, 0)
91 end
92end

onSell

Description
Called on sell
Definition
onSell()
Code
1059function Placeable:onSell()
1060 if self.isServer then
1061 self:setTipOcclusionAreaDirty()
1062 end
1063
1064 g_messageCenter:publish(MessageType.FARM_PROPERTY_CHANGED, {self:getOwnerFarmId()})
1065end

readStream

Description
Called on client side on join
Definition
readStream(integer streamId, table connection)
Arguments
integerstreamIdstream ID
tableconnectionconnection
Code
266function Placeable:readStream(streamId, connection)
267 Placeable:superClass().readStream(self, streamId, connection)
268 if connection:getIsServer() then
269 local configFileName = NetworkUtil.convertFromNetworkFilename(streamReadString(streamId))
270 local x=streamReadFloat32(streamId)
271 local y=streamReadFloat32(streamId)
272 local z=streamReadFloat32(streamId)
273 local rx=NetworkUtil.readCompressedAngle(streamId)
274 local ry=NetworkUtil.readCompressedAngle(streamId)
275 local rz=NetworkUtil.readCompressedAngle(streamId)
276 local isNew = self.configFileName == nil
277 if isNew then
278 self:load(configFileName, x,y,z, rx,ry,rz, false, false)
279 end
280 self.age = streamReadUInt16(streamId)
281 self.price = streamReadInt32(streamId)
282
283 if isNew then
284 self:finalizePlacement()
285 end
286
287 for _, animatedObject in ipairs(self.animatedObjects) do
288 local animatedObjectId = NetworkUtil.readNodeObjectId(streamId)
289 animatedObject:readStream(streamId, connection)
290 g_client:finishRegisterObject(animatedObject, animatedObjectId)
291 end
292
293 -- We have a custom function. The values for animated objects have been overridden by readStream of them above
294 self:setOwnerFarmId(self.ownerFarmId, true)
295 end
296end

saveToXMLFile

Description
Get save attributes and nodes
Definition
saveToXMLFile(string nodeIdent)
Arguments
stringnodeIdentnode ident
Return Values
stringattributesattributes
stringnodesnodes
Code
993function Placeable:saveToXMLFile(xmlFile, key, usedModNames)
994 local x,y,z = getTranslation(self.nodeId)
995 local xRot,yRot,zRot = getRotation(self.nodeId)
996
997 setXMLString(xmlFile, key.."#filename", HTMLUtil.encodeToHTML(NetworkUtil.convertToNetworkFilename(self.configFileName)))
998 setXMLString(xmlFile, key.."#position", string.format("%.4f %.4f %.4f", x, y, z))
999 setXMLString(xmlFile, key.."#rotation", string.format("%.4f %.4f %.4f", math.deg(xRot), math.deg(yRot), math.deg(zRot)))
1000 setXMLInt(xmlFile, key.."#age", self.age)
1001 setXMLFloat(xmlFile, key.."#price", self.price)
1002 setXMLInt(xmlFile, key.."#farmId", self:getOwnerFarmId())
1003
1004 if self.mapBoundId ~= nil then
1005 setXMLString(xmlFile, key.."#mapBoundId", self.mapBoundId)
1006 end
1007
1008 for i, animatedObject in ipairs(self.animatedObjects) do
1009 animatedObject:saveToXMLFile(xmlFile, string.format("%s.animatedObjects.animatedObject(%d)", key, i - 1), usedModNames)
1010 end
1011end

setCollisionMask

Description
Set collision mask of node and its children
Definition
setCollisionMask(integer nodeId, integer mask)
Arguments
integernodeIdid of node
integermaskcollision mask
Code
215function Placeable:setCollisionMask(nodeId, mask)
216 setCollisionMask(nodeId, mask)
217 local numChildren = getNumOfChildren(nodeId)
218 for i=0,numChildren-1 do
219 local childId = getChildAt(nodeId, i)
220 self:setCollisionMask(childId, mask)
221 end
222end

setPlaceablePreviewState

Description
Set placable preview state
Definition
setPlaceablePreviewState(int state)
Arguments
intstatePlacement state [Placeable.PREVIEW_STATE.CHECKING | Placeable.PREVIEW_STATE.VALID | Placeable.PREVIEW_STATE.INVALID]
Code
421function Placeable:setPlaceablePreviewState(state)
422 if not self.isInPreviewMode then
423 self.isInPreviewMode = true
424
425 self.previewGlowingNodes = {}
426 if Placeable.GLOW_MATERIAL ~= nil then
427 -- replace materials with glowing material
428 self:setPreviewMaterials(self.nodeId, self.previewGlowingNodes)
429 end
430 end
431 if Placeable.GLOW_MATERIAL ~= nil then
432 local color = Placeable.PREVIEW_COLOR[state]
433 for node in pairs(self.previewGlowingNodes) do
434 setShaderParameter(node, "colorScale", color[1], color[2], color[3], color[4], false)
435 end
436 end
437end

setPreviewMaterials

Description
Sets the preview material to all nodes in the placeable
Definition
setPreviewMaterials(integer node, table nodeTable)
Arguments
integernodeid of node
tablenodeTabletable to save the nodes
Code
406function Placeable:setPreviewMaterials(node, nodeTable)
407 if getHasClassId(node, ClassIds.SHAPE) then
408 nodeTable[node] = node
409 setMaterial(node, Placeable.GLOW_MATERIAL, 0)
410 end
411
412 local numChildren = getNumOfChildren(node)
413 for i=0, numChildren-1 do
414 self:setPreviewMaterials(getChildAt(node, i), nodeTable)
415 end
416end

setTipOcclusionAreaDirty

Description
Set tip occlusion area dirty
Definition
setTipOcclusionAreaDirty()
Code
851function Placeable:setTipOcclusionAreaDirty()
852 if self.tipOcclusionUpdateArea ~= nil and self.nodeId ~= 0 then
853 local x, z, sizeX, sizeZ = unpack(self.tipOcclusionUpdateArea)
854 local x1,_,z1 = localToWorld(self.nodeId, x+sizeX*0.5, 0, z+sizeZ*0.5)
855 local x2,_,z2 = localToWorld(self.nodeId, x-sizeX*0.5, 0, z+sizeZ*0.5)
856 local x3,_,z3 = localToWorld(self.nodeId, x+sizeX*0.5, 0, z-sizeZ*0.5)
857 local x4,_,z4 = localToWorld(self.nodeId, x-sizeX*0.5, 0, z-sizeZ*0.5)
858 local minX = math.min(math.min(x1, x2), math.min(x3, x4))
859 local maxX = math.max(math.max(x1, x2), math.max(x3, x4))
860 local minZ = math.min(math.min(z1, z2), math.min(z3, z4))
861 local maxZ = math.max(math.max(z1, z2), math.max(z3, z4))
862 g_densityMapHeightManager:setCollisionMapAreaDirty(minX, minZ, maxX, maxZ)
863 end
864end

update

Description
Update
Definition
update(float dt)
Arguments
floatdttime since last call in ms
Code
1021function Placeable:update(dt)
1022end

weatherChanged

Description
Called if weather changed
Definition
weatherChanged()
Code
1128function Placeable:weatherChanged()
1129 if g_currentMission ~= nil and g_currentMission.environment ~= nil and self.dayNightObjects ~= nil then
1130 for _, dayNightObject in pairs(self.dayNightObjects) do
1131 if dayNightObject.visibleDay ~= nil and dayNightObject.visibleNight ~= nil then
1132 setVisibility(dayNightObject.node, (g_currentMission.environment.isSunOn and dayNightObject.visibleDay) or (dayNightObject.visibleNight and not g_currentMission.environment.isSunOn))
1133
1134 elseif dayNightObject.intensityDay ~= nil and dayNightObject.intensityNight ~= nil then
1135 local intensity = dayNightObject.intensityNight
1136 if g_currentMission.environment.isSunOn then
1137 intensity = dayNightObject.intensityDay
1138 end
1139
1140 local _,y,z,w = getShaderParameter(dayNightObject.node, "lightControl")
1141 setShaderParameter(dayNightObject.node, "lightControl", intensity, y, z, w, false)
1142 end
1143 end
1144 end
1145end

writeStream

Description
Called on server side on join
Definition
writeStream(integer streamId, table connection)
Arguments
integerstreamIdstream ID
tableconnectionconnection
Code
302function Placeable:writeStream(streamId, connection)
303 Placeable:superClass().writeStream(self, streamId, connection)
304 if not connection:getIsServer() then
305 streamWriteString(streamId, NetworkUtil.convertToNetworkFilename(self.configFileName))
306 local x,y,z = getTranslation(self.nodeId)
307 local x_rot,y_rot,z_rot = getRotation(self.nodeId)
308 streamWriteFloat32(streamId, x)
309 streamWriteFloat32(streamId, y)
310 streamWriteFloat32(streamId, z)
311 NetworkUtil.writeCompressedAngle(streamId, x_rot)
312 NetworkUtil.writeCompressedAngle(streamId, y_rot)
313 NetworkUtil.writeCompressedAngle(streamId, z_rot)
314 streamWriteUInt16(streamId, self.age)
315 streamWriteInt32(streamId, self.price)
316
317 for _, animatedObject in ipairs(self.animatedObjects) do
318 NetworkUtil.writeNodeObjectId(streamId, NetworkUtil.getObjectId(animatedObject))
319 animatedObject:writeStream(streamId, connection)
320 g_server:registerObjectInStream(connection, animatedObject)
321 end
322 end
323end