/* AMX Mod X script. Round Non-Stop Plugin (c) Copyright 2006-2007, Simon Logic (slspam@land.ru) This file is provided as is (no warranties). Preamble: This plugin is intended to replace old & buggy Fake Team Bot plugin (by OneEyed). Info: This plugin allows you to play a map without round restart. Attention! Using this plugin has sense only when free for all mode is enabled (e.g. by CSDM) and/or there is a registered client command or server time-function which respawns dead players. Such a command/function is usually provided by another plugin. By this time plugin has autodetect feature to be activated only if the following plugins have been detected: * amx_respawn by f117bomb * CSDM by CSDM Team Still you have a chance to test this plugin on your own without having above plugins by using a command 'amx_force_round_nonstop' or cvar 'amx_round_nonstop'. Note: This plugin will not 100% stop round restart because it does not remove map CS specific objectives. Use should use another plugin (e.g CSDM) in conjunction with this one. Requirements: * CS/CZ mod * Fakemeta & CStrike modules New commands: amx_force_round_nonstop turns round non-stop on/off/autodetect immediately amx_round_nonstop_state prints plugin status New cvars: amx_round_nonstop <-1|0|1> (default=-1) controls plugin behaviour: -1 - autodetect 0 - always off 1 - always on amx_round_nonstop_flags (default=abdfg) customize plugin: a - hide system bots (visually); turning this flag off is ususally useful for testing or fun b - move system bots high enough to free up spawn area (recommended) c - activate anti-idle-kick mechanism (recommended when 'mp_autokick' is on); feature is activated on bot spawn only d - kick system bots when server is full (actually max players per each team is considered) e - kick system bots when server is empty; otherwise system bots will enter the server before any player f - show bots as spectators on Score Table; this flag also makes bots totally be hidden on the radar g - show bots ping as BOT on Score Table amx_round_nonstop_botname_t (default='.') customize system bot name for TERRORIST team (applied on bot respawn only) amx_round_nonstop_botname_ct (default=':') customize system bot name for CT team (applied on bot respawn only) Known issues: * make sure a map has at least two spawn points for each side, otherwise you may get troubles on game commencing Credits: * OneEyed for the plugin main idea * Space Headed Productions team for their BotAPI * VEN for his discovery of CBasePlayer::m_fLastMovement offset * jim_yang for the idea of showing system bots as spectators on Score Table (idea has been taken from Roundend Blocker plugin) TODO: * introduce plugin management via menu * fix a bug with round restart absence when system bot enters the opposite team and becomes the first & only player per team History: 0.3.6 [2007-11-16] ! bots won't left the server when there were no real players & 'amx_round_nonstop_flags' had 'e' flag 0.3.5 [2007-10-04] ! fixed bot creating attempts when there are no spawn points per team at all 0.3.4 [2007-06-24] ! fixed absence of METAMOD return result within onStartFrame() + system bots get ADMIN_IMMUNITY privilege now ! fixed an issue with senseless of moving bots high on DE maps ! fixed description of flag 'e' * plugin does not scramble radar now * plugin does not block timer ticking now 0.3.3 [2007-02-26] ! fixed a bug with incorrect detecting of full server using 'maxplayers' value ! fixed bad porting of BotAPI partial functionality (system bots won't hidden at all on respawn; they were hidden on round restart only) + added new flag 'g' for 'amx_round_nonstop_flags' cvar (see description above) * only one generic task (taskCheckCvar) is allowed (optimization) 0.3.2 [2007-02-22] + cvar 'amx_round_nonstop_botnamet' + cvar 'amx_round_nonstop_botnamect' + extended 'amx_force_round_nonstop' command - avoid using of BotAPI (too many complains on compiling, sorry) - removed redundant mod validation because CStrike module is required ! forgot to check 'maxplayers' on detecting a full server (spawn points quantity was previously used) 0.3.1 [2007-02-16] + added possibilty to show system bots as spectators on Score Table (see new flag 'f' for 'amx_round_nonstop_flags' cvar) 0.3.0 [2007-02-15] ! many fixes + added anti-idle-kick mechanism for system bots * renamed cvar 'amx_round_nonstop_safety' to 'amx_round_nonstop_flags' + added more flag values for cvar 'amx_round_nonstop_flags' to extend plugin features * plugins is rewritten from task-driven to event-driven mechanism (as i planned long ago) 0.2.0 [2006-12-21] * ported from AMX Mod 0.9.9 to AMX Mod X + system bots now hidden on a radar + replaced 'Game commencing' with 'Round Non-Stop commencing' 0.1.2 [2006-11-26] + added 'amx_round_nonstop_safety' cvar 0.1.1 [2006-11-16] * first public beta release 0.1.0 [2006-11-12] * internal release for testing */ #include #include // is_running() #include #include #include //#include #pragma tabsize 4 #define MY_PLUGIN_NAME "Round Non-Stop" #define MY_PLUGIN_VERSION "0.3.6" #define MY_PLUGIN_AUTHOR "Simon Logic" #define MAX_CLIENTS 32 // max number of players on server #define MAX_BOT_NAME 31 // max length of the bot name #define MAX_TEAMS 3 // max number of teams in game + one for index padding #define MAX_MAP_COORD_Z 4095.0 #define TASK_ANTIIDLEKICK_BASE 65 #define TASK_CHECKCVAR_ID 70 #define OFFSET_LAST_MOVEMENT 124 // for 32bit cpu only // plugin flags... #define SFL_HIDE (1<<0) #define SFL_TAKEOFFSPAWNAREA (1<<1) #define SFL_ANTIIDLEKICK (1<<2) #define SFL_KICKONSERVERFULL (1<<3) #define SFL_KICKONSERVEREMPTY (1<<4) #define SFL_SHOWASSPECTATORS (1<<5) #define SFL_SHOWBOTPINGASBOT (1<<6) // spawn IDs (first two must match CS_TEAM_X)... #define SPID_T 1 #define SPID_CT 2 #define SPID_VIP 3 // compilation options... //#define _DEBUG // compile with debug messages //#define _ASSERT // compile with assertions enum t_bot { i_id, i_task_antikick // s_name[MAX_BOT_NAME+1] } new const g_arrBotNameDef[MAX_TEAMS][] = { // default names "", // a pad to match array index to team index ".", // terrorist bot name ":" // ct bot name } new const g_arrSpawnClasses[][] = { // index must match SPID_* "", "info_player_deathmatch", // CS_TEAM_T "info_player_start", // CS_TEAM_CT "info_vip_start" } new const // other resource strings g_sClassname[] = "classname", g_sGameCommencing[] = "#Game_Commencing" new g_sSpectator[] = "SPECTATOR" new bool:g_bAMD64 new bool:g_bFalseRespawn[MAX_CLIENTS+1] new g_iRoundTime new g_iMaxPlayers new g_iSpawnCount[MAX_TEAMS] new g_tBots[MAX_TEAMS][t_bot] // 1st element is dummy element new Float:g_fGenericTaskTime new g_cvarState, g_cvarFlags, g_cvarAmxRespawn, g_cvarCsdmActive new g_cvarBotNames[MAX_TEAMS] new g_fmSetOrigin forward bool:existsRespawnEngine() forward bool:isActive() //----------------------------------------------------------------------------- public plugin_init() { g_bAMD64 = bool:is_amd64_server() g_iMaxPlayers = get_maxplayers() g_tBots[_:CS_TEAM_T][i_id] = 0 g_tBots[_:CS_TEAM_CT][i_id] = 0 register_plugin(MY_PLUGIN_NAME, MY_PLUGIN_VERSION, MY_PLUGIN_AUTHOR) g_iRoundTime = 1 g_cvarState = register_cvar("amx_round_nonstop", "-1") g_cvarFlags = register_cvar("amx_round_nonstop_flags", "abdfg") g_cvarBotNames[_:CS_TEAM_T] = register_cvar("amx_round_nonstop_botname_t", ".") g_cvarBotNames[_:CS_TEAM_CT] = register_cvar("amx_round_nonstop_botname_ct", ":") register_concmd("amx_force_round_nonstop", "cmdForce", ADMIN_CFG, "Sets Round Non-Stop state immediately") register_clcmd("amx_round_nonstop_state", "cmdState", ADMIN_ALL, "Prints Round Non-Stop state") register_event("ResetHUD", "onPlayerSpawn", "be") register_event("TextMsg", "onRestartNotify", "a", "2=#Game_will_restart_in") register_event("HLTV", "onNewRound", "a", "1=0", "2=0") register_logevent("onRoundStart", 2, "1=Round_Start") register_message(get_user_msgid("TextMsg"), "msgTextMsg") register_message(get_user_msgid("TeamInfo"), "msgTeamInfo") register_forward(FM_StartFrame, "onStartFrame") server_print("[AMXX] Plugin %s initialized", MY_PLUGIN_NAME) } //----------------------------------------------------------------------------- public plugin_cfg() { g_cvarAmxRespawn = get_cvar_pointer("amx_respawn") g_cvarCsdmActive = get_cvar_pointer("csdm_active") // NOTE: PSP Fixer plugin balances spawn points within plugin_cfg(), // thus we need to put a small delay to count _proper_ spawn points set_task(0.1, "taskCountSP") //launchGenericTask(0.2) hookSetOrigin() } //----------------------------------------------------------------------------- public taskCountSP() { g_iSpawnCount[SPID_T] = countSP(SPID_T) g_iSpawnCount[SPID_CT] = countSP(SPID_CT) g_iRoundTime = get_cvar_num("mp_roundtime") } //----------------------------------------------------------------------------- stock launchGenericTask(Float:interval) { if(task_exists(TASK_CHECKCVAR_ID)) { static Float:fTime; fTime = get_gametime() if((fTime - g_fGenericTaskTime) > interval) { remove_task(TASK_CHECKCVAR_ID) set_task(interval, "taskCheckCvar", TASK_CHECKCVAR_ID) g_fGenericTaskTime = fTime #if defined _DEBUG log_amx("re-launchGenericTask(interval=%.1f)", interval) #endif } } else { set_task(interval, "taskCheckCvar", TASK_CHECKCVAR_ID) g_fGenericTaskTime = get_gametime() #if defined _DEBUG log_amx("launchGenericTask(interval=%.1f)", interval) #endif } } //----------------------------------------------------------------------------- public client_disconnect(id) { #if defined _DEBUG log_amx("client_disconnect(%d)::begin", id) #endif if(isFakeBot(id)) cleanupBotData(id) else { // NOTE: thus we can avoid simultaneous bots creation & kick during // server shutdown launchGenericTask(0.5) } #if defined _DEBUG log_amx("client_disconnect(%d)::end", id) #endif } //----------------------------------------------------------------------------- public msgTextMsg(msg_id, msg_dest, msg_entity) { if(get_msg_arg_int(1) == 4 && isActive() && (g_tBots[_:CS_TEAM_CT][i_id] || g_tBots[_:CS_TEAM_T][i_id])) { static sTemp[sizeof(g_sGameCommencing)] get_msg_arg_string(2, sTemp, sizeof(sTemp)) if(equal(sTemp, g_sGameCommencing)) set_msg_arg_string(2, "Round Non-Stop Commencing!") } return PLUGIN_CONTINUE } //----------------------------------------------------------------------------- public msgTeamInfo(msg_id, msg_dest, msg_entity) { static id id = get_msg_arg_int(1) if(id && (id == g_tBots[_:CS_TEAM_CT][i_id] || id == g_tBots[_:CS_TEAM_T][i_id]) && (SFL_SHOWASSPECTATORS & getPCvarAsFlags(g_cvarFlags))) set_msg_arg_string(2, g_sSpectator) return PLUGIN_CONTINUE } //----------------------------------------------------------------------------- public onStartFrame() { if(g_tBots[_:CS_TEAM_T][i_id]) updateBotMarker(g_tBots[_:CS_TEAM_T][i_id]) if(g_tBots[_:CS_TEAM_CT][i_id]) updateBotMarker(g_tBots[_:CS_TEAM_CT][i_id]) return FMRES_IGNORED } //----------------------------------------------------------------------------- public updateBotMarker(id) { if(pev_valid(id)) { static Float:fMSec set_pev(id, pev_flags, pev(id, pev_flags) | FL_FAKECLIENT) global_get(glb_frametime, fMSec) fMSec *= 1000.0 // NOTE: without that a bot will not receive in-game events EF_RunPlayerMove(id, Float:{0.0,0.0,0.0}, 0.0, 0.0, 0.0, 0, 0, floatround(fMSec)) } } //----------------------------------------------------------------------------- stock hookSetOrigin() { if(SFL_TAKEOFFSPAWNAREA & getPCvarAsFlags(g_cvarFlags)) { if(!g_fmSetOrigin) { g_fmSetOrigin = register_forward(FM_SetOrigin, "onSetOrigin") #if defined _DEBUG log_amx("SetOrigin is hooked") #endif } } else { if(g_fmSetOrigin) { unregister_forward(FM_SetOrigin, g_fmSetOrigin) g_fmSetOrigin = 0 #if defined _DEBUG log_amx("SetOrigin is unhooked") #endif } } } //----------------------------------------------------------------------------- public onNewRound() { #if defined _DEBUG log_amx("onNewRound()::begin") #endif hookSetOrigin() // NOTE: after new round is triggered onPlayerSpawn does not // called on system bots, thus i make a fix; it's long enough // to make sure a double respawn is gone (double respawn is // specific for CSDM) if(SFL_TAKEOFFSPAWNAREA & getPCvarAsFlags(g_cvarFlags)) set_task(1.0, "taskMoveBotsHigh") #if defined _DEBUG new arr[1] arr[0] = g_tBots[1][i_id] set_task(0.2, "taskShowBotOrigin", 700, arr, sizeof(arr), "a", 10) log_amx("onNewRound()::end") #endif } //----------------------------------------------------------------------------- public taskMoveBotsHigh() { new pid for(new i=1; i") } //----------------------------------------------------------------------------- bool:isActive() { new bool:bResult switch(get_pcvar_num(g_cvarState)) { case -1: bResult = existsRespawnEngine() case 0: bResult = false case 1: bResult = true default: { new i for(i=1; i