I made a grappling hook script that works on single player without issues. However in multiplayer, one person can use it fine, then the other person can use it, and then their "trajectories" get all messed up, and then it just stops working. Like one person will use the grappling hook and then get sent to where the other person launched it. I am looking for some ideas on things to try.
One thing I have tried was to add each function handle to a global table keeping track of all handlers and states per player index. That did not make a difference and it made the code quite uglier than it needed to be.
Here is the code. Bear with me, it is a bit long. You probably want to copy it into a text editor or IDE.
Code: Select all
-- local function to safely destroy the timer and release the timer elapse function
local safeEndTimerFunc = function(timer, timerHandle, timerElapseFuncHandle)
if timer ~= nil then
DestroyTimer(timer)
--else
-- print("timer was nil")
end
timerHandle = nil
if timerElapseFuncHandle ~= nil then
ReleaseTimerElapse(timerElapseFuncHandle)
--else
-- print("timer elapse handle was nil")
end
timerElapseFuncHandle = nil
end
-- This is a local (only visible in this file) function used in the function "enableGrapplingHook"
-- moves a character to an object with a speed in meters per second.
-- odfName is the ODF name of the deployed autoturret
local moveUnitToObjectAlongTrajectory = function(character, object, speed, trajectoryCoordTable)
local unit = GetCharacterUnit(character)
--check if unit is alive (will be nil if dead)
if unit then
--print("unit is alive, starting move")
-- get start and end coordinates
local xStart, yStart, zStart = GetWorldPosition(unit)
--local xEnd, yEnd, zEnd = GetWorldPosition(object)
-- variable to track the coordinate index
local coordIndex = 1
local xEnd = trajectoryCoordTable[coordIndex].x
local yEnd = trajectoryCoordTable[coordIndex].y
local zEnd = trajectoryCoordTable[coordIndex].z
-- position deltas - create the vector
local dX = xEnd - xStart
local dY = yEnd - yStart
local dZ = zEnd - zStart
local distance = math.sqrt(dX * dX + dY * dY + dZ * dZ)
local objLocation = GetEntityMatrix(object)
-- move object a little bit temporarily so we can calc some rotations
SetEntityMatrix(object, CreateMatrix(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, objLocation))
local xTemp, yTemp, zTemp = GetWorldPosition(object)
-- move it back to original position
SetEntityMatrix(object, objLocation)
--calculate angle (y-axis rotation)
local adjacent = xTemp - xEnd
local opposite = zTemp - zEnd
local hypotenuse = math.sqrt(((xTemp - xEnd) * (xTemp - xEnd)) + ((zTemp - zEnd) * (zTemp - zEnd)))
local phi = math.acos(adjacent / hypotenuse)
--correct the angle
-- this was a pain in the Diet Dr. Pepper
if opposite < 0 then
phi = phi + (math.pi) / 2
else
phi = -(phi - (math.pi) / 2)
end
local count = 0
--every time interval move this distance. These two variables control speed
-- units in meters per second
local incrementDistance = speed / 100
local timeInterval = 1/100 -- one hundredth of a second or 100 fps. Change this to control how "smooth" the unit moves
local moveUnitTimer = CreateTimer("moveUnitTimer")
SetTimerValue(moveUnitTimer, timeInterval) -- in seconds
-- variable to track the coordinate index
local coordIndex = 1
local moveUnitTimerTracker = nil
moveUnitTimerTracker = OnTimerElapse(function(timer)
count = count + incrementDistance -- move 1 meter per time interval unit we hit distance
--print("timer count is " .. tostring(count))
---- Tried to do this, but it crashed. Maybe you can figure it out
----draw the cable. DrawBeamBetween is the C function used in galactic conquest
--DrawBeamBetween(
-- GetEntityMatrix(unit),
-- objLocation,
-- "troop_icon", 1.0, 255, 255, 255, 255, 0, 1
--)
local factor = count / distance
--check if unit is alive
if GetCharacterUnit(character) then
--print("unit is alive, moving")
-- move character once every timer interval
-- stop a little above the destination to avoid impact damage
-- by making the last argument "nil" you can use absolute position/rotation
SetEntityMatrix(unit, CreateMatrix(phi, 0.0, 1.0, 0.0, xStart + ((dX) * factor), yStart + ((dY + 0.5) * factor), zStart + ((dZ) * factor), nil))
else
--print(" unit is dead. stopping ")
count = 0
coordIndex = 1
safeEndTimerFunc(timer, moveUnitTimer, moveUnitTimerTracker)
end
if count < distance then
--print("continuing")
--StopTimer(timer)
SetTimerValue(timer, timeInterval)
StartTimer(timer)
else
coordIndex = coordIndex + 1
-- if there are more grapple coords
if coordIndex < table.getn(trajectoryCoordTable) then
--print("moving to next coord")
count = 0
xStart = trajectoryCoordTable[coordIndex-1].x
yStart = trajectoryCoordTable[coordIndex-1].y
zStart = trajectoryCoordTable[coordIndex-1].z
-- recalculate the deltas
dX = trajectoryCoordTable[coordIndex].x - xStart
dY = trajectoryCoordTable[coordIndex].y - yStart
dZ = trajectoryCoordTable[coordIndex].z - zStart
distance = math.sqrt(dX * dX + dY * dY + dZ * dZ)
--start timer again
SetTimerValue(timer, timeInterval)
StartTimer(timer)
-- if we've reached the last grapple coord
else
count = 0
coordIndex = 1
safeEndTimerFunc(timer, moveUnitTimer, moveUnitTimerTracker)
end
end
end
, moveUnitTimer)
--print("starting move timer")
StartTimer(moveUnitTimer)
else
--print("unit is dead, not moving")
end
--print("moving unit")
end
-- example: "rep_bldg_inf_autoturret", 100
-- odfName, a string which is the name of the deployed autoturret ODF
-- speed, a number, the speed to travel the grappling hook in meters per second
enableGrapplingHookFollowTrajectory = function(odfName, speed)
-- grappling hook code
grappleLaunchArc = OnCharacterDispenseControllable(
(function(player, controllable)
--print("dispensed entity")
if GetEntityClass(controllable) == FindEntityClass(odfName) then
--print("dispensed autoturret")
-- this timer will track the grapple's position over time.
-- If it detects that the grapple is stopped, it will pull the unit towards it.
-- If the grapple dies or never stops, the timer stops and nothing happens.
local hookIsAlive = true
local grappleCoordTable = {}
local loopCount = 0
local grappleTimeInterval = 1/60 --record the path of the grappling hook at 60 fps so it looks nice! Can lower if desired
local moveGrappleTimer = CreateTimer("moveGrappleTimer")
SetTimerValue(moveGrappleTimer, grappleTimeInterval) -- in seconds
local moveGrappleTimerTracker = nil
moveGrappleTimerTracker = OnTimerElapse(function(timer)
loopCount = loopCount + 1
--print("timer count is " .. tostring(loopCount))
if hookIsAlive == true then
--print("hook still alive, continuing")
local xGrap, yGrap, zGrap = GetWorldPosition(controllable)
table.insert(grappleCoordTable, { x = xGrap, y = yGrap, z = zGrap })
-- check if there is more than 1 entry in the table and if the last and second to last are equal
-- probably should clean out all but the last 2 of the table....
if table.getn(grappleCoordTable) > 1
and grappleCoordTable[table.getn(grappleCoordTable)].x == grappleCoordTable[table.getn(grappleCoordTable) - 1].x
and grappleCoordTable[table.getn(grappleCoordTable)].y == grappleCoordTable[table.getn(grappleCoordTable) - 1].y
and grappleCoordTable[table.getn(grappleCoordTable)].z == grappleCoordTable[table.getn(grappleCoordTable) - 1].z
then
--print("grapple is stationary")
-- call the function that does the actual moving of our unit
moveUnitToObjectAlongTrajectory(player, controllable, speed, grappleCoordTable)
--exit the timer, clean up
loopCount = 0
grappleCoordTable = {}
hookIsAlive = true
--StopTimer(timer) -- timer should already be stopped if we are in this function?
safeEndTimerFunc(timer, moveGrappleTimer,moveGrappleTimerTracker)
--print("exiting")
--exit the callback function
return
end
--StopTimer(moveUnitTimer)
SetTimerValue(timer, grappleTimeInterval)
StartTimer(timer)
elseif hookIsAlive == false then
--print("hook is dead, stopping")
-- clean up
loopCount = 0
grappleCoordTable = {}
hookIsAlive = true
safeEndTimerFunc(timer, moveGrappleTimer,moveGrappleTimerTracker)
else
--print("Grapple code: something else happened!")
end
end
, moveGrappleTimer)
-- this event listener simply sets alive to false.
-- I tried to use IsObjectAlive but that crashed when it was dead. Lol
-- Could potentially run into some synchronization issues, but that hasn't happened during testing
-- If there are glitches with the weapon then consider using the "LifeTime" value in the autoturret odf ...
-- ... instead of this event. make the timer only go until a the LifeTime of the autoturret
local hookArcDeath = nil
hookArcDeath = OnObjectKill(function(object, killer)
--print("object died")
if object == controllable then
--print(" hook died")
hookIsAlive = false
--print("alive is " .. tostring(alive))
if hookArcDeath ~= nil then
ReleaseObjectKill(hookArcDeath)
end
hookArcDeath = nil
end
end)
--print("starting grapple timer")
-- kick off the timer to track the grapple's position over time
StartTimer(moveGrappleTimer)
end
end
))
end
https://www.moddb.com/games/star-wars-b ... pling-hook
Thanks