Skip to main content
Skip to main content

HandToolHands

HandToolHands

Description

The hand tool specialisation for "no tool", which is still technically a tool that cannot be dropped or stored.

Functions

calculateItemMass

Description

Definition

calculateItemMass()

Arguments

anyitemNode

Code

function HandToolHands.calculateItemMass(itemNode)

-- Ensure the given node exists.
if itemNode = = nil or not entityExists(itemNode) then
return nil
end

-- If the node has an object associated with it, check to see if it can calculate its own mass.
local mission = g_currentMission
local object = mission:getNodeObject(itemNode)
local mass
if object ~ = nil and object.getTotalMass ~ = nil then
mass = object:getTotalMass()
end

-- Return the object's mass; or the node's mass if it's nil.
return mass or getMass(itemNode)
end

consoleCommandToggleSuperStrength

Description

Definition

consoleCommandToggleSuperStrength()

Code

function HandToolHands:consoleCommandToggleSuperStrength()
local spec = self.spec_hands
spec.hasSuperStrength = not spec.hasSuperStrength
local text = "Disabled super strength"

if spec.hasSuperStrength then
spec.currentMaximumMass = HandToolHands.SUPER_STRENGTH_PICKUP_MASS
spec.pickupDistance = 10
text = "Enabled super strength"
else
spec.currentMaximumMass = HandToolHands.MAXIMUM_PICKUP_MASS
spec.pickupDistance = HandToolHands.PICKUP_DISTANCE
end

local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer = = nil or not carryingPlayer.isOwner then
return text
end

-- Remove the target type from the player's targeter.
carryingPlayer.targeter:removeTargetType( HandToolHands )
carryingPlayer.targeter:addTargetType( HandToolHands , HandToolHands.TARGET_MASK, 0.5 , spec.pickupDistance)

return text
end

createItemJoint

Description

Creates a joint between the currently held item and the kinematic node.

Definition

createItemJoint()

Code

function HandToolHands:createItemJoint()

-- Reset the phyics index.
local spec = self.spec_hands
spec.pickupPhysicsIndex = nil

if not self.isServer then
--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "createItemJoint can only be used on the server!")
return
end
--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Creating item joint")

-- Ensure an item is being held, and a joint creation is being awaited.
if not self:getIsHoldingItem() then
Logging.error( "Cannot create joint for held item when no item is being held!" )
return
end

-- Create the joint between the kinematic node and target object.
local joint = JointConstructor.new()
joint:setActors(spec.kinematicNode, spec.heldItemNode)

-- Get the centre of mass of the target, and set the position of the joint to it.
local targetCentreX, targetCentreY, targetCentreZ = getCenterOfMass(spec.heldItemNode)
local targetWorldX, targetWorldY, targetWorldZ = localToWorld(spec.heldItemNode, targetCentreX, targetCentreY, targetCentreZ)
joint:setJointWorldPositions(targetWorldX, targetWorldY, targetWorldZ, targetWorldX, targetWorldY, targetWorldZ)

-- Set the limits.
joint:setTranslationLimit( 0 , true , 0 , 0 )
joint:setTranslationLimit( 1 , true , 0 , 0 )
joint:setTranslationLimit( 2 , true , 0 , 0 )
joint:setRotationLimit( 0 , 0 , 0 )
joint:setRotationLimit( 1 , 0 , 0 )
joint:setRotationLimit( 2 , 0 , 0 )

-- Set the axes and normals.
local targetLeftX, targetLeftY, targetLeftZ = localDirectionToWorld(spec.heldItemNode, 1 , 0 , 0 )
joint:setJointWorldAxes(targetLeftX, targetLeftY, targetLeftZ, targetLeftX, targetLeftY, targetLeftZ)

local targetUpX, targetUpY, targetUpZ = localDirectionToWorld(spec.heldItemNode, 0 , 1 , 0 )
joint:setJointWorldNormals(targetUpX, targetUpY, targetUpZ, targetUpX, targetUpY, targetUpZ)

-- Disable collisions between the kinematic node and the item.
joint:setEnableCollision( false )

-- Position and orient the rotation node so that it matches the target item.
self:orientRotationNodeToItem(spec.heldItemNode)

-- set spring/damper ?!
local dampingRatio = 1.0
local mass = HandToolHands.calculateItemMass(spec.heldItemNode)

local rotationLimitSpring = { }
local rotationLimitDamper = { }
for i = 1 , 3 do
rotationLimitSpring[i] = mass * 60
rotationLimitDamper[i] = dampingRatio * 2 * math.sqrt(mass * rotationLimitSpring[i])
end
joint:setRotationLimitSpring(rotationLimitSpring[ 1 ], rotationLimitDamper[ 1 ], rotationLimitSpring[ 2 ], rotationLimitDamper[ 2 ], rotationLimitSpring[ 3 ], rotationLimitDamper[ 3 ])

local translationLimitSpring = { }
local translationLimitDamper = { }
for i = 1 , 3 do
translationLimitSpring[i] = mass * 60
translationLimitDamper[i] = dampingRatio * 2 * math.sqrt(mass * translationLimitSpring[i])
end
joint:setTranslationLimitSpring(translationLimitSpring[ 1 ], translationLimitDamper[ 1 ], translationLimitSpring[ 2 ], translationLimitDamper[ 2 ], translationLimitSpring[ 3 ], translationLimitDamper[ 3 ])

if not spec.hasSuperStrength then
local forceAcceleration = 4
local forceLimit = forceAcceleration * mass * 40.0
joint:setBreakable(forceLimit, forceLimit)
end

-- Create the joint.
spec.heldItemJointId = joint:finalize()

-- Listen for the joint being broken due to excess force.
addJointBreakReport(spec.heldItemJointId, "onHeldItemJointBroken" , self )

--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Attached to %s", getName(spec.heldItemNode))
end

dropHeldItem

Description

Drops the currently held item.

Definition

dropHeldItem(boolean? noEventSend)

Arguments

boolean?noEventSend

Code

function HandToolHands:dropHeldItem(noEventSend)

if not self:getIsHoldingItem() then
return
end

HandsThrowObjectEvent.sendEvent( self , 0 , 0 , 0 , 0 , noEventSend)
self:onHeldItemJointBroken( nil , nil , true )
end

findFootballCallback

Description

Definition

findFootballCallback()

Arguments

anytransformId

Code

function HandToolHands:findFootballCallback(transformId)
if transformId ~ = 0 and getHasClassId(transformId, ClassIds.SHAPE) then
local mission = g_currentMission
local object = mission:getNodeObject(transformId)
if object ~ = nil and object:isa( Football ) then
local spec = self.spec_hands
spec.football = object

return false
end
end

return true
end

getCanBeDropped

Description

Definition

getCanBeDropped()

Arguments

anysuperFunc

Code

function HandToolHands:getCanBeDropped(superFunc)
return false
end

getCanPickUpNode

Description

Finds if these hands can pick up the given target object.

Definition

getCanPickUpNode(entityId node)

Arguments

entityIdnodeThe target from the player's targeter.

Return Values

entityIdcanPickUpTrue if the hands can pick up the given target; otherwise false.

Code

function HandToolHands:getCanPickUpNode(node)

-- If there is no target, return false.
if node = = nil or node = = 0 or not entityExists(node) then
return false
end

if self.isServer and getRigidBodyType(node) ~ = RigidBodyType.DYNAMIC then -- all dynamic objects are kinematic on clients, so only do this check on the server
return false
end

-- If the hands are holding something, they cannot pick something else up, so return false.
if self:getIsHoldingItem() then
return false
end

-- If the node has no mass, or the object is too heavy for the player, return false.
local spec = self.spec_hands
local mass = HandToolHands.calculateItemMass(node)
if self.isServer and(mass = = nil or mass > spec.currentMaximumMass) then
return false
end

-- If the node has an object associated with it, check to ensure the object is valid.
local mission = g_currentMission
local object = mission:getNodeObject(node)
if object ~ = nil then
-- If the object is a mount object, return false.
if object.dynamicMountObject ~ = nil or object.tensionMountObject ~ = nil then
return false
end

-- If the object specifically says that the player cannot pick it up, return false.
if not spec.hasSuperStrength then
if object.getCanBePickedUp ~ = nil and not object:getCanBePickedUp( self:getCarryingPlayer()) then
return false
end
end
end

-- Return true, as all previous checks have passed.
return true
end

getHasJoint

Description

Finds if the hands have a joint between the kinematic node and the held item.

Definition

getHasJoint()

Return Values

entityIdhasJointTrue if there is a joint between the kinematic node and the held item; otherwise false.

Code

function HandToolHands:getHasJoint()
return self.spec_hands.heldItemJointId ~ = nil
end

getIsAwaitingJointCreation

Description

Finds if an item has been picked up, but not yet attached to the kinematic node.

Definition

getIsAwaitingJointCreation()

Return Values

entityIdisAwaitingJointCreationTrue if the hands are awaiting a physics update before creating the joint.

Code

function HandToolHands:getIsAwaitingJointCreation()
return self.spec_hands.pickupPhysicsIndex ~ = nil
end

getIsHoldingItem

Description

Finds if the hands are currently holding something. Note that this does not mean there is a joint.

Definition

getIsHoldingItem()

Return Values

entityIdisHoldingItemTrue if the hands are holding something; otherwise false.

Code

function HandToolHands:getIsHoldingItem()
return self.spec_hands.heldItemNode ~ = nil
end

getIsThrowingItem

Description

Finds if the player is currently charging up a throw.

Definition

getIsThrowingItem()

Return Values

entityIdisThrowingTrue if the player is charging a throw; otherwise false.

Code

function HandToolHands:getIsThrowingItem()
return self.spec_hands.currentThrowTime ~ = nil
end

getKinematicNode

Description

Gets the kinematic helper of the hands. This will be nil until it is loaded.

Definition

getKinematicNode()

Return Values

entityIdkinematicNodeThe kinematic node of the hands, or nil if it is not yet loaded.
entityIdkinematicRotationNodeThe rotation node of the hands, or nil if it is not yet loaded.

Code

function HandToolHands:getKinematicNode()
return self.spec_hands.kinematicNode, self.spec_hands.kinematicRotationNode
end

levelHeldItem

Description

Levels the held item so that it is flat to the floor, preserving its yaw. Should only be uesd for square bales and pallets.

Definition

levelHeldItem()

Code

function HandToolHands:levelHeldItem()

-- If no item is being held, do nothing.
if not self:getIsHoldingItem() then
return
end

-- Get the direction of the held item relative to the kinematic node.
local spec = self.spec_hands
local kinematicNode, rotationNode = self:getKinematicNode()
local itemDirectionX, _, itemDirectionZ = localDirectionToLocal(spec.heldItemNode, kinematicNode, 0 , 0 , 1 )

-- Set the local yaw of the rotation node to preserve the held item's yaw.Zero out the pitch and roll.
local preservedYaw = MathUtil.getYRotationFromDirection(itemDirectionX, itemDirectionZ)
setRotation(rotationNode, 0 , preservedYaw, 0 )

-- Set the joint frame for the joint, so that it updates.
if self.isServer then
setJointFrame(spec.heldItemJointId, 0 , rotationNode)
end
end

onCarryingPlayerChanged

Description

Definition

onCarryingPlayerChanged()

Arguments

anyplayer
anylastPlayer

Code

function HandToolHands:onCarryingPlayerChanged(player, lastPlayer)
-- This function should only ever be called once, as the player's hands cannot be put down or picked up.
-- Add the super strength command.
if player ~ = nil and player.isOwner then
addConsoleCommand( "gsPlayerSuperStrengthToggle" , "Toggles the super strength mode for the player" , "consoleCommandToggleSuperStrength" , self )
elseif lastPlayer ~ = nil and lastPlayer.isOwner then
removeConsoleCommand( "gsPlayerSuperStrengthToggle" )
end
end

onDebugDraw

Description

Definition

onDebugDraw()

Code

function HandToolHands:onDebugDraw()

local kinematicNode, rotationNode = self:getKinematicNode()
if kinematicNode ~ = nil then
DebugUtil.drawDebugNode(kinematicNode, "kinematic" )
DebugUtil.drawDebugNode(rotationNode, "rotation" )
end

if self:getIsHoldingItem() then
local heldItemNode = self:getHeldItem()
DebugUtil.drawDebugNode(heldItemNode, "heldItem" )

local heldItemPositionX, heldItemPositionY, heldItemPositionZ = getWorldTranslation(heldItemNode)
local kinematicPositionX, kinematicPositionY, kinematicPositionZ = getWorldTranslation(kinematicNode)

local distance = MathUtil.vector3Length(heldItemPositionX - kinematicPositionX, heldItemPositionY - kinematicPositionY, heldItemPositionZ - kinematicPositionZ)

DebugLine.renderBetweenNodes(kinematicNode, heldItemNode, Color.PRESETS.WHITE, true , string.format( "%.3fm" , distance), 0.03 , Color.PRESETS.GREEN, Color.PRESETS.RED)
end
end

onDelete

Description

Definition

onDelete()

Code

function HandToolHands:onDelete()
local spec = self.spec_hands

self:dropHeldItem( true )

-- Release the shared i3d file for the kinematic node.
if spec.kinematicSharedLoadRequestId ~ = nil then
g_i3DManager:releaseSharedI3DFile(spec.kinematicSharedLoadRequestId)
spec.kinematicSharedLoadRequestId = nil
end

if spec.kinematicNode ~ = nil then
delete(spec.kinematicNode)
spec.kinematicNode = nil
end

local player = self:getCarryingPlayer()
if player ~ = nil and player.isOwner then
removeConsoleCommand( "gsPlayerSuperStrengthToggle" )
end

if spec.crosshair ~ = nil then
spec.crosshair:delete()
spec.crosshair = nil
end

if spec.pickUpCrosshair ~ = nil then
spec.pickUpCrosshair:delete()
spec.pickUpCrosshair = nil
end

if spec.throwCrosshair ~ = nil then
spec.throwCrosshair:delete()
spec.throwCrosshair = nil
end
end

onDraw

Description

Definition

onDraw()

Code

function HandToolHands:onDraw()
local spec = self.spec_hands

local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer ~ = nil and carryingPlayer.isOwner then
if carryingPlayer.camera.isFirstPerson then
local player = self:getCarryingPlayer()
local target = player.targeter.closestTargetsByKey[ HandToolHands ]
local node = target and target.node or nil
if self:getIsThrowingItem() then
-- Scale the throw crosshair based on how powerful the player's throw is.
local throwForceScalar = math.clamp(spec.currentThrowTime / HandToolHands.MAXIMUM_THROW_TIME, 0 , 1 )
spec.throwCrosshair:setScale(throwForceScalar, throwForceScalar)

spec.throwCrosshair:render()
elseif spec.heldItemNode ~ = nil or self:getCanPickUpNode(node) then
spec.pickUpCrosshair:render()
else
spec.crosshair:render()
end
end
end
end

onHeldEnd

Description

Definition

onHeldEnd()

Code

function HandToolHands:onHeldEnd()
local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer = = nil or not carryingPlayer.isOwner then
return
end

-- Remove the target type from the player's targeter.
carryingPlayer.targeter:removeTargetType( HandToolHands )

-- Drop any held item.
self:dropHeldItem()
end

onHeldItemJointBroken

Description

Fired when the item joint is broken, or from dropHeldItem, and handles removing the joint and state.

Definition

onHeldItemJointBroken(entityId jointIndex, float breakingImpulse, boolean noEventSend)

Arguments

entityIdjointIndex
floatbreakingImpulse
booleannoEventSendIf this is true, no network event will be sent.

Code

function HandToolHands:onHeldItemJointBroken(jointIndex, breakingImpulse, noEventSend)

local spec = self.spec_hands

local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer ~ = nil then

--#debug if entityExists(spec.heldItemNode) then
--#debug carryingPlayer:debugLog(Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Detached from %s", getName(spec.heldItemNode))
--#debug end

if carryingPlayer.isOwner then
-- Disable any actions that should only be available while an item is held.
g_inputBinding:setActionEventActive(spec.throwActionEventId, false )
g_inputBinding:setActionEventActive(spec.pitchActionEventId, false )
g_inputBinding:setActionEventActive(spec.yawActionEventId, false )
g_inputBinding:setActionEventActive(spec.levelActionEventId, false )

-- Disable the action and set its text to "pick up" again.
g_inputBinding:setActionEventActive(spec.pickUpActionEventId, false )
g_inputBinding:setActionEventText(spec.pickUpActionEventId, g_i18n:getText( "action_pickUpObject" ))
end
end

-- Reset the collision mask of the node.
if entityExists(spec.heldItemNode) and self:getIsHoldingItem() then
-- Enable collision against cct
local player = self:getCarryingPlayer()
if player ~ = nil and player.capsuleController ~ = nil then
local cctIndex = player.capsuleController.capsuleId
if cctIndex ~ = nil then
setCCTPairCollision(cctIndex, spec.heldItemNode, true )
end
end
end

-- Reset the held item state.
local itemExists = entityExists(spec.heldItemNode)
spec.heldItemNode = nil

if not self.isServer then
return
end

-- The server tells the clients about the broken joint by telling them that they threw the object.
HandsThrowObjectEvent.sendEvent( self , 0 , 0 , 0 , 0 , noEventSend)

-- Remove the joint and unset the id.
if self:getHasJoint() then
if itemExists then
removeJoint(spec.heldItemJointId)
end
spec.heldItemJointId = nil
-- If there's no joint, and one is being awaited, cancel it.
elseif self:getIsAwaitingJointCreation() then
spec.pickupPhysicsIndex = nil
end
end

onHeldStart

Description

Definition

onHeldStart()

Code

function HandToolHands:onHeldStart()
local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer = = nil or not carryingPlayer.isOwner then
return
end

-- Ensure objects are targeted.
local spec = self.spec_hands
local targeter = self:getCarryingPlayer().targeter
targeter:addTargetType( HandToolHands , HandToolHands.TARGET_MASK, 0.5 , spec.pickupDistance)
end

onKinematicHelperLoaded

Description

Definition

onKinematicHelperLoaded()

Arguments

anykinematicNode
anyfailedReason

Code

function HandToolHands:onKinematicHelperLoaded(kinematicNode, failedReason)
local spec = self.spec_hands
self:finishLoadingTask(spec.kinematicLoadingTask)
spec.kinematicLoadingTask = nil

-- Ensure the node loaded correctly.
if kinematicNode = = nil or kinematicNode = = 0 then
Logging.error( "Hands could not load kinematic helper i3d!" )
return
end

spec.kinematicNode = getChildAt(kinematicNode, 0 )
link(getRootNode(), spec.kinematicNode)
addToPhysics(spec.kinematicNode)

-- Create the rotation helper node.
spec.kinematicRotationNode = createTransformGroup( "kinematicRotationHelper" )
link(spec.kinematicNode, spec.kinematicRotationNode)

delete(kinematicNode)
end

onLevelAction

Description

Definition

onLevelAction()

Arguments

any_
anyinputValue

Code

function HandToolHands:onLevelAction(_, inputValue)
self:levelHeldItem()
end

onLoad

Description

Definition

onLoad()

Arguments

anyxmlFile
anybaseDirectory

Code

function HandToolHands:onLoad(xmlFile, baseDirectory)

local spec = self.spec_hands

spec.hasSuperStrength = false

-- The current maximum mass that the player can pick up.
spec.currentMaximumMass = HandToolHands.MAXIMUM_PICKUP_MASS
spec.pickupDistance = HandToolHands.PICKUP_DISTANCE

-- The current amount of throw time charged up by the player.Is nil unless the player is charging a throw.
spec.currentThrowTime = nil

-- The action event ids.
spec.pickUpActionEventId = nil
spec.throwActionEventId = nil
spec.pitchActionEventId = nil
spec.yawActionEventId = nil

-- The held item data.
spec.heldItemJointId = nil
spec.heldItemNode = nil

-- The distance away from the camera that the object is held.This starts at the max distance, but is calculated whenever the player picks something up.
spec.currentHoldDistance = HandToolHands.PICKUP_DISTANCE

-- The kinematic nodes, used for attaching the item to the hands.
spec.kinematicNode = nil
spec.kinematicRotationNode = nil

-- Workaround for the weird input system.Keeps track of the last update loop index where the throw button was held.If it does not match with the current update loop index, the item is thrown.
spec.lastThrowUpdateIndex = nil

-- The physics index of the pickup of an object.The joint is created once this physics frame has been simulated.
spec.pickupPhysicsIndex = nil

spec.dirtyFlag = self:getNextDirtyFlag()

-- Begin loading the kinematic helper.
spec.kinematicLoadingTask = self:createLoadingTask(spec)
spec.kinematicSharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync( "dataS/character/shared/kinematicHelper.i3d" , true , false , HandToolHands.onKinematicHelperLoaded, self , nil )

spec.lastShootTime = g_ time
spec.canShoot = false
spec.football = nil

spec.actionPassText = g_i18n:getText( "action_passFootball" )
spec.actionShootText = g_i18n:getText( "action_shootFootball" )
spec.pickupText = g_i18n:getText( "action_pickUpObject" )
spec.throwText = g_i18n:getText( "input_THROW_OBJECT" )

if self.isClient then
spec.crosshair = self:createCrosshairOverlay( "gui.crosshairDefault" , HandTool.DEFAULT_CROSSHAIR_SIZE_PIXELS * 0.5 )
spec.crosshair:setColor( nil , nil , nil , 0.25 )
spec.pickUpCrosshair = self:createCrosshairOverlay( "gui.grab" )
spec.throwCrosshair = self:createCrosshairOverlay( "gui.crosshairThrow" )
end
end

onPickUpAction

Description

Definition

onPickUpAction()

Arguments

any_
anyinputValue

Code

function HandToolHands:onPickUpAction(_, inputValue)

-- Only allow picking up for the owning player.
local player = self:getCarryingPlayer()
if not player.isOwner then
return
end

local spec = self.spec_hands
if spec.football ~ = nil then
local camera = g_cameraManager:getActiveCamera()
local dirX, _, dirZ = localDirectionToWorld(camera, 0 , 0 , - 1 )
dirX, _, dirZ = MathUtil.vector3Normalize(dirX, 0 , dirZ)

self:shootBall(spec.football, dirX, dirZ, 0 , HandToolHands.FOOTBALL_PASS_VELOCITY, Football.TYPE_PASS)
return
end

-- If the player is currently holding an item, drop it.
if self:getIsHoldingItem() then
self:dropHeldItem()
-- Otherwise; pick up the targeted item.
else
local target = player.targeter.closestTargetsByKey[ HandToolHands ]
self:pickUpTarget(target)
end
end

onPitchAction

Description

Definition

onPitchAction()

Arguments

any_
anyinputDelta

Code

function HandToolHands:onPitchAction(_, inputDelta)

-- The pitching should always be relative to the player, so no matter which way the held object is facing, it's tilted towards/away from the player.
-- Otherwise; the item will be tilted relative to its own axis, which doesn't really make sense if they have no idea what part of the item is the "front".
local kinematicNode, rotationNode = self:getKinematicNode()
local directionX, directionY, directionZ = localDirectionToLocal(kinematicNode, rotationNode, 1 , 0 , 0 )

self:rotateHeldItem(inputDelta, directionX, directionY, directionZ)
end

onReadUpdateStream

Description

Definition

onReadUpdateStream()

Arguments

anystreamId
anytimestamp
anyconnection

Code

function HandToolHands:onReadUpdateStream(streamId, timestamp, connection)
if not connection:getIsServer() then
if streamReadBool(streamId) then
local kinematicNode, kinematicRotationNode = self:getKinematicNode()

local paramsXZ = g_currentMission.vehicleXZPosCompressionParams
local paramsY = g_currentMission.vehicleYPosCompressionParams

local kinematicNodePositionX = NetworkUtil.readCompressedWorldPosition(streamId, paramsXZ)
local kinematicNodePositionY = NetworkUtil.readCompressedWorldPosition(streamId, paramsY)
local kinematicNodePositionZ = NetworkUtil.readCompressedWorldPosition(streamId, paramsXZ)

local kinematicNodeDirectionX = NetworkUtil.readCompressedRange(streamId, - 1 , 1 , HandToolHands.KINEMATIC_NODE_DIRECTION_NUM_BITS)
local kinematicNodeDirectionZ = NetworkUtil.readCompressedRange(streamId, - 1 , 1 , HandToolHands.KINEMATIC_NODE_DIRECTION_NUM_BITS)

local kinematicNodePitch = NetworkUtil.readCompressedAngle(streamId)
local kinematicNodeYaw = NetworkUtil.readCompressedAngle(streamId)
local kinematicNodeRoll = NetworkUtil.readCompressedAngle(streamId)

setWorldTranslation(kinematicNode, kinematicNodePositionX, kinematicNodePositionY, kinematicNodePositionZ)
setWorldDirection(kinematicNode, kinematicNodeDirectionX, 0 , kinematicNodeDirectionZ, 0 , 1 , 0 )
setWorldRotation(kinematicRotationNode, kinematicNodePitch, kinematicNodeYaw, kinematicNodeRoll)

if self:getHasJoint() then
local spec = self.spec_hands
setJointFrame(spec.heldItemJointId, 0 , kinematicRotationNode)
end
end
end
end

onRegisterActionEvents

Description

Definition

onRegisterActionEvents()

Code

function HandToolHands:onRegisterActionEvents()
if not self:getIsActiveForInput( true ) then
return
end

local spec = self.spec_hands

-- The pick up/drop action.
local _, actionEventId = self:addActionEvent(InputAction.ACTIVATE_HANDTOOL, self , HandToolHands.onPickUpAction, true , false , false , false , nil )
g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_VERY_HIGH)
g_inputBinding:setActionEventText(actionEventId, spec.pickupText)
spec.pickUpActionEventId = actionEventId

-- The throw action.
_, actionEventId = self:addActionEvent(InputAction.ACTIVATE_HANDTOOL_SECONDARY, self , HandToolHands.onThrowAction, false , true , true , false , nil )
g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_VERY_HIGH)
g_inputBinding:setActionEventText(actionEventId, spec.throwText)
spec.throwActionEventId = actionEventId

-- The pitch/yaw actions.
_, actionEventId = self:addActionEvent(InputAction.ROTATE_OBJECT_UP_DOWN, self , HandToolHands.onPitchAction, false , false , true , false , nil )
g_inputBinding:setActionEventText(actionEventId, g_i18n:getText( "action_rotateObjectVertically" ))
spec.pitchActionEventId = actionEventId

_, actionEventId = self:addActionEvent(InputAction.ROTATE_OBJECT_LEFT_RIGHT, self , HandToolHands.onYawAction, false , false , true , false , nil )
g_inputBinding:setActionEventText(actionEventId, g_i18n:getText( "action_rotateObjectHorizontally" ))
spec.yawActionEventId = actionEventId

-- The level action for pallets and square bales.
_, actionEventId = self:addActionEvent(InputAction.HANDS_LEVEL_ITEM, self , HandToolHands.onLevelAction, true , false , false , false , nil )
spec.levelActionEventId = actionEventId
end

onThrowAction

Description

Definition

onThrowAction()

Arguments

any_
anyinputValue

Code

function HandToolHands:onThrowAction(_, inputValue)

-- Do nothing if the player is not the owner.
local player = self:getCarryingPlayer()
if not player.isOwner then
return
end

local spec = self.spec_hands
if spec.football ~ = nil then
local camera = g_cameraManager:getActiveCamera()
local dirX, _, dirZ = localDirectionToWorld(camera, 0 , 0 , - 1 )
dirX, _, dirZ = MathUtil.vector3Normalize(dirX, 0 , dirZ)

self:shootBall(spec.football, dirX, dirZ, 30 , HandToolHands.FOOTBALL_SHOT_VELOCITY, Football.TYPE_SHOT)
end

-- Do nothing if no item is held.
if not self:getIsHoldingItem() then
return
end

-- Keep track of the update loop index that this input was held down for.
spec.lastThrowUpdateIndex = g_updateLoopIndex

-- If there is no throw time, set it to 0.Otherwise; increment it by the dt.
if spec.currentThrowTime = = nil then
spec.currentThrowTime = 0
else
spec.currentThrowTime + = g_currentDt
end
end

onUpdate

Description

Definition

onUpdate()

Arguments

anydt

Code

function HandToolHands:onUpdate(dt)
local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer = = nil then
return
end

if not self:getIsHeld() then
return
end

-- If a joint creation is being awaited, and the physics frame has been simulated, create the joint.This naturally only triggers on the server.
local spec = self.spec_hands
if self:getIsAwaitingJointCreation() and getIsPhysicsUpdateIndexSimulated(spec.pickupPhysicsIndex) then
self:createItemJoint()
end

-- Update the kinematic node.
self:updateKinematicNode()

if not carryingPlayer.isOwner then
return
end

spec.canShoot = (spec.lastShootTime + HandToolHands.FOOTBALL_SHOOT_THRESHOLD) < g_ time
spec.football = nil

if self:getIsHoldingItem() then
if self.isServer and spec.heldItemNode ~ = nil and not entityExists(spec.heldItemNode) then
self:dropHeldItem()
end
end

-- If the player is not holding anything, check to see if they're looking at something that can be picked up.
if not self:getIsHoldingItem() then

-- Determine if the moused over object can be picked up.
local targetNode = carryingPlayer.targeter:getClosestTargetedNodeFromType( HandToolHands )
local canPickUpTarget = self:getCanPickUpNode(targetNode)

if not canPickUpTarget then
local mission = g_currentMission
local object = mission:getNodeObject(targetNode)
if object ~ = nil and object:isa( Football ) then
spec.football = object
end

if spec.football = = nil then
local x, y, z = getWorldTranslation(carryingPlayer.graphicsComponent.graphicsRootNode)
overlapSphere(x, y, z, HandToolHands.FOOTBALL_DETECTION_RADIUS, "findFootballCallback" , self , CollisionFlag.DYNAMIC_OBJECT, true , false , false )
end
end

local canShoot = (spec.football ~ = nil and spec.canShoot)

-- Activate or deactivate the input binding based on if the target object can be picked up or not.
g_inputBinding:setActionEventActive(spec.pickUpActionEventId, canPickUpTarget or canShoot)
g_inputBinding:setActionEventActive(spec.throwActionEventId, spec.heldItemNode or canShoot)

if canShoot then
g_inputBinding:setActionEventText(spec.pickUpActionEventId, spec.actionPassText)
g_inputBinding:setActionEventText(spec.throwActionEventId, spec.actionShootText)
else
g_inputBinding:setActionEventText(spec.pickUpActionEventId, spec.pickupText)
g_inputBinding:setActionEventText(spec.throwActionEventId, spec.throwText)
end

-- Otherwise; if the player is holding something and has finished throwing it, throw it.
elseif self:getIsThrowingItem() and spec.lastThrowUpdateIndex ~ = nil and spec.lastThrowUpdateIndex ~ = g_updateLoopIndex then
local throwForceScalar = math.clamp(spec.currentThrowTime / HandToolHands.MAXIMUM_THROW_TIME, 0 , 1 )
self:throwHeldItemWithForceScalar(throwForceScalar)
end

if spec.football ~ = nil then
if spec.canShoot then
local footballNode = spec.football.nodeId
local px, py, pz = getWorldTranslation(carryingPlayer.graphicsComponent.graphicsRootNode)
local bx, by, bz = getWorldTranslation(footballNode)
local distance = MathUtil.vector3Length(px - bx, py - by, pz - bz)

if distance > HandToolHands.FOOTBALL_DRIBBLE_MIN_DISTANCE and distance < HandToolHands.FOOTBALL_DRIBBLE_MAX_DISTANCE then
local camera = g_cameraManager:getActiveCamera()
local dirX, _, dirZ = localDirectionToWorld(camera, 0 , 0 , - 1 )
dirX, _, dirZ = MathUtil.vector3Normalize(dirX, 0 , dirZ)

self:shootBall(spec.football, dirX, dirZ, 0 , HandToolHands.FOOTBALL_DRIBBLE_VELOCITY, Football.TYPE_DRIBBLE)
end
end
end
end

onWriteUpdateStream

Description

Definition

onWriteUpdateStream()

Arguments

anystreamId
anyconnection
anydirtyMask

Code

function HandToolHands:onWriteUpdateStream(streamId, connection, dirtyMask)
if connection:getIsServer() then
local spec = self.spec_hands
if streamWriteBool(streamId, bit32.btest(dirtyMask, spec.dirtyFlag)) then
local kinematicNode, kinematicRotationNode = self:getKinematicNode()

local kinematicNodePitch, kinematicNodeYaw, kinematicNodeRoll = getWorldRotation(kinematicRotationNode)
local kinematicNodePositionX, kinematicNodePositionY, kinematicNodePositionZ = getWorldTranslation(kinematicNode)
local kinematicNodeDirectionX, _, kinematicNodeDirectionZ = localDirectionToWorld(kinematicNode, 0 , 0 , 1 )
kinematicNodeDirectionX, kinematicNodeDirectionZ = MathUtil.vector2Normalize(kinematicNodeDirectionX, kinematicNodeDirectionZ)

local paramsXZ = g_currentMission.vehicleXZPosCompressionParams
local paramsY = g_currentMission.vehicleYPosCompressionParams

NetworkUtil.writeCompressedWorldPosition(streamId, kinematicNodePositionX, paramsXZ)
NetworkUtil.writeCompressedWorldPosition(streamId, kinematicNodePositionY, paramsY)
NetworkUtil.writeCompressedWorldPosition(streamId, kinematicNodePositionZ, paramsXZ)

NetworkUtil.writeCompressedRange(streamId, kinematicNodeDirectionX, - 1 , 1 , HandToolHands.KINEMATIC_NODE_DIRECTION_NUM_BITS)
NetworkUtil.writeCompressedRange(streamId, kinematicNodeDirectionZ, - 1 , 1 , HandToolHands.KINEMATIC_NODE_DIRECTION_NUM_BITS)

NetworkUtil.writeCompressedAngle(streamId, kinematicNodePitch)
NetworkUtil.writeCompressedAngle(streamId, kinematicNodeYaw)
NetworkUtil.writeCompressedAngle(streamId, kinematicNodeRoll)
end
end
end

onYawAction

Description

Definition

onYawAction()

Arguments

any_
anyinputDelta

Code

function HandToolHands:onYawAction(_, inputDelta)

-- The yaw should always be absolute, so that it spins as if on a pole.
local kinematicNode, rotationNode = self:getKinematicNode()
local directionX, directionY, directionZ = localDirectionToLocal(kinematicNode, rotationNode, 0 , 1 , 0 )

self:rotateHeldItem(inputDelta, directionX, directionY, directionZ)
end

orientRotationNodeToItem

Description

Definition

orientRotationNodeToItem()

Arguments

anytargetItemNode

Code

function HandToolHands:orientRotationNodeToItem(targetItemNode)

local targetCentreX, targetCentreY, targetCentreZ = getCenterOfMass(targetItemNode)
local targetWorldX, targetWorldY, targetWorldZ = localToWorld(targetItemNode, targetCentreX, targetCentreY, targetCentreZ)

-- Position and orient the rotation node so that it matches the target item.
local _, kinematicRotationNode = self:getKinematicNode()
setWorldTranslation(kinematicRotationNode, targetWorldX, targetWorldY, targetWorldZ)
setWorldRotation(kinematicRotationNode, getWorldRotation(targetItemNode))
end

pickupFailed

Description

Definition

pickupFailed()

Code

function HandToolHands:pickupFailed()
Logging.devInfo( "HandToolHands.pickupFailed" )

local spec = self.spec_hands
--#debug if spec.heldItemNode ~ = nil and entityExists(spec.heldItemNode) then
--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Detached from %s", getName(spec.heldItemNode))
--#debug end

local carryingPlayer = self:getCarryingPlayer()
if carryingPlayer ~ = nil and carryingPlayer.isOwner then

-- Disable any actions that should only be available while an item is held.
g_inputBinding:setActionEventActive(spec.throwActionEventId, false )
g_inputBinding:setActionEventActive(spec.pitchActionEventId, false )
g_inputBinding:setActionEventActive(spec.yawActionEventId, false )
g_inputBinding:setActionEventActive(spec.levelActionEventId, false )

-- Disable the action and set its text to "pick up" again.
g_inputBinding:setActionEventActive(spec.pickUpActionEventId, false )
g_inputBinding:setActionEventText(spec.pickUpActionEventId, g_i18n:getText( "action_pickUpObject" ))
end

if spec.heldItemNode ~ = nil and entityExists(spec.heldItemNode) and self:getIsHoldingItem() then
-- Enable collision against cct
local player = self:getCarryingPlayer()
if player ~ = nil and player.capsuleController ~ = nil then
local cctIndex = player.capsuleController.capsuleId
if cctIndex ~ = nil then
setCCTPairCollision(cctIndex, spec.heldItemNode, true )
end
end
end

-- Reset the held item state.
spec.heldItemNode = nil
end

pickUpTarget

Description

Picks up the given target.

Definition

pickUpTarget(table target, boolean noEventSend)

Arguments

tabletargetThe target from the player's targeter.
booleannoEventSendIf this is true, no network event will be sent.

Return Values

booleansuccessTrue if the target was picked up; otherwise false.

Code

function HandToolHands:pickUpTarget(target, noEventSend)

-- If the item cannot be picked up, do nothing.
if target = = nil or not self:getCanPickUpNode(target.node) then
--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Failed to pick up object")
return false
end

HandsPickUpObjectEvent.sendEvent( self , target, noEventSend)

local mission = g_currentMission
local heldObject = mission:getNodeObject(target.node)
local heldItemNode = (heldObject ~ = nil and heldObject.rootNode ~ = nil ) and heldObject.rootNode or target.node

-- Set the held item to the given item, and save its collision mask.
local spec = self.spec_hands
spec.heldItemNode = heldItemNode

-- Disable collision against cct
local player = self:getCarryingPlayer()
if player ~ = nil and player.capsuleController ~ = nil then
local cctIndex = player.capsuleController.capsuleId
if cctIndex ~ = nil then
setCCTPairCollision(cctIndex, spec.heldItemNode, false )
end
end

-- Set the current hold distance to the distance from the player's camera to the hit position, then update the kinematic helper to ensure it's 100% in the correct position.
spec.currentHoldDistance = target.distance
self:updateKinematicNode()

-- Enable the actions that are available while holding an item.
g_inputBinding:setActionEventActive(spec.throwActionEventId, true )
g_inputBinding:setActionEventActive(spec.pitchActionEventId, true )
g_inputBinding:setActionEventActive(spec.yawActionEventId, true )

-- Activate the throw option.
g_inputBinding:setActionEventActive(spec.pickUpActionEventId, true )
g_inputBinding:setActionEventText(spec.pickUpActionEventId, g_i18n:getText( "action_dropObject" ))

-- Get the object that represents the node.If the node is a square bale or a pallet, enable the level action.
if heldObject ~ = nil and(heldObject.isPallet or(heldObject:isa( Bale ) and not heldObject.isRoundbale)) then
g_inputBinding:setActionEventActive(spec.levelActionEventId, true )

-- Work out the object name for the levelling action, defaulting to "object".
local objectName = "object"
if heldObject.isPallet then
objectName = g_i18n:getText( "typeDesc_pallet" )
elseif heldObject:isa( Bale ) then
objectName = g_i18n:getText( "fillType_squareBale" )
end
g_inputBinding:setActionEventText(spec.levelActionEventId, string.namedFormat(g_i18n:getText( "action_levelObject" ), "objectName" , objectName))
end

if self.isServer then
-- The kinematic node is a physics object, so moving it around has a 2 frame delay.If the joint were to be created now, it would fling off once the physics system has finished moving the kinematic node.
-- Due to this, the current physics index is saved, and the joint is created when it has been simulated.
spec.pickupPhysicsIndex = getPhysicsUpdateIndex()
--#debug Player.debugLog(self:getCarryingPlayer(), Player.DEBUG_DISPLAY_FLAG.HANDTOOLS, "Awaiting physics index %d for attachment to %s", spec.pickupPhysicsIndex, getName(spec.heldItemNode))
else
self:orientRotationNodeToItem(heldItemNode)
end

return true
end

prerequisitesPresent

Description

Definition

prerequisitesPresent()

Arguments

anyspecializations

Code

function HandToolHands.prerequisitesPresent(specializations)
return true
end

registerEventListeners

Description

Definition

registerEventListeners()

Arguments

anyhandToolType

Code

function HandToolHands.registerEventListeners(handToolType)
SpecializationUtil.registerEventListener(handToolType, "onLoad" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onDelete" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onUpdate" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onDraw" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onWriteUpdateStream" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onReadUpdateStream" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onRegisterActionEvents" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onCarryingPlayerChanged" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onHeldStart" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onHeldEnd" , HandToolHands )
SpecializationUtil.registerEventListener(handToolType, "onDebugDraw" , HandToolHands )
end

registerFunctions

Description

Definition

registerFunctions()

Arguments

anyhandToolType

Code

function HandToolHands.registerFunctions(handToolType)
SpecializationUtil.registerFunction(handToolType, "updateKinematicNode" , HandToolHands.updateKinematicNode)

SpecializationUtil.registerFunction(handToolType, "getKinematicNode" , HandToolHands.getKinematicNode)
SpecializationUtil.registerFunction(handToolType, "getIsHoldingItem" , HandToolHands.getIsHoldingItem)
SpecializationUtil.registerFunction(handToolType, "getIsThrowingItem" , HandToolHands.getIsThrowingItem)
SpecializationUtil.registerFunction(handToolType, "getIsAwaitingJointCreation" , HandToolHands.getIsAwaitingJointCreation)
SpecializationUtil.registerFunction(handToolType, "getHasJoint" , HandToolHands.getHasJoint)
SpecializationUtil.registerFunction(handToolType, "getHeldItem" , HandToolHands.getHeldItem)
SpecializationUtil.registerFunction(handToolType, "getCanPickUpNode" , HandToolHands.getCanPickUpNode)

SpecializationUtil.registerFunction(handToolType, "orientRotationNodeToItem" , HandToolHands.orientRotationNodeToItem)
SpecializationUtil.registerFunction(handToolType, "createItemJoint" , HandToolHands.createItemJoint)

SpecializationUtil.registerFunction(handToolType, "pickUpTarget" , HandToolHands.pickUpTarget)
SpecializationUtil.registerFunction(handToolType, "pickupFailed" , HandToolHands.pickupFailed)
SpecializationUtil.registerFunction(handToolType, "dropHeldItem" , HandToolHands.dropHeldItem)
SpecializationUtil.registerFunction(handToolType, "throwHeldItemWithForceScalar" , HandToolHands.throwHeldItemWithForceScalar)
SpecializationUtil.registerFunction(handToolType, "throwHeldItemWithForceVector" , HandToolHands.throwHeldItemWithForceVector)

SpecializationUtil.registerFunction(handToolType, "rotateHeldItem" , HandToolHands.rotateHeldItem)
SpecializationUtil.registerFunction(handToolType, "levelHeldItem" , HandToolHands.levelHeldItem)

SpecializationUtil.registerFunction(handToolType, "onHeldItemJointBroken" , HandToolHands.onHeldItemJointBroken)

SpecializationUtil.registerFunction(handToolType, "findFootballCallback" , HandToolHands.findFootballCallback)
SpecializationUtil.registerFunction(handToolType, "shootBall" , HandToolHands.shootBall)

SpecializationUtil.registerFunction(handToolType, "consoleCommandToggleSuperStrength" , HandToolHands.consoleCommandToggleSuperStrength)
end

registerOverwrittenFunctions

Description

Definition

registerOverwrittenFunctions()

Arguments

anyvehicleType

Code

function HandToolHands.registerOverwrittenFunctions(vehicleType)
SpecializationUtil.registerOverwrittenFunction(vehicleType, "getCanBeDropped" , HandToolHands.getCanBeDropped)
end

registerXMLPaths

Description

Definition

registerXMLPaths()

Arguments

anyxmlSchema

Code

function HandToolHands.registerXMLPaths(xmlSchema)
xmlSchema:setXMLSpecializationType( "HandToolHands" )
xmlSchema:setXMLSpecializationType()
end

rotateHeldItem

Description

Rotates the rotation node locally about the given direction axis.

Definition

rotateHeldItem(float inputDelta, float directionX, float directionY, float directionZ)

Arguments

floatinputDeltaThe rotation about the axis to make, in radians.
floatdirectionXThe x direction of the axis.
floatdirectionYThe y direction of the axis.
floatdirectionZThe z direction of the axis.

Code

function HandToolHands:rotateHeldItem(inputDelta, directionX, directionY, directionZ)

-- Calculate the rotation to make.
local rotation = ( math.pi * 0.5 ) * (g_physicsDt * 0.001 ) * inputDelta

-- Rotate the rotation node about the given local axis.
local spec = self.spec_hands
local _, rotationNode = self:getKinematicNode()
rotateAboutLocalAxis(rotationNode, rotation, directionX, directionY, directionZ)

-- Set the joint frame for the joint, so that it updates.
if self.isServer and spec.heldItemJointId ~ = nil then
setJointFrame(spec.heldItemJointId, 0 , rotationNode)
end
end

shootBall

Description

Definition

shootBall()

Arguments

anyfootball
anydirX
anydirZ
anyangleDeg
anyvelocity
anyshotType

Code

function HandToolHands:shootBall(football, dirX, dirZ, angleDeg, velocity, shotType)
local spec = self.spec_hands
spec.lastShootTime = g_ time
spec.canShoot = false
football:shoot(dirX, dirZ, angleDeg, velocity, shotType)
end

throwHeldItemWithForceScalar

Description

Throws the currently held item with the given force scalar (from 0 to 1, where 1 is a fully powered throw) in the direction of the player's camera.

Definition

throwHeldItemWithForceScalar(float throwForceScalar, boolean noEventSend)

Arguments

floatthrowForceScalarThe throw power from 0 to 1, where 1 is a fully powered throw.
booleannoEventSendIf this is true, no network event will be sent.

Code

function HandToolHands:throwHeldItemWithForceScalar(throwForceScalar, noEventSend)

-- Do nothing if no item is being held.
if not self:getIsHoldingItem() then
return
end

local player = self:getCarryingPlayer()
if player = = nil or player.targeter = = nil then
Logging.error( "HandToolHands:throwHeldItemWithForceScalar can only be used on the owning player, as it uses their camera direction!" )
return
end

local _, _, _, cameraDirectionX, cameraDirectionY, cameraDirectionZ = player.targeter:getLastLookRay()
self:throwHeldItemWithForceVector(cameraDirectionX, cameraDirectionY, cameraDirectionZ, throwForceScalar, noEventSend)
end

updateKinematicNode

Description

Updates the position of the kinematic node so that it is always in front of the player.

Definition

updateKinematicNode()

Code

function HandToolHands:updateKinematicNode()

-- If there is no kinematic node yet, do nothing.
local spec = self.spec_hands
if spec.kinematicNode = = nil then
return
end

-- If the hands are not active, do nothing.
if not self:getIsHeld() then
return
end

-- If the player is not the owner, do nothing.
if not self:getCarryingPlayer().isOwner then
return
end

-- Get the player's last targeter ray.If it is nil, do nothing.
local x, y, z, dx, dy, dz = self:getCarryingPlayer().targeter:getLastLookRay()
if x = = nil then
return
end

self:raiseDirtyFlags(spec.dirtyFlag)

-- Calculate the position of the kinematic helper along the ray.
x, y, z = x + (dx * spec.currentHoldDistance), y + (dy * spec.currentHoldDistance), z + (dz * spec.currentHoldDistance)

-- Set the position and rotation of the kinematic node.Keep the kinematic node level to the world.
setWorldTranslation(spec.kinematicNode, x, y, z)
dx, dz = MathUtil.vector2Normalize(dx, dz)
setWorldDirection(spec.kinematicNode, dx, 0 , dz, 0 , 1 , 0 )
end