g_ezplatform_sequence = g_ezplatform_sequence or {["Moonlander"]= {"p5","v20;z1500;x-3000;y-200","h"}, ["Trigger1"] = {"h","v1;y-2","h","y+2"}, ["Platform1"]= {"h","p2","v10;x800","p2","x-800","p2"}, ["Platform2"]= {"h","v5;m200","m200;t75;y-50","m200;t-75;y-50","t-90", "v2;m50","m70","m100","m150","m200","m300", "m400", "v4;m800", "v2;m300", "m200", "m150", "m100", "m50", "v5;t-90;m400", "m400","t-90;m400", "m400", "m935", "m900", "m630;t-90;y100","h","r"}, ["Wall1"] = {"h","v11;y180","h", "y-180"}, -- Seq 1 2 3 4 5 6 7 8 9 10 11 12 ["Lift1"] = {"h","v6;y120","h","y125","h","y125","h","y-125","h","y-125","h","y-120",}, ["LiftTrigger1"] = {"h"}, ["LiftTrigger2"] = {"h"}, ["LiftTrigger3"] = {"h"}, ["LiftTrigger4"] = {"h"}, -- ["plat1"] = {} -- new platforms ["platA"] = {"t-20;m200","t-20;m200","t-20;m200","t-20;m200","t-20;m200","t-20;m200","t-20;m200",}, ["platB"] = {"t45;m100"}, -- old stuff ["middle"]= {"t-90"}, ["middle2"]= {"t90"}, ["inner"] = {"m-314;t-90","m-314;t-90","m-314;t-90","m-314;t-90","r"}, ["inner2"]= {"m76;t90","m76;t90","m76;t90","m76;t90","r"}, ["outer"] = {"m-628;t-90","m-628;t-90","m-628;t-90","m-628;t-90","r"}, ["outer2"]= {"m152;t90","m152;t90","m152;t90","m152;t90","r"}, ["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"}, ["Rug1"] = {"p3","y100","z1000","x2000","z-1000","x-1000","z-3000","x2000","z-1000","x3000","h"} } g_ezplatform_dimensions = g_ezplatform_dimensions or -- circular platforms only need radius and height {["Moonlander"]={radius = 0}, -- denotes no 'player-on-platform' detection ["Trigger1"] = {radius = 100, length = 60, width = 50, height = 10}, ["Platform1"]= {radius = 142, length = 200, width = 200, height = 20}, ["Platform2"]= {radius = 142, length = 200, width = 200, height = 20}, ["Wall1"] = {radius = 0}, -- denotes no 'player-on-platform' detection ["Lift1"] = {radius = 142, length = 200, width = 200, height = 10}, ["LiftTrigger1"] = {radius = 100, length = 60, width = 50, height = 10}, ["LiftTrigger2"] = {radius = 100, length = 60, width = 50, height = 10}, ["LiftTrigger3"] = {radius = 100, length = 60, width = 50, height = 10}, ["LiftTrigger4"] = {radius = 100, length = 60, width = 50, height = 10}, -- new platforms ["platA"] = {radius = 142, length = 100, width = 100, height = 20}, ["platB"] = {radius = 142, length = 100, width = 100, height = 20}, -- old stuff ["middle"]= {radius = 142, length = 200, width = 200, height = 20}, ["inner"] = {radius = 142, length = 200, width = 200, height = 20}, ["outer"] = {radius = 142, length = 200, width = 200, height = 20}, ["middle2"]= {radius = 70, length = 50, width = 50, height = 50}, ["inner2"]= {radius = 70, length = 50, width = 50, height = 50}, ["outer2"]= {radius = 70, length = 50, width = 50, height = 50}, ["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}, ["Rug1"] = {radius = 142, length = 100, width = 500, height = 2} } g_player_on_platform = nil g_ezplatform_list = {} g_ezplatform_name = {} g_movement_keys_pressed = false g_jump_pressed_only = false g_Player_Angle = 0 g_default_spd = 10 -- seconds per sequence function ezplatform_init_name(e, name) g_ezplatform_name[e] = {name = string.match(name, "(%w+)_"), seqn = string.match(name, "_(%d+)"), multi = true} if g_ezplatform_name[e].name == nil then g_ezplatform_name[e] = {name = name, seqn = 1, multi = false} 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 parse_sequence (Plat, m, a) if m == nil then return end local Mode = string.sub(string.lower(m),1,1) if Mode == 'm' then if a ~= nil then if (0 + a) < 0 then Plat.Direction = 'B' else Plat.Direction = 'F' end Plat.Dist_Left = math.abs(a) Plat.seq_in_progress = true Plat.sequence_time = g_Time + Plat.speed end elseif Mode == 's' then if a ~= nil then if 0 + a < 0 then Plat.Direction = 'L' else Plat.Direction = 'R' end Plat.Dist_Left = math.abs(a) Plat.seq_in_progress = true Plat.sequence_time = g_Time + Plat.speed end elseif Mode == 'v' then Plat.speed = math.abs(a or g_default_spd) if Plat.speed < 500 then Plat.speed = Plat.speed * 1000 end 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 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 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 end elseif Mode == 't' then if a ~= nil then Plat.TargAng = Plat.curr_rot + a Plat.seq_in_progress = true Plat.sequence_time = g_Time + Plat.speed end -- Pause elseif Mode == 'p' then local pause_time = math.abs(a or g_default_spd) if pause_time < 500 then pause_time = pause_time * 1000 end Plat.sequence_time = g_Time + pause_time Plat.seq_in_progress = true end end ------------------------------------------------------------ function platform_status (e) -- returns Sequence (e.g. 3) local Plat = g_ezplatform_list[e] if Plat ~= nil then return Plat.sequence end return 0 end ------------------------------------------------------------ function platform_set_sequence (e, sequence) local Plat = g_ezplatform_list[e] if Plat ~= nil and not Plat.seq_in_progress then Plat.sequence = (sequence or Plat.sequence + 1) end end ------------------------------------------------------------ function get_platform_from_name(name) for k, _ in pairs(g_ezplatform_name) do if g_ezplatform_name[k].name == name then return k end end 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 -- ... 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, curr_x = Ent.x, curr_y = Ent.y, curr_z = Ent.z, curr_rot = Ent.angley, speed = g_default_spd * 1000, 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, Direction = nil}; return end -- if we have got here the platform is initialised and we are good to go local Plat = g_ezplatform_list[e] -- Detect ke presses, need arrow keys added 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 -- 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 params.radius > 0 and ((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) and Py < (Plat.curr_y + params.height * 5) 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.curr_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}; if g_PlayerThirdPerson == 1 then g_Player_Angle = GetEntityAngleY(GetGamePlayerControlThirdpersonCharactere()) else g_Player_Angle = Py end 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 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 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.curr_rot < -180 or Plat.curr_rot > 180 do if Plat.curr_rot < -180 then Plat.curr_rot = Plat.curr_rot + 360 elseif Plat.curr_rot > 180 then Plat.curr_rot = Plat.curr_rot - 360 end end Plat.TargAng = nil if string.lower(string.sub(current_seq,1,1)) == 'r' then if g_ezplatform_name[e].multi 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.curr_rot = g_ezplatform_list[k].init_rot break end end else Plat.curr_x = Plat.init_x Plat.curr_y = Plat.init_y Plat.curr_z = Plat.init_z Plat.curr_rot = Plat.init_rot end Plat.sequence = 1 if g_player_on_platform ~= nil and g_player_on_platform.entity == e then g_player_on_platform = nil 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 (Plat, m1, a1) parse_sequence (Plat, m2, a2) parse_sequence (Plat, m3, a3) parse_sequence (Plat, m4, a4) parse_sequence (Plat, m5, a5) end elseif Plat.seq_in_progress and string.lower(string.sub(current_seq,1,1)) == 'h' then Plat.seq_in_progress = false end local yrot = 0 if Plat.seq_in_progress then local time_left = Plat.sequence_time - g_Time local num_steps = g_scheduler[e].frames_per_second * time_left / 1000 if Plat.Dist_Left ~= nil then local ang = 0 if Plat.Direction == 'F' then ang = math.rad(Plat.curr_rot) elseif Plat.Direction == 'R' then ang = math.rad(Plat.curr_rot + 90) elseif Plat.Direction == 'L' then ang = math.rad(Plat.curr_rot - 90) else ang = math.rad(Plat.curr_rot - 180) end local distthisframe = 0 if num_steps < 1 then distthisframe = Plat.Dist_Left else distthisframe = Plat.Dist_Left / num_steps end 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 if Plat.Dist_Left == 0 then Plat.Dist_Left = nil Plat.Direction = nil if Plat.seq_in_progress then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end end 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.curr_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.curr_rot) / num_steps) Plat.curr_rot = Plat.curr_rot + yrot -- Make sure platform angle in 'normal' range Plat.curr_rot, Plat.TargAng = canonical_angle(Plat.curr_rot, Plat.TargAng) end end if g_Time > Plat.sequence_time then Plat.seq_in_progress = false Plat.sequence = Plat.sequence + 1 end end -- PromptLocal(e, Plat.sequence .. ", " .. current_seq) 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.curr_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)) if g_PlayerThirdPerson == 0 then if yrot ~= 0 then if math.abs(g_PlayerAngY - g_Player_Angle) > 1 then g_Player_Angle = g_PlayerAngY else g_Player_Angle = g_Player_Angle + yrot -- Make sure angle in 'normal' range g_Player_Angle = canonical_angle(g_Player_Angle) SetFreezeAngle(g_PlayerAngX, g_Player_Angle, g_PlayerAngZ) TransportToFreezePosition() end else TransportToFreezePositionOnly() end else if yrot ~= 0 then g_Player_Angle = g_Player_Angle + yrot ResetRotation(GetGamePlayerControlThirdpersonCharactere(), 0, g_Player_Angle, 0) end TransportToFreezePositionOnly() end end 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 SendMessageI("collisionoff", e) ResetPosition(e, Plat.curr_x, Plat.curr_y, Plat.curr_z) ResetRotation(e, 0, Plat.curr_rot, 0) SendMessageI("collisionon", e) end ------------------------------------------------------------ function canonical_angle (ang1, ang2) if ang1 < -180 then if ang2 == nil then return (ang1 + 360), nil else return (ang1 + 360), (ang2 + 360) end elseif ang1 > 180 then if ang2 == nil then return (ang1 - 360), nil else return (ang1 - 360), (ang2 - 360) end end return ang1, ang2 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