--[[ --Spectator Mode Addon for SRB2 introduces an innovative feature that allows --players to view the game, as free-floating camera or by tracking a specific --object. --In 'freecamera' mode, the spectator's viewpoint is detached from any in-game --object, granting them the freedom to navigate the entire level. --The 'tracking object' mode is designed to follow a chosen entity. --The camera can be toggled between players and certain objects, --allowing for a focused perspective on the action. --]] if sirexer_spectator_mode print("\x85".."ERROR".."\x80"..": Spectator Mode is already added in the game!") return else rawset(_G, "sirexer_spectator_mode", true) end local sm_addon = { name = "Spectator Camera", version = 1, subversion = 0, author = "Sirexer", } if not addoninfo then rawset(_G, "addoninfo", {}) end table.insert(addoninfo, sm_addon) freeslot("MT_SPECCAMERA", "MT_CATCHIN4K") -- Initializes slots for camera mobjinfo[MT_SPECCAMERA] = { doomednum = -1, spawnstate = S_INVISIBLE, spawnhealth = 1000, reactiontime = 5, radius = 16*FU, height = 32*FU, flags = MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_ENEMY } -- Object config mobjinfo[MT_CATCHIN4K] = { doomednum = -1, spawnstate = S_INVISIBLE, spawnhealth = 1000, reactiontime = 5, radius = 32*FU, height = 32*FU, speed = 128*FU, flags = MF_NOGRAVITY|MF_SPECIAL|MF_SOLID } -- Object config local sm_allownoclip = CV_RegisterVar({ name = "sm_allownoclip", defaultvalue = 1, flags = CV_NETVAR|CV_SAVE, PossibleValue = {No = 0, Yes = 1}} ) -- Allow all cameras to be able to go through walls local sm_chase = nil -- Saves third-person mode -- Checks for the existence of userdata local IsValid = function(args, ...) if args != #{...} then return false end for k, v in ipairs({...}) do if not v or v.valid == false then return false end end return true end -- Searches for a player by name and by node local findplayer = function(p) if tonumber(p) == 0 or p == "00" then return server end -- Host node local node = tonumber(p) -- First checks by node if node ~= nil and node >= 0 and node < 32 then for player in players.iterate do -- List all the players if #player == node then return player -- If the node exists, return the player end end end for player in players.iterate do -- If not found by node if string.lower(p) == string.lower(player.name) return player -- Return the player with that name end if string.find(string.lower(player.name), string.lower(p)) return player -- Return the player who has a part in his name. end end return nil -- Fail: Player not found end local DAMP = FU*30/32 -- local STOP_EPS = FU/256 local function Damp(value, damp, stop_ers) value = FixedMul(value, damp) if abs(value) < stop_ers then return 0 end return value end -- launches the object like a missile local P_SpawnMissileAngle = function(source, speed, h_angle, v_angle, type) local x, y, z = source.x, source.y, source.z-(8*source.scale) -- Source coordinates local mo = P_SpawnMobj(x, y, z, type) -- Spawn object -- Sets the velocity of the object given the horizontal and vertical angle P_Thrust(mo, h_angle, cos(v_angle)*speed) mo.momz = sin(v_angle)*speed mo.source = source -- The source of the object from which it launched return mo -- Return the spawned object end -- Spawn Camera local SpawnCamera = function(player, object, x, y, z) local pmo = player.realmo -- Player model if not object then object = pmo end -- Object to which the camera is linked -- Set user coordinates from console, if any if x == nil then x = object.x end if y == nil then y = object.y end if z == nil then z = object.z end player.speccam = { -- Camera data mo = P_SpawnMobj(x, y, z, MT_SPECCAMERA), -- The camera object itself type = "freecamera", -- Camera mode. Freecam is not attached to anything -- Used to make the camera move away from the player smoothly movexy = TICRATE, movez = TICRATE, fov = 0, -- Camera zoom speed = 100, -- Camera movement speed in freecamera mode noclip = true, -- Allows the camera to move through obstacles gravity = false, -- Won't let the camera fly cinema = false, invert = false, follow = true, -- Allow the camera to move behind a given object blockplayer = true, -- Block a player's movement t_angle = true, -- Looks at the object or moves the camera depending on where the object is looking chase = true, -- Third-person view enabled or not hud = true, -- Enables HUD -- If the button is held down, then the value is true c1_p = false, c2_p = false, c3_p = false, w_p = false, tf_p = false } player.awayviewaiming = 0 player.speccam.mo.target = player.realmo -- Camera owner if player == consoleplayer then camera.chase = false end -- Pov if player == consoleplayer then sm_chase = camera.chase end -- Save third-person mode end -- Remove the camera if the player is no longer using it local RemoveCamera = function(player) player.viewrollangle = 0 player.realmo.angle = player.drawangle if IsValid(2, player, player.speccam.mo) == true then P_RemoveMobj(player.speccam.mo) end if player == consoleplayer then -- Turn on HUD Vanilla Spectator if hud.enabled("textspectator") == false then hud.enable("textspectator") end camera.chase = sm_chase end player.speccam = nil end -- The Thinker camera movement in freecamera mode local MoveCamera = function(player) local speccam = player.speccam -- Camera data local cmd_p = player.cmd -- Camera control local pmo, smo = player.realmo, speccam.mo -- Player model and camera -- That's what the camera's looking at. local angle, aim, roll = smo.angle, player.awayviewaiming, player.viewrollangle -- Speed and angle of camera movement local speed, moveangle, div = speccam.speed, angle, 4 -- Get sine and cosine to regulate the movements local sinaim, sinang, sinrol = sin(aim), sin(angle), sin(roll) local cosaim, cosang, cosrol = cos(aim), cos(angle), cos(roll) -- Camera moves faster when Custom 1 button is pressed if (cmd_p.buttons & BT_CUSTOM1) then div = 1 end if speccam.cinema then speed = $/16 end -- Moving the camera forward or backward if cmd_p.forwardmove != 0 if cmd_p.forwardmove < 0 -- Moving the camera to the target P_Thrust(smo, moveangle, cosaim*-speed/div) if speccam.gravity == false then smo.momz = $ + sinaim*-speed/div end else -- Moving the camera away from the target P_Thrust(smo, moveangle, cosaim *speed/div) if speccam.gravity == false then smo.momz = $ + sinaim*speed/div end end end -- Moving the camera left or right if cmd_p.sidemove != 0 and not (cmd_p.buttons & BT_FIRENORMAL) -- If the camera's gonna roll over, don't move it. if cmd_p.sidemove < 0 moveangle = $ + (speccam.invert and -ANGLE_90 or ANGLE_90) if speccam.gravity == false then smo.momz = $ + FixedMul(cosaim, sinrol)*speed/div end elseif cmd_p.sidemove > 0 moveangle = $ - (speccam.invert and -ANGLE_90 or ANGLE_90) if speccam.gravity == false then smo.momz = $ + -(FixedMul(cosaim, sinrol)*speed/div) end end -- Adjusting the camera movement if the camera is not rolled over P_Thrust(smo, moveangle, FixedMul(cosrol, cosaim)*speed/div) local abs_aim = AngleFixed(aim)/FU local abs_roll = AngleFixed(roll)/FU -- Adjusting the camera movement if the camera is rolled over if (abs_aim > 0 and abs_aim <= 90) or (abs_aim > 180 and abs_aim <= 270) P_Thrust(smo, moveangle, FixedMul(cosrol, sinaim)*speed/div) else P_Thrust(smo, moveangle, FixedMul(cosrol, sinaim)*-speed/div) end -- Adjusting the camera movement if the camera is rolled over and looking up or down if abs_roll > 180 and abs_roll < 360 P_Thrust(smo, moveangle-roll, FixedMul(sin(InvAngle(roll)), sinaim)*-speed/div) else P_Thrust(smo, moveangle-roll, FixedMul(sin(InvAngle(roll)), sinaim)*speed/div) end end -- Moves the camera up and down if P_MobjFlip(pmo) == -1 then speed = -speed end -- if the camera's gravity is altered, invert the speed. if (cmd_p.buttons & BT_JUMP) -- Move the camera up or down if it is upside down if speccam.gravity == false then smo.momz = $ + FixedMul(cosaim, cosrol)*speed/div end -- If the camera is tilted, move in xy coordinates P_Thrust(smo, moveangle, FixedMul(cosrol, sinaim)*-speed/div) -- Adjusting the camera movement if the camera is rolled over and looking up or down if roll > ANGLE_180 and roll < ANGLE_MAX P_Thrust(smo, aim-roll-ANGLE_45, FixedMul(sinrol, sinaim)*speed/div) P_Thrust(smo, moveangle-roll, FixedMul(sinrol, cosaim)*-speed/div) else P_Thrust(smo, aim-roll-ANGLE_45, FixedMul(sinrol, sinaim)*-speed/div) P_Thrust(smo, moveangle-roll, FixedMul(sinrol, cosaim)*speed/div) end end if (cmd_p.buttons & BT_SPIN) -- Move the camera down or up if it is upside down if speccam.gravity == false then smo.momz = $ + FixedMul(cosaim, cosrol)*-speed/div end -- If the camera is tilted, move in xy coordinates P_Thrust(smo, moveangle, FixedMul(cosrol, sinaim)*speed/div) -- Adjusting the camera movement if the camera is rolled over and looking up or down if roll > ANGLE_180 and roll < ANGLE_MAX P_Thrust(smo, moveangle+roll, FixedMul(sinrol, cosaim)*-speed/div) P_Thrust(smo, aim-roll-ANGLE_45, FixedMul(sinrol, sinaim)*-speed/div) else P_Thrust(smo, moveangle+roll, FixedMul(sinrol, cosaim)*speed/div) P_Thrust(smo, aim-roll-ANGLE_45, FixedMul(sinrol, sinaim)*speed/div) end end end -- True roll-aware 3D camera rotation local function RotateCamera(player) local speccam = player.speccam local cmd = player.cmd local cammo = speccam.mo local yaw = cammo.angle local pitch = player.awayviewaiming local roll = player.viewrollangle local sens = FU -- Mouse input local mx = cmd.angleturn local my = cmd.aiming -- Precompute sin/cos of roll local sr = sin(roll) local cr = cos(roll) -- Rotate mouse input into camera local space local local_x = FixedMul(cr, mx) - FixedMul(sr, my) local local_y = FixedMul(sr, mx) + FixedMul(cr, my) if abs(local_x) <= 8 then local_x = 0 end if abs(local_y) <= 8 then local_y = 0 end if speccam.cinema then speccam.c_mx = $ or 0 speccam.c_my = $ or 0 if abs(local_x) >=16 then speccam.c_mx = $ + local_x/16 else speccam.c_mx = Damp($, FU*30/32, 16) end if abs(local_y) >=16 then speccam.c_my = $ + local_y/16 else speccam.c_my = Damp($, FU*30/32, 16) end local_x = speccam.c_mx local_y = speccam.c_my else speccam.c_mx = 0 speccam.c_my = 0 end -- Apply rotations -- Detect upside-down state local aiming = AngleFixed(pitch)/FU if aiming >= 90 and aiming <= 270 then speccam.invert = true else speccam.invert = false end -- Apply rotations if speccam.invert then yaw = $ - (local_x * sens) else yaw = $ + (local_x * sens) end pitch = $ + (local_y * sens) -- Roll control (hold fire + sidemove) if speccam.cinema then speccam.c_mz = $ or 0 if (cmd.buttons & BT_FIRENORMAL) speccam.c_mz = $ + (cmd.sidemove * FU) / 2 end local maxspeed = 512*FU if abs(speccam.c_mz) > maxspeed then speccam.c_mz = (speccam.c_mz > 0 and maxspeed or -maxspeed) elseif abs(cmd.sidemove) <=5 then speccam.c_mz = Damp(speccam.c_mz, FU*30/32, 32) end roll = $ + speccam.c_mz else if (cmd.buttons & BT_FIRENORMAL) and abs(cmd.sidemove) >= 5 then roll = $ + cmd.sidemove * 8 * FU end end -- Apply orientation cammo.angle = yaw player.awayviewaiming = pitch player.viewrollangle = roll if player.realmo then player.realmo.angle = 0 end player.aiming = 0 end -- Distance and angles to the object local DistAngleToObject = function (source, target, use_height) if IsValid(1, source) == false or IsValid(1, target) == false then return nil end local z_offset = target.z if use_height == true if P_MobjFlip(target) == -1 z_offset = target.z-target.height elseif P_MobjFlip(target) != -1 z_offset = target.z+target.height end end -- Initialize local variables for angles and distance local horizontal_angle, vertical_angle, distance -- Set the angle to the passed-in 'ang' or default to the source's angle if not provided local angle = source.angle -- Calculate the horizontal angle horizontal_angle = R_PointToAngle2(source.x, source.y, target.x, target.y) distance = R_PointToDist2(source.x, source.y, target.x, target.y) -- Apply z_offset to the calculation vertical_angle = R_PointToAngle2(0, 0, distance, z_offset - source.z) -- Return the calculated values return distance, horizontal_angle, vertical_angle end -- Changes the perspective around the object local RotateAround = function(player, h_move) local cmd_p = player.cmd local speccam = player.speccam local pmo, smo, omo = player.realmo, speccam.mo, speccam.object local R_Angle1 = R_PointToAngle2(smo.x, smo.y, omo.x, omo.y) local cosang, sinang = cos(R_Angle1), sin(R_Angle1) -- Remove the MF_ENEMY flag so it can pass through invisible walls for enemies if (smo.flags % MF_ENEMY) smo.flags = $ - MF_ENEMY end if (cmd_p.buttons & BT_WEAPONPREV) -- Move the object closer if smo.sm_dist.xy < 1024*omo.scale P_TryMove(smo, smo.x-FixedMul(omo.scale, cosang)*32, smo.y-FixedMul(omo.scale, sinang)*32, true) smo.sm_dist.xy = R_PointToDist2(smo.x, smo.y, omo.x, omo.y) end cmd_p.buttons = $ - BT_WEAPONPREV elseif (cmd_p.buttons & BT_WEAPONNEXT) -- Move the object further away if smo.sm_dist.xy > 32*omo.scale P_TryMove(smo, smo.x+FixedMul(omo.scale, cosang)*32, smo.y+FixedMul(omo.scale, sinang)*32, true) smo.sm_dist.xy = R_PointToDist2(smo.x, smo.y, omo.x, omo.y) end cmd_p.buttons = $ - BT_WEAPONNEXT end -- Raises the camera up and down (does not rotate vertically around the object) if player.aiming != 0 if player.aiming > 0 if smo.sm_dist.z < 512*omo.scale P_MoveOrigin(smo, smo.x, smo.y, smo.z + FixedMul(omo.scale, cmd_p.aiming)*512) else smo.sm_dist.z = 512*omo.scale end elseif player.aiming < 0 if smo.sm_dist.z > -512*omo.scale P_MoveOrigin(smo, smo.x, smo.y, smo.z + FixedMul(omo.scale, cmd_p.aiming)*512) else smo.sm_dist.z = -512*omo.scale end end end -- Rotate camera around an object if (h_move/FU) > 5 or (h_move/FU) < -5 local x_offset = omo.x - smo.x local y_offset = omo.y - smo.y local z_offset = omo.z - smo.z local distance, horizontal_angle, vertical_angle = DistAngleToObject(omo, smo, true) -- Apply the rotation local new_angle = horizontal_angle + FixedMul(omo.scale, h_move) local radius = smo.sm_dist.xy/FU local new_x = omo.x + cos(new_angle) * radius local new_y = omo.y + sin(new_angle) * radius -- Update the object's position P_TryMove(smo, new_x, new_y, true) end smo.sm_dist.x = smo.x - omo.x smo.sm_dist.y = smo.y - omo.y smo.sm_dist.z = smo.z - omo.z local distance, horizontal_angle, vertical_angle = DistAngleToObject(smo, omo, true) player.awayviewaiming = vertical_angle pmo.angle = horizontal_angle smo.angle = horizontal_angle player.aiming = 0 -- Return flag smo.flags = $ + MF_ENEMY end -- Is used to monitor other players when a player presses F12 local vs_sm = false local nextplayer = nil local change_chase = 5 addHook("ThinkFrame", do -- Start spectating the next player if vs_sm == true COM_BufInsertText(player, "spectator_mode player "..#nextplayer) displayplayer = consoleplayer vs_sm = false end -- Zoom in or zoom out if IsValid(1, consoleplayer) == true if consoleplayer.speccam local fov = CV_FindVar("fov").value if change_chase > 0 then change_chase = $ - 1 end consoleplayer.fovadd = consoleplayer.speccam.fov+(90*FU-fov) -- Doesn't work in "PreThinkFrame" hook else change_chase = 5 end end -- PreThinkFrame camera lags behind the player for player in players.iterate do if IsValid(2, player, player.realmo) == true and player.speccam and IsValid(1, player.speccam.mo) if player.speccam.type == "object" and IsValid(1, player.speccam.object) == true and player.speccam.chase == false -- Making a first-person view for objects if (player.speccam.object.flags2 & MF2_OBJECTFLIP) P_SetOrigin(player.speccam.mo, player.speccam.object.x, player.speccam.object.y, player.speccam.object.height+(player.speccam.object.scale*24)) else P_SetOrigin(player.speccam.mo, player.speccam.object.x, player.speccam.object.y, player.speccam.object.z+player.speccam.object.height-(player.speccam.object.scale*24)) end end end end end) addHook("PreThinkFrame", do if not sirexer_spectator_mode then sirexer_spectator_mode = true end for player in players.iterate do if IsValid(2, player, player.realmo) == true and player.speccam and IsValid(1, player.speccam.mo) local speccam = player.speccam local cmd_p = player.cmd local pmo = player.realmo local smo = speccam.mo -- You can't use the camera if you're in the game and in non friendly gametypes if G_CoopGametype() == false if player.spectator == true speccam.movexy = 0 speccam.movez = 0 else RemoveCamera(player) return end end -- Smooth camera movement away from the player if speccam.movexy > 0 then speccam.movexy = $ - 1 elseif not speccam.cinema then smo.momx = 0 smo.momy = 0 end if speccam.movez > 0 then speccam.movez = $ - 1 elseif not speccam.cinema then smo.momz = 0 end -- Assign the camera to an alternate viewpoint player.awayviewmobj = smo player.awayviewtics = 5 -- malfunction at 0 -- Adjusts the zoom of the screen if speccam.type == "freecamera" and not (cmd_p.buttons & BT_CUSTOM1) or speccam.type == "object" and not (cmd_p.buttons & BT_FIRENORMAL) if (cmd_p.buttons & BT_WEAPONPREV) and speccam.fov < 88*FU speccam.fov = $ + 4*FU elseif (cmd_p.buttons & BT_WEAPONNEXT) and speccam.fov > -88*FU speccam.fov = $ - 4*FU end end -- Switching camera modes, using the RedRing button if (cmd_p.buttons & BT_WEAPONMASK) == 1 and speccam.w_p == false if speccam.type == "freecamera" and IsValid(1, player.mo) == true speccam.type = "object" pmo.angle = player.drawangle player.viewrollangle = 0 speccam.noclip = true speccam.gravity = false speccam.cinema = false speccam.blockplayer = false speccam.object = pmo local distance, horizontal_angle, vertical_angle = DistAngleToObject(smo, pmo, true) player.awayviewaiming = vertical_angle smo.angle = horizontal_angle pmo.angle = horizontal_angle if player == consoleplayer then camera.chase = true change_chase = 5 end elseif speccam.type == "object" speccam.type = "freecamera" player.speccam.d_angle = player.drawangle pmo.angle = 0 cmd_p.angleturn = 0 speccam.chase = true speccam.blockplayer = true smo.sm_dist = nil end speccam.w_p = true elseif (cmd_p.buttons & BT_WEAPONMASK) != 1 -- Does not switch again if the button is held down and speccam.w_p == true speccam.w_p = false end -- Tracking object Thinker if speccam.type == "object" local omo = speccam.object if IsValid(1, omo) == false -- If the object has been deleted, switch the mode to freecamera speccam.type = "freecamera" player.speccam.d_angle = player.drawangle pmo.angle = 0 cmd_p.angleturn = 0 speccam.blockplayer = true smo.sm_dist = nil elseif speccam.chase == false smo.angle = omo.angle if IsValid(1, omo.player) == true player.awayviewaiming = omo.player.aiming if player == consoleplayer displayplayer = player//omo.player end end -- Disable third-person view if player == consoleplayer omo.dontdrawforviewmobj = smo if camera.chase == true and change_chase == 0 change_chase = 5 COM_BufInsertText(consoleplayer, "spectator_mode chase") end end elseif speccam.chase == true -- Enable third-person view if player == consoleplayer omo.dontdrawforviewmobj = nil if camera.chase == false and change_chase == 0 change_chase = 5 COM_BufInsertText(consoleplayer, "spectator_mode chase") end end -- Changing the player's control if not (player.pflags & PF_ANALOGMODE) then player.pflags = $ + PF_ANALOGMODE end if not (player.pflags & PF_DIRECTIONCHAR) then player.pflags = $ + PF_DIRECTIONCHAR end -- Switches the movement behind an object if (cmd_p.buttons & BT_CUSTOM1) and speccam.c1_p == false if speccam.follow == true smo.sm_dist = nil speccam.follow = false else speccam.follow = true end end -- Switches to look at the object or move the camera depending on where the subject is looking if (cmd_p.buttons & BT_CUSTOM2) and speccam.c2_p == false if speccam.t_angle == true speccam.t_angle = false else speccam.t_angle = true end end -- Switches the player's self-movement blocking if (cmd_p.buttons & BT_CUSTOM3) and speccam.c3_p == false if speccam.blockplayer == true speccam.blockplayer = false else speccam.blockplayer = true end end -- Moves the camera behind the object if enabled if speccam.follow == true -- Create a table, distances from the object by all coordinates if smo.sm_dist == nil smo.sm_dist = { x = smo.x - omo.x, y = smo.y - omo.y, z = smo.z - omo.z, xy = R_PointToDist2(smo.x, smo.y, omo.x, omo.y), xyz = R_PointToDist2(smo.x, smo.y, omo.x, omo.y) + abs(smo.z - omo.z) } end -- The camera is always looking at the subject local distance, horizontal_angle, vertical_angle = DistAngleToObject(smo, omo, true) if distance != nil player.awayviewaiming = vertical_angle smo.angle = horizontal_angle pmo.angle = horizontal_angle end -- The camera can't go through walls if smo.sm_dist.xy < 1024*omo.scale and P_CheckSight(smo, omo) == true P_MoveOrigin(smo, smo.x, smo.y, omo.z + smo.sm_dist.z) -- Remove the MF_ENEMY flag so it can pass through invisible walls for enemies if (smo.flags % MF_ENEMY) smo.flags = $ - MF_ENEMY end -- Trying to move the camera P_TryMove(smo, omo.x + smo.sm_dist.x, omo.y + smo.sm_dist.y, true) -- Return flag smo.flags = $ + MF_ENEMY end -- Move the camera closer to the subject if it is too far away if smo.sm_dist.xy > 1024*omo.scale and P_CheckSight(smo, omo) == true P_MoveOrigin(smo, smo.x+(cos(horizontal_angle)*128), smo.y+(sin(horizontal_angle)*128), smo.z) smo.sm_dist.x = $ + (cos(horizontal_angle)*128) smo.sm_dist.y = $ + (sin(horizontal_angle)*128) smo.sm_dist.xy = R_PointToDist2(smo.x, smo.y, omo.x, omo.y) end if smo.sm_dist.z > 512*omo.scale smo.sm_dist.z = 512*omo.scale end -- Keep the camera from going through the wall if P_CheckSight(smo, omo) == false P_MoveOrigin(smo, smo.x+(cos(horizontal_angle)*128), smo.y+(sin(horizontal_angle)*128), smo.z) end -- Change the perspective by pressing the Firenormal button if (cmd_p.buttons & BT_FIRENORMAL) and speccam.chase == true -- Player's self-movement blocking cmd_p.forwardmove = 0 cmd_p.sidemove = 0 -- The problem is that player.cmd.angleturn does not mean XY mouse sensitivity -- it depends on where the player is looking, thanks STjR local h_move = (cmd_p.angleturn*FU)-horizontal_angle RotateAround(player, h_move) end else -- Fixed camera local PosZ -- The camera should be higher than the object if P_MobjFlip(omo) == -1 PosZ = omo.z - 128*omo.scale else PosZ = omo.z + 128*omo.scale end -- Where the camera should appear if the object disappears from the field of view if not speccam.ps2 then speccam.ps2 = {x = omo.x, y = omo.y, z = PosZ} end local ps2 = speccam.ps2 -- These variables are needed to realize that the object is out of sight local distance, horizontal_angle, vertical_angle = DistAngleToObject(smo, omo, true) local h_angleview = smo.angle local v_angleview = player.awayviewaiming local h_ang =(h_angleview-horizontal_angle) local v_ang =(v_angleview-vertical_angle) -- If the subject is behind a wall or too far away, change the camera position if P_CheckSight(smo, omo) == false or R_PointToDist2(omo.x, omo.y, smo.x, smo.y) > 3072*omo.scale or (cmd_p.buttons & BT_ATTACK) local dist = R_PointToDist2(omo.x, omo.y, ps2.x, ps2.y) if dist > 256*FRACUNIT or speccam.prevps2 == nil P_SetOrigin(smo, ps2.x, ps2.y, ps2.z) else P_SetOrigin(smo, speccam.prevps2.x, speccam.prevps2.y, speccam.prevps2.z) end end -- If the object is out of view, change the camera position if 10000 < h_ang/FU or -10000 > h_ang/FU or 7000 < v_ang/FU or -7000 > v_ang/FU P_SetOrigin(smo, ps2.x, ps2.y, ps2.z) smo.angle = horizontal_angle if P_MobjFlip(omo) == -1 player.awayviewaiming = ANG30 elseif P_MobjFlip(omo) != -1 player.awayviewaiming = -ANG30 end end -- The camera should not be too low if P_MobjFlip(omo) == -1 and omo.z - 128*omo.scale < smo.z or P_MobjFlip(omo) != -1 and omo.z + 128*omo.scale > smo.z P_SetOrigin(smo, smo.x, smo.y, PosZ) end if distance > 512*omo.scale speccam.prevps2 = speccam.ps2 speccam.ps2 = {x = omo.x, y = omo.y, z = PosZ} end if cmd_p.forwardmove != 0 or cmd_p.sidemove != 0 and distance < 128*omo.scale speccam.ps2.moved = true end end if speccam.t_angle == true -- Move the camera depending on where the subject is looking if speccam.follow == true -- Remove the MF_ENEMY flag so it can pass through invisible walls for enemies if (smo.flags % MF_ENEMY) smo.flags = $ - MF_ENEMY end local dist = smo.sm_dist.xy/FU P_TryMove(smo, omo.x-(cos(omo.angle)*dist), omo.y-(sin(omo.angle)*dist), true) smo.sm_dist.x = smo.x - omo.x smo.sm_dist.y = smo.y - omo.y smo.sm_dist.z = smo.z - omo.z local angle = R_PointToAngle2(smo.x, smo.y, omo.x, omo.y) smo.angle = angle -- Return flag smo.flags = $ + MF_ENEMY else -- Look at object local distance, horizontal_angle, vertical_angle = DistAngleToObject(smo, omo, true) smo.angle = horizontal_angle player.awayviewaiming = vertical_angle end end -- To prevent the camera from rotating on its own if speccam.follow == false and speccam.ps2 and speccam.ps2.moved == true and cmd_p.sidemove == 0 and cmd_p.forwardmove == 0 speccam.ps2.moved = false end if speccam.follow == true or speccam.follow == false and speccam.ps2 and speccam.ps2.moved != true player.aiming = 0 pmo.angle = smo.angle end -- Turn off gravity and noclip. if (smo.flags & MF_NOCLIP) then smo.flags = $ - MF_NOCLIP end if (smo.flags & MF_NOCLIPHEIGHT) then smo.flags = $ - MF_NOCLIPHEIGHT end if not (smo.flags & MF_NOGRAVITY) then smo.flags = $ + MF_NOGRAVITY end end -- Freecamara Thinker elseif speccam.type == "freecamera" -- Turn off third-person mode to remove the crosshair if player == consoleplayer then camera.chase = false end -- Run Rotate Thinker and save angle RotateCamera(player) player.drawangle = player.speccam.d_angle -- Shitcode if smo.c_delay == nil then smo.c_delay = 0 end if smo.c_delay > 0 then smo.c_delay = $ - 1 end if (cmd_p.buttons & BT_ATTACK) -- launches a missile into the object to spectate it and smo.c_delay == 0 -- The delay is needed to prevent lags local catch = P_SpawnMissileAngle(smo, 128, smo.angle, player.awayviewaiming, MT_CATCHIN4K) smo.c_delay = 10 -- Set Delay end /* -- Enables gravity if (cmd_p.buttons & BT_CUSTOM2) and speccam.c2_p == false if speccam.gravity == true then speccam.gravity = false else speccam.gravity = true end end */ -- Enables Cinematographic camera if (cmd_p.buttons & BT_CUSTOM2) and speccam.c2_p == false if speccam.cinema == true then speccam.cinema = false else speccam.cinema = true end end -- Enables noclip if (cmd_p.buttons & BT_CUSTOM3) and speccam.c3_p == false if speccam.noclip == true then speccam.noclip = false else speccam.noclip = true end end -- Change camera's speed if (cmd_p.buttons & BT_CUSTOM1) if (cmd_p.buttons & BT_WEAPONNEXT) and speccam.speed < 1000 speccam.speed = $ + 10 elseif (cmd_p.buttons & BT_WEAPONPREV) and speccam.speed > 0 speccam.speed = $ - 10 end end -- Running Movement Thinker if cmd_p.forwardmove != 0 or cmd_p.sidemove != 0 or (cmd_p.buttons & BT_JUMP) or (cmd_p.buttons & BT_SPIN) MoveCamera(player) speccam.movexy = 0 if speccam.gravity == false then speccam.movez = 0 end end if speccam.cinema then local div = 4 local maxspeed = (speccam.speed * FU) / div local hspeed = R_PointToDist2(0, 0, smo.momx, smo.momy) local vspeed = abs(smo.momz) local abs_speed = hspeed + vspeed if abs_speed > maxspeed then local scale = FixedDiv(maxspeed, abs_speed) smo.momx = FixedMul(smo.momx, scale) smo.momy = FixedMul(smo.momy, scale) smo.momz = FixedMul(smo.momz, scale) else smo.momx = Damp(smo.momx, FU*30/32, FU/256) smo.momy = Damp(smo.momy, FU*30/32, FU/256) smo.momz = Damp(smo.momz, FU*30/32, FU/256) end end -- whether the object should move through walls or not if speccam.noclip == true if not (smo.flags & MF_NOCLIP) then smo.flags = $ + MF_NOCLIP end if not (smo.flags & MF_NOCLIPHEIGHT) then smo.flags = $ + MF_NOCLIPHEIGHT end else if (smo.flags & MF_NOCLIP) then smo.flags = $ - MF_NOCLIP end if (smo.flags & MF_NOCLIPHEIGHT) then smo.flags = $ - MF_NOCLIPHEIGHT end end -- Whether the object should fall or not if speccam.gravity == true speccam.movez = 2 player.viewrollangle = 0 if (smo.flags & MF_NOGRAVITY) then smo.flags = $ - MF_NOGRAVITY end else if not (smo.flags & MF_NOGRAVITY) then smo.flags = $ + MF_NOGRAVITY end end end -- Enables HUD if (cmd_p.buttons & BT_TOSSFLAG) and speccam.tf_p == false if speccam.hud == true then speccam.hud = false else speccam.hud = true end end -- Sets whether or not the button has been pressed if (cmd_p.buttons & BT_CUSTOM1) and speccam.c1_p == false speccam.c1_p = true elseif not (cmd_p.buttons & BT_CUSTOM1) and speccam.c1_p == true speccam.c1_p = false end if (cmd_p.buttons & BT_CUSTOM2) and speccam.c2_p == false speccam.c2_p = true elseif not (cmd_p.buttons & BT_CUSTOM2) and speccam.c2_p == true speccam.c2_p = false end if (cmd_p.buttons & BT_CUSTOM3) and speccam.c3_p == false speccam.c3_p = true elseif not (cmd_p.buttons & BT_CUSTOM3) and speccam.c3_p == true speccam.c3_p = false end if (cmd_p.buttons & BT_TOSSFLAG) and speccam.tf_p == false speccam.tf_p = true elseif not (cmd_p.buttons & BT_TOSSFLAG) and speccam.tf_p == true speccam.tf_p = false end if (cmd_p.buttons & BT_WEAPONMASK) and speccam.w_p == false speccam.w_p = true elseif not (cmd_p.buttons & BT_WEAPONMASK) and speccam.w_p == true speccam.w_p = false end -- Never change the gravity of an object if (smo.flags2 & MF2_OBJECTFLIP) then smo.flags2 = $ - MF2_OBJECTFLIP end if (smo.eflags & MFE_VERTICALFLIP) then smo.eflags = $ - MFE_VERTICALFLIP end -- Block player's movement if speccam.blockplayer == true cmd_p.forwardmove = 0 cmd_p.sidemove = 0 cmd_p.buttons = 0 end -- If noclip is disabled on the server, it is not available if sm_allownoclip.value == 0 then speccam.noclip = false end -- Deleting the camera if the owner is not present elseif player.speccam RemoveCamera(player) else player.speccam = nil end end end) -- Run the camera in freecamera mode local cmd_sm_freecamera = function(player, args) local pmo = player.realmo local x, y, z = pmo.x, pmo.y, pmo.z -- The coordinates where the camera should appear local dontmove = false -- Turns off smooth movement away from the player local last_arg -- key, the next argument will be as a value -- If the camera exists, then change the coordinates of the spawn where the camera is located if player.speccam x = player.speccam.mo.x y = player.speccam.mo.y z = player.speccam.mo.z end -- Sets where the camera should be for i, v in ipairs(args) do if string.lower(v) == "-x" or string.lower(v) == "-y" or string.lower(v) == "-z" or string.lower(v) == "-rx" or string.lower(v) == "-ry" or string.lower(v) == "-rz" last_arg = string.lower(v) end local n = tonumber(v) -- if last_arg == "-x" and n != nil then x = n*FU dontmove = true end if last_arg == "-y" and n != nil then y = n*FU dontmove = true end if last_arg == "-z" and n != nil then z = n*FU dontmove = true end -- Teleports the camera relative to his current location if last_arg == "-rx" and n != nil then x = $+(n*FU) dontmove = true end if last_arg == "-ry" and n != nil then y = $+(n*FU) dontmove = true end if last_arg == "-rz" and n != nil then z = $+(n*FU) dontmove = true end end -- Create a new camera if there is no camera if not player.speccam SpawnCamera(player, pmo, x ,y, z) local smo = player.speccam.mo if dontmove == false then P_InstaThrust(smo, pmo.angle, -8*FU) end player.aiming = 0 player.speccam.d_angle = player.drawangle if (pmo.flags2 & MF2_OBJECTFLIP) if dontmove == false then P_SetObjectMomZ(smo, -2*FU) end else player.viewrollangle = 0 if dontmove == false then P_SetObjectMomZ(smo, 2*FU) end end -- Teleporting the camera, not creating a new one elseif player.speccam and dontmove == true P_MoveOrigin(player.speccam.mo, x, y, z) -- Remove the camera if it's there and isn't going to teleport anywhere else RemoveCamera(player) end end -- Run the camera in tracking object mode local cmd_sm_object = function(player, args) --Remove the camera if it exists if player.speccam then RemoveCamera(player) end -- The second argument will be the other player to be tracked, otherwise it will be the player himself local object = player.mo if args[2] != nil if findplayer(args[2]) then object = findplayer(args[2]).mo else CONS_Printf(player, "There is no player named \""..args[2].."\"") return end end -- Check if the player is entered in the game and exists if IsValid(1, object) == false if args[2] != nil CONS_Printf(player, "The player is probably not entered in the game or does not exist") else CONS_Printf(player, "That's not gonna work :3") end return end -- Enable third-person or first-person view local chase = true if args[3] != nil if string.lower(args[3]) == "off" or string.lower(args[3]) == "false" or string.lower(args[3]) == "no" or string.lower(args[3]) == "0" chase = false if player == consoleplayer then camera.chase = chase end end end -- Spawn new camera SpawnCamera(player, object) local speccam = player.speccam local pmo, smo = player.realmo, speccam.mo speccam.type = "object" speccam.blockplayer = false speccam.object = object smo.target = player.realmo speccam.chase = chase if player == consoleplayer then camera.chase = chase end player.aiming = 0 player.speccam.d_angle = player.drawangle if chase == true -- The camera will appear above the object P_MoveOrigin(smo, smo.x-(cos(object.angle)*168), smo.y-(sin(object.angle)*168), smo.z) if (object.flags2 & MF2_OBJECTFLIP) P_MoveOrigin(smo, smo.x, smo.y, smo.z-64*FU) else player.viewrollangle = 0 P_MoveOrigin(smo, smo.x, smo.y, smo.z+64*FU) end else -- Moving the camera to the object if (object.flags2 & MF2_OBJECTFLIP) P_MoveOrigin(smo, smo.x, smo.y, smo.z-object.height) else P_MoveOrigin(smo, smo.x, smo.y, smo.z+object.height) end end end -- Console command to turn on the camera COM_AddCommand("spectator_mode", function(player, ...) local args = {...} -- A table of all the arguments listed if args[1] != nil and string.lower(args[1]) == "help" local ht = "" ht = ht.."///Spectator Mode\\\\\\\n" ht = ht.."Version: 0.4\n" ht = ht.."Author: Sirexer\n" ht = ht.."The camera can be enabled by pressing the F6 button\n" ht = ht.."Commands:\n" ht = ht.."spectator_mode freecamera - enables the camera\n" ht = ht.."spectator_mode freecamera -x -y -z - enables the camera and teleports the camera directly to the specified coordinates\n" ht = ht.."spectator_mode freecamera -rx -ry -rz - enables the camera and teleports the camera relative to camera or player current location\n" ht = ht.."spectator_mode player - enables the camera in tracking object mode" CONS_Printf(player, ht) return end if G_CoopGametype() == false and player.spectator == false CONS_Printf(player, "Doesn't work when you're in game and in non friendly gametypes") return end if IsValid(2, player, player.realmo) == true if args[1] != nil and string.lower(args[1]) == "player" cmd_sm_object(player, args) elseif args[1] != nil and string.lower(args[1]) == "chase" if IsValid(1, player) == true and player.speccam local speccam = player.speccam local smo = speccam.mo local omo = speccam.object if player.speccam.chase == true speccam.chase = false if player == consoleplayer then camera.chase = false change_chase = 5 end else smo.sm_dist = nil P_MoveOrigin(smo, smo.x-(cos(omo.angle)*128), smo.y-(sin(omo.angle)*128), smo.z) if (omo.flags2 & MF2_OBJECTFLIP) P_MoveOrigin(smo, smo.x, smo.y, smo.z-64*FU) else player.viewrollangle = 0 P_MoveOrigin(smo, smo.x, smo.y, smo.z+64*FU) end player.speccam.chase = true if player == consoleplayer then camera.chase = true change_chase = 5 end end end else cmd_sm_freecamera(player, args) end end end) -- Does pretty much the same thing as ViewpointSwitch, but in object tracking mode addHook("ViewpointSwitch", function(player, nextp, forced) if IsValid(2, player, player.realmo) == false then return end if not player.speccam then return end -- Determine the actual next player if IsValid(1, nextplayer) == false and IsValid(2, nextp, nextp.mo) == true nextplayer = nextp else local selectnext, selectn1 = false, true for p in players.iterate do if selectnext == false and p == nextplayer then selectnext = true elseif selectnext == true if IsValid(2, p, p.mo) == true nextplayer = p selectn1 = false break else selectnext = false end end end if selectnext == true and selectn1 == true for p in players.iterate do if IsValid(2, p, p.mo) == true nextplayer = p break end end end end if IsValid(1, nextplayer) == false or IsValid(1, nextplayer.mo) == false then nextplayer = nil end if nextplayer != nil then vs_sm = true end end) -- Set HUD for spectator camera local function spectator_hud (v) local player = consoleplayer if IsValid(2, player, player.realmo) == true and player.speccam and IsValid(1, player.speccam.mo) -- Remove the unnecessary HUD if hud.enabled("textspectator") then hud.disable("textspectator") end local speccam = player.speccam local smo = speccam.mo if speccam.hud == false then return end -- view angles local roll = AngleFixed(player.viewrollangle)/FU local angle = AngleFixed(smo.angle)/FU local aim = AngleFixed(player.awayviewaiming)/FU -- Speed and zoom local speed_f = speccam.speed/100 local speed = "x"..speed_f.."."..(speccam.speed-speed_f*100)/10 local fov = speccam.fov/FU+88 -- Other values local cinema = speccam.cinema if cinema == true cinema = "Yes" elseif cinema == false cinema = "No" end local noclip = speccam.noclip if noclip == true noclip = "Yes" elseif noclip == false noclip = "No" end local follow = speccam.follow if follow == true follow = "Yes" elseif follow == false follow = "No" end local t_angle = speccam.t_angle if t_angle == true t_angle = "Yes" elseif t_angle == false t_angle = "No" end local blockplayer = speccam.blockplayer if blockplayer == true blockplayer = "Yes" elseif blockplayer == false blockplayer = "No" end -- Displaying text on the screen v.drawString(320, 0, "View X: "..angle, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 8, "View Y: "..aim, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 16, "View Z: "..roll, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 32, "Pos X: "..smo.x/FU, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 40, "Pos Y: "..smo.y/FU, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 48, "Pos Z: "..smo.z/FU, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 64, "Fov: "..fov, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") if speccam.type == "freecamera" v.drawString(320, 72, "Speed: "..speed, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") v.drawString(320, 80, (R_PointToDist2(0, 0, smo.momx, smo.momy)+abs(smo.momz))/FU, V_SNAPTORIGHT|V_SNAPTOTOP, "thin-right") end v.drawString(0, 104, "Toss Flag - Turn off HUD", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") if speccam.type == "freecamera" v.drawString(0, 112, "C1 - Speed Up", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 120, "C2 - Cinematographic camera: "..cinema, V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 128, "C3 - Noclip: "..noclip, V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 136, "Weapon Next/Prev - FOV Control", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 144, "C1+Weapon Next/Prev - Speed Control", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 152, "FN+Move Left/Right - Roll camera", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 160, "F12 - Tracking player", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") elseif speccam.type == "object" v.drawString(0, 112, "F12 - Tracking player", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 120, "1 - Switch to freecamera", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") if speccam.chase == true v.drawString(0, 128, "C1 - Object tracking: "..follow, V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 136, "C2 - Angle tracking: "..t_angle, V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 144, "C3 - block player's movement "..blockplayer, V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") v.drawString(0, 152, "FN+Mouse - Change perspective", V_SNAPTOLEFT|V_SNAPTOBOTTOM, "thin") end end end end hud.add(spectator_hud) -- Shitcode addHook("MobjMoveBlocked", function(mo, thing, line) if IsValid(2, mo, line) == false return end if (mo.flags % MF_ENEMY) mo.flags = $ - MF_ENEMY P_TryMove(mo, mo.x+mo.momx, mo.y+mo.momy, true) mo.flags = $ + MF_ENEMY end end, MT_SPECCAMERA) -- Hotkey to call the camera addHook("KeyDown", function(key) if key.name == "f6" and key.repeated == false COM_BufInsertText(consoleplayer, "spectator_mode") end end) -- The camera fires an invisible projectile, if this projectile hits an object, the camera will track it addHook("MobjMoveCollide", function(mo, tmthing) -- Avoid console errors that the object does not exist if IsValid(2, mo, tmthing) == false then return end if IsValid(1, mo.source) == false or IsValid(1, mo.source.target) == false or IsValid(1, mo.source.target.player) == false then return end -- If the camera shell doesn't hit the object, skip it. if P_MobjFlip(tmthing) == -1 and (tmthing.z+(4*tmthing.scale) < mo.z or tmthing.z-tmthing.height-(4*tmthing.scale) > mo.z) or P_MobjFlip(tmthing) != -1 and (tmthing.z-(4*tmthing.scale) > mo.z or tmthing.z+tmthing.height+(4*tmthing.scale) < mo.z) return end -- Starting to keep an eye on the subject if it's... if tmthing.player -- Player or (tmthing.flags & MF_ENEMY) -- Enemy or (tmthing.flags & MF_BOSS) -- Boss or tmthing.target -- if it's an object from the enemy and ((tmthing.target.flags & MF_ENEMY) or (tmthing.target.flags & MF_BOSS)) if not (tmthing.flags & MF_ENEMY) and not (tmthing.flags & MF_BOSS) and tmthing.target and ((tmthing.target.flags & MF_ENEMY) or (tmthing.target.flags & MF_BOSS)) tmthing = tmthing.target end local player = mo.source.target.player if player then RemoveCamera(player) end local pmo = player.realmo SpawnCamera(player) local speccam = player.speccam local smo = speccam.mo speccam.type = "object" speccam.blockplayer = false speccam.object = tmthing smo.target = player.realmo P_MoveOrigin(smo, tmthing.x-(cos(speccam.object.angle)*168), tmthing.y-(sin(speccam.object.angle)*168), tmthing.z) player.aiming = 0 player.speccam.d_angle = player.drawangle if (speccam.object.flags2 & MF2_OBJECTFLIP) P_MoveOrigin(smo, smo.x, smo.y, tmthing.z-tmthing.height-64*speccam.object.scale) else player.viewrollangle = 0 P_MoveOrigin(smo, smo.x, smo.y, tmthing.z+tmthing.height+64*speccam.object.scale) end P_RemoveMobj(mo) if player == consoleplayer then camera.chase = true change_chase = 5 end end end, MT_CATCHIN4K) addHook("MobjThinker", function(mo) -- The projectile should disappear in the case of... if IsValid(1, mo) == false then return end if abs(mo.momx/FU) < 64 and abs(mo.momy/FU) < 64 and abs(mo.momz/FU) < 64 -- Lost speed or P_IsObjectOnGround(mo) == true -- If it touched the ground or IsValid(1, mo.source) == false -- If the camera does not exist or IsValid(1, mo.source.target) == false or IsValid(1, mo.source.target.player) == false -- If the player no longer exists or mo.source.target.player.speccam.type == "object" -- If the camera is already tracking the object if mo.fuse == 0 then mo.fuse = 1 end end end, MT_CATCHIN4K) -- Remove all cameras when loading a level addHook("MapLoad", do for player in players.iterate do player.speccam = nil end end) -- The camera can't die addHook("MobjDeath", function(mo) return true end, MT_SPECCAMERA)