g_ezplatform_sequence = {["plat1"] = {"y300;x-100","t90","x100","y-300;t90","z100;t90", "z-100", "t90"}, ["plat2"] = {"x200","p5","y200","p5","x200;t75","p5","x-400","p5","y-200;t-75","p5"}, ["plat3"] = {"v20","x-300;y300;t90;z200","v20;x300;y-300;t-90;z-200"}, ["plat4"] = {"Z400", "P1", "Z-400", "P1"}, ["plat5"] = {"y100;t45", "y200", "p5", "y-300;t75"}, ["plat6"] = {"x200","p1","z200","p1","x-200","p1","z-200","p1"}, ["plat7"] = {"v8";"m150;t60"}, ["escalator1"] = {"y20","m180;y20","m100;y20","m100;y20","m100;y20","m100;y20","m180","r"}, ["lift1"] = {"p2","y130","p2","y-130"} } g_ezplatform_dimensions = -- circular platforms only need radius and height {["plat1"] = {radius = 142, length = 200, width = 200, height = 20}, ["plat2"] = {radius = 142, length = 200, width = 200, height = 20}, ["plat3"] = {radius = 200, height = 20}, ["plat4"] = {radius = 300, height = 20}, ["plat5"] = {radius = 142, length = 200, width = 200, height = 20}, ["plat6"] = {radius = 142, length = 200, width = 200, height = 20}, ["plat7"] = {radius = 142, length = 200, width = 200, height = 20}, ["escalator1"] = {radius = 142, length = 200, width = 200, height = 20}, ["lift1"] = {radius = 142, length = 200, width = 200, height = 20} } g_player_on_platform = nil g_ezplatform_list = {} g_ezplatform_name = {} g_movement_keys_pressed = false g_jump_pressed_only = false g_accumulatedY = 0 g_Player_Angle = 0 -- used for 3rd person view g_player_id = 37 -- for 3rd person !! need to set manually !! g_default_spd = 5 -- seconds per sequence function ezplatform_init_name(e, name) g_ezplatform_name[e] = {name = string.match(name, "(%w+)_"), seqn = string.match(name, "_(%d+)")} if g_ezplatform_name[e].name == nil then g_ezplatform_name[e] = {name = name, seqn = 1} end end function ezplatform_exit (e) g_ezplatform_list[e] = nil g_ezplatform_name[e] = nil end function RotatePoint2D (x, y, Ang) -- Ang in radians local Sa, Ca = math.sin(Ang), math.cos(Ang) return x*Ca - y*Sa, x*Sa + y*Ca end -- this function checks if given x,z coordinates are overlapping a particular -- box taking into account rotation function is_overlapping(x1, z1, x2, z2, Ang, L, W, H) local HL, HW = L / 2, W / 2 local DX, DZ = x1 - x2, z1 - z2 local Pang = math.rad(Ang) local NDX, NDZ = RotatePoint2D (DX, DZ, Pang) return (NDZ > -HL and NDZ < HL and NDX > -HW and NDX < HW), NDX, NDZ; end function ezplatform_main(e) -- don't do anything until init has been called if g_ezplatform_name[e] == nil then return end local name = g_ezplatform_name[e].name -- PromptLocal(e, name .. ", " .. g_ezplatform_name[e].seqn) -- ... or if name is not in dimensions list if g_ezplatform_dimensions[name] == nil then return end local params = g_ezplatform_dimensions[name] local Ent = g_Entity[e] -- initialise list entry if g_ezplatform_list[e] == nil then g_ezplatform_list[e] = {init_x = Ent.x, init_y = Ent.y, init_z = Ent.z, init_rot = Ent.angley, speed = g_default_spd, curr_x = Ent.x, curr_y = Ent.y, curr_z = Ent.z, rot = Ent.angley, sequence = (0 + g_ezplatform_name[e].seqn), seq_in_progress = false, sequence_time = 0, TargX = nil, TargY = nil, TargZ = nil, TargAng = nil, Dist_Left = nil}; return end -- if we have got here the platform is initialised and we are good to go local Plat = g_ezplatform_list[e] if (g_KeyPressW + g_KeyPressS + g_KeyPressA + g_KeyPressD + g_KeyPressSPACE) ~= 0 then g_movement_keys_pressed = true g_jump_pressed_only = g_KeyPressSPACE ~= 0 and (g_KeyPressW + g_KeyPressS + g_KeyPressA + g_KeyPressD) == 0 end local do_this_frame = scheduler(e) if do_this_frame and (g_player_on_platform == nil or g_player_on_platform.entity == e) then -- TODO: -- check if player is on platform: -- use either radius for circular(ish) platforms or length/width for calcs -- Time for a bandaid fix, due to physics now being before Lua there is a potential for the -- physics engine to have moved the player relative to the platform since the last check we -- made. This results in the player drifting around in a strange way on the platform. -- To try and improve matters we keep track of whether the movement keys have been pressed -- and only update player position if they have if (g_movement_keys_pressed and not g_jump_pressed_only) or g_player_on_platform == nil then local Px, Pz, Py = g_PlayerPosX, g_PlayerPosZ, g_PlayerPosY local player_on_platform = false local xo, zo = 0, 0 if CloserThan (Plat.curr_x, Plat.curr_z, Px, Pz, params.radius) then -- coarse check for squares if Py > (Plat.curr_y + params.height) then -- are we potentially on platform? if params.length ~= nil and params.width ~= nil then player_on_platform, xo, zo = is_overlapping (Px, Pz, Plat.curr_x, Plat.curr_z, Plat.rot, params.length, params.width, params.height); if player_on_platform then g_player_on_platform = {entity = e, Xoffset = xo, Yoffset = Py - Plat.curr_y, Zoffset = zo}; g_Player_Angle = GetEntityAngleY(g_player_id) end -- else -- todo: calculate offset for circular case and include rotation end end end if not player_on_platform and g_player_on_platform ~= nil then if g_player_on_platform.entity == e then g_player_on_platform = nil end end end end -- check if platform needs to be moved (i.e. seq_in_progress is true) -- if so move platform (plus player if on platform, plus any pickuppable items stacked on platform) -- if not parse next sequence: -- if "Halt" set sequence index and set seq_in_progress to false -- if "Reset" set curr_x/y/z/rot to init values and set sequence index to 1, set target values to 0} -- if "Move" work out direction and step then move x,z to new pos, calc move left -- if x,y,z calc targets local current_seq = g_ezplatform_sequence[name][Plat.sequence] -- if we've reached the end of the sequence start back at the start if current_seq == nil then Plat.sequence = 1 current_seq = g_ezplatform_sequence[name][Plat.sequence] end function parse_sequence (m, a) if m == nil then return end local Mode = string.lower(m) if Mode == 'm' then if a ~= nil then Plat.Dist_Left = a Plat.seq_in_progress = true Plat.sequence_time = g_Time + (Plat.speed * 1000) end elseif Mode == 'v' then Plat.speed = math.abs(a or g_default_spd) Plat.seq_in_progress = true elseif Mode == 'x' then if a ~= nil then Plat.TargX = Plat.curr_x + a Plat.seq_in_progress = true Plat.sequence_time = g_Time + (Plat.speed * 1000) end elseif Mode == 'y' then if a ~= nil then Plat.TargY = Plat.curr_y + a Plat.seq_in_progress = true Plat.sequence_time = g_Time + (Plat.speed * 1000) end elseif Mode == 'z' then if a ~= nil then Plat.TargZ = Plat.curr_z + a Plat.seq_in_progress = true Plat.sequence_time = g_Time + (Plat.speed * 1000) end elseif Mode == 't' then if a ~= nil then Plat.TargAng = Plat.rot + a Plat.seq_in_progress = true Plat.sequence_time = g_Time + (Plat.speed * 1000) end -- Pause elseif Mode == 'p' then if a ~= nil then Plat.seq_in_progress = true Plat.sequence_time = g_Time + (math.abs(a) * 1000) end end end if not Plat.seq_in_progress and string.lower(string.sub(current_seq,1,1)) ~= 'h' then -- Make sure platform angle in 'normal' range while Plat.rot < -180 or Plat.rot > 180 do if Plat.rot < -180 then Plat.rot = Plat.rot + 360 elseif Plat.rot > 180 then Plat.rot = Plat.rot - 360 end end Plat.TargAng = nil if string.lower(string.sub(current_seq,1,1)) == 'r' then -- Get initial x,y,z from first platform in sequence for k, _ in pairs(g_ezplatform_name) do if g_ezplatform_name[k].name == name and g_ezplatform_name[k].seqn == '1' then Plat.curr_x = g_ezplatform_list[k].init_x Plat.curr_y = g_ezplatform_list[k].init_y Plat.curr_z = g_ezplatform_list[k].init_z Plat.sequence = 1 break end end else local m1, a1 = string.match(current_seq, "(%a)([+-]?%d+)(;?)") local m2, a2 = string.match(current_seq, ";(%a)([+-]?%d+)") local m3, a3 = string.match(current_seq, ";%a[+-]?%d+;(%a)([+-]?%d+)") local m4, a4 = string.match(current_seq, ";%a[+-]?%d+;%a[+-]?%d+;(%a)([+-]?%d+)") local m5, a5 = string.match(current_seq, ";%a[+-]?%d+;%a[+-]?%d+;%a[+-]?%d+;(%a)([+-]?%d+)") parse_sequence (m1, a1) parse_sequence (m2, a2) parse_sequence (m3, a3) parse_sequence (m4, a4) parse_sequence (m5, a5) end end local yrot = 0 if Plat.seq_in_progress then local num_steps = g_scheduler[e].frames_per_second * (Plat.sequence_time - g_Time) / 1000 if Plat.Dist_Left ~= nil then local distthisframe = 0 if num_steps < 1 then distthisframe = Plat.Dist_Left else distthisframe = Plat.Dist_Left / num_steps end local ang = math.rad(Plat.rot) Plat.curr_x = Plat.curr_x + math.sin(ang) * distthisframe Plat.curr_z = Plat.curr_z + math.cos(ang) * distthisframe Plat.Dist_Left = Plat.Dist_Left - distthisframe -- make sure 'move' command cancels any x,z movement 'just in case' Plat.TargX = nil Plat.TargZ = nil end if Plat.TargX ~= nil then if num_steps < 1 then Plat.curr_x = Plat.TargX Plat.TargX = nil if Plat.seq_in_progress then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end else Plat.curr_x = Plat.curr_x + ((Plat.TargX - Plat.curr_x) / num_steps) end end if Plat.TargY ~= nil then if num_steps < 1 then Plat.curr_y = Plat.TargY Plat.TargY = nil if Plat.seq_in_progress then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end else Plat.curr_y = Plat.curr_y + ((Plat.TargY - Plat.curr_y) / num_steps) end end if Plat.TargZ ~= nil then if num_steps < 1 then Plat.curr_z = Plat.TargZ Plat.TargZ = nil if Plat.seq_in_progress then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end else Plat.curr_z = Plat.curr_z + ((Plat.TargZ - Plat.curr_z) / num_steps) end end if Plat.TargAng ~= nil then if num_steps < 1 then Plat.rot = Plat.TargAng Plat.TargAng = nil if Plat.seq_in_progress then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end else yrot = ((Plat.TargAng - Plat.rot) / num_steps) Plat.rot = Plat.rot + yrot -- Make sure platform angle in 'normal' range if Plat.rot < -180 then Plat.rot = Plat.rot + 360 Plat.TargAng = Plat.TargAng + 360 elseif Plat.rot > 180 then Plat.rot = Plat.rot - 360 Plat.TargAng = Plat.TargAng - 360 end end end if g_Time > Plat.sequence_time then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end end if g_player_on_platform ~= nil then if g_player_on_platform.entity == e and not g_movement_keys_pressed then local Pang = math.rad(Plat.rot) local NDX, NDZ = RotatePoint2D (g_player_on_platform.Xoffset, g_player_on_platform.Zoffset, -Pang) SetFreezePosition ((Plat.curr_x + NDX), (Plat.curr_y + g_player_on_platform.Yoffset), (Plat.curr_z + NDZ)) -- Prompt(math.modf(g_player_on_platform.Xoffset) .. ", " .. -- math.modf(g_player_on_platform.Zoffset) .. ", " .. -- math.modf(NDX) .. ", " .. math.modf(NDZ) .. ", " .. -- math.modf(g_PlayerPosX) .. ", " .. -- math.modf(g_PlayerPosZ) .. ", " .. -- math.modf(Plat.curr_x) .. ", " .. -- math.modf(Plat.curr_z) .. ", " .. -- Plat.rot -- ) if g_PlayerThirdPerson == 0 then g_Player_Angle = g_Player_Angle + yrot SetFreezeAngle(g_PlayerAngX, (g_Player_Angle), 0) TransportToFreezePosition() else if yrot ~= 0 then g_Player_Angle = g_Player_Angle + yrot ResetRotation(g_player_id, 0, g_Player_Angle, 0) end TransportToFreezePositionOnly() end end else g_accumulatedY = 0 end if do_this_frame and (g_player_on_platform == nil or g_player_on_platform.entity == e) then g_movement_keys_pressed = false end CollisionOff(e) ResetPosition(e, Plat.curr_x, Plat.curr_y, Plat.curr_z) ResetRotation(e, 0, Plat.rot, 0) CollisionOn(e) end function CloserThan(Ex, Ez, Px, Pz, dist) dist = dist or 100 local DX, DZ = Ex - Px, Ez - Pz return (DX*DX + DZ*DZ) <= (dist * dist) end ---------------------------------------------------------------- -- Scheduler, this nifty little function keeps track of time. -- -- The main routine should be called every frame and returns -- -- a flag indicating whether the passed in time has expired -- -- since the last time it was called. -- -- If no time period is specified 100ms is used by default, -- -- i.e. 1/10th of a second. -- -- The caller can either ignore the return flag or use it to -- -- trigger time sensitive functionality. -- -- A global value giving the frames per second count is also -- -- generated, this can be used in script to make animations -- -- independent of frames. -- -- The reason this function is tied to an entity rather than -- -- being global is so that each entity can be triggered at a -- -- different time rather than all being triggered in the same -- -- frame. ---------------------------------------------------------------- g_scheduler = {} function scheduler(e, period) period = period or 100 -- defaults to tenths of a second (100ms) if g_Time == nil then return false end if g_scheduler[e] == nil then g_scheduler[e] = {frames_per_second = 60, period_accumulated = math.random(0, period), accumulated_time = 0, timer_value_last_frame = g_Time, frame_counter = 0}; -- 'test' mode sometimes doesn't clear Lua globals so if -- this appears to be the case initialise everything elseif g_scheduler[e].accumulated_time > 2000 then g_scheduler[e].frames_per_second = 60 g_scheduler[e].period_accumulated = math.random(0, period) g_scheduler[e].accumulated_time = 0 g_scheduler[e].timer_value_last_frame = g_Time g_scheduler[e].frame_counter = 0 end local entry = g_scheduler[e] local do_this_frame_flag = false entry.frame_counter = entry.frame_counter + 1 local time_since_last_frame = g_Time - entry.timer_value_last_frame if (entry.period_accumulated + time_since_last_frame) > period then entry.period_accumulated = (entry.period_accumulated + time_since_last_frame) - period; do_this_frame_flag = true else entry.period_accumulated = entry.period_accumulated + time_since_last_frame end if (entry.accumulated_time + time_since_last_frame) > 1000 then -- more than a second passed? entry.accumulated_time = (entry.accumulated_time + time_since_last_frame) - 1000; entry.frames_per_second = entry.frame_counter entry.frame_counter = 0 else entry.accumulated_time = entry.accumulated_time + time_since_last_frame end entry.timer_value_last_frame = entry.timer_value_last_frame + time_since_last_frame return do_this_frame_flag end