-- name: v0.3 \\#5a5c7e\\Vani\\#816069\\lla\\#bc674a\\24 -- description: Server utilities for sm64vanilla 24/7. local VERSION = "vanilla24_0.3.1" local VOTE_DURATION = 30 * 30 local RESET_COUNTDOWN = 10 * 30 local VOTE_PERCENT = 0.60 local KICK_DURATION = 5 * 60 * 30 local VOTE_NONE = 0 local VOTE_KICK = 1 local VOTE_RESET = 2 local PACKET_VOTE_START = 1 local PACKET_VOTE_CAST = 2 local PACKET_ADMIN = 3 local PACKET_ADMIN_APPLY = 4 local zero = { x = 0, y = 0, z = 0 } local voteChoices = {} local voteReason = "" local voteStarter = -1 local localPopup = "" local localPopupColor = "\\#ffffff\\" local localPopupTimer = 0 local localPopupSoundTimer = 0 local serverFrames = 0 local resetExecuted = false local adminMenuOpen = false local adminSelection = 1 local adminTarget = -1 local adminSpeed = 1.0 local adminSize = 1.0 local localKickedUntil = 0 local hudPulse = 0 local hudPulseDirection = 1 local serverUsage = 0 local serverAnimCount = 0 local serverObjectCount = 0 local previousObjectCount = 0 local objectCountTimer = 0 local function player_count() local count = 0 for i = 0, MAX_PLAYERS - 1 do if gNetworkPlayers[i].connected then count = count + 1 end end return count end local function find_player(text) text = string.lower(text or "") if text == "" then return -1 end local numeric = tonumber(text) if numeric ~= nil then local index = math.floor(numeric) if index >= 0 and index < MAX_PLAYERS and gNetworkPlayers[index].connected then return index end end for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] if np.connected and string.lower(np.name) == text then return i end end for i = 0, MAX_PLAYERS - 1 do local np = gNetworkPlayers[i] if np.connected and string.lower(np.name):find(text, 1, true) then return i end end return -1 end local function popup(text, color, duration, sound) localPopup = text localPopupColor = color or "\\#ffffff\\" localPopupTimer = duration or 90 if sound ~= false then play_sound(SOUND_MENU_MESSAGE_APPEAR, gMarioStates[0].marioObj.header.gfx.cameraToObject) end end local function chat(text) djui_chat_message_create("\\#00d9ff\\[Vanilla24]\\#ffffff\\ " .. text) end local function reset_vote_state() gGlobalSyncTable.v24VoteType = VOTE_NONE gGlobalSyncTable.v24VoteTarget = -1 gGlobalSyncTable.v24VoteTime = 0 gGlobalSyncTable.v24VoteYes = 0 gGlobalSyncTable.v24VoteNo = 0 gGlobalSyncTable.v24VoteTotal = 0 gGlobalSyncTable.v24VoteStarter = -1 gGlobalSyncTable.v24VoteReason = "" voteChoices = {} voteReason = "" voteStarter = -1 end local function initialize_sync() if not network_is_server() then return end reset_vote_state() gGlobalSyncTable.v24ResetTimer = 0 gGlobalSyncTable.v24ResetStage = 0 gGlobalSyncTable.v24ServerFrames = 0 gGlobalSyncTable.v24KickIndex = -1 gGlobalSyncTable.v24KickUntil = 0 gGlobalSyncTable.v24AdminTarget = -1 gGlobalSyncTable.v24AdminFrozen = false gGlobalSyncTable.v24AdminSpeed = 1.0 gGlobalSyncTable.v24AdminSize = 1.0 end local function active_vote() return gGlobalSyncTable.v24VoteType ~= VOTE_NONE end local function required_votes(total) if total <= 0 then return 1 end return math.ceil(total * VOTE_PERCENT) end local function vote_yes_count() return gGlobalSyncTable.v24VoteYes or 0 end local function vote_no_count() return gGlobalSyncTable.v24VoteNo or 0 end local function local_index() return gNetworkPlayers[0].localIndex end local function cast_vote(choice) if not active_vote() then chat("There is no active vote.") return true end local index = local_index() if voteChoices[index] ~= nil then chat("You already voted.") return true end voteChoices[index] = choice if network_is_server() then if choice then gGlobalSyncTable.v24VoteYes = vote_yes_count() + 1 else gGlobalSyncTable.v24VoteNo = vote_no_count() + 1 end else network_send(true, { v24Packet = PACKET_VOTE_CAST, choice = choice, player = index }) end play_sound(SOUND_MENU_CHANGE_SELECT, zero) return true end local function start_vote_local(voteType, target, reason) if active_vote() or (gGlobalSyncTable.v24ResetTimer or 0) > 0 then chat("A vote or reset is already active.") return false end local total = player_count() if total < 1 then return false end network_send(true, { v24Packet = PACKET_VOTE_START, voteType = voteType, target = target, reason = reason or "No reason provided.", starter = local_index() }) return true end local function server_start_vote(voteType, target, reason, starter) if not network_is_server() then return false end if active_vote() or (gGlobalSyncTable.v24ResetTimer or 0) > 0 then return false end local total = player_count() if total < 1 then return false end voteChoices = {} voteStarter = starter or 0 voteReason = reason or "No reason provided." gGlobalSyncTable.v24VoteStarter = voteStarter gGlobalSyncTable.v24VoteReason = voteReason gGlobalSyncTable.v24VoteType = voteType gGlobalSyncTable.v24VoteTarget = target or -1 gGlobalSyncTable.v24VoteTime = VOTE_DURATION gGlobalSyncTable.v24VoteYes = 1 gGlobalSyncTable.v24VoteNo = 0 gGlobalSyncTable.v24VoteTotal = total voteChoices[voteStarter] = true local targetName = target >= 0 and gNetworkPlayers[target].name or "the server" if voteType == VOTE_KICK then popup("Votekick", "\\#ff4040\\", 75) chat("\\#ffffff\\" .. gNetworkPlayers[voteStarter].name .. "\\#ffffff\\ requests " .. targetName .. " to be kicked.") else popup("VoteReset", "\\#ffff00\\", 75) chat("\\#ffffff\\" .. gNetworkPlayers[voteStarter].name .. "\\#ffffff\\ requests a full server reset.") end return true end local function finish_vote() if not network_is_server() or not active_vote() then return end local total = player_count() local yes = vote_yes_count() local passed = yes >= required_votes(total) local voteType = gGlobalSyncTable.v24VoteType local target = gGlobalSyncTable.v24VoteTarget local name = target >= 0 and gNetworkPlayers[target].name or "" if voteType == VOTE_KICK then if passed and target >= 0 and gNetworkPlayers[target].connected then gGlobalSyncTable.v24KickIndex = target gGlobalSyncTable.v24KickUntil = serverFrames + KICK_DURATION popup("\\#ffffff\\" .. name .. " \\#ff4040\\has been votekicked!", "\\#ff4040\\", 100, true) chat("\\#ffffff\\" .. name .. " \\#ff4040\\has been votekicked!") network_send(true, { v24Packet = PACKET_ADMIN, action = "votekick_result", target = target, untilFrame = serverFrames + KICK_DURATION }) else popup("Not enough votes to votekick user \\#ffffff\\" .. name .. "\\#ff4040\\!", "\\#ff4040\\", 100, true) chat("Not enough votes to votekick user \\#ffffff\\" .. name .. "\\#ff4040\\!") end reset_vote_state() elseif voteType == VOTE_RESET then if passed then gGlobalSyncTable.v24ResetTimer = RESET_COUNTDOWN gGlobalSyncTable.v24ResetStage = 1 gGlobalSyncTable.v24VoteType = VOTE_NONE gGlobalSyncTable.v24VoteTime = 0 voteChoices = {} chat("VoteReset passed. The server will reset in 10 seconds.") play_sound(SOUND_MENU_MESSAGE_APPEAR, zero) else popup("Not enough votes to reset the game!", "\\#ff4040\\", 100, true) chat("Not enough votes to reset the game!") reset_vote_state() end end end local function reset_save_file() local file = get_current_save_file_num() - 1 for course = -1, COURSE_MAX - 1 do if course >= -1 then local flags = save_file_get_star_flags(file, course) if flags ~= 0 then save_file_remove_star_flags(file, course, flags) end end end save_file_clear_flags( SAVE_FLAG_HAVE_WING_CAP | SAVE_FLAG_HAVE_METAL_CAP | SAVE_FLAG_HAVE_VANISH_CAP | SAVE_FLAG_HAVE_KEY_1 | SAVE_FLAG_HAVE_KEY_2 | SAVE_FLAG_UNLOCKED_BASEMENT_DOOR | SAVE_FLAG_UNLOCKED_UPSTAIRS_DOOR | SAVE_FLAG_DDD_MOVED_BACK | SAVE_FLAG_MOAT_DRAINED | SAVE_FLAG_UNLOCKED_PSS_DOOR | SAVE_FLAG_UNLOCKED_WF_DOOR | SAVE_FLAG_UNLOCKED_CCM_DOOR | SAVE_FLAG_UNLOCKED_JRB_DOOR | SAVE_FLAG_UNLOCKED_BITDW_DOOR | SAVE_FLAG_UNLOCKED_BITFS_DOOR | SAVE_FLAG_UNLOCKED_50_STAR_DOOR | SAVE_FLAG_COLLECTED_TOAD_STAR_1 | SAVE_FLAG_COLLECTED_TOAD_STAR_2 | SAVE_FLAG_COLLECTED_TOAD_STAR_3 | SAVE_FLAG_COLLECTED_MIPS_STAR_1 | SAVE_FLAG_COLLECTED_MIPS_STAR_2 ) save_file_do_save(file, 1) end local function execute_reset() if not network_is_server() or resetExecuted then return end resetExecuted = true reset_save_file() for i = 0, MAX_PLAYERS - 1 do if gNetworkPlayers[i].connected then gPlayerSyncTable[i].v24Reset = true end end network_send(true, { v24Packet = PACKET_ADMIN, action = "reset_execute" }) popup("\\#ffff00\\SERVER RESET", "\\#ffff00\\", 120) chat("\\#ffff00\\The game has been reset to 0 stars.") gGlobalSyncTable.v24ResetTimer = 0 gGlobalSyncTable.v24ResetStage = 0 end local function apply_local_reset() if not gPlayerSyncTable[0].v24Reset then return end gPlayerSyncTable[0].v24Reset = false warp_to_start_level() gMarioStates[0].numCoins = 0 gMarioStates[0].numStars = 0 end local function unstuck_command() warp_to_start_level() gMarioStates[0].freeze = 0 gMarioStates[0].input = 0 popup("\\#ffffff\\Returned to level spawn.", "\\#00ff80\\", 75) return true end local function votekick_command(msg) if msg == "" then chat("Usage: /votekick ") return true end local name, reason = msg:match("^(%S+)%s*(.*)$") local target = find_player(name) if target < 0 then chat("Player not found.") return true end if target == local_index() then chat("You cannot votekick yourself.") return true end if reason == "" then reason = "No reason provided." end if network_is_server() then server_start_vote(VOTE_KICK, target, reason, local_index()) return true end return start_vote_local(VOTE_KICK, target, reason) end local function votereset_command() if network_is_server() then server_start_vote(VOTE_RESET, -1, "Full server reset", local_index()) return true end return start_vote_local(VOTE_RESET, -1, "Full server reset") end local function serverinfo_command() local uptime = serverFrames local seconds = math.floor(uptime / 30) local h = math.floor(seconds / 3600) local m = math.floor(seconds / 60) % 60 local s = seconds % 60 chat("\\#dcdcdc\\stats:") chat("\\#ffffff\\usage: \\#00ff80\\" .. tostring(serverUsage) .. "%") local pingTotal = 0 local pingCount = 0 for i = 0, MAX_PLAYERS - 1 do if gNetworkPlayers[i].connected then pingTotal = pingTotal + (gNetworkPlayers[i].ping or 0) pingCount = pingCount + 1 end end local globalPing = pingCount > 0 and math.floor(pingTotal / pingCount) or 0 chat("\\#ffffff\\global ping: \\#00d9ff\\" .. tostring(globalPing) .. "ms") chat("\\#ffffff\\anim: \\#ffff00\\" .. tostring(serverAnimCount)) chat("\\#ffffff\\objects: \\#ff80ff\\" .. tostring(serverObjectCount)) chat("\\#ffffff\\players: \\#00ffff\\" .. tostring(player_count()) .. "/" .. tostring(MAX_PLAYERS)) chat("\\#ffffff\\version: \\#dcdcdc\\" .. VERSION) chat("\\#ffffff\\uptime: \\#dcdcdc\\" .. string.format("%02dh %02dm %02ds", h, m, s)) return true end local function rules_command() chat("\\#00d9ff\\rules:") chat("\\#ffffff\\no nsfw content") chat("follow coopnet rules") chat("no racism or hate speech") chat("respect other players") chat("dont be annoying to others") chat("no cheating or exploiting") chat("dont spam chat") chat("dont intentionally crash or lag the server") return true end local function is_moderator() return network_is_server() or network_is_moderator() end local function admin_target_from_arg(text) local target = find_player(text) if target < 0 then chat("Player not found.") return -1 end return target end local function admin_command(msg) if not is_moderator() then chat("You do not have permission to use admin tools.") return true end local command, rest = msg:match("^(%S+)%s*(.*)$") command = string.lower(command or "") if command == "" then adminMenuOpen = not adminMenuOpen chat(adminMenuOpen and "Admin panel opened. Use /admin help." or "Admin panel closed.") return true end if command == "help" then chat("Admin: /admin freeze | /admin unfreeze | /admin speed | /admin size ") return true end local arg1, arg2 = rest:match("^(%S+)%s*(.*)$") local target = admin_target_from_arg(arg1) if target < 0 then return true end if command == "freeze" then network_send(true, { v24Packet = PACKET_ADMIN, action = "freeze", target = target }) chat("\\#ffff00\\Freeze request sent to " .. gNetworkPlayers[target].name .. ".") elseif command == "unfreeze" then network_send(true, { v24Packet = PACKET_ADMIN, action = "unfreeze", target = target }) chat("\\#00ff80\\Unfreeze request sent to " .. gNetworkPlayers[target].name .. ".") elseif command == "speed" then local value = tonumber(arg2) if value == nil then chat("Usage: /admin speed ") return true end value = math.max(0.1, math.min(value, 5.0)) network_send(true, { v24Packet = PACKET_ADMIN, action = "speed", target = target, value = value }) chat("Speed set to " .. tostring(value) .. "x for " .. gNetworkPlayers[target].name .. ".") elseif command == "size" then local value = tonumber(arg2) if value == nil then chat("Usage: /admin size ") return true end value = math.max(0.25, math.min(value, 3.0)) network_send(true, { v24Packet = PACKET_ADMIN, action = "size", target = target, value = value }) chat("Size set to " .. tostring(value) .. "x for " .. gNetworkPlayers[target].name .. ".") else chat("Unknown admin command. Use /admin help.") end return true end local function on_packet_receive(data) if not data or not data.v24Packet then return end if data.v24Packet == PACKET_VOTE_START then if network_is_server() then local target = tonumber(data.target) or -1 local starter = tonumber(data.starter) or -1 if starter >= 0 and starter < MAX_PLAYERS and gNetworkPlayers[starter].connected then if data.voteType == VOTE_KICK then if target >= 0 and target < MAX_PLAYERS and gNetworkPlayers[target].connected and target ~= starter then server_start_vote(VOTE_KICK, target, tostring(data.reason or "No reason provided."), starter) end elseif data.voteType == VOTE_RESET then server_start_vote(VOTE_RESET, -1, "Full server reset", starter) end end end elseif data.v24Packet == PACKET_VOTE_CAST then if network_is_server() then if not active_vote() then return end local index = tonumber(data.player) or -1 if index < 0 or index >= MAX_PLAYERS then return end if not gNetworkPlayers[index].connected then return end if voteChoices[index] ~= nil then return end voteChoices[index] = data.choice and true or false if data.choice then gGlobalSyncTable.v24VoteYes = vote_yes_count() + 1 else gGlobalSyncTable.v24VoteNo = vote_no_count() + 1 end end elseif data.v24Packet == PACKET_ADMIN then local action = tostring(data.action or "") local target = tonumber(data.target) or -1 if network_is_server() then if target < 0 or target >= MAX_PLAYERS or not gNetworkPlayers[target].connected then return end if action == "freeze" or action == "unfreeze" or action == "speed" or action == "size" then network_send_to(target, true, { v24Packet = PACKET_ADMIN_APPLY, action = action, target = target, value = tonumber(data.value) or 1.0 }) if target == 0 then if action == "freeze" then gPlayerSyncTable[0].v24Frozen = true end if action == "unfreeze" then gPlayerSyncTable[0].v24Frozen = false end if action == "speed" then gPlayerSyncTable[0].v24Speed = tonumber(data.value) or 1.0 end if action == "size" then gPlayerSyncTable[0].v24Size = tonumber(data.value) or 1.0 end end end return end if action == "votekick_result" and target == local_index() then localKickedUntil = tonumber(data.untilFrame) or 0 popup("\\#ff4040\\You have been votekicked.", "\\#ff4040\\", 150) elseif action == "reset_execute" then gPlayerSyncTable[0].v24Reset = true end elseif data.v24Packet == PACKET_ADMIN_APPLY then local target = tonumber(data.target) or -1 if target ~= local_index() then return end local action = tostring(data.action or "") if action == "freeze" then gPlayerSyncTable[0].v24Frozen = true elseif action == "unfreeze" then gPlayerSyncTable[0].v24Frozen = false elseif action == "speed" then gPlayerSyncTable[0].v24Speed = tonumber(data.value) or 1.0 elseif action == "size" then gPlayerSyncTable[0].v24Size = tonumber(data.value) or 1.0 end end end local function apply_admin_state(m) local s = gPlayerSyncTable[m.playerIndex] if not s then return end if s.v24Frozen then m.freeze = 1 m.vel.x = 0 m.vel.y = 0 m.vel.z = 0 m.forwardVel = 0 else m.freeze = 0 end local speed = tonumber(s.v24Speed) or 1.0 if speed ~= 1.0 then m.intendedMag = math.max(0, math.min(32, (m.intendedMag or 0) * speed)) end local size = tonumber(s.v24Size) or 1.0 if m.marioObj then obj_set_gfx_scale(m.marioObj, size, size, size) end end local function update_server_stats() if not network_is_server() then return end serverFrames = serverFrames + 1 gGlobalSyncTable.v24ServerFrames = serverFrames local active = 0 local anim = 0 for i = 0, MAX_PLAYERS - 1 do if gNetworkPlayers[i].connected then active = active + 1 local m = gMarioStates[i] if m and m.marioObj then anim = anim + (m.marioObj.header.gfx.animInfo.animFrame or 0) end end end serverAnimCount = active > 0 and math.floor(anim / active) or 0 objectCountTimer = objectCountTimer + 1 if objectCountTimer >= 30 then objectCountTimer = 0 local current = 0 for i = 0, MAX_PLAYERS - 1 do local m = gMarioStates[i] if m and m.marioObj then current = current + 1 end end previousObjectCount = serverObjectCount serverObjectCount = current end local frameLoad = 0 if active > 0 then frameLoad = math.min(99, math.floor((serverAnimCount / 30) * 10 + active * 2)) end serverUsage = math.max(0, math.min(99, frameLoad)) if active_vote() then gGlobalSyncTable.v24VoteTime = math.max(0, gGlobalSyncTable.v24VoteTime - 1) if gGlobalSyncTable.v24VoteTime <= 0 then finish_vote() end end if (gGlobalSyncTable.v24ResetTimer or 0) > 0 then gGlobalSyncTable.v24ResetTimer = math.max(0, gGlobalSyncTable.v24ResetTimer - 1) if gGlobalSyncTable.v24ResetTimer <= 0 then execute_reset() end end if gGlobalSyncTable.v24KickUntil and gGlobalSyncTable.v24KickUntil > 0 and serverFrames >= gGlobalSyncTable.v24KickUntil then gGlobalSyncTable.v24KickIndex = -1 gGlobalSyncTable.v24KickUntil = 0 end end local function update_client_state() apply_local_reset() apply_admin_state(gMarioStates[0]) if localKickedUntil > 0 then local now = gGlobalSyncTable.v24ServerFrames or 0 if now < localKickedUntil then gMarioStates[0].freeze = 1 else localKickedUntil = 0 gMarioStates[0].freeze = 0 end end end local function render_serverinfo_hud() local uptime = gGlobalSyncTable.v24ServerFrames or 0 local seconds = math.floor(uptime / 30) local h = math.floor(seconds / 3600) local m = math.floor(seconds / 60) % 60 local s = seconds % 60 local text = string.format("uptime: %02dh %02dm %02ds", h, m, s) local width = djui_hud_get_screen_width() djui_hud_set_font(FONT_NORMAL) local scale = 0.55 local tw = djui_hud_measure_text(text) * scale djui_hud_set_color(0, 0, 0, 150) djui_hud_render_rect(width - tw - 18, 8, tw + 12, 18) djui_hud_set_color(255, 255, 255, 255) djui_hud_print_text(text, width - tw - 12, 11, scale) end local function render_vote_hud() if not active_vote() and (gGlobalSyncTable.v24ResetTimer or 0) <= 0 and localPopupTimer <= 0 then return end djui_hud_set_resolution(RESOLUTION_N64) djui_hud_set_font(FONT_NORMAL) local width = djui_hud_get_screen_width() local height = djui_hud_get_screen_height() if active_vote() then local boxW = 190 local boxH = gGlobalSyncTable.v24VoteType == VOTE_KICK and 108 or 86 local x = (width - boxW) * 0.5 local y = (height - boxH) * 0.5 local remaining = math.max(0, math.min(1, (gGlobalSyncTable.v24VoteTime or 0) / VOTE_DURATION)) djui_hud_set_color(0, 0, 0, 225) djui_hud_render_rect(x, y, boxW, boxH) djui_hud_set_color(255, 35, 35, 255) djui_hud_render_rect(x, y, boxW * remaining, 2) djui_hud_set_color(255, 255, 255, 255) local title = gGlobalSyncTable.v24VoteType == VOTE_KICK and "Votekick" or "VoteReset" local seconds = math.max(0, math.ceil((gGlobalSyncTable.v24VoteTime or 0) / 30)) djui_hud_print_text(title, x + 10, y + 7, 0.75) djui_hud_print_text("(" .. tostring(seconds) .. "s)", x + boxW - 34, y + 7, 0.5) if gGlobalSyncTable.v24VoteType == VOTE_KICK then local target = gGlobalSyncTable.v24VoteTarget local name = target >= 0 and gNetworkPlayers[target].name or "Unknown" local starter = gGlobalSyncTable.v24VoteStarter or -1 local starterName = starter >= 0 and gNetworkPlayers[starter].name or "A player" djui_hud_print_text(starterName, x + 10, y + 26, 0.5) djui_hud_print_text("requests the user", x + 10, y + 37, 0.5) djui_hud_print_text(name, x + 10, y + 48, 0.65) djui_hud_print_text("to be kicked.", x + 10, y + 59, 0.5) djui_hud_print_text("Reason:", x + 10, y + 74, 0.5) djui_hud_print_text(gGlobalSyncTable.v24VoteReason or "No reason provided.", x + 48, y + 74, 0.5) else djui_hud_print_text("A full server reset", x + 10, y + 31, 0.58) djui_hud_print_text("has been requested.", x + 10, y + 43, 0.58) end local required = required_votes(math.max(1, gGlobalSyncTable.v24VoteTotal or player_count())) local yes = vote_yes_count() local no = vote_no_count() djui_hud_set_color(255, 255, 255, 255) djui_hud_print_text("yes " .. tostring(yes) .. "/" .. tostring(required), x + 10, y + boxH - 18, 0.5) djui_hud_print_text("no " .. tostring(no), x + 65, y + boxH - 18, 0.5) djui_hud_print_text("/vote-yes", x + 96, y + boxH - 18, 0.5) djui_hud_print_text("/vote-no", x + 145, y + boxH - 18, 0.5) end if (gGlobalSyncTable.v24ResetTimer or 0) > 0 then local seconds = math.max(0, math.ceil(gGlobalSyncTable.v24ResetTimer / 30)) djui_hud_set_color(0, 0, 0, 220) djui_hud_render_rect(0, height * 0.5 - 16, width, 32) djui_hud_set_color(255, 255, 255, 255) local text = "RESETTING THE SERVER IN " .. tostring(seconds) .. "..." local tw = djui_hud_measure_text(text) * 0.55 djui_hud_print_text(text, width * 0.5 - tw * 0.5, height * 0.5 - 7, 0.55) end if localPopupTimer > 0 then localPopupTimer = localPopupTimer - 1 local boxW = 230 local x = (width - boxW) * 0.5 local y = height - 38 djui_hud_set_color(0, 0, 0, 220) djui_hud_render_rect(x, y, boxW, 22) djui_hud_set_color(255, 255, 255, 255) djui_hud_print_text(localPopupColor .. localPopup, x + 8, y + 6, 0.48) end end local function on_hud_render() render_serverinfo_hud() render_vote_hud() end local function packet_safe_init() gGlobalSyncTable.v24VoteType = gGlobalSyncTable.v24VoteType or VOTE_NONE gGlobalSyncTable.v24VoteTarget = gGlobalSyncTable.v24VoteTarget or -1 gGlobalSyncTable.v24VoteTime = gGlobalSyncTable.v24VoteTime or 0 gGlobalSyncTable.v24VoteYes = gGlobalSyncTable.v24VoteYes or 0 gGlobalSyncTable.v24VoteNo = gGlobalSyncTable.v24VoteNo or 0 gGlobalSyncTable.v24VoteTotal = gGlobalSyncTable.v24VoteTotal or 0 gGlobalSyncTable.v24VoteStarter = gGlobalSyncTable.v24VoteStarter or -1 gGlobalSyncTable.v24VoteReason = gGlobalSyncTable.v24VoteReason or "" gGlobalSyncTable.v24ResetTimer = gGlobalSyncTable.v24ResetTimer or 0 gGlobalSyncTable.v24ResetStage = gGlobalSyncTable.v24ResetStage or 0 gGlobalSyncTable.v24ServerFrames = gGlobalSyncTable.v24ServerFrames or 0 end local function on_player_connected(m) if network_is_server() then if gGlobalSyncTable.v24VoteType == nil then initialize_sync() end end end packet_safe_init() initialize_sync() hook_chat_command("votekick", " - Start a votekick.", votekick_command) hook_chat_command("votereset", "- Start a vote to reset the server.", votereset_command) hook_chat_command("vote-yes", "- Vote yes on the active vote.", function() return cast_vote(true) end) hook_chat_command("vote-no", "- Vote no on the active vote.", function() return cast_vote(false) end) hook_chat_command("unstuck", "- Return to the level spawn.", unstuck_command) hook_chat_command("serverinfo", "- Display server information.", serverinfo_command) hook_chat_command("rules", "- Display the server rules.", rules_command) hook_chat_command("admin", "[help|freeze|unfreeze|speed|size] - Admin tools.", admin_command) hook_event(HOOK_UPDATE, update_server_stats) hook_event(HOOK_MARIO_UPDATE, update_client_state) hook_event(HOOK_ON_HUD_RENDER, on_hud_render) hook_event(HOOK_ON_PACKET_RECEIVE, on_packet_receive) hook_event(HOOK_ON_PLAYER_CONNECTED, on_player_connected) hook_event(HOOK_ON_PLAYER_DISCONNECTED, function(m) if not network_is_server() then return end local index = m.playerIndex voteChoices[index] = nil if gGlobalSyncTable.v24VoteTarget == index then reset_vote_state() chat("The active vote was cancelled because its target disconnected.") elseif active_vote() then gGlobalSyncTable.v24VoteTotal = player_count() end end) hook_event(HOOK_ON_LEVEL_INIT, function() resetExecuted = false end) hook_event(HOOK_ON_EXIT, function() if network_is_server() then reset_vote_state() gGlobalSyncTable.v24ResetTimer = 0 end end) if network_is_server() then gGlobalSyncTable.v24ServerFrames = 0 end