430 | function Mountable:getAdditionalMountingMass() |
431 | local distance = self:getAdditionalMountingDistance() |
432 | if distance > 0 then |
433 | local spec = self.spec_mountable |
434 | local x, y, z = getWorldTranslation(self.rootNode) |
435 | spec.additionalMountingMass = 0 |
436 | raycastAll(x, y + 0.1, z, 0, 1, 0, "additionalMountingMassRaycastCallback", distance, self, CollisionFlag.DYNAMIC_OBJECT, false, false) |
437 | |
438 | return spec.additionalMountingMass |
439 | end |
440 | |
441 | return 0 |
442 | end |
21 | function Mountable.initSpecialization() |
22 | local schema = Vehicle.xmlSchema |
23 | schema:setXMLSpecializationType("Mountable") |
24 | |
25 | schema:register(XMLValueType.FLOAT, "vehicle.dynamicMount#forceLimitScale", "Force limit scale", 1) |
26 | schema:register(XMLValueType.NODE_INDEX, "vehicle.dynamicMount#triggerNode", "Trigger node") |
27 | schema:register(XMLValueType.NODE_INDEX, "vehicle.dynamicMount#jointNode", "Joint node") |
28 | schema:register(XMLValueType.FLOAT, "vehicle.dynamicMount#triggerForceAcceleration", "Trigger force acceleration", 4) |
29 | schema:register(XMLValueType.BOOL, "vehicle.dynamicMount#singleAxisFreeY", "Single axis free Y") |
30 | schema:register(XMLValueType.BOOL, "vehicle.dynamicMount#singleAxisFreeX", "Single axis free X") |
31 | |
32 | schema:register(XMLValueType.FLOAT, "vehicle.dynamicMount#jointTransY", "Fixed Y translation of local placed joint", "not defined") |
33 | schema:register(XMLValueType.BOOL, "vehicle.dynamicMount#jointLimitToRotY", "Local placed joint will only be adjusted on Y axis to the target mounter object. X and Z will be 0.", false) |
34 | |
35 | schema:register(XMLValueType.FLOAT, "vehicle.dynamicMount#additionalMountDistance", "Distance from root node to the object laying on top (normally height of object). If defined the mass of this object has influence in mounting.", 0) |
36 | |
37 | schema:register(XMLValueType.BOOL, "vehicle.dynamicMount#allowMassReduction", "Defines if mass can be reduced by the mount vehicle", true) |
38 | |
39 | schema:register(XMLValueType.STRING, "vehicle.dynamicMount.lockPosition(?)#xmlFilename", "XML filename of vehicle to lock on (needs to match only the end of the filename)") |
40 | schema:register(XMLValueType.STRING, "vehicle.dynamicMount.lockPosition(?)#jointNode", "Joint node of other vehicle (path or i3dMapping name)", "vehicle root node") |
41 | schema:register(XMLValueType.VECTOR_TRANS, "vehicle.dynamicMount.lockPosition(?)#transOffset", "Translation offset from joint node", "0 0 0") |
42 | schema:register(XMLValueType.VECTOR_ROT, "vehicle.dynamicMount.lockPosition(?)#rotOffset", "Rotation offset from joint node", "0 0 0") |
43 | |
44 | schema:setXMLSpecializationType() |
45 | end |
237 | function Mountable:mount(object, node, x,y,z, rx,ry,rz) |
238 | local spec = self.spec_mountable |
239 | |
240 | -- set isDelete = true to remove Mountable from pendingDynamicMountObjects (no delete leave callback) |
241 | self:unmountDynamic(true) |
242 | |
243 | if spec.mountObject == nil then |
244 | removeFromPhysics(spec.componentNode) |
245 | end |
246 | link(node, spec.componentNode) |
247 | |
248 | local wx,wy,wz = localToWorld(node, x,y,z) |
249 | local wqx,wqy,wqz,wqw = mathEulerToQuaternion( localRotationToWorld(node, rx,ry,rz) ) |
250 | self:setWorldPositionQuaternion(wx,wy,wz, wqx,wqy,wqz,wqw, 1, true) |
251 | |
252 | spec.mountObject = object |
253 | end |
195 | function Mountable:mountableTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId) |
196 | local spec = self.spec_mountable |
197 | |
198 | if onEnter then |
199 | if spec.mountObject == nil then |
200 | local vehicle = g_currentMission.nodeToObject[otherActorId] |
201 | |
202 | if vehicle ~= nil and vehicle.spec_dynamicMountAttacher ~= nil then |
203 | local dynamicMountAttacher = vehicle.spec_dynamicMountAttacher |
204 | |
205 | if dynamicMountAttacher ~= nil and dynamicMountAttacher.dynamicMountAttacherNode ~= nil then |
206 | if spec.dynamicMountObjectActorId == nil then |
207 | self:mountDynamic(vehicle, otherActorId, dynamicMountAttacher.dynamicMountAttacherNode, DynamicMountUtil.TYPE_FORK, spec.dynamicMountTriggerForceAcceleration*dynamicMountAttacher.dynamicMountAttacherForceLimitScale) |
208 | spec.dynamicMountObjectTriggerCount = 1 |
209 | elseif otherActorId ~= spec.dynamicMountObjectActorId and spec.dynamicMountObjectTriggerCount == nil then |
210 | -- we are already attached to another actor, but not from our mount trigger (e.g. a bale trailer) |
211 | self:unmountDynamic() |
212 | self:mountDynamic(vehicle, otherActorId, dynamicMountAttacher.dynamicMountAttacherNode, DynamicMountUtil.TYPE_FORK, spec.dynamicMountTriggerForceAcceleration*dynamicMountAttacher.dynamicMountAttacherForceLimitScale) |
213 | spec.dynamicMountObjectTriggerCount = 1 |
214 | elseif otherActorId == spec.dynamicMountObjectActorId then |
215 | if spec.dynamicMountObjectTriggerCount ~= nil then |
216 | spec.dynamicMountObjectTriggerCount = spec.dynamicMountObjectTriggerCount + 1 |
217 | end |
218 | end |
219 | end |
220 | |
221 | end |
222 | |
223 | end |
224 | elseif onLeave then |
225 | if otherActorId == spec.dynamicMountObjectActorId and spec.dynamicMountObjectTriggerCount ~= nil then |
226 | spec.dynamicMountObjectTriggerCount = spec.dynamicMountObjectTriggerCount-1 |
227 | if spec.dynamicMountObjectTriggerCount == 0 then |
228 | self:unmountDynamic() |
229 | spec.dynamicMountObjectTriggerCount = nil |
230 | end |
231 | end |
232 | end |
233 | end |
353 | function Mountable:mountDynamic(object, objectActorId, jointNode, mountType, forceAcceleration) |
354 | local spec = self.spec_mountable |
355 | |
356 | if not self:getSupportsMountDynamic() or spec.mountObject ~= nil then |
357 | return false |
358 | end |
359 | |
360 | -- do not allow to mount myself to an object that has the same root vehicle as an object that i got already mounted |
361 | local dynamicMountSpec = self.spec_dynamicMountAttacher |
362 | if dynamicMountSpec ~= nil then |
363 | for _, mountedObject in pairs(dynamicMountSpec.dynamicMountedObjects) do |
364 | if mountedObject:isa(Vehicle) then |
365 | if mountedObject.rootVehicle == object.rootVehicle then |
366 | return false |
367 | end |
368 | end |
369 | end |
370 | end |
371 | |
372 | -- do not allow mounting of vehicles that already have a connection to the vehicle 'chain' |
373 | -- otherwise we could get endless loops |
374 | if object.rootVehicle == self.rootVehicle then |
375 | return false |
376 | end |
377 | |
378 | jointNode = spec.jointNode or jointNode |
379 | |
380 | if spec.dynamicMountTriggerId ~= nil then |
381 | -- while we use mount type fork we put our local joint to our center of mass |
382 | -- like this we have one joint in the center of the fork and one in the center of the loaded object |
383 | -- this results in a optimal physics behaviour with joints close to the center of mass |
384 | local x, y, z |
385 | if mountType == DynamicMountUtil.TYPE_FORK then |
386 | local _, _, zOffset = worldToLocal(jointNode, localToWorld(spec.componentNode, getCenterOfMass(spec.componentNode))) |
387 | x, y, z = localToLocal(jointNode, getParent(spec.dynamicMountJointNodeDynamic), 0, 0, zOffset) |
388 | else |
389 | x, y, z = localToLocal(jointNode, getParent(spec.dynamicMountJointNodeDynamic), 0, 0, 0) |
390 | end |
391 | |
392 | y = spec.dynamicMountJointTransY or y |
393 | setTranslation(spec.dynamicMountJointNodeDynamic, x, y, z) |
394 | |
395 | local rx, ry, rz |
396 | if spec.dynamicMountJointLimitToRotY then |
397 | local dx, _, dz = localDirectionToLocal(jointNode, getParent(spec.dynamicMountJointNodeDynamic), 0, 0, 1) |
398 | dx, dz = MathUtil.vector2Normalize(dx, dz) |
399 | rx, ry, rz = 0, MathUtil.getYRotationFromDirection(dx, dz), 0 |
400 | else |
401 | rx, ry, rz = localRotationToLocal(jointNode, getParent(spec.dynamicMountJointNodeDynamic), 0, 0, 0) |
402 | end |
403 | |
404 | setRotation(spec.dynamicMountJointNodeDynamic, rx, ry, rz) |
405 | end |
406 | |
407 | local additionalWeight = self:getAdditionalMountingMass() |
408 | local mass = self:getTotalMass() |
409 | local massFactor = (additionalWeight+mass) / mass |
410 | forceAcceleration = forceAcceleration * massFactor |
411 | |
412 | return DynamicMountUtil.mountDynamic(self, spec.componentNode, object, objectActorId, jointNode, mountType, forceAcceleration*spec.dynamicMountForceLimitScale, spec.dynamicMountJointNodeDynamic) |
413 | end |
279 | function Mountable:mountKinematic(object, node, x,y,z, rx,ry,rz) |
280 | local spec = self.spec_mountable |
281 | |
282 | -- set isDelete = true to remove Mountable from pendingDynamicMountObjects (no delete leave callback) |
283 | self:unmountDynamic(true) |
284 | |
285 | removeFromPhysics(spec.componentNode) |
286 | |
287 | link(node, spec.componentNode) |
288 | |
289 | local wx,wy,wz = localToWorld(node, x,y,z) |
290 | local wqx,wqy,wqz,wqw = mathEulerToQuaternion( localRotationToWorld(node, rx,ry,rz) ) |
291 | self:setWorldPositionQuaternion(wx,wy,wz, wqx,wqy,wqz,wqw, 1, true) |
292 | |
293 | addToPhysics(spec.componentNode) |
294 | |
295 | -- on clients we are kinematic all the time |
296 | if self.isServer then |
297 | setRigidBodyType(spec.componentNode, RigidBodyType.KINEMATIC) |
298 | |
299 | self.components[1].isKinematic = true |
300 | self.components[1].isDynamic = false |
301 | end |
302 | |
303 | if object.getParentComponent ~= nil then |
304 | local componentNode = object:getParentComponent(node) |
305 | if getRigidBodyType(componentNode) == RigidBodyType.DYNAMIC then |
306 | setPairCollision(componentNode, spec.componentNode, false) |
307 | end |
308 | end |
309 | |
310 | spec.mountObject = object |
311 | spec.mountJointNode = node |
312 | end |
92 | function Mountable:onLoad(savegame) |
93 | local spec = self.spec_mountable |
94 | |
95 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.dynamicMount#triggerIndex", "vehicle.dynamicMount#triggerNode") --FS17 to FS19 |
96 | |
97 | spec.dynamicMountJointIndex = nil |
98 | spec.dynamicMountObject = nil |
99 | spec.dynamicMountObjectActorId = nil |
100 | spec.dynamicMountForceLimitScale = self.xmlFile:getValue("vehicle.dynamicMount#forceLimitScale", 1) |
101 | spec.mountObject = nil |
102 | |
103 | spec.componentNode = self.rootNode |
104 | spec.dynamicMountTriggerId = self.xmlFile:getValue("vehicle.dynamicMount#triggerNode", nil, self.components, self.i3dMappings) |
105 | if spec.dynamicMountTriggerId ~= nil then |
106 | if self.isServer then |
107 | addTrigger(spec.dynamicMountTriggerId, "mountableTriggerCallback", self) |
108 | end |
109 | |
110 | spec.componentNode = self:getParentComponent(spec.dynamicMountTriggerId) |
111 | |
112 | if spec.dynamicMountJointNodeDynamic == nil then |
113 | spec.dynamicMountJointNodeDynamic = createTransformGroup("dynamicMountJointNodeDynamic") |
114 | link(spec.componentNode, spec.dynamicMountJointNodeDynamic) |
115 | end |
116 | |
117 | spec.dynamicMountJointTransY = self.xmlFile:getValue("vehicle.dynamicMount#jointTransY") |
118 | spec.dynamicMountJointLimitToRotY = self.xmlFile:getValue("vehicle.dynamicMount#jointLimitToRotY", false) |
119 | end |
120 | |
121 | spec.jointNode = self.xmlFile:getValue("vehicle.dynamicMount#jointNode", nil, self.components, self.i3dMappings) |
122 | |
123 | spec.dynamicMountTriggerForceAcceleration = self.xmlFile:getValue("vehicle.dynamicMount#triggerForceAcceleration", 4) |
124 | spec.dynamicMountSingleAxisFreeY = self.xmlFile:getValue("vehicle.dynamicMount#singleAxisFreeY") |
125 | spec.dynamicMountSingleAxisFreeX = self.xmlFile:getValue("vehicle.dynamicMount#singleAxisFreeX") |
126 | |
127 | spec.additionalMountDistance = self.xmlFile:getValue("vehicle.dynamicMount#additionalMountDistance", 0) |
128 | |
129 | spec.allowMassReduction = self.xmlFile:getValue("vehicle.dynamicMount#allowMassReduction", true) |
130 | spec.reducedComponentMass = false |
131 | |
132 | spec.lockPositions = {} |
133 | self.xmlFile:iterate("vehicle.dynamicMount.lockPosition", function(index, key) |
134 | local entry = {} |
135 | |
136 | entry.xmlFilename = self.xmlFile:getValue(key .. "#xmlFilename") |
137 | entry.jointNode = self.xmlFile:getValue(key .. "#jointNode", "0>") |
138 | if entry.xmlFilename ~= nil and entry.jointNode ~= nil then |
139 | entry.xmlFilename = entry.xmlFilename:gsub("$data", "data") |
140 | |
141 | entry.transOffset = self.xmlFile:getValue(key .. "#transOffset", "0 0 0", true) |
142 | entry.rotOffset = self.xmlFile:getValue(key .. "#rotOffset", "0 0 0", true) |
143 | |
144 | table.insert(spec.lockPositions, entry) |
145 | else |
146 | Logging.xmlWarning(self.xmlFile, "Invalid lock position '%s'. Missing xmlFilename or jointNode!", key) |
147 | end |
148 | end) |
149 | end |
49 | function Mountable.registerFunctions(vehicleType) |
50 | SpecializationUtil.registerFunction(vehicleType, "getSupportsMountDynamic", Mountable.getSupportsMountDynamic) |
51 | SpecializationUtil.registerFunction(vehicleType, "getSupportsMountKinematic", Mountable.getSupportsMountKinematic) |
52 | SpecializationUtil.registerFunction(vehicleType, "onDynamicMountJointBreak", Mountable.onDynamicMountJointBreak) |
53 | SpecializationUtil.registerFunction(vehicleType, "mountableTriggerCallback", Mountable.mountableTriggerCallback) |
54 | SpecializationUtil.registerFunction(vehicleType, "mount", Mountable.mount) |
55 | SpecializationUtil.registerFunction(vehicleType, "unmount", Mountable.unmount) |
56 | SpecializationUtil.registerFunction(vehicleType, "mountKinematic", Mountable.mountKinematic) |
57 | SpecializationUtil.registerFunction(vehicleType, "unmountKinematic", Mountable.unmountKinematic) |
58 | SpecializationUtil.registerFunction(vehicleType, "mountDynamic", Mountable.mountDynamic) |
59 | SpecializationUtil.registerFunction(vehicleType, "unmountDynamic", Mountable.unmountDynamic) |
60 | SpecializationUtil.registerFunction(vehicleType, "getAdditionalMountingDistance", Mountable.getAdditionalMountingDistance) |
61 | SpecializationUtil.registerFunction(vehicleType, "getAdditionalMountingMass", Mountable.getAdditionalMountingMass) |
62 | SpecializationUtil.registerFunction(vehicleType, "additionalMountingMassRaycastCallback", Mountable.additionalMountingMassRaycastCallback) |
63 | SpecializationUtil.registerFunction(vehicleType, "getMountObject", Mountable.getMountObject) |
64 | SpecializationUtil.registerFunction(vehicleType, "getDynamicMountObject", Mountable.getDynamicMountObject) |
65 | SpecializationUtil.registerFunction(vehicleType, "setReducedComponentMass", Mountable.setReducedComponentMass) |
66 | SpecializationUtil.registerFunction(vehicleType, "getAllowComponentMassReduction", Mountable.getAllowComponentMassReduction) |
67 | SpecializationUtil.registerFunction(vehicleType, "getMountableLockPositions", Mountable.getMountableLockPositions) |
68 | end |
597 | function Mountable:setWorldPositionQuaternion(superFunc, x, y, z, qx, qy, qz, qw, i, changeInterp) |
598 | if not self.isServer then |
599 | -- while the object is mounted on client side kinematically or default |
600 | -- received positions are not applied since the position is dependent on the mount object |
601 | -- only the first component is kinematically linked, so still receive the others translation |
602 | if self:getMountObject() == nil or i > 1 then |
603 | return superFunc(self, x, y, z, qx, qy, qz, qw, i, changeInterp) |
604 | end |
605 | |
606 | return |
607 | end |
608 | |
609 | return superFunc(self, x, y, z, qx, qy, qz, qw, i, changeInterp) |
610 | end |
257 | function Mountable:unmount() |
258 | local spec = self.spec_mountable |
259 | |
260 | if spec.mountObject ~= nil then |
261 | spec.mountObject = nil |
262 | local x,y,z = getWorldTranslation(spec.componentNode) |
263 | local qx,qy,qz,qw = getWorldQuaternion(spec.componentNode) |
264 | |
265 | link(getRootNode(), spec.componentNode) |
266 | self:setWorldPositionQuaternion(x,y,z, qx,qy,qz,qw, 1, true) |
267 | |
268 | addToPhysics(spec.componentNode) |
269 | |
270 | self:setReducedComponentMass(false) |
271 | return true |
272 | end |
273 | |
274 | return false |
275 | end |
316 | function Mountable:unmountKinematic() |
317 | local spec = self.spec_mountable |
318 | |
319 | if spec.mountObject ~= nil then |
320 | if spec.mountObject.getParentComponent ~= nil then |
321 | local componentNode = spec.mountObject:getParentComponent(spec.mountJointNode) |
322 | if getRigidBodyType(componentNode) == RigidBodyType.DYNAMIC then |
323 | setPairCollision(componentNode, spec.componentNode, true) |
324 | end |
325 | end |
326 | |
327 | spec.mountObject = nil |
328 | spec.mountJointNode = nil |
329 | local x,y,z = getWorldTranslation(spec.componentNode) |
330 | local qx,qy,qz,qw = getWorldQuaternion(spec.componentNode) |
331 | |
332 | removeFromPhysics(spec.componentNode) |
333 | link(getRootNode(), spec.componentNode) |
334 | self:setWorldPositionQuaternion(x,y,z, qx,qy,qz,qw, 1, true) |
335 | addToPhysics(spec.componentNode) |
336 | |
337 | if self.isServer then |
338 | setRigidBodyType(spec.componentNode, RigidBodyType.DYNAMIC) |
339 | |
340 | self.components[1].isKinematic = false |
341 | self.components[1].isDynamic = true |
342 | end |
343 | |
344 | self:setReducedComponentMass(false) |
345 | return true |
346 | end |
347 | |
348 | return false |
349 | end |