-- name: AFKer -- author: codeflex -- incompatible: gamemode -- description: AFKer 1.0\nby \\#88ccff\\codeflex\\#ffffff\\\n\n\\#ffff00\\I found you, AFKer!\\#ffffff\\\n\nNeed to take a break without being messed with? Want to just chill and spectate your buddies? Then AFKer is for you! With this mod, pressing D-PAD LEFT will toggle AFK mode - transparent, intangible, and immobile!\nWhile in AFK mode, you can press D-PAD UP or DOWN to toggle the camera between other players in the same level!\n\n* D-PAD LEFT toggles AFK mode on!\n* Any character input toggles it back off!\n* 10 second cooldown (from deactivation) to prevent exploiting\n* Cycle the camera between other players in the level with D-PAD UP & DOWN\n* Counter trolling with completely intangible AFKing\n* Must be on the ground to AFK -- ============================================================ -- AFKer - Fixed Multiplayer Edition -- ============================================================ -- -- ROOT CAUSE FIXES: -- -- BUG 1 & 2 (input overlap / message spam): -- The original is_local() checked (np.localIndex >= 0). -- In CoopDX, localIndex is each player's slot number (0, 1, 2 …), -- which is ALWAYS >= 0 for every connected player. This made every -- player's HOOK_MARIO_UPDATE call look "local", so input processing -- and chat messages fired for EVERY player slot on every machine. -- FIX: In CoopDX, gMarioStates[0]/gNetworkPlayers[0]/gPlayerSyncTable[0] -- are ALWAYS the local player (docs: "indexed by the local playerIndex"). -- So the only correct local-player check is m.playerIndex == 0. -- -- BUG 3 (Vanish Cap broken): -- The AFK-off else-branch was unconditionally clearing MARIO_VANISH_CAP -- and capTimer every frame for non-AFK players, including when the player -- legitimately collected the Vanish Cap. -- FIX: Track whether WE set the vanish cap with afkWasVanish[]. -- Only clear it when transitioning OUT of AFK (afkWasVanish was true). -- Never touch flags for non-AFK players who haven't been AFK. -- -- BUG 4 (Spectate camera not moving): -- set_camera_mode(CAMERA_MODE_FIXED) was used, but FIXED mode in SM64 -- uses level-defined fixed positions, NOT gLakituState.pos. Our manual -- position writes were silently overridden. -- FIX: Do NOT force FIXED mode. Override gLakituState.pos/focus AND -- gLakituState.nextPos/nextFocus directly every frame in HOOK_UPDATE. -- HOOK_UPDATE fires after the game's camera calculations so our values -- are what the renderer actually sees. -- Camera orbit: right-stick (extStick) + L/R triggers for classic players. -- -- BUG 5 (Cannot re-activate after deactivation): -- Two issues: -- (a) afkJustToggled was only decremented inside "if isAfk", but it is -- set to 8 on BOTH toggle-on AND toggle-off. After turning AFK off, -- isAfk=false so the counter never decremented → D-PAD LEFT was -- permanently blocked. -- FIX: Always decrement afkJustToggled at the top of the local block, -- regardless of AFK state. -- (b) The 10-second cooldown was started on AFK ACTIVATION, so it counted -- down during the AFK session — not from deactivation as intended. -- FIX: Set afkCooldown when LEAVING AFK (in toggle_afk else branch). -- -- EXTRA: Spectate cycle now includes the local player (watching yourself), -- auto-cycles when a spectated player leaves, and correctly reports when -- no other players are present. -- ============================================================ local afkJustToggled = {} -- grace-period frames after each toggle local afkCooldown = {} -- re-activate cooldown frames (starts on deactivation) local prevAfk = {} -- previous AFK state (for change detection) local afkWasVanish = {} -- did WE set the vanish-cap for this player? local afkFrozenFrame = {} -- animation frame captured at AFK activation (for freeze) local afkOrigPos = {} -- world position saved when entering AFK, restored on exit local afkOrigAngle = {} -- facing angle saved when entering AFK local COOLDOWN_FRAMES = 30 * 10 -- 10 seconds at 30 fps -- Spectate state (local-machine only, not synced) local spectateTarget = 0 -- 0 = watching self; number = player index of target local spectateCam = { pos = { x = 0, y = 0, z = 0 }, focus = { x = 0, y = 0, z = 0 }, yaw = 0, pitch = 0, dist = 900, goalYaw = 0, goalPitch = 0, goalDist = 900, } -- ============================================================ -- LOCAL PLAYER CHECK -- m.playerIndex == 0 is ALWAYS the local player in CoopDX. -- ============================================================ local function is_local(m) return m.playerIndex == 0 end -- ============================================================ -- HELPERS -- ============================================================ local function can_activate_afk(m) if (m.action & ACT_FLAG_AIR) ~= 0 then return false end if (m.action & ACT_FLAG_SWIMMING) ~= 0 then return false end if m.heldObj ~= nil then return false end local badActions = { ACT_IN_CANNON, ACT_SHOOTING_FROM_CANNON, ACT_RIDING_SHELL, ACT_RIDING_SHELL_FALL, ACT_RIDING_SHELL_JUMP, ACT_THROWN, ACT_BOWSER_THROW, ACT_BOWSER_JUMP, ACT_CUTSCENE, ACT_WAITING_FOR_DIALOG, ACT_READING_AUTOMATIC_DIALOG, ACT_READING_NPC_DIALOG, ACT_DEATH, ACT_STAND_DEATH, ACT_WATER_DEATH, ACT_DROWNING, ACT_SUFFOCATION, ACT_BURNING_DEATH, ACT_ELECTROCUTION, ACT_SHOCKED } for _, act in ipairs(badActions) do if m.action == act then return false end end if (m.action & ACT_FLAG_INTANGIBLE) ~= 0 then return false end return true end local function is_dead(m) if m.health <= 0x100 then return true end local a = m.action return a == ACT_DEATH or a == ACT_STAND_DEATH or a == ACT_WATER_DEATH or a == ACT_DROWNING or a == ACT_SUFFOCATION or a == ACT_BURNING_DEATH or a == ACT_ELECTROCUTION or a == ACT_SHOCKED end local function limit_angle(a) return (a + 0x8000) % 0x10000 - 0x8000 end local function clampf(v, lo, hi) if v < lo then return lo end if v > hi then return hi end return v end local function same_level(npA, npB) return npA and npB and npA.connected and npB.connected and npA.currLevelNum == npB.currLevelNum and npA.currAreaIndex == npB.currAreaIndex and npA.currActNum == npB.currActNum end local function is_bad_spectate_action(m) if not m then return true end return is_dead(m) or m.action == ACT_IN_CANNON or m.action == ACT_SHOOTING_FROM_CANNON end local function is_valid_spectate_target(index) if not index or index <= 0 or index >= MAX_PLAYERS then return false end local np0 = gNetworkPlayers[0] local np = gNetworkPlayers[index] local ps = gPlayerSyncTable[index] local tm = gMarioStates[index] if not same_level(np0, np) then return false end if np0.currAreaSyncValid == false or np.currAreaSyncValid == false then return false end if not ps or ps.afk then return false end if is_bad_spectate_action(tm) then return false end return true end local function get_spectate_focus_pos(target) if target ~= nil and target ~= 0 then local tm = gMarioStates[target] if tm and tm.pos then return tm.pos end end if afkOrigPos[0] then return afkOrigPos[0] end local m0 = gMarioStates[0] return m0 and m0.pos or nil end local function reset_spectate_camera(m, focusPos) if not focusPos then return end spectateCam.focus.x = focusPos.x spectateCam.focus.y = focusPos.y + 120 spectateCam.focus.z = focusPos.z local yaw = 0 if m and m.area and m.area.camera then yaw = m.area.camera.yaw or 0 end spectateCam.yaw = yaw spectateCam.goalYaw = yaw spectateCam.pitch = 0 spectateCam.goalPitch = 0 spectateCam.dist = 900 spectateCam.goalDist = 900 end local function resolve_spectate_camera_collision(focusPos, camPos) local raisedCam = { x = camPos.x, y = camPos.y, z = camPos.z, } local floorRay = collision_find_surface_on_ray( raisedCam.x, raisedCam.y + 300, raisedCam.z, 0, -300, 0) if floorRay and floorRay.hitPos then raisedCam.y = floorRay.hitPos.y + 20 end local hit = collision_find_surface_on_ray( focusPos.x, focusPos.y, focusPos.z, raisedCam.x - focusPos.x, raisedCam.y - focusPos.y, raisedCam.z - focusPos.z) if hit and hit.hitPos then local dx = hit.hitPos.x - focusPos.x local dy = hit.hitPos.y - focusPos.y local dz = hit.hitPos.z - focusPos.z local len = math.sqrt(dx * dx + dy * dy + dz * dz) if len > 0 then local pad = 24 local scale = math.max(0, (len - pad) / len) raisedCam.x = focusPos.x + dx * scale raisedCam.y = focusPos.y + dy * scale raisedCam.z = focusPos.z + dz * scale else raisedCam.x = hit.hitPos.x raisedCam.y = hit.hitPos.y raisedCam.z = hit.hitPos.z end end return raisedCam end -- Friendly display name for a spectate target (0 = self) local function spectate_name(target) if target == nil or target == 0 then local np = gNetworkPlayers[0] return (np and np.name or "You") .. " (you)" end local np = gNetworkPlayers[target] return (np and np.connected and np.name) or "?" end -- Returns true if at least one OTHER player is connected local function has_other_players() for j = 1, MAX_PLAYERS - 1 do if is_valid_spectate_target(j) then return true end end return false end -- Cycle spectate target through [self=0, remote1, remote2, …] -- dir = +1 (next) or -1 (prev) -- Uses 0 (not nil) as the "watch self" sentinel so the candidates -- table has no holes — nil holes break Lua's # operator and ipairs. local function find_next_spectate_target(current, dir) local candidates = {} table.insert(candidates, 0) -- 0 = local player (always present) for j = 1, MAX_PLAYERS - 1 do if is_valid_spectate_target(j) then table.insert(candidates, j) end end if #candidates == 1 then return 0 end -- only self available -- Locate current in list; default to slot 1 (idx in list) local idx = 1 for k, v in ipairs(candidates) do if v == (current or 0) then idx = k; break end end local newIdx = ((idx - 1 + dir) % #candidates) + 1 return candidates[newIdx] end -- ============================================================ -- AFK TOGGLE -- ============================================================ local function toggle_afk(m) local i = m.playerIndex -- always 0 for the local player local ps = gPlayerSyncTable[i] local wasAfk = ps.afk or false ps.afk = not wasAfk afkJustToggled[i] = 8 -- frames to ignore new input after toggling play_sound(SOUND_MENU_PAUSE, m.marioObj.header.gfx.cameraToObject) if ps.afk then -- Entering AFK: store position so we can restore it on exit set_mario_action(m, ACT_IDLE, 0) spectateTarget = 0 -- start by watching yourself (0 = local) afkOrigPos[i] = { x = m.pos.x, y = m.pos.y, z = m.pos.z } afkOrigAngle[i] = { x = m.faceAngle.x, y = m.faceAngle.y, z = m.faceAngle.z } reset_spectate_camera(m, afkOrigPos[i]) else -- Leaving AFK: restore stored position and START the 10-second cooldown afkCooldown[i] = COOLDOWN_FRAMES spectateTarget = 0 if afkOrigPos[i] then m.pos.x = afkOrigPos[i].x m.pos.y = afkOrigPos[i].y m.pos.z = afkOrigPos[i].z afkOrigPos[i] = nil end if afkOrigAngle[i] then m.faceAngle.x = afkOrigAngle[i].x m.faceAngle.y = afkOrigAngle[i].y m.faceAngle.z = afkOrigAngle[i].z afkOrigAngle[i] = nil end if afkWasVanish[i] then m.flags = m.flags & ~MARIO_VANISH_CAP m.capTimer = 0 m.invincTimer = 0 afkWasVanish[i] = false end end end -- ============================================================ -- CANCEL-INPUT CHECK (called only while AFK, after grace period) -- Left-stick movement or any non-D-Pad / non-camera button cancels AFK. -- D-pad is reserved for spectate cycling. -- C-buttons and L/R triggers are camera controls — don't cancel AFK. -- ============================================================ local function has_cancel_input(m) local leftStickMoved = math.abs(m.controller.stickX) > 12 or math.abs(m.controller.stickY) > 12 local bp = m.controller.buttonPressed -- Exclude D-pad (spectate cycle), C-buttons and triggers (camera controls). local ignoreMask = L_JPAD | U_JPAD | D_JPAD | R_JPAD | U_CBUTTONS | D_CBUTTONS | L_CBUTTONS | R_CBUTTONS | L_TRIG | R_TRIG return leftStickMoved or (bp & ~ignoreMask) ~= 0 end -- ============================================================ -- SPECTATE POSITION UPDATE (called every frame from HOOK_UPDATE) -- -- APPROACH: Teleport the local player (already invisible via vanish -- cap) to the spectated player's world position every frame. -- The game's normal behind-mario camera follows naturally — right -- stick works without any intervention, and there are zero skybox -- or camera-mode side effects. No gLakituState/gCamera writes needed. -- On AFK exit, toggle_afk restores the original stored position. -- ============================================================ local function spectator_camera_update() local ps0 = gPlayerSyncTable[0] if not (ps0 and ps0.afk) then return end -- Auto-cycle if the currently spectated player disconnected local target = spectateTarget or 0 if target ~= 0 then local np = gNetworkPlayers[target] if not (np and np.connected) then local next = find_next_spectate_target(target, 1) spectateTarget = next if next == 0 then djui_chat_message_create("\\#ffff55\\Spectated player left. Watching yourself.") else djui_chat_message_create( "Spectated player left; now spectating: " .. spectate_name(next)) end target = next or 0 end end -- Teleport local (invisible) player to spectated player's position. -- Camera follows naturally — no camera struct writes needed. if target ~= 0 then local m0 = gMarioStates[0] local tm = gMarioStates[target] if m0 and tm and tm.pos then m0.pos.x = tm.pos.x m0.pos.y = tm.pos.y m0.pos.z = tm.pos.z end end end local function spectator_camera_update_v2() local ps0 = gPlayerSyncTable[0] if not (ps0 and ps0.afk) then return end local m0 = gMarioStates[0] if not (m0 and m0.area and m0.area.camera) then return end local target = spectateTarget or 0 if target ~= 0 and not is_valid_spectate_target(target) then spectateTarget = 0 target = 0 djui_chat_message_create("\\#ffff55\\Spectated player unavailable. Watching yourself.") local selfFocus = get_spectate_focus_pos(0) if selfFocus then reset_spectate_camera(m0, selfFocus) end end local focusPos = get_spectate_focus_pos(target) if not focusPos then return end spectateCam.focus.x = focusPos.x spectateCam.focus.y = focusPos.y + 120 spectateCam.focus.z = focusPos.z if not is_game_paused() then local stickX = m0.controller.extStickX local stickY = m0.controller.extStickY if stickX == 0 then if (m0.controller.buttonDown & R_CBUTTONS) ~= 0 then stickX = 96 end if (m0.controller.buttonDown & L_CBUTTONS) ~= 0 then stickX = -96 end end if stickY == 0 then if (m0.controller.buttonDown & U_CBUTTONS) ~= 0 then stickY = 96 end if (m0.controller.buttonDown & D_CBUTTONS) ~= 0 then stickY = -96 end end spectateCam.goalYaw = limit_angle(spectateCam.goalYaw - stickX * 10) spectateCam.goalPitch = clampf(spectateCam.goalPitch - stickY * 8, -0x3E00, 0x3E00) if (m0.controller.buttonDown & L_TRIG) ~= 0 and target ~= 0 then local tm = gMarioStates[target] if tm then spectateCam.goalYaw = limit_angle(tm.faceAngle.y + 0x8000) spectateCam.goalPitch = 0 end end if (m0.controller.buttonPressed & R_TRIG) ~= 0 then spectateCam.goalDist = spectateCam.goalDist + 400 if spectateCam.goalDist > 1700 then spectateCam.goalDist = 500 end end end spectateCam.yaw = approach_s16_symmetric( spectateCam.yaw, spectateCam.goalYaw, math.min(0x500, math.abs(limit_angle(spectateCam.goalYaw - spectateCam.yaw)) / 5)) spectateCam.pitch = approach_s16_symmetric( spectateCam.pitch, spectateCam.goalPitch, math.abs(limit_angle(spectateCam.goalPitch - spectateCam.pitch)) / 5) spectateCam.dist = spectateCam.dist + (spectateCam.goalDist - spectateCam.dist) / 5 spectateCam.pos.x = spectateCam.focus.x + spectateCam.dist * sins(spectateCam.yaw) * coss(spectateCam.pitch) spectateCam.pos.y = spectateCam.focus.y + spectateCam.dist * sins(spectateCam.pitch) spectateCam.pos.z = spectateCam.focus.z + spectateCam.dist * coss(spectateCam.yaw) * coss(spectateCam.pitch) spectateCam.pos = resolve_spectate_camera_collision(spectateCam.focus, spectateCam.pos) vec3f_copy(gLakituState.focus, spectateCam.focus) vec3f_copy(gLakituState.curFocus, spectateCam.focus) vec3f_copy(gLakituState.goalFocus, spectateCam.focus) vec3f_copy(m0.area.camera.focus, spectateCam.focus) vec3f_copy(gLakituState.pos, spectateCam.pos) vec3f_copy(gLakituState.curPos, spectateCam.pos) vec3f_copy(gLakituState.goalPos, spectateCam.pos) vec3f_copy(m0.area.camera.pos, spectateCam.pos) m0.area.camera.yaw = spectateCam.yaw gLakituState.posHSpeed = 0 gLakituState.posVSpeed = 0 gLakituState.focHSpeed = 0 gLakituState.focVSpeed = 0 end -- ============================================================ -- MAIN PER-PLAYER UPDATE -- ============================================================ local function mario_update(m) -- Guard: m or playerIndex may be nil/invalid during the join handshake if not m then return end local i = m.playerIndex if i == nil or i < 0 or i >= MAX_PLAYERS then return end -- Guard: sync table may not be ready yet for freshly-joining players if not gPlayerSyncTable[i] then return end -- Initialise per-player state tables (first time we see this index) if afkJustToggled[i] == nil then afkJustToggled[i] = 0 afkCooldown[i] = 0 prevAfk[i] = false afkWasVanish[i] = false afkFrozenFrame[i] = 0 end -- ------------------------------------------------------- -- AFK state-change announcements -- Runs for EVERY player on EVERY client (djui_chat is local). -- Each client generates the message locally when it observes -- the state change via the sync table — correct behaviour: -- everyone sees "[name] IS NOW AFK!" exactly once. -- ------------------------------------------------------- local wasAfk = prevAfk[i] local nowAfk = gPlayerSyncTable[i].afk or false if wasAfk ~= nowAfk then local p = gNetworkPlayers[i] if p and p.connected then if nowAfk then djui_chat_message_create(p.name .. " IS NOW AFK!") -- Capture current animation frame so we can freeze it local gfx = m.marioObj and m.marioObj.header and m.marioObj.header.gfx if gfx and gfx.animInfo then afkFrozenFrame[i] = gfx.animInfo.animFrame or 0 end else djui_chat_message_create(p.name .. " IS NO LONGER AFK!") end end prevAfk[i] = nowAfk end -- ------------------------------------------------------- -- LOCAL PLAYER INPUT (m.playerIndex == 0 only) -- ------------------------------------------------------- if is_local(m) then -- Always count down both timers, regardless of AFK state. -- FIX (Bug 5a): afkJustToggled MUST decrement even when not AFK, -- otherwise turning AFK off leaves it stuck > 0 and D-PAD LEFT -- is permanently blocked. if afkJustToggled[i] > 0 then afkJustToggled[i] = afkJustToggled[i] - 1 end if afkCooldown[i] > 0 then afkCooldown[i] = afkCooldown[i] - 1 end local ps = gPlayerSyncTable[i] local isAfk = ps.afk or false -- Auto-deactivate on death if isAfk and is_dead(m) then toggle_afk(m) isAfk = false end -- Keep the AFK player fully anchored and inert. if isAfk then if m.action ~= ACT_IDLE then set_mario_action(m, ACT_IDLE, 0) end m.forwardVel = 0 m.vel.x = 0 m.vel.y = 0 m.vel.z = 0 m.faceAngle.x = 0 m.faceAngle.z = 0 m.intendedMag = 0 if afkOrigPos[i] then m.pos.x = afkOrigPos[i].x m.pos.y = afkOrigPos[i].y m.pos.z = afkOrigPos[i].z end end -- D-PAD LEFT: toggle AFK on/off -- afkJustToggled guard prevents same press from immediately re-toggling. if (m.controller.buttonPressed & L_JPAD) ~= 0 and afkJustToggled[i] == 0 then if not isAfk then if not can_activate_afk(m) then play_sound(SOUND_MENU_CAMERA_BUZZ, m.marioObj.header.gfx.cameraToObject) djui_chat_message_create("\\#ff5555\\AFK cannot be activated at this time!") elseif afkCooldown[i] > 0 then local seconds = math.ceil(afkCooldown[i] / 30) djui_chat_message_create("AFK COOLDOWN: " .. seconds .. " SECONDS") play_sound(SOUND_MENU_CAMERA_BUZZ, m.marioObj.header.gfx.cameraToObject) else toggle_afk(m) isAfk = true end else toggle_afk(m) isAfk = false end end -- D-PAD UP / DOWN: cycle spectate target (only while AFK) if isAfk and (m.controller.buttonPressed & (U_JPAD | D_JPAD)) ~= 0 then if not has_other_players() then play_sound(SOUND_MENU_CAMERA_BUZZ, m.marioObj.header.gfx.cameraToObject) djui_chat_message_create("\\#ff5555\\NO OTHER PLAYERS IN THIS LEVEL TO SPECTATE") else local dir = (m.controller.buttonPressed & U_JPAD) ~= 0 and 1 or -1 local snd = dir == 1 and SOUND_MENU_CAMERA_ZOOM_IN or SOUND_MENU_CAMERA_ZOOM_OUT play_sound(snd, m.marioObj.header.gfx.cameraToObject) spectateTarget = find_next_spectate_target(spectateTarget, dir) djui_chat_message_create("Now spectating: " .. spectate_name(spectateTarget)) end end -- Cancel AFK on movement / button press (after grace period) if isAfk and afkJustToggled[i] == 0 and has_cancel_input(m) then toggle_afk(m) isAfk = false end end -- end is_local -- ------------------------------------------------------- -- AFK VISUAL EFFECTS (all players, driven by sync table) -- afkWasVanish[] tracks whether WE activated vanish-cap, -- so we never accidentally clear a legitimately collected -- Vanish Cap when AFK is not (or never was) active. -- ------------------------------------------------------- local isAfkNow = gPlayerSyncTable[i].afk or false if isAfkNow then m.capTimer = 9999 m.flags = m.flags | MARIO_VANISH_CAP m.interactObj = nil m.usedObj = nil m.invincTimer = 9999 m.hurtCounter = 0 m.healCounter = 0 afkWasVanish[i] = true -- Freeze animation: hold the captured frame every tick local gfx = m.marioObj and m.marioObj.header and m.marioObj.header.gfx if gfx and gfx.animInfo then gfx.animInfo.animFrame = afkFrozenFrame[i] or 0 end elseif afkWasVanish[i] then -- Transitioning out of AFK: undo only what we set m.flags = m.flags & ~MARIO_VANISH_CAP m.capTimer = 0 m.invincTimer = 0 afkWasVanish[i] = false end -- If afkWasVanish[i] == false and not AFK, we touch nothing -- → real Vanish Cap (or any other cap) works normally. end -- ============================================================ -- FLOATING AFK NAMETAG -- ============================================================ local function on_nametags_render(playerIndexStr, pos) local i = tonumber(playerIndexStr) if i == nil then return nil end local ps = gPlayerSyncTable[i] if ps and ps.afk then if i == 0 and afkOrigPos[0] and pos then pos.x = afkOrigPos[0].x pos.y = afkOrigPos[0].y pos.z = afkOrigPos[0].z end return { name = "AFK" } end return nil end -- ============================================================ -- HUD -- ============================================================ local function on_hud_render() djui_hud_set_resolution(RESOLUTION_DJUI) djui_hud_set_font(FONT_NORMAL) for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] local ps = gPlayerSyncTable[i] if np and np.connected and ps and ps.afk then djui_hud_set_color(255, 255, 255, 255) djui_hud_print_text(np.name .. " [AFK]", 20, 40 + (i * 20), 0.65) end end -- Bottom-centre: who we are spectating (local machine only) if gPlayerSyncTable[0] and gPlayerSyncTable[0].afk then local name = spectate_name(spectateTarget) local screenW = djui_hud_get_screen_width() djui_hud_set_color(255, 255, 100, 255) djui_hud_print_text( "Spectating: " .. name, screenW / 2 - 150, djui_hud_get_screen_height() - 80, 1.0) end end -- ============================================================ -- HOOKS -- ============================================================ hook_event(HOOK_MARIO_UPDATE, mario_update) hook_event(HOOK_ON_HUD_RENDER, on_hud_render) hook_event(HOOK_ON_NAMETAGS_RENDER, on_nametags_render) hook_event(HOOK_UPDATE, spectator_camera_update_v2)