Script v1.4.4.0
- Handtools
- Events
- Objects
- Placeables
- AnimatedObjectPlaceable
- BeehivePlaceable
- GreenhousePlaceable
- HayLoftPlaceable
- HeatingPlantPlaceable
- HighPressureWasherPlaceab...
- Placeable
- SolarCollectorPlaceable
- WindTurbinePlaceable
- Triggers
- Utils
- Vehicles
- Specializations
Engine v7.0.0.2
- General
- Entity
- Node
- Scenegraph
- Lighting
- Camera
- Shape
- Particle System
- Physics
- Spline
- Animation
- Overlays
- Sound
- Input
- XML
- Network
- Callbacks
- Text Rendering
- Terrain Detail
- Tire Track
- Editor
- Rendering
- String
- Math
- I3D
- Fillplanes
Foundation Reference
Placeable
DescriptionClass for placeablesFunctions
- onCreateGlowMaterial
- new
- delete
- deleteFinal
- setCollisionMask
- getIsPlayerInRange
- isInActionDistance
- readStream
- writeStream
- createNode
- loadDefaultMaterials
- setIsPlaceable
- load
- loadClearAreaFromXML
- finalizePlacement
- setTipOcclusionAreaDirty
- alignToTerrain
- clearFoliageAndTipAreas
- initPose
- collectPickObjects
- loadFromAttributesAndNodes
- getSaveAttributesAndNodes
- update
- onDoorTrigger
- playDoorSound
- getPrice
- onSell
- getDailyUpKeep
- getSellPrice
- hourChanged
- dayChanged
- weatherChanged
- getSpecValueIncomePerHour
onCreateGlowMaterial
DescriptionOn create glow materialDefinition
onCreateGlowMaterial(empty empty, integer id)Arguments
empty | empty | empty |
integer | id | id of node |
18 | function Placeable.onCreateGlowMaterial(_, id) |
19 | if getHasShaderParameter(id, "colorScale") then |
20 | Placeable.GLOW_MATERIAL = getMaterial(id, 0) |
21 | end |
22 | 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 |
30 | function Placeable:new(isServer, isClient, mt) |
31 | if mt == nil then |
32 | mt = Placeable_mt; |
33 | end; |
34 | |
35 | local self = Object:new(isServer, isClient, mt); |
36 | self.nodeId = 0; |
37 | |
38 | self.useRandomYRotation = false; |
39 | self.useManualYRotation = false; |
40 | self.placementSizeX = 1; |
41 | self.placementSizeZ = 1; |
42 | self.placementTestSizeX = 1; |
43 | self.placementTestSizeZ = 1; |
44 | self.pickObjects = {}; |
45 | self.isolated = false; |
46 | self.isDeleted = false; |
47 | self.useMultiRootNode = false; |
48 | self.price = 0; |
49 | self.age = 0 |
50 | |
51 | registerObjectClassName(self, "Placeable"); |
52 | return self; |
53 | end; |
delete
DescriptionDeleting placeableDefinition
delete()Code
57 | function Placeable:delete() |
58 | self.isDeleted = true; |
59 | if self.i3dFilename ~= nil then |
60 | Utils.releaseSharedI3DFile(self.i3dFilename, nil, true); |
61 | end |
62 | if g_currentMission ~= nil and g_currentMission.environment ~= nil then |
63 | g_currentMission.environment:removeDayChangeListener(self) |
64 | g_currentMission.environment:removeWeatherChangeListener(self); |
65 | g_currentMission.environment:removeHourChangeListener(self); |
66 | end |
67 | unregisterObjectClassName(self); |
68 | g_currentMission:removeItemToSave(self); |
69 | for _, node in pairs(self.pickObjects) do |
70 | g_currentMission:removeNodeObject(node); |
71 | end; |
72 | if self.nodeId ~= 0 and entityExists(self.nodeId) then |
73 | self:setCollisionMask(self.nodeId, 0); |
74 | setVisibility(self.nodeId, false); |
75 | else |
76 | self.nodeId = 0; |
77 | end; |
78 | if self.door ~= nil then |
79 | removeTrigger(self.door.triggerNode); |
80 | end; |
81 | if self.sampleDoor ~= nil then |
82 | SoundUtil.deleteSample(self.sampleDoor); |
83 | end; |
84 | if self.sampleIdle ~= nil then |
85 | SoundUtil.deleteSample(self.sampleIdle); |
86 | end |
87 | |
88 | g_currentMission:removeOwnedItem(self); |
89 | |
90 | g_currentMission:addPlaceableToDelete(self, 300); |
91 | end; |
deleteFinal
DescriptionDeleting placeable finalDefinition
deleteFinal()Code
95 | function Placeable:deleteFinal() |
96 | if self.nodeId ~= 0 then |
97 | delete(self.nodeId); |
98 | self.nodeId = 0; |
99 | end; |
100 | 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 |
106 | function Placeable:setCollisionMask(nodeId, mask) |
107 | setCollisionMask(nodeId, mask); |
108 | local numChildren = getNumOfChildren(nodeId); |
109 | for i=0,numChildren-1 do |
110 | local childId = getChildAt(nodeId, i); |
111 | self:setCollisionMask(childId, mask) |
112 | end; |
113 | 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 |
120 | function Placeable:getIsPlayerInRange(distance, player) |
121 | if self.nodeId ~= 0 then |
122 | distance = Utils.getNoNil(distance, 10); |
123 | if player == nil then |
124 | for _, player in pairs(g_currentMission.players) do |
125 | if self:isInActionDistance(player, self.nodeId, distance) then |
126 | return true, player; |
127 | end; |
128 | end; |
129 | else |
130 | return self:isInActionDistance(player, self.nodeId, distance), player; |
131 | end; |
132 | end |
133 | return false, nil; |
134 | 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 |
142 | function Placeable:isInActionDistance(player, refNode, distance) |
143 | local x,_,z = getWorldTranslation(refNode); |
144 | local px,_,pz = getWorldTranslation(player.rootNode); |
145 | local dx,dz = px-x, pz-z; |
146 | if dx*dx + dz*dz < distance*distance then |
147 | return true; |
148 | end |
149 | |
150 | return false; |
151 | end; |
readStream
DescriptionCalled on client side on joinDefinition
readStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
157 | function Placeable:readStream(streamId, connection) |
158 | Placeable:superClass().readStream(self, streamId, connection); |
159 | if connection:getIsServer() then |
160 | local configFileName = Utils.convertFromNetworkFilename(streamReadString(streamId)); |
161 | local x=streamReadFloat32(streamId); |
162 | local y=streamReadFloat32(streamId); |
163 | local z=streamReadFloat32(streamId); |
164 | local rx=Utils.readCompressedAngle(streamId); |
165 | local ry=Utils.readCompressedAngle(streamId); |
166 | local rz=Utils.readCompressedAngle(streamId); |
167 | self:load(configFileName, x,y,z, rx,ry,rz, false, false); |
168 | self.age = streamReadUInt16(streamId); |
169 | self.price = streamReadInt32(streamId); |
170 | self:finalizePlacement(); |
171 | end; |
172 | end; |
writeStream
DescriptionCalled on server side on joinDefinition
writeStream(integer streamId, table connection)Arguments
integer | streamId | stream ID |
table | connection | connection |
178 | function Placeable:writeStream(streamId, connection) |
179 | Placeable:superClass().writeStream(self, streamId, connection); |
180 | if not connection:getIsServer() then |
181 | streamWriteString(streamId, Utils.convertToNetworkFilename(self.configFileName)); |
182 | local x,y,z = getTranslation(self.nodeId) |
183 | local x_rot,y_rot,z_rot = getRotation(self.nodeId); |
184 | streamWriteFloat32(streamId, x); |
185 | streamWriteFloat32(streamId, y); |
186 | streamWriteFloat32(streamId, z); |
187 | Utils.writeCompressedAngle(streamId, x_rot); |
188 | Utils.writeCompressedAngle(streamId, y_rot); |
189 | Utils.writeCompressedAngle(streamId, z_rot); |
190 | streamWriteUInt16(streamId, self.age); |
191 | streamWriteInt32(streamId, self.price); |
192 | end; |
193 | end; |
createNode
DescriptionCreate nodeDefinition
createNode(string i3dFilename)Arguments
string | i3dFilename | i3d file name |
boolean | success | success |
205 | function Placeable:createNode(i3dFilename) |
206 | self.i3dFilename = i3dFilename; |
207 | local nodeRoot = Utils.loadSharedI3DFile(i3dFilename, nil, false, false); |
208 | if nodeRoot == 0 then |
209 | return false; |
210 | end; |
211 | |
212 | if self.useMultiRootNode then |
213 | link(getRootNode(), nodeRoot); |
214 | self.nodeId = nodeRoot; |
215 | else |
216 | local nodeId = getChildAt(nodeRoot, 0); |
217 | if nodeId == 0 then |
218 | delete(nodeRoot); |
219 | return false; |
220 | end |
221 | link(getRootNode(), nodeId); |
222 | delete(nodeRoot); |
223 | self.nodeId = nodeId; |
224 | end |
225 | |
226 | if Placeable.GLOW_MATERIAL ~= nil then |
227 | self.defaultMaterials = {} |
228 | self:loadDefaultMaterials(self.nodeId, self.defaultMaterials) |
229 | end |
230 | |
231 | return true; |
232 | end; |
loadDefaultMaterials
DescriptionLoad default materialsDefinition
loadDefaultMaterials(integer node, table nodeTable)Arguments
integer | node | id of node |
table | nodeTable | table to save the nodes |
238 | function Placeable:loadDefaultMaterials(node, nodeTable) |
239 | if getHasClassId(node, ClassIds.SHAPE) then |
240 | nodeTable[node] = getMaterial(node, 0) |
241 | setMaterial(node, Placeable.GLOW_MATERIAL, 0) |
242 | end |
243 | |
244 | local numChildren = getNumOfChildren(node); |
245 | if numChildren > 0 then |
246 | for i=0, numChildren-1 do |
247 | self:loadDefaultMaterials(getChildAt(node, i), nodeTable) |
248 | end; |
249 | end; |
250 | end |
setIsPlaceable
DescriptionSet is in placeable modeDefinition
setIsPlaceable(boolean isPlaceable)Arguments
boolean | isPlaceable | is in placeable mode |
255 | function Placeable:setIsPlaceable(isPlaceable) |
256 | if Placeable.GLOW_MATERIAL ~= nil then |
257 | local color = nil |
258 | if isPlaceable then |
259 | color = {0, 1, 0, 1} |
260 | else |
261 | color = {1, 0, 0, 1} |
262 | end |
263 | for node,_ in pairs(self.defaultMaterials) do |
264 | setShaderParameter(node, "colorScale", color[1], color[2], color[3], color[3], false) |
265 | end |
266 | end |
267 | 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 |
280 | function Placeable:load(xmlFilename, x,y,z, rx,ry,rz, initRandom) |
281 | self.configFileName = xmlFilename; |
282 | self.customEnvironment, self.baseDirectory = Utils.getModNameAndBaseDirectory(xmlFilename); |
283 | |
284 | local xmlFile = loadXMLFile("TempXML", xmlFilename); |
285 | if xmlFile == 0 then |
286 | return false; |
287 | end; |
288 | local i3dFilename = getXMLString(xmlFile, "placeable.filename"); |
289 | self.placementSizeX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeX"), self.placementSizeX); |
290 | self.placementSizeZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#sizeZ"), self.placementSizeZ); |
291 | self.placementTestSizeX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeX"), self.placementSizeX); |
292 | self.placementTestSizeZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.placement#testSizeZ"), self.placementSizeZ); |
293 | self.useRandomYRotation = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#useRandomYRotation"), self.useRandomYRotation); |
294 | self.useManualYRotation = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#useManualYRotation"), self.useManualYRotation); |
295 | self.alignToWorldY = Utils.getNoNil(getXMLBool(xmlFile, "placeable.placement#alignToWorldY"), true); |
296 | |
297 | self.incomePerHour = getXMLFloat(xmlFile, "placeable.incomePerHour" .. g_currentMission.missionInfo.difficulty) |
298 | -- fallback for old single value format |
299 | if self.incomePerHour == nil then |
300 | self.incomePerHour = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.incomePerHour"), 0) |
301 | if g_currentMission.missionInfo.difficulty == 1 then |
302 | self.incomePerHour = self.incomePerHour * 1.5 |
303 | elseif g_currentMission.missionInfo.difficulty == 3 then |
304 | self.incomePerHour = self.incomePerHour / 1.5 |
305 | end |
306 | end |
307 | |
308 | local storeItem = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()] |
309 | if self.price == 0 or self.price == nil then |
310 | self.price = StoreItemsUtil.getDefaultPrice(storeItem); |
311 | end |
312 | |
313 | if g_currentMission ~= nil and storeItem.canBeSold then |
314 | g_currentMission.environment:addDayChangeListener(self) |
315 | end |
316 | |
317 | if i3dFilename == nil then |
318 | delete(xmlFile); |
319 | return false; |
320 | end; |
321 | self.i3dFilename = Utils.getFilename(i3dFilename, self.baseDirectory); |
322 | |
323 | if not self:createNode(self.i3dFilename) then |
324 | delete(xmlFile); |
325 | return false; |
326 | end; |
327 | self:initPose(x,y,z, rx,ry,rz, initRandom); |
328 | |
329 | if hasXMLProperty(xmlFile, "placeable.dayNightObjects") then |
330 | local i = 0 |
331 | while true do |
332 | local key = string.format("placeable.dayNightObjects.dayNightObject(%d)", i) |
333 | if not hasXMLProperty(xmlFile, key) then |
334 | break |
335 | end |
336 | local node = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, key.."#index")) |
337 | if node ~= nil then |
338 | if self.dayNightObjects == nil then |
339 | self.dayNightObjects = {} |
340 | g_currentMission.environment:addWeatherChangeListener(self); |
341 | end |
342 | table.insert(self.dayNightObjects, {node=node, visibleDay=Utils.getNoNil(getXMLBool(xmlFile, key.."#visibleDay"), false), visibleNight=Utils.getNoNil(getXMLBool(xmlFile, key.."visibleNight"), true)}) |
343 | end |
344 | i = i + 1 |
345 | end |
346 | end |
347 | |
348 | self.clearAreas = {}; |
349 | local i = 0; |
350 | while true do |
351 | local key = string.format("placeable.clearAreas.clearArea(%d)", i); |
352 | if not hasXMLProperty(xmlFile, key) then |
353 | break; |
354 | end; |
355 | local clearArea = {}; |
356 | if self:loadClearAreaFromXML(clearArea, xmlFile, key) then |
357 | table.insert(self.clearAreas, clearArea); |
358 | end; |
359 | i = i + 1; |
360 | end; |
361 | |
362 | if hasXMLProperty(xmlFile, "placeable.tipOcclusionUpdateArea") then |
363 | local sizeX = getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#sizeX"); |
364 | local sizeZ = getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#sizeZ"); |
365 | |
366 | if sizeX ~= nil and sizeZ ~= nil then |
367 | local centerX = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#centerX"), 0); |
368 | local centerZ = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.tipOcclusionUpdateArea#centerZ"), 0); |
369 | self.tipOcclusionUpdateArea = {centerX, centerZ, sizeX, sizeZ}; |
370 | end |
371 | end |
372 | |
373 | if not self.alignToWorldY then |
374 | self.pos1Node = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos1Node")); |
375 | self.pos2Node = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos2Node")); |
376 | self.pos3Node = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.placement#pos3Node")); |
377 | if self.pos1Node == nil or self.pos2Node == nil or self.pos3Node == nil then |
378 | self.alignToWorldY = true; |
379 | print("Warning: Pos1Node, Pos2Node and Pos3Node has to be set when alignToWorldY is false!"); |
380 | end; |
381 | end; |
382 | |
383 | local leftDoorNode = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.door#leftDoorNode")); |
384 | local rightDoorNode = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.door#rightDoorNode")); |
385 | local triggerNode = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.door#triggerNode")); |
386 | if rightDoorNode ~= nil and leftDoorNode ~= nil and triggerNode ~= nil then |
387 | local movement = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.door#movement"), 1); |
388 | local movementSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "placeable.door#movementSpeed"), 1)*0.001; |
389 | self.door = {isMoving=false, isOpening=false,leftDoorNode=leftDoorNode, rightDoorNode=rightDoorNode, triggerNode=triggerNode, movement=movement, movementSpeed=movementSpeed}; |
390 | |
391 | if self.isClient then |
392 | self.sampleDoor = SoundUtil.loadSample(xmlFile, {}, "placeable.door#soundFilename", "$data/sounds/door_air.wav", self.baseDirectory, self.nodeId); |
393 | self.sampleDoor.isPlaying = false; |
394 | end; |
395 | self.sampleDoorStartTime = -1 |
396 | end; |
397 | |
398 | if self.isClient then |
399 | self.sampleIdle = SoundUtil.loadSample(xmlFile, {}, "placeable.idleSound", nil, self.baseDirectory, self.nodeId); |
400 | end |
401 | |
402 | delete(xmlFile); |
403 | |
404 | return true; |
405 | end; |
loadClearAreaFromXML
DescriptionLoad a single clearArea from xmlDefinition
loadClearAreaFromXML(table clearArea, integer xmlFile, string key)Arguments
table | clearArea | table containing the clear areas |
integer | xmlFile | id of the xml file |
string | key | string key to the xml element |
boolean | success | success |
413 | function Placeable:loadClearAreaFromXML(clearArea, xmlFile, key) |
414 | local start = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#startIndex")); |
415 | local width = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#widthIndex")); |
416 | local height = Utils.indexToObject(self.nodeId, getXMLString(xmlFile, key .. "#heightIndex")); |
417 | |
418 | if start ~= nil and width ~= nil and height ~= nil then |
419 | clearArea.start = start; |
420 | clearArea.width = width; |
421 | clearArea.height = height; |
422 | return true; |
423 | end; |
424 | return false; |
425 | end; |
finalizePlacement
DescriptionCalled if placeable is placedDefinition
finalizePlacement()Code
429 | function Placeable:finalizePlacement() |
430 | if not self.isolated then |
431 | if Placeable.GLOW_MATERIAL ~= nil then |
432 | for node, material in pairs(self.defaultMaterials) do |
433 | setMaterial(node, material, 0) |
434 | end |
435 | end |
436 | |
437 | self:alignToTerrain() |
438 | |
439 | addToPhysics(self.nodeId); |
440 | g_currentMission:addItemToSave(self); |
441 | g_currentMission:addOwnedItem(self); |
442 | self:collectPickObjects(self.nodeId); |
443 | |
444 | for _, node in pairs(self.pickObjects) do |
445 | g_currentMission:addNodeObject(node, self); |
446 | end; |
447 | |
448 | local missionInfo = g_currentMission.missionInfo; |
449 | if self.isServer then |
450 | if not self.isLoadedFromSavegame or (missionInfo.isValid and not missionInfo:getIsTipCollisionValid(g_currentMission)) then |
451 | self:setTipOcclusionAreaDirty(); |
452 | end |
453 | end |
454 | end; |
455 | |
456 | if self.door ~= nil then |
457 | addTrigger(self.door.triggerNode, "onDoorTrigger", self); |
458 | end; |
459 | |
460 | -- initially update dayNightObjects |
461 | self:weatherChanged() |
462 | g_currentMission.environment:addHourChangeListener(self) |
463 | end |
setTipOcclusionAreaDirty
DescriptionSet tip occlusion area dirtyDefinition
setTipOcclusionAreaDirty()Code
467 | function Placeable:setTipOcclusionAreaDirty() |
468 | if self.tipOcclusionUpdateArea ~= nil and self.nodeId ~= 0 then |
469 | local x, z, sizeX, sizeZ = unpack(self.tipOcclusionUpdateArea); |
470 | local x1,_,z1 = localToWorld(self.nodeId, x+sizeX*0.5, 0, z+sizeZ*0.5); |
471 | local x2,_,z2 = localToWorld(self.nodeId, x-sizeX*0.5, 0, z+sizeZ*0.5); |
472 | local x3,_,z3 = localToWorld(self.nodeId, x+sizeX*0.5, 0, z-sizeZ*0.5); |
473 | local x4,_,z4 = localToWorld(self.nodeId, x-sizeX*0.5, 0, z-sizeZ*0.5); |
474 | local minX = math.min(math.min(x1, x2), math.min(x3, x4)); |
475 | local maxX = math.max(math.max(x1, x2), math.max(x3, x4)); |
476 | local minZ = math.min(math.min(z1, z2), math.min(z3, z4)); |
477 | local maxZ = math.max(math.max(z1, z2), math.max(z3, z4)); |
478 | TipUtil.setCollisionMapAreaDirty(minX, minZ, maxX, maxZ); |
479 | end |
480 | end |
alignToTerrain
DescriptionAlign placeable to terrainDefinition
alignToTerrain()Code
484 | function Placeable:alignToTerrain() |
485 | if not self.alignToWorldY then |
486 | local x1,y1,z1 = getWorldTranslation(self.nodeId); |
487 | y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x1,y1,z1); |
488 | setTranslation(self.nodeId, x1,y1,z1); |
489 | local x2,y2,z2 = getWorldTranslation(self.pos1Node) |
490 | y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x2,y2,z2); |
491 | local x3,y3,z3 = getWorldTranslation(self.pos2Node) |
492 | y3 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x3,y3,z3); |
493 | local x4,y4,z4 = getWorldTranslation(self.pos3Node); |
494 | y4 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x4,y4,z4); |
495 | local dirX = x2 - x1; |
496 | local dirY = y2 - y1; |
497 | local dirZ = z2 - z1; |
498 | local dir2X = x3 - x4; |
499 | local dir2Y = y3 - y4; |
500 | local dir2Z = z3 - z4; |
501 | local upX,upY,upZ = Utils.crossProduct(dir2X, dir2Y, dir2Z, dirX, dirY, dirZ); |
502 | setDirection(self.nodeId, dirX, dirY, dirZ, upX,upY,upZ); |
503 | end; |
504 | end; |
clearFoliageAndTipAreas
DescriptionClear foliage and tipAny from clearAreasDefinition
clearFoliageAndTipAreas()Code
508 | function Placeable:clearFoliageAndTipAreas() |
509 | if self.isServer then |
510 | local numAreas = table.getn(self.clearAreas); |
511 | for i=1, numAreas do |
512 | local x,_,z = getWorldTranslation(self.clearAreas[i].start); |
513 | local x1,_,z1 = getWorldTranslation(self.clearAreas[i].width); |
514 | local x2,_,z2 = getWorldTranslation(self.clearAreas[i].height); |
515 | -- clear foliage |
516 | Utils.updateDestroyCommonArea(x, z, x1, z1, x2, z2, false) |
517 | Utils.eraseTireTrack(x, z, x1, z1, x2, z2) |
518 | -- clear tipAny |
519 | TipUtil.clearArea(x, z, x1, z1, x2, z2) |
520 | end; |
521 | end |
522 | 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 |
533 | function Placeable:initPose(x,y,z, rx,ry,rz, initRandom) |
534 | setTranslation(self.nodeId, x, y, z); |
535 | setRotation(self.nodeId, rx, ry, rz); |
536 | end; |
collectPickObjects
DescriptionCollect pick objectsDefinition
collectPickObjects(integer node)Arguments
integer | node | node id |
541 | function Placeable:collectPickObjects(node) |
542 | if getRigidBodyType(node) ~= "NoRigidBody" then |
543 | table.insert(self.pickObjects, node); |
544 | end; |
545 | local numChildren = getNumOfChildren(node); |
546 | for i=1, numChildren do |
547 | self:collectPickObjects(getChildAt(node, i-1)); |
548 | end; |
549 | end; |
loadFromAttributesAndNodes
DescriptionLoading from attributes and nodesDefinition
loadFromAttributesAndNodes(integer xmlFile, string key, boolean resetVehicles)Arguments
integer | xmlFile | id of xml object |
string | key | key |
boolean | resetVehicles | reset vehicles |
boolean | success | success |
557 | function Placeable:loadFromAttributesAndNodes(xmlFile, key, resetVehicles) |
558 | |
559 | local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#position")); |
560 | local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotation")); |
561 | if x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil then |
562 | return false; |
563 | end; |
564 | local xmlFilename = getXMLString(xmlFile, key.."#filename"); |
565 | if xmlFilename == nil then |
566 | return false; |
567 | end; |
568 | xmlFilename = Utils.convertFromNetworkFilename(xmlFilename); |
569 | |
570 | if self:load(xmlFilename, x,y,z, xRot, yRot, zRot, false, false) then |
571 | self.age = Utils.getNoNil(getXMLFloat(xmlFile, key.."#age"), 0); |
572 | self.price = Utils.getNoNil(getXMLInt(xmlFile, key.."#price"), self.price); |
573 | self.isLoadedFromSavegame = true; |
574 | self:finalizePlacement(); |
575 | return true; |
576 | else |
577 | return false; |
578 | end; |
579 | end; |
getSaveAttributesAndNodes
DescriptionGet save attributes and nodesDefinition
getSaveAttributesAndNodes(string nodeIdent)Arguments
string | nodeIdent | node ident |
string | attributes | attributes |
string | nodes | nodes |
586 | function Placeable:getSaveAttributesAndNodes(nodeIdent) |
587 | local x,y,z = getTranslation(self.nodeId); |
588 | local xRot,yRot,zRot = getRotation(self.nodeId); |
589 | local attributes = 'filename="'.. Utils.encodeToHTML(Utils.convertToNetworkFilename(self.configFileName))..'" position="'..x..' '..y..' '..z..'" rotation="'..xRot..' '..yRot..' '..zRot..'" age="'..tostring(self.age)..'" price="'..tostring(self.price)..'"'; |
590 | local nodes = ""; |
591 | return attributes, nodes; |
592 | end; |
update
DescriptionUpdateDefinition
update(float dt)Arguments
float | dt | time since last call in ms |
602 | function Placeable:update(dt) |
603 | if self.door ~= nil then |
604 | local door = self.door; |
605 | if door.isMoving then |
606 | local movement = door.movementSpeed*dt; |
607 | if door.isOpening then |
608 | movement = -movement; |
609 | end; |
610 | local x,y,z = getTranslation(door.leftDoorNode); |
611 | z = Utils.clamp(z + movement, -door.movement, 0); |
612 | if z == 0 or z == -door.movement then |
613 | door.isMoving = false; |
614 | end; |
615 | setTranslation(door.leftDoorNode, x,y,z); |
616 | |
617 | local x,y,z = getTranslation(door.rightDoorNode); |
618 | z = Utils.clamp(z - movement, 0, door.movement); |
619 | if z == 0 or z == door.movement then |
620 | door.isMoving = false; |
621 | end; |
622 | setTranslation(door.rightDoorNode, x,y,z); |
623 | end; |
624 | if self.isClient then |
625 | if self.sampleDoor.isPlaying and (self.sampleDoorStartTime < g_currentMission.time or not self.door.isMoving) then |
626 | self:playDoorSound(false); |
627 | end |
628 | end |
629 | end; |
630 | |
631 | if self.sampleIdle ~= nil then |
632 | if not self.sampleIdle.isPlaying then |
633 | SoundUtil.play3DSample(self.sampleIdle) |
634 | self.sampleIdle.isPlaying = true |
635 | end |
636 | end |
637 | end; |
onDoorTrigger
DescriptionDoor triggerDefinition
onDoorTrigger(integer triggerId, integer otherId, boolean onEnter, boolean onLeave, boolean onStay, integer otherShapeId)Arguments
integer | triggerId | id of trigger |
integer | otherId | id of actor |
boolean | onEnter | on enter |
boolean | onLeave | on leave |
boolean | onStay | on stay |
integer | otherShapeId | id of other actor |
650 | function Placeable:onDoorTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId) |
651 | if onEnter or onLeave then |
652 | if g_currentMission.players[otherId] ~= nil then |
653 | if onEnter then |
654 | if not self.door.isOpening then |
655 | self.door.isOpening = true; |
656 | self.door.isMoving = true; |
657 | self:playDoorSound(true); |
658 | end |
659 | elseif onLeave then |
660 | if self.door.isOpening then |
661 | self.door.isOpening = false; |
662 | self.door.isMoving = true; |
663 | self:playDoorSound(true); |
664 | end; |
665 | end; |
666 | end; |
667 | end; |
668 | end; |
playDoorSound
DescriptionPlay door soundFilenameDefinition
playDoorSound(boolean play)Arguments
boolean | play | play soundFilename |
673 | function Placeable:playDoorSound(play) |
674 | if self.sampleDoor ~= nil then |
675 | if play and not self.sampleDoor.isPlaying then |
676 | SoundUtil.play3DSample(self.sampleDoor); |
677 | self.sampleDoorStartTime = g_currentMission.time + self.sampleDoor.duration; |
678 | self.sampleDoor.isPlaying = true; |
679 | else |
680 | SoundUtil.stop3DSample(self.sampleDoor); |
681 | self.sampleDoor.isPlaying = false; |
682 | end |
683 | end |
684 | end |
getPrice
DescriptionReturns priceDefinition
getPrice()Return Values
integer | price | price |
689 | function Placeable:getPrice() |
690 | return self.price; |
691 | end |
onSell
DescriptionCalled on sellDefinition
onSell()Code
703 | function Placeable:onSell() |
704 | if self.isServer then |
705 | self:setTipOcclusionAreaDirty(); |
706 | end |
707 | end |
getDailyUpKeep
DescriptionReturns daily up keepDefinition
getDailyUpKeep()Return Values
integer | dailyUpKeep | daily up keep |
712 | function Placeable:getDailyUpKeep() |
713 | local storeItem = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()] |
714 | local multiplier = 1 |
715 | if storeItem.lifetime ~= nil and storeItem.lifetime ~= 0 then |
716 | local ageMultiplier = math.min(self.age/storeItem.lifetime, 1) |
717 | multiplier = 1 + EconomyManager.MAX_DAILYUPKEEP_MULTIPLIER * ageMultiplier |
718 | end |
719 | return StoreItemsUtil.getDailyUpkeep(storeItem, nil) * multiplier |
720 | end |
getSellPrice
DescriptionReturns sell priceDefinition
getSellPrice()Return Values
integer | sellPrice | sell price |
725 | function Placeable:getSellPrice() |
726 | local priceMultiplier = 0.5 |
727 | local maxVehicleAge = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()].lifetime; |
728 | |
729 | if maxVehicleAge ~= nil and maxVehicleAge ~= 0 then |
730 | priceMultiplier = priceMultiplier * math.exp(-3.5 * math.min(self.age/maxVehicleAge, 1)) |
731 | end |
732 | |
733 | return math.floor(self.price * math.max(priceMultiplier, 0.05)); |
734 | end |
hourChanged
DescriptionCalled if hour changedDefinition
hourChanged()Code
738 | function Placeable:hourChanged() |
739 | if self.isServer then |
740 | g_currentMission:addSharedMoney(self.incomePerHour, "propertyIncome"); |
741 | g_currentMission:addMoneyChange(self.incomePerHour, EconomyManager.MONEY_TYPE_PROPERTY_INCOME); |
742 | end; |
743 | end |
dayChanged
DescriptionCalled if day changedDefinition
dayChanged()Code
747 | function Placeable:dayChanged() |
748 | self.age = self.age + 1 |
749 | end |
weatherChanged
DescriptionCalled if weather changedDefinition
weatherChanged()Code
753 | function Placeable:weatherChanged() |
754 | if g_currentMission ~= nil and g_currentMission.environment ~= nil and self.dayNightObjects ~= nil then |
755 | for _, dayNightObject in pairs(self.dayNightObjects) do |
756 | setVisibility(dayNightObject.node, (g_currentMission.environment.isSunOn and dayNightObject.visibleDay) or (dayNightObject.visibleNight and (not g_currentMission.environment.isSunOn and g_currentMission.environment.currentRain == nil))) |
757 | end |
758 | end; |
759 | 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 |
766 | function Placeable.getSpecValueIncomePerHour(storeItem, realItem) |
767 | if storeItem.incomePerHour[1] ~= 0 and storeItem.incomePerHour[2] ~= 0 and storeItem.incomePerHour[3] ~= 0 and g_currentMission ~= nil then |
768 | return string.format(g_i18n:getText("shop_incomeValue"), g_i18n:formatMoney(storeItem.incomePerHour[g_currentMission.missionInfo.difficulty])) |
769 | end |
770 | return nil |
771 | end |