Script v1.7.1.0
- AI
- Animals
- Contracts
- Debug
- Economy
- Effects
- Events
- Farms
- GUI
- Handtools
- I3d
- Materials
- Misc
- Objects
- Placeables
- Player
- Shop
- Sounds
- Specializations
- Triggers
- Utils
- Vehicles
- Weather
Engine v1.7.1.0
- AI
- Animation
- Camera
- Entity
- Fillplanes
- General
- I3D
- Input
- Lighting
- Math
- Network
- Node
- Overlays
- Particle System
- Physics
- Rendering
- Scenegraph
- Shape
- Sound
- Spline
- String
- Terrain Detail
- Text Rendering
- Tire Track
- XML
- general
Foundation Reference
Placeable
DescriptionBase 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
ObjectXML Configuration Parameters
placeable.filename | string File name of associated I3D file |
placeable.placement#sizeX | float Nominal local space X size |
placeable.placement#sizeZ | float Nominal local space Z size |
placeable.placement#testSizeX | float Local space X overlap testing size |
placeable.placement#testSizeZ | float Local space Z overlap testing size |
placeable.placement#useRandomYRotation | bool If true, Y rotation is set randomly on placement |
placeable.placement#useManualYRotation | bool If true, Y rotation is set manually by the player |
placeable.placement#alignToWorldY | bool If true, the placeable is aligned with the ground on placement |
placeable.placement#pos1Node | string Required if alignToWorldY is false. Auxiliary positioning node. |
placeable.placement#pos2Node | string Required if alignToWorldY is false. Auxiliary positioning node. |
placeable.placement#pos3Node | string Required if alignToWorldY is false. Auxiliary positioning node. |
placeable.incomePerHour1 | float Income per hour on easy difficulty |
placeable.incomePerHour2 | float Income per hour on normal difficulty |
placeable.incomePerHour3 | float Income per hour on hard difficulty |
placeable.dayNightObjects.dayNightObject#index | string Node index |
placeable.dayNightObjects.dayNightObject#visibleDay | bool If true, the node is visible during the day |
placeable.dayNightObjects.dayNightObject#visibleNight | bool If true, the node is visible during the night |
placeable.clearAreas.clearArea.start | string Clear area start node index |
placeable.clearAreas.clearArea.width | string Clear area width node index |
placeable.clearAreas.clearArea.height | string Clear area height node index |
placeable.leveling#requireLeveling | bool [optional, default=false] If true, the ground around the placeable is leveled and all other leveling properties are used |
placeable.leveling#maxSmoothDistance | float [optional] Radius around leveling areas where terrain will be smoothed towards the placeable |
placeable.leveling#maxSlope | float [optional] Maximum slope of terrain created by outside smoothing expressed as an angle in degrees [0, 90] |
placeable.leveling#maxEdgeAngle | float [optional] Maximum angle between polygons in smoothed areas expressed as an angle in degrees [0, 90] |
placeable.leveling#smoothingGroundType | int [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.groundType | string [optional] Ground type used to paint the ground directly under area (one of the ground types defined in groundTypes.xml) |
placeable.leveling.levelAreas.levelArea.start | string [optional] Leveling area start node index |
placeable.leveling.levelAreas.levelArea.width | string [optional] Leveling area width node index |
placeable.leveling.levelAreas.levelArea.height | string [optional] Leveling area height node index |
placeable.leveling.rampAreas.rampArea.groundType | string [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.maxSlope | float [optional, deprecated] Maximum slope of this ramp inclination expressed as an angle in degrees [0, 90] |
placeable.leveling.rampAreas.rampArea.root | string [optional, deprecated] Ramp area transform root node index |
placeable.leveling.rampAreas.rampArea.start | string [optional, deprecated] Ramp area start node index, must be a child of root |
placeable.leveling.rampAreas.rampArea.width | string [optional, deprecated] Ramp area width node index, must be a sibling of start |
placeable.leveling.rampAreas.rampArea.height | string [optional, deprecated] Ramp area height node index, must be a sibling of start |
placeable.tipOcclusionUpdateArea#sizeX | float |
placeable.tipOcclusionUpdateArea#sizeZ | float |
placeable.tipOcclusionUpdateArea#centerX | float |
placeable.tipOcclusionUpdateArea#centerZ | float |
placeable.animatedObjects | Animated objects configurations |
placable.sounds | node [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
- canBuy
- clearFoliageAndTipAreas
- collectPickObjects
- createNode
- dayChanged
- delete
- finalizePlacement
- getDailyUpkeep
- getIsPlayerInRange
- getPrice
- getSellPrice
- getSpecValueIncomePerHour
- hourChanged
- initPose
- isInActionDistance
- load
- loadAreaFromXML
- loadFoliageAreaFromXML
- loadFromXMLFile
- loadHotspotFromXML
- loadSpecValueIncomePerHour
- new
- onBuy
- onCreateGlowMaterial
- onSell
- readStream
- saveToXMLFile
- setCollisionMask
- setPlaceablePreviewState
- setPreviewMaterials
- setTipOcclusionAreaDirty
- update
- weatherChanged
- writeStream
alignToTerrain
DescriptionAlign placeable to terrainDefinition
alignToTerrain()Code
868 | function 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 |
890 | end |
canBuy
DescriptionReturns true if we can place a building checking item countDefinition
canBuy()Return Values
bool |
1038 | function 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 |
1042 | end |
clearFoliageAndTipAreas
DescriptionClear foliage and tipAny from clearAreasDefinition
clearFoliageAndTipAreas()Code
894 | function 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 |
915 | end |
collectPickObjects
DescriptionCollect pick objectsDefinition
collectPickObjects(integer node)Arguments
integer | node | node id |
934 | function 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 |
942 | end |
createNode
DescriptionCreate nodeDefinition
createNode(string i3dFilename)Arguments
string | i3dFilename | i3d file name |
boolean | success | success |
378 | function 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 |
400 | end |
dayChanged
DescriptionCalled if day changedDefinition
dayChanged()Code
1122 | function Placeable:dayChanged() |
1123 | self.age = self.age + 1 |
1124 | end |
delete
DescriptionDeleting placeableDefinition
delete()Code
153 | function 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) |
209 | end |
finalizePlacement
DescriptionCalled if placeable is placedDefinition
finalizePlacement()Code
795 | function 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()}) |
847 | end |
getDailyUpkeep
DescriptionReturns daily up keepDefinition
getDailyUpkeep()Return Values
integer | dailyUpkeep | daily up keep |
1070 | function 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 |
1078 | end |
getIsPlayerInRange
DescriptionReturns true if player is in rangeDefinition
getIsPlayerInRange(float distance, table player)Arguments
float | distance | distance |
table | player | player |
boolean | isInRange | is in range |
229 | function 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 |
243 | end |
getPrice
DescriptionReturns priceDefinition
getPrice()Return Values
integer | price | price |
1030 | function Placeable:getPrice() |
1031 | return self.price |
1032 | end |
getSellPrice
DescriptionReturns sell priceDefinition
getSellPrice()Return Values
integer | sellPrice | sell price |
1091 | function 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)) |
1101 | end |
getSpecValueIncomePerHour
DescriptionReturns value of income per hourDefinition
getSpecValueIncomePerHour(table storeItem, table realItem)Arguments
table | storeItem | store item |
table | realItem | real item |
integer | incomePerHour | income per hour |
1339 | function 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])) |
1344 | end |
hourChanged
DescriptionCalled if hour changedDefinition
hourChanged()Code
1112 | function 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 |
1118 | end |
initPose
DescriptionInitialize poseDefinition
initPose(float x, float y, float z, float rx, float ry, float rz, boolean initRandom)Arguments
float | x | x world position |
float | y | y world position |
float | z | z world position |
float | rx | rx world rotation |
float | ry | ry world rotation |
float | rz | rz world rotation |
boolean | initRandom | initialize random |
926 | function Placeable:initPose(x,y,z, rx,ry,rz, initRandom) |
927 | setTranslation(self.nodeId, x, y, z) |
928 | setRotation(self.nodeId, rx, ry, rz) |
929 | end |
isInActionDistance
DescriptionReturns true if player is in rangeDefinition
isInActionDistance(table player, integer refNode, float distance)Arguments
table | player | player |
integer | refNode | id of reference node |
float | distance | distance |
boolean | isInRange | is in range |
251 | function 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 |
260 | end |
load
DescriptionLoad placeableDefinition
load(string xmlFilename, float x, float y, float z, float rx, float ry, float rz, boolean initRandom)Arguments
string | xmlFilename | xml file name |
float | x | x world position |
float | y | z world position |
float | z | z world position |
float | rx | rx world rotation |
float | ry | ry world rotation |
float | rz | rz world rotation |
boolean | initRandom | initialize random |
boolean | success | success |
450 | function 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 |
635 | end |
loadAreaFromXML
DescriptionLoad 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
table | area | Empty area definition table which receives the loaded node IDs. |
integer | xmlFile | ID of the XML file |
string | key | String Key to the XML element |
bool | isRamp | If true, the targeted areas are ramps |
boolean | success | success |
675 | function 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 |
702 | end |
loadFoliageAreaFromXML
DescriptionLoad foliage definitons from XML.Definition
loadFoliageAreaFromXML()Code
706 | function 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 |
721 | end |
loadFromXMLFile
DescriptionLoading from attributes and nodesDefinition
loadFromXMLFile(integer xmlFile, string key, boolean resetVehicles)Arguments
integer | xmlFile | id of xml object |
string | key | key |
boolean | resetVehicles | reset vehicles |
boolean | success | success |
950 | function 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 |
986 | end |
loadHotspotFromXML
DescriptionLoad hotspot from XML definitonsDefinition
loadHotspotFromXML()Code
725 | function 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 |
791 | end |
loadSpecValueIncomePerHour
DescriptionLoads capacity spec valueDefinition
loadSpecValueIncomePerHour(integer xmlFile, string customEnvironment)Arguments
integer | xmlFile | id of xml object |
string | customEnvironment | custom environment |
table | capacityAndUnit | capacity and unit |
1322 | function 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 |
1332 | end |
new
DescriptionCreating placeableDefinition
new(boolean isServer, boolean isClient, table customMt)Arguments
boolean | isServer | is server |
boolean | isClient | is client |
table | customMt | custom metatable |
table | instance | Instance of object |
104 | function 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 |
149 | end |
onBuy
DescriptionCalled on buyDefinition
onBuy()Code
1054 | function Placeable:onBuy() |
1055 | end |
onCreateGlowMaterial
DescriptionOn create glow materialDefinition
onCreateGlowMaterial(empty empty, integer id)Arguments
empty | empty | empty |
integer | id | id of node |
88 | function Placeable.onCreateGlowMaterial(_, id) |
89 | if getHasShaderParameter(id, "colorScale") then |
90 | Placeable.GLOW_MATERIAL = getMaterial(id, 0) |
91 | end |
92 | end |
onSell
DescriptionCalled on sellDefinition
onSell()Code
1059 | function Placeable:onSell() |
1060 | if self.isServer then |
1061 | self:setTipOcclusionAreaDirty() |
1062 | end |
1063 | |
1064 | g_messageCenter:publish(MessageType.FARM_PROPERTY_CHANGED, {self:getOwnerFarmId()}) |
1065 | end |
readStream
DescriptionCalled on client side on joinDefinition
readStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
266 | function 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 |
296 | end |
saveToXMLFile
DescriptionGet save attributes and nodesDefinition
saveToXMLFile(string nodeIdent)Arguments
string | nodeIdent | node ident |
string | attributes | attributes |
string | nodes | nodes |
993 | function 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 |
1011 | end |
setCollisionMask
DescriptionSet collision mask of node and its childrenDefinition
setCollisionMask(integer nodeId, integer mask)Arguments
integer | nodeId | id of node |
integer | mask | collision mask |
215 | function 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 |
222 | end |
setPlaceablePreviewState
DescriptionSet placable preview stateDefinition
setPlaceablePreviewState(int state)Arguments
int | state | Placement state [Placeable.PREVIEW_STATE.CHECKING | Placeable.PREVIEW_STATE.VALID | Placeable.PREVIEW_STATE.INVALID] |
421 | function 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 |
437 | end |
setPreviewMaterials
DescriptionSets the preview material to all nodes in the placeableDefinition
setPreviewMaterials(integer node, table nodeTable)Arguments
integer | node | id of node |
table | nodeTable | table to save the nodes |
406 | function 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 |
416 | end |
setTipOcclusionAreaDirty
DescriptionSet tip occlusion area dirtyDefinition
setTipOcclusionAreaDirty()Code
851 | function 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 |
864 | end |
update
DescriptionUpdateDefinition
update(float dt)Arguments
float | dt | time since last call in ms |
1021 | function Placeable:update(dt) |
1022 | end |
weatherChanged
DescriptionCalled if weather changedDefinition
weatherChanged()Code
1128 | function 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 |
1145 | end |
writeStream
DescriptionCalled on server side on joinDefinition
writeStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
302 | function 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 |
323 | end |