--Ultimate AFK-Handling Script --Created by Wizard, in 2012/2013 --This script completely revamps AFK-handling in a revolutionary new way. --Version 1.01 (released July 30th 2014) --This is how many minutes before a player will get set to the AFK mode. --The AFK mode means they're hidden, and put under the map so no one will see them. timeafkbeforeset = 0.75 -- minutes --This is how many minutes before an afk player will be kicked from the server --REMINDER: This will ONLY kick if maxplayersbeforekick is reached. timeafkbeforekick = 2 -- minutes --This determines how many people need to be in the server before the script will kick an afk. --The script will kick 1 afk, and it will kick the player with the LONGEST AFK TIME. maxplayersbeforekick = 16 --Set this to true if you're using zombies, false if not. zombies = false --Admins can still be set to the AFK mode even if this is true. dont_kick_admins = true --General messages, set to nil to disable them. afkkick_msg = "%s is afk and is now being kicked from the server!" afkset_msg = "%s is now afk!" afkunset_msg = "%s is no longer afk!" --this was never finished, expect this in a later release! afk_portal_to_teamhog_race = true -- Set this to true to allow afks to be put into hogs of their teammates (RACE ONLY) --don't touch anything below this point -> . CurrentAim = {} playerWasKilled = {} afk = {} afktime = {} currentplayers = 0 isKicked = {} justSpawned = {} afkSpot = {} pdeaths = {} randomNames = {"Butcher", "Caboose", "Crazy", "Cupid", "Darling", "Dasher", "Disco", "Donut", "Dopey", "Ghost", "Goat", "Grumpy", "Hambone", "Hollywood", "Howard", "Jack", "Killer", "King", "Mopey", "Noodle", "Penguin", "Pirate", "Prancer", "Saucy", "Shadow", "Sleepy", "Snake", "Sneak", "Stompy", "Stumpy", "The Bear", "The Big L", "Tooth", "Walla Walla", "Weasel", "Wheezy", "Whicker", "Whisp", "Wilshire"} sharedhashes = { "f443106bd82fd6f3c22ba2df7c5e4094", "c702226e783ea7e091c0bb44c2d0ec64", "d72b3f33bfb7266a8d0f13b37c62fddb", "55d368354b5021e7dd5d3d1525a4ab82", "3d5cd27b3fa487b040043273fa00f51b", "b661a51d4ccf44f5da2869b0055563cb", "740da6bafb23c2fbdc5140b5d320edb1", "10440b462f6cbc3160c6280c2734f184", "7503dad2a08026fc4b6cfb32a940cfe0", "4486253cba68da6786359e7ff2c7b467", "f1d7c0018e1648d7d48f257dc35e9660", "40da66d41e9c79172a84eef745739521", "2863ab7e0e7371f9a6b3f0440c06c560", "34146dc35d583f2b34693a83469fac2a", "b315d022891afedf2e6bc7e5aaf2d357", "81f9c914b3402c2702a12dc1405247ee", "63bf3d5a51b292cd0702135f6f566bd1", "6891d0a75336a75f9d03bb5e51a53095", "325a53c37324e4adb484d7a9c6741314", "0e3c41078d06f7f502e4bb5bd886772a", "fc65cda372eeb75fc1a2e7d19e91a86f", "f35309a653ae6243dab90c203fa50000", "50bbef5ebf4e0393016d129a545bd09d", "a77ee0be91bd38a0635b65991bc4b686", "3126fab3615a94119d5fe9eead1e88c1", } function GetRequiredVersion() return 200 end function OnScriptLoad(processid, game, persistent) if zombies then zombie_team = getZombieTeam() end for i = 0,15 do if getplayer(i) then local index = getname(i) afktime[index] = 0 currentplayers = currentplayers + 1 afkSpot[index] = {} end end GetGameAddresses(game) registertimer(500, "gameStartedCheck") gametype = readbyte(gametype_base + 0x30) if gametype == 1 then gametype = "CTF" elseif gametype == 2 then gametype = "Slayer" elseif gametype == 4 then gametype = "KOTH" end afktimer = registertimer(500, "afkTimer") handleafks = registertimer(1000, "handleAfks") --using the hide method seemed to crash the server occasionally --hiddentimer = registertimer(20, "hiddenTimer") end function gameStartedCheck(id, count) if not game_started then setGameVariables() end return false end function GetGameAddresses(game) if game == "PC" then ctf_globals = 0x639B98 -- Confirmed. oddball_globals = 0x639E18 -- Confirmed. slayer_globals = 0x63A0E8 name_base = 0x745D4A flag_respawn_addr = 0x488A7E specs_addr = 0x662D04 map_addr = 0x698F21 hashcheck_addr = 0x59c280 versioncheck_addr = 0x5152E7 map_pointer = 0x63525c gametype_base = 0x671340 gametime_base = 0x671420 network_server_globals = 0x69B934 -- Confirmed. machine_pointer = 0x745BA0 version_address = 0x5DF840 timelimit_address = 0x626630 profilepath = 0x6A0A99 special_chars = 0x517D6B -- special chars in svname patch gametype_patch = 0x481F3C -- gametype patch devmode_patch1 = 0x4A4DBF -- devmode devmode_patch2 = 0x4A4E7F -- devmode network_base = 0x745BA8 else ctf_globals = 0x5BDBB8 -- Confirmed. oddball_globals = 0x5BDE78 -- Confirmed. slayer_globals = 0x5BE108 name_base = 0x6C7B6A specs_addr = 0x5E6E63 map_addr = 0x61D151 hashcheck_addr = 0x530130 versioncheck_addr = 0x4CB587 map_pointer = 0x5B927C gametype_base = 0x5F5498 gametime_base = 0x5F55BC network_server_globals = 0x61FB64 -- Confirmed. machine_pointer = 0x6C7980 version_address = 0x564B34 gametype_patch = 0x45E50C devmode_patch1 = 0x47DF0C devmode_patch2 = 0x47DFBC -- devmode network_base = 0x6C7988 end end function setGameVariables() LoadTags() teamplay = readbyte(gametype_base + 0x34) if teamplay == 1 then teamplay = true else teamplay = false end gametype_lives = readbyte(gametype_base + 0x50) if gametype_lives == 0 then gametype_lives = false end gametype_game = readdword(gametype_base + 0x30) if gametype_game == 1 then func = CtfClientUpdate registertimer(200, "CtfTimer") gametype = "CTF" elseif gametype_game == 2 then killinorder = readbyte(gametype_base + 0x7E) if killinorder == 1 then killinorder = true else killinorder = false end gametype = "Slayer" elseif gametype_game == 3 then --func = OddballClientUpdate gametype = "Oddball" elseif gametype_game == 4 then --registertimer(200, "KothTimer") gametype = "King" elseif gametype_game == 5 then race_type = readbyte(gametype_base + 0x7C) if race_type == 1 or race_type == 2 then --func = RaceClientUpdate registertimer(200, "RaceTimer") elseif race_type == 3 then --func = RallyClientUpdate end gametype = "Race" total_checkpoints = readdword(race_globals) end end function OnScriptUnload() removetimer(afktimer) removetimer(handleafks) end function LoadTags() -- Globals global_distanceId = gettagid("jpt!", "globals\\distance") global_fallingId = gettagid("jpt!", "globals\\falling") end function OnNewGame(map) if not afktimer then afktimer = registertimer(500, "afkTimer") end game_started = true setGameVariables() ongameend = false end function OnGameEnd(stage) if stage == 1 then removetimer(afktimer) ongameend = true end end function OnServerChat(player, mode, message) if message:find("!afkstatus") then player = tonumber(message:sub(12, 13)) privatesay(player, tostring(getname(player)) .. ": " .. tostring(playerIsAfk(player))) return false end if tonumber(player) then if player >= 0 and player <= 15 then if getplayer(player) then local index = getname(player) afktime[index] = 0 end end end end --[[ function OnServerCommandAttempt(player, command, password) --return true end --]] --[[ function OnServerCommand(player, command) --return true end --]] --[[ function OnNameRequest(hash, name) --return true, name end --]] --[[ function OnTeamDecision(team) --return team end --]] function OnPlayerJoin(player) if not ongameend then local index = getname(player) currentplayers = currentplayers + 1 afktime[index] = 0 CurrentAim[index] = {} afkSpot[index] = {} else sayWizard("WTF! ONGAMEEND IS TRUE BRO") end end function OnPlayerLeave(player) if not ongameend then local index = getname(player) currentplayers = currentplayers - 1 afktime[index] = 0 afk[index] = nil CurrentAim[index] = nil isKicked[player] = nil afkSpot[index] = {} playerWasKilled[player] = nil justSpawned[player] = nil local hash = gethash(player) local name = getname(player) local ip = getip(player) local ipport = getip(player) .. ":" .. getport(player) for i = 1,#indexes do if indexes[i] == hash or indexes[i] == name or indexes[i] == ip or indexes[i] == ipport then indexes[i] = nil end end end end function OnPlayerKill(killer, victim, mode) if tonumber(victim) then playerWasKilled[victim] = true if justSpawned[victim] then removetimer(justSpawned[victim]) justSpawned[victim] = nil end end end --[[ function OnKillMultiplier(player, multiplier) end --]] --[[ function OnPlayerSpawn(player) end --]] function OnPlayerSpawnEnd(player) justSpawned[player] = registertimer(800, "JustSpawned", player) playerWasKilled[player] = nil end function JustSpawned(id, count, player) updatePlayerAim(player) --if getname(player) then sayWizard(tostring(getname(player)) .. " has spawned! Possibly no longer afk!") end if getplayer(player) then local index = getname(player) if afk[index] then local m_objectId = getplayerobjectid(player) if m_objectId and m_objectId ~= 0xFFFFFFFF then local m_object = getobject(m_objectId) if m_object then --comment all of below for hide method (remember to uncomment hidetimer at top) writebit(m_object + 0x10, 7, 0) local x,y,z = getobjectcoords(m_objectId) z = z or 69 movobjectcoords(m_objectId, x, y, z-100) local m_object = getobject(m_objectId) afkSpot[index] = {x, y, z} end end end end justSpawned[player] = nil return false end function OnTeamChange(player, old_team, new_team, voluntary) playerWasKilled[player] = true --return true end --[[ function OnClientUpdate(player) end --]] --[[ function OnObjectInteraction(player, objId, mapId) --return true end --]] --[[ function OnWeaponReload(player, weapId) --return true end --]] --[[ function OnVehicleEntry(player, vehiId, seat, mapId, voluntary) --return true end --]] --[[ function OnVehicleEject(player, voluntary) --return true end --]] function OnDamageLookup(receiving_obj, causing_obj, mapId, tagdata) if mapId == global_distanceId or mapId == global_fallingId then odl_multiplier(0.00001) end --return true end --[[ function OnDamageApplication(receiver, causer, mapId, location, backtap) --return true end --]] --[[ function OnWeaponAssignment(player, objId, slot, weapId) return true end --]] --[[ function OnObjectCreationAttempt(mapId, parentId, player) --return mapId end --]] --[[ function OnObjectCreation(objId) end --]] --This timer will add the AFK times of all the players to a table --so it can be used by the other timers. This timer also unsets afk players. function afkTimer(id, count) for i = 0,15 do if getplayer(i) then local index = getname(i) if not playerWasKilled[i] and not justSpawned[i] then if playerIsAfk(i) then afktime[index] = afktime[index] + (1/120) --sayWizard(tostring(getname(i)) .. ": is not moving! AFKTIME: " .. tostring(afktime[index])) else --check if the player is set as afk --if they are, unafk them. --REM: THIS NEEDS TO BE IN AFKTIMER AND NOT HANDLEAFKS if afk[index] then afk[index] = nil unsetAfkPlayer(i) end afktime[index] = 0 --sayWizard(tostring(getname(i)) .. " is moving!") end end end end return true end --this timer is responsible for handling AFK players if they're AFK for a set amount of time function handleAfks(id, count) local player = getLongestAfkPlayer() if player[2] >= timeafkbeforekick then if currentplayers >= maxplayersbeforekick and not kickingHappening then if (not isadmin(player[1]) and dont_kick_admins) or not dont_kick_admins then if afkkick_msg then say(string.format(afkkick_msg, getname(player[1]))) end svcmd("sv_kick " .. resolveplayer(player[1])) log_msg(3, "AFKBEAST IS NOW KICKING " .. tostring(resolveplayer(player[1])) .. " NAME: " .. tostring(getname(player[1])) .. ", DEBUG INFO FOLLOWS: Currentplayers: " .. currentplayers .. " Maxplayersbeforekick: " .. maxplayersbeforekick .. "KickHappening: " .. tostring(kickingHappening) .. "isKickedTable: " .. tostring(isKicked[player[1]]) .. " AFKTime: " .. tostring(player[2])) isKicked[player[1]] = true kickingHappening = true end end end for i = 0,15 do if getplayer(i) then if not playerWasKilled[i] and not justSpawned[i] then local index = getname(i) if afktime[index] >= timeafkbeforeset and not afk[index] then if gametype ~= "CTF" or (gametype == "CTF" and not isHoldingFlag(i)) then setAfkPlayer(i) --sayWizard("SETTING AFK PLAYER: " .. i .. " NOW") if afkset_msg then say(string.format(afkset_msg, getname(i))) end end else --say(tostring(afktime[index]) .. " NOT ENOUGH AFK TO SET") end end end end if zombies then checkIfAllZombiesAfk() end return true end function isHoldingFlag(player) local redflag = readdword(ctf_globals) local blueflag = readdword(ctf_globals + 0x4) local m_objectId = getplayerobjectid(player) if m_objectId and m_objectId ~= 0xFFFFFFFF then local m_object = getobject(m_objectId) if m_object then local weapId = readdword(m_object + 0x118) if weapId == redflag or weapId == blueflag then return true end end end return false end --this timer is responsible for hiding afk players. --sometimes causes crash, therefore not used. function hiddenTimer(id, count) if afk ~= {} then for k,v in pairs(afk) do local m_player = getplayer(k) if m_player then --say("HIDING: " .. tostring(getname(k))) .. " Player #: " .. k) writefloat(m_player + 0xF8, 999) writefloat(m_player + 0xFC, 999) writefloat(m_player + 0x100, 999) end end end return true end --this function will loop through all the players and return the player --that has been AFK the longest. function getLongestAfkPlayer() local max = {0,0} for k,v in pairs(afktime) do if max[2] < afktime[k] and not afk[k] then max = {k, v} end end return max end --This function will update the player's aim in the currentaim table. function updatePlayerAim(player) local m_player = getplayer(player) if m_player then local index = getname(player) local m_objectId = getplayerobjectid(player) if m_objectId and m_objectId ~= 0xFFFFFFFF then local m_object = getobject(m_objectId) if m_object then local x_aim = readfloat(m_object + 0x230) local y_aim = readfloat(m_object + 0x234) local z_aim = readfloat(m_object + 0x238) CurrentAim[index] = {x_aim, y_aim, z_aim} end end end end --This function will determine if the passed player is AFK function playerIsAfk(player) local m_player = getplayer(player) if m_player then if not justSpawned[player] then --if getname(player) then sayWizard("PLAYERISAFK") end local index = getname(player) local m_objectId = getplayerobjectid(player) if m_objectId and m_objectId ~= 0xFFFFFFFF then local m_object = getobject(m_objectId) if m_object then local x_aim = readfloat(m_object + 0x230) local y_aim = readfloat(m_object + 0x234) local z_aim = readfloat(m_object + 0x238) if not CurrentAim[index] then CurrentAim[index] = {x_aim, y_aim, z_aim} --if getname(player) then sayWizard(getname(player) .. " this shouldn't be called when killed") end --if getname(player) then sayWizard(getname(player) .. ": CURRENT AIM CREATED") end return false else if (x_aim ~= CurrentAim[index][1]) or (y_aim ~= CurrentAim[index][2]) or (z_aim ~= CurrentAim[index][3]) then CurrentAim[index] = {x_aim, y_aim, z_aim} --if getname(player) then sayWizard(tostring(getname(player)) .. ": CURRENT AIM UPDATED IN PLAYERISAFK") end return false else --if getname(player) then sayWizard(tostring(getname(player)) .. " is afk (towiz)") end return true end end end else --if getname(player) then sayWizard("M_OBJECTID IS NIL") end return false end else --if getname(player) then sayWizard("JustSpawned") end return true end else --if getname(player) then sayWizard("M_PLAYER IS NIL IN PLAYERISAFK") end return true end return false end function checkIfAfk(player) local m_player = getplayer(player) if m_player then local index = getname(player) if afk[index] then return true end end return false end --This function will check if all the zombies are afk, if they are --then this function will change a random person's team function checkIfAllZombiesAfk() local zombies = getZombies() if #zombies >= 1 then --this next section will determine if all the zombies are afk or not local bool = true for i = 1,#zombies do if not checkIfAfk(zombies[i]) then bool = false break end end --now we change a random person to zombie cuz all the zombies are currently afk if bool then local player = ChooseRandomPlayer(1) --if getname(player) then say(tostring(getname(player)) .. " will be changed to zombie because all the zombies are afk!") end --if getname(player) then sayWizard(tostring(#zombies)) end changeteam(player, false) kill(player) else --if getname(player) then sayWizard("NO ZOMBIES ARE AFK") end end end end function sayWizard(message) for i = 0,15 do if getplayer(i) then if gethash(i):find("56d5f") then privatesay(i, message) break end end end end -- this function searches through current players and selects a random one function ChooseRandomPlayer(excludeTeam) local t = {} -- loop through all 16 possible spots and add to table for i = 0,15 do -- check if the player exists if getplayer(i) then local team = getteam(i) if team and team ~= excludeTeam then table.insert(t, i) end end end if #t > 0 then -- generate a random number that we will use to select a player local tableCount = #t local r = math.random(1, tableCount) return t[r] else return nil end end --This function will set the player as AFK. It will put them under the map, and turn fall damage off for the player. function setAfkPlayer(player) local index = getname(player) updatePlayerAim(player) local m_objectId = getplayerobjectid(player) or 0xFFFFFFFF local m_object = getobject(m_objectId) if m_object then local killplayer if gametype_lives then local m_player = getplayer(player) if m_player then local deaths = readshort(m_player+0xAE) pdeaths[player] = deaths writeshort(m_player+0xAE, gametype_lives-1) writeshort(stat_globals+player*0x30+0x14, gametype_lives-1) killplayer = true end end if zombies then if getteam(player) ~= zombie_team then --sayWizard("CHANGING TEAM") changeteam(player, false) killplayer = true end end if not killplayer then local x,y,z = getobjectcoords(m_objectId) if tonumber(z) then movobjectcoords(m_objectId, x, y, z-100) end afkSpot[index] = {x, y, z} writebit(m_object + 0x10, 0, 1) else kill(player) afkSpot[index] = {} end end afk[index] = true end --This function unsets an AFK player so they can return to the game. function unsetAfkPlayer(player) local index = getname(player) if afkunset_msg then say(string.format(afkunset_msg, getname(player))) end afk[index] = nil local m_objectId = getplayerobjectid(player) if m_objectId and m_objectId ~= 0xFFFFFFFF then local m_object = getobject(m_objectId) if m_object then movobjectcoords(m_objectId, afkSpot[index][1], afkSpot[index][2], afkSpot[index][3] + 2) writebit(m_object + 0x10, 0, 0) afkSpot[index] = {} end end if gametype_lives then local m_player = getplayer(player) if m_player then writeshort(m_player+0xAE, pdeaths[player]-1) writeshort(stat_globals+player*0x30+0x14, pdeaths[player]-1) writedword(m_player+0x2C, 0) kill(player) end end end function getZombies() local zombies = {} for i = 0,15 do local m_player = getplayer(i) if m_player then local team = readbyte(m_player + 0x20) if team == zombie_team then table.insert(zombies, i) end end end --[[local msgz = "" for i = 1,#zombies do local name = getname(zombies[i]) if name then msgz = msgz .. name .. " " end end sayWizard(msgz)--]] return zombies end function getZombieTeam() return 1 end function hashtoplayer(hash) for i = 0,15 do local m_player = getplayer(i) if m_player then if gethash(i) == hash then return i end end end end --REM: fix this function... doesn't seem to uniquely index right.. shame.. :/ indexes = {} function getindex(player, method) local m_player = getplayer(player) if m_player then local name = getname(player) local ip = getip(player) local hash = gethash(player) local ipport = getip(player) .. ":" .. getport(player) for i = 1,#indexes do if indexes[i] == ip then for k,v in pairs(_G) do if type(v) == "table" then for k2,v2 in pairs(v) do if k2 == ip then indexes[i] = nil v[k2] = nil indexes[i] = v2 v[getindex(player, "hash")] = v2 end end end end break elseif indexes[i] == name or indexes[i] == hash or indexes[i] == ipport then return indexes[i] end end if not method then method = "name" end if method == "hash" then for i = 1,#sharedhashes do if sharedhashes[i] == hash then --log_msg(2, "UNABLE TO FIND UNIQUE INDEX FOR PLAYER " .. resolveplayer(player) .. ". NAME: " .. name .. " IP: " .. ip .. " Hash: " .. hash) table.insert(indexes, ipport) return ipport end end table.insert(indexes, hash) return hash elseif method == "ip" then for i = 0,15 do if getplayer(i) then local ip2 = getip(i) if ip2 == ip then return getindex(player, "hash") end end end table.insert(indexes, ip) return ip elseif method == "name" then for i = 1,#randomNames do if randomNames[i] == name then return getindex(player, "ip") end end table.insert(indexes, name) return name end end end --Phasor's getrandomnumber and lua's math.random really suck. math.randomseed(os.time()) getsuckyrand = math.random --Low and High are INCLUSIVE. function math.random(low, high) low = tonumber(low) or raiseerror("Bad argument #1 to 'math.random' (number expected, got " .. tostring(type(low)) .. ")") high = tonumber(high) or raiseerror("Bad argument #2 to 'math.random' (number expected, got " .. tostring(type(high)) .. ")") getsuckyrand(low, high) getsuckyrand(low, high) getsuckyrand(low, high) -- I really don't trust it... haha return getsuckyrand(low, high) end