From f3ff80c5abb4ace0f818ba91942ea90cf7356573 Mon Sep 17 00:00:00 2001 From: Jackz Date: Wed, 3 Apr 2024 11:16:15 -0500 Subject: [PATCH] Fixes / Updates --- plugins/adminpanel.smx | Bin 23426 -> 25679 bytes scripting/adminpanel.sp | 350 +++++++++++++++++++++-------- scripting/include/anymap.inc | 206 +++++++++++++++++ scripting/include/hats/hats.sp | 8 +- scripting/l4d2_extraplayeritems.sp | 24 +- scripting/l4d2_hats.sp | 4 +- 6 files changed, 488 insertions(+), 104 deletions(-) create mode 100644 scripting/include/anymap.inc diff --git a/plugins/adminpanel.smx b/plugins/adminpanel.smx index c4d6ea098a87d34139f77043111acfca9a1797f1..073dbc8c352a1139365d458ae011ca4110ca88d9 100644 GIT binary patch literal 25679 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 XQes7NukXepFZeuVM1dao>394OdKN9z diff --git a/scripting/adminpanel.sp b/scripting/adminpanel.sp index f2a9b8d..6e867de 100644 --- a/scripting/adminpanel.sp +++ b/scripting/adminpanel.sp @@ -14,6 +14,7 @@ #include #include #include +#include #pragma newdecls required @@ -32,14 +33,15 @@ Regex CommandArgRegex; ConVar cvar_debug; ConVar cvar_gamemode; char gamemode[32]; ConVar cvar_difficulty; int gameDifficulty; +ConVar cvar_maxplayers, cvar_visibleMaxPlayers; ConVar cvar_address; char serverIp[16] = "127.0.0.1"; int serverPort = DEFAULT_SERVER_PORT; ConVar cvar_authToken; char authToken[256]; char currentMap[64]; int numberOfPlayers = 0; +int numberOfViewers; int campaignStartTime; int uptime; -bool g_inTransition; bool isL4D1Survivors; int lastReceiveTime; @@ -54,8 +56,18 @@ Handle receiveTimeoutTimer = null; bool lateLoaded; Socket g_socket; -bool g_isPaused; -bool isAuthenticated; +enum AuthState { + Auth_Fail = -1, + Auth_Pending, + Auth_Success, +} +AuthState authState; +enum GameState { + State_None, + State_Transitioning, + State_Hibernating +} +GameState g_gameState; #define BUFFER_SIZE 2048 Buffer sendBuffer; Buffer receiveBuffer; // Unfortunately there's no easy way to have this not be the same as BUFFER_SIZE @@ -73,6 +85,7 @@ public void OnPluginStart() g_socket = new Socket(SOCKET_TCP, OnSocketError); g_socket.SetOption(SocketKeepAlive, 1); g_socket.SetOption(SocketReuseAddr, 1); + g_socket.SetOption(SocketSendBuffer, BUFFER_SIZE); uptime = GetTime(); cvar_debug = CreateConVar("sm_adminpanel_debug", "1", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0); @@ -86,7 +99,8 @@ public void OnPluginStart() cvar_address.GetString(serverIp, sizeof(serverIp)); OnCvarChanged(cvar_address, "", serverIp); - + cvar_maxplayers = FindConVar("sv_maxplayers"); + cvar_visibleMaxPlayers = FindConVar("sv_visiblemaxplayers"); cvar_gamemode = FindConVar("mp_gamemode"); cvar_gamemode.AddChangeHook(OnCvarChanged); @@ -107,6 +121,8 @@ public void OnPluginStart() HookEvent("player_first_spawn", Event_PlayerFirstSpawn); HookEvent("map_transition", Event_MapTransition); HookEvent("player_death", Event_PlayerDeath); + HookEvent("player_bot_replace", Event_PlayerToBot); + HookEvent("bot_player_replace", Event_BotToPlayer); campaignStartTime = GetTime(); for(int i = 1; i <= MaxClients; i++) { @@ -147,7 +163,10 @@ void OnSocketError(Socket socket, int errorType, int errorNumber, int any) { } void SendFullSync() { - if(StartPayload()) { + PrintToServer("SendFullSync"); + if(StartPayload(true)) { + PrintToServer("SendFullSync : Started"); + AddGameRecord(); int stage = L4D2_GetCurrentFinaleStage(); if(stage != 0) @@ -165,16 +184,17 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int if(cvar_debug.BoolValue) { PrintToServer("[AdminPanel] Received response=%d size=%d bytes", response, dataSize); } - if(!isAuthenticated) { + if(authState == Auth_Pending) { if(response == Live_OK) { - isAuthenticated = true; + authState = Auth_Success; PrintToServer("[AdminPanel] Authenticated with server successfully."); SendFullSync(); } else if(response == Live_Error) { + authState = Auth_Fail; g_socket.Disconnect(); char message[128]; receiveBuffer.ReadString(message, sizeof(message)); - SetFailState("Failed to authenticate with socket: %s", message); + LogError("Failed to authenticate with socket: %s", message); } else { // Ignore packets when not authenticated } @@ -195,8 +215,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int ProcessCommand(id, command, cmdNamespace); } case Live_OK: { - int viewerCount = receiveBuffer.ReadByte(); - g_isPaused = viewerCount == 0; + numberOfViewers = receiveBuffer.ReadByte(); } case Live_Error: { @@ -205,7 +224,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int CreateTimer(5.0, Timer_Reconnect); case Live_Refresh: { PrintToServer("[AdminPanel] Sync requested, performing"); - SendFullSync(); + // SendFullSync(); } } if(receiveTimeoutTimer != null) { @@ -270,7 +289,7 @@ int ProcessBuiltin(const char[] fullCommand, CommandResultType &type = Result_Bo int player = FindPlayer(arg); if(player > 0) { // Is a player, kick em - if(matches >= 2) + if(matches >= 3) CommandArgRegex.GetSubString(0, arg, sizeof(arg), 2); KickClient(player, arg); type = Result_Integer; @@ -288,10 +307,10 @@ int ProcessBuiltin(const char[] fullCommand, CommandResultType &type = Result_Bo int player = FindPlayer(arg); if(player > 0) { // Is a player, kick em - if(matches >= 2) + if(matches >= 3) CommandArgRegex.GetSubString(0, arg, sizeof(arg), 2); int time = StringToInt(arg); - if(matches >= 2) + if(matches >= 4) CommandArgRegex.GetSubString(0, arg, sizeof(arg), 3); type = Result_Integer; return BanClient(player, time, BANFLAG_AUTHID, arg, arg, "sm_adminpanel"); @@ -311,8 +330,30 @@ int ProcessBuiltin(const char[] fullCommand, CommandResultType &type = Result_Bo strcopy(output, maxlen, "Lobby reservation has been removed"); return true; } else { + strcopy(output, maxlen, "Lobby reservation has been already been removed"); return false; } + } else if(StrEqual(command, "cvar")) { + char arg[64]; + if(matches >= 2) + CommandArgRegex.GetSubString(0, arg, sizeof(arg), 1); + ConVar cvar = FindConVar(arg); + if(cvar == null) { + type = Result_Error; + strcopy(output, maxlen, "Cvar not found"); + return -1; + } else { + if(matches >= 3) { + CommandArgRegex.GetSubString(0, arg, sizeof(arg), 2); + cvar.SetString(arg); + type = Result_Boolean; + return true; + } else { + type = Result_Float; + cvar.GetString(output, maxlen); + return view_as(cvar.FloatValue); + } + } } else { type = Result_Error; strcopy(output, maxlen, "Unknown builtin command"); @@ -371,7 +412,7 @@ void OnSocketConnect(Socket socket, int any) { } void OnSocketDisconnect(Socket socket, int attempt) { - isAuthenticated = false; + authState = Auth_Pending; g_socket.SetArg(attempt + 1); float nextAttempt = Exponential(float(attempt) / 2.0) + 2.0; if(nextAttempt > MAX_ATTEMPT_TIMEOUT) nextAttempt = MAX_ATTEMPT_TIMEOUT; @@ -379,6 +420,52 @@ void OnSocketDisconnect(Socket socket, int attempt) { CreateTimer(nextAttempt, Timer_Reconnect); } +Handle hibernateTimer; + +public void L4D_OnServerHibernationUpdate(bool hibernating) { + if(hibernating) { + g_gameState = State_Hibernating; + PrintToServer("[AdminPanel] Server is hibernating, disconnecting from socket"); + hibernateTimer = CreateTimer(30.0, Timer_Wake, 0, TIMER_REPEAT); + } else { + g_gameState = State_None; + PrintToServer("[AdminPanel] Server is not hibernating"); + if(hibernateTimer != null) { + delete hibernateTimer; + } + } + + if(StartPayload(true)) { + AddGameRecord(); + SendPayload(); + } + + if(hibernating) { + g_socket.Disconnect(); + } else { + ConnectSocket(); + } +} + +Action Timer_Wake(Handle h) { + PrintToServer("[AdminPanel] Waking server from hibernation"); + return Plugin_Continue; +} + +public void SteamWorks_SteamServersConnected() { + if(StartPayload(true)) { + AddMetaRecord(true); + SendPayload(); + } +} + +public void SteamWorks_SteamServersDisconnected() { + if(StartPayload(true)) { + AddMetaRecord(false); + SendPayload(); + } +} + Action Timer_Reconnect(Handle h, int type) { if(type == 1) { PrintToServer("[AdminPanel] No response after %f seconds, attempting reconnect", SOCKET_TIMEOUT_DURATION); @@ -392,7 +479,8 @@ void ConnectSocket() { if(g_socket.Connected) g_socket.Disconnect(); if(authToken[0] == '\0') return; - g_socket.SetOption(DebugMode, cvar_debug.BoolValue); + // Do not try to reconnect on auth failure, until token has changed + if(authState == Auth_Fail) return; g_socket.Connect(OnSocketConnect, OnSocketReceive, OnSocketDisconnect, serverIp, serverPort); } @@ -406,10 +494,11 @@ Action Command_PanelDebug(int client, int args) { else ConnectSocket(); } else if(StrEqual(arg, "info")) { - ReplyToCommand(client, "Connected: %b\tAuthenticated: %b", g_socket.Connected, isAuthenticated); - ReplyToCommand(client, "Paused: %b\t#Players: %d", g_isPaused, numberOfPlayers); + ReplyToCommand(client, "Connected: %b\tAuthenticated: %d\tState: %d", g_socket.Connected, authState, g_gameState); + ReplyToCommand(client, "#Viewers: %d\t#Players: %d", numberOfViewers, numberOfPlayers); ReplyToCommand(client, "Target Host: %s:%d", serverIp, serverPort); ReplyToCommand(client, "Buffer Size: %d", BUFFER_SIZE); + ReplyToCommand(client, "Can Send: %b\tCan Force-Send: %b", CanSendPayload(), CanSendPayload(true)); } else if(StrEqual(arg, "builtin")) { if(args < 2) { ReplyToCommand(client, "Usage: builtin "); @@ -420,13 +509,21 @@ Action Command_PanelDebug(int client, int args) { char output[128]; CommandResultType type; int result = ProcessBuiltin(command, type, output, sizeof(output)); - ReplyToCommand(client, "Result: %d (type=%d)", result, view_as(type)); + if(type == Result_Float) + ReplyToCommand(client, "Result: %f (type=%d)", result, view_as(type)); + else + ReplyToCommand(client, "Result: %d (type=%d)", result, view_as(type)); + ReplyToCommand(client, "Output: %s", output); } else if(g_socket.Connected) { if(StrEqual(arg, "game")) { - StartPayload(); - AddGameRecord(); - SendPayload(); + if(StartPayload(true)) { + AddGameRecord(); + SendPayload(); + ReplyToCommand(client, "Sent Game record"); + } else { + ReplyToCommand(client, "StartPayload(): false"); + } } else if(StrEqual(arg, "players")) { SendPlayers(); } else if(StrEqual(arg, "sync")) { @@ -456,7 +553,8 @@ void StopServer() { void Event_GameStart(Event event, const char[] name, bool dontBroadcast) { campaignStartTime = GetTime(); - if(StartPayload()) { + g_gameState = State_None; + if(StartPayload(true)) { AddGameRecord(); SendPayload(); } @@ -466,8 +564,8 @@ void Event_GameEnd(Event event, const char[] name, bool dontBroadcast) { } void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) { - g_inTransition = true; - if(StartPayload()) { + g_gameState = State_Transitioning; + if(StartPayload(true)) { AddGameRecord(); SendPayload(); } @@ -476,10 +574,26 @@ void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(client > 0) { - PrintToServer("death: %N", client); TriggerHealthUpdate(client, true); } } +public void Event_PlayerToBot(Handle event, char[] name, bool dontBroadcast) { + int player = GetClientOfUserId(GetEventInt(event, "player")); + int bot = GetClientOfUserId(GetEventInt(event, "bot")); + if(player > 0 && !IsFakeClient(player) && StartPayload(true)) { + AddSurvivorRecord(player); + SendPayload(); + } +} + +public void Event_BotToPlayer(Handle event, char[] name, bool dontBroadcast) { + int player = GetClientOfUserId(GetEventInt(event, "player")); + int bot = GetClientOfUserId(GetEventInt(event, "bot")); + if(player > 0 && !IsFakeClient(player) && StartPayload(true)) { + AddPlayerRecord(player); + SendPayload(); + } +} void Event_HealStart(Event event, const char[] name, bool dontBroadcast) { int subject = GetClientOfUserId(event.GetInt("subject")); @@ -521,34 +635,27 @@ void RecalculatePlayerCount() { void SendPlayers() { for(int i = 1; i <= MaxClients; i++) { if(IsClientInGame(i)) { - StartPayload(); - AddPlayerRecord(i); - SendPayload(); + if(StartPayload(true)) { + AddPlayerRecord(i); + SendPayload(); + } } } } -// public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2]) { -// float time = GetGameTime(); -// // if(time - lastUpdateTime[client] > 7.0) { -// // if(StartPayload()) { -// // lastUpdateTime[client] = time; -// // AddSurvivorRecord(client); -// // SendPayload(); -// // } -// // } -// } +public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float vel[3], const float angles[3], int weapon, int subtype, int cmdnum, int tickcount, int seed, const int mouse[2]) { + if(updateHealthTimer[client] != null) + TriggerHealthUpdate(client); +} public void OnMapStart() { GetCurrentMap(currentMap, sizeof(currentMap)); numberOfPlayers = 0; - if(lateLoaded) { - if(StartPayload()) { - AddGameRecord(); - SendPayload(); + if(StartPayload(true)) { + AddGameRecord(); + SendPayload(); - SendPlayers(); - } + SendPlayers(); } } @@ -557,8 +664,8 @@ public void OnConfigsExecuted() { } // Player counts public void OnClientPutInServer(int client) { - if(g_inTransition) { - g_inTransition = false; + if(g_gameState == State_Transitioning) { + g_gameState = State_None; if(StartPayload()) { AddGameRecord(); SendPayload(); @@ -570,9 +677,7 @@ public void OnClientPutInServer(int client) { numberOfPlayers++; } else { // Check if they are not a bot, such as ABMBot or EPIBot, etc - char classname[32]; - GetEntityClassname(client, classname, sizeof(classname)); - if(StrContains(classname, "bot", false) > -1) { + if(StrContains(nameCache[client], "bot", false) > -1) { return; } strcopy(steamidCache[client], 32, "BOT"); @@ -701,6 +806,7 @@ void OnCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue) if(serverPort == 0) serverPort = DEFAULT_SERVER_PORT; } PrintToServer("[AdminPanel] Sending data to %s:%d", serverIp, serverPort); + authState = Auth_Pending; ConnectSocket(); } } else if(cvar_gamemode == convar) { @@ -717,6 +823,7 @@ void OnCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue) } } else if(cvar_authToken == convar) { strcopy(authToken, sizeof(authToken), newValue); + authState = Auth_Pending; // Token changed, re-try authentication ConnectSocket(); } @@ -831,8 +938,20 @@ enum LiveRecordType { Live_Finale, Live_SurvivorItems, Live_CommandResponse, - Live_Auth + Live_Auth, + Live_Meta } +char LIVE_RECORD_NAMES[view_as(Live_Meta)+1][] = { + "Game", + "Player", + "Survivor", + "Infected", + "Finale", + "Items", + "Commands", + "Auth", + "Meta" +}; enum LiveRecordResponse { Live_OK, @@ -842,64 +961,84 @@ enum LiveRecordResponse { Live_RunComand } +char pendingRecords[64]; + +bool CanSendPayload(bool ignorePause = false) { + if(!g_socket.Connected || authState != Auth_Success) return false; + if(!ignorePause && (numberOfViewers == 0 || numberOfPlayers == 0)) return false; + return true; +} + bool StartPayload(bool ignorePause = false) { - // PrintToServer("conn=%b auth=%b igPause=%b debug=%b paused=%b player=%b", ) - if(!g_socket.Connected || !isAuthenticated) return false; - if(!ignorePause && (g_isPaused || numberOfPlayers == 0)) return false; + if(!CanSendPayload(ignorePause)) return false; StartPayloadEx(); return true; } /// Starts payload, ignoring if the payload can even be sent bool hasRecord; +int recordStart; + void StartPayloadEx() { sendBuffer.Reset(); hasRecord = false; + pendingRecords[0] = '\0'; + recordStart = 0; } + void StartRecord(LiveRecordType type) { if(hasRecord) { sendBuffer.WriteChar('\x1e'); } + if(cvar_debug.BoolValue) + Format(pendingRecords, sizeof(pendingRecords), "%s%s ", pendingRecords, LIVE_RECORD_NAMES[view_as(type)]); + recordStart = sendBuffer.offset; + sendBuffer.WriteByte(0); // write temp NULL to be replaced sendBuffer.WriteByte(view_as(type)); hasRecord = true; } +void EndRecord() { + int length = sendBuffer.offset - recordStart - 1; // subtract 1, as don't count length inside + sendBuffer.WriteByteAt(length, recordStart); + // if(cvar_debug.BoolValue) { + // int type = sendBuffer.ReadByteAt(recordStart + 1); + // PrintToServer("End record %s(%d) (start: %d, end: %d) length: %d", LIVE_RECORD_NAMES[view_as(type)], type, recordStart, sendBuffer.offset, length); + // } +} + void AddGameRecord() { StartRecord(Live_Game); sendBuffer.WriteInt(uptime); sendBuffer.WriteInt(campaignStartTime); sendBuffer.WriteByte(gameDifficulty); - sendBuffer.WriteByte(g_inTransition); + sendBuffer.WriteByte(view_as(g_gameState)); + sendBuffer.WriteByte(GetMaxPlayers()); sendBuffer.WriteString(gamemode); sendBuffer.WriteString(currentMap); + EndRecord(); } + +int GetMaxPlayers() { + if(cvar_visibleMaxPlayers != null && cvar_visibleMaxPlayers.IntValue > 0) return cvar_visibleMaxPlayers.IntValue; + if(cvar_maxplayers != null) return cvar_maxplayers.IntValue; + return L4D_IsVersusMode() ? 8 : 4; +} + void AddFinaleRecord(int stage) { StartRecord(Live_Finale); sendBuffer.WriteByte(stage); // finale stage sendBuffer.WriteByte(L4D_IsFinaleEscapeInProgress()); // escape or not + EndRecord(); } void AddPlayerRecord(int client, bool connected = true) { // fake bots are ignored: + if(steamidCache[client][0] == '\0') return; - int originalClient = client; bool isIdle = false; - if(connected) { - // If this is an idle player's bot, then we use the real player's info instead. - if(IsFakeClient(client)) { - int realPlayer = L4D_GetIdlePlayerOfBot(client); - if(realPlayer > 0) { - PrintToServer("%d is idle bot of %N", client, realPlayer); - isIdle = true; - client = realPlayer; - } else if(steamidCache[client][0] == '\0') { - PrintToServer("skipping %N %s", client, steamidCache[client]); - return; - } - } - } StartRecord(Live_Player); sendBuffer.WriteInt(GetClientUserId(client)); sendBuffer.WriteString(steamidCache[client]); @@ -907,49 +1046,67 @@ void AddPlayerRecord(int client, bool connected = true) { sendBuffer.WriteByte(isIdle); sendBuffer.WriteInt(playerJoinTime[client]); sendBuffer.WriteString(nameCache[client]); + EndRecord(); - if(GetClientTeam(originalClient) == 2) { - AddSurvivorRecord(originalClient, client); - AddSurvivorItemsRecord(originalClient, client); + if(GetClientTeam(client) == 2) { + AddSurvivorRecord(client); + AddSurvivorItemsRecord(client); } else if(GetClientTeam(client) == 3) { AddInfectedRecord(client); } + } else { + EndRecord(); } } -void AddSurvivorRecord(int client, int forClient = 0) { - if(forClient == 0) forClient = client; +void AddSurvivorRecord(int client) { + if(steamidCache[client][0] == '\0') return; + + int userid = GetClientUserId(client); + int bot = L4D_GetBotOfIdlePlayer(client); + if(bot > 0) client = bot; + int survivor = GetEntProp(client, Prop_Send, "m_survivorCharacter"); // The icons are mapped for survivors as 4,5,6,7; so inc to that for L4D1 survivors if(isL4D1Survivors) { survivor += 4; } - if(survivor >= 8) return; + if(survivor >= 8) LogError("invalid survivor %d", survivor); + StartRecord(Live_Survivor); - sendBuffer.WriteInt(GetClientUserId(forClient)); + sendBuffer.WriteInt(userid); sendBuffer.WriteByte(survivor); sendBuffer.WriteByte(L4D_GetPlayerTempHealth(client)); //temp health - sendBuffer.WriteByte(GetEntProp(client, Prop_Send, "m_iHealth")); //perm health + int health = IsPlayerAlive(client) ? GetEntProp(client, Prop_Send, "m_iHealth"): 0; + sendBuffer.WriteByte(health); //perm health sendBuffer.WriteByte(L4D2_GetVersusCompletionPlayer(client)); // flow% sendBuffer.WriteInt(GetPlayerStates(client)); // state (incl. alive) sendBuffer.WriteInt(GetPlayerMovement(client)); // move sendBuffer.WriteInt(GetAction(client)); // action + EndRecord(); } -void AddSurvivorItemsRecord(int client, int forClient = 0) { - if(forClient == 0) forClient = client; +void AddSurvivorItemsRecord(int client) { + if(steamidCache[client][0] == '\0') return; + + int userid = GetClientUserId(client); + int bot = L4D_GetBotOfIdlePlayer(client); + if(bot > 0) client = bot; + StartRecord(Live_SurvivorItems); - sendBuffer.WriteInt(GetClientUserId(client)); + sendBuffer.WriteInt(userid); char name[32]; for(int slot = 0; slot < 6; slot++) { name[0] = '\0'; GetClientWeaponNameSmart2(client, slot, name, sizeof(name)); sendBuffer.WriteString(name); } + EndRecord(); } void AddInfectedRecord(int client) { StartRecord(Live_Infected); sendBuffer.WriteInt(GetClientUserId(client)); - sendBuffer.WriteShort(GetEntProp(client, Prop_Send, "m_iHealth")); //cur health + int health = IsPlayerAlive(client) ? GetEntProp(client, Prop_Send, "m_iHealth"): 0; + sendBuffer.WriteShort(health); //cur health sendBuffer.WriteShort(GetEntProp(client, Prop_Send, "m_iMaxHealth")); //max health sendBuffer.WriteByte(L4D2_GetPlayerZombieClass(client)); // class int victim = L4D2_GetSurvivorVictim(client); @@ -957,6 +1114,7 @@ void AddInfectedRecord(int client) { sendBuffer.WriteInt(GetClientUserId(victim)); else sendBuffer.WriteInt(0); + EndRecord(); } void AddCommandResponseRecord(int id, CommandResultType resultType = Result_None, int resultValue = 0, const char[] message = "") { @@ -965,18 +1123,27 @@ void AddCommandResponseRecord(int id, CommandResultType resultType = Result_None sendBuffer.WriteByte(view_as(resultType)); sendBuffer.WriteByte(resultValue); sendBuffer.WriteString(message); + EndRecord(); } void AddAuthRecord() { StartRecord(Live_Auth); sendBuffer.WriteByte(LIVESTATUS_VERSION); sendBuffer.WriteString(authToken); + EndRecord(); +} + +void AddMetaRecord(bool state) { + StartRecord(Live_Meta); + sendBuffer.WriteByte(state); + EndRecord(); } void SendPayload() { - sendBuffer.Finish(); - if(cvar_debug.BoolValue) - PrintToServer("[AdminPanel] Sending %d bytes of data", sendBuffer.offset); + if(sendBuffer.offset == 0) return; + if(cvar_debug.BoolValue) { + PrintToServer("[AdminPanel] Sending %d bytes of data (records = %s)", sendBuffer.offset, pendingRecords); + } g_socket.Send(sendBuffer.buffer, sendBuffer.offset); } @@ -1018,6 +1185,10 @@ enum struct Buffer { this.buffer[this.offset++] = value & 0xFF; } + void WriteByteAt(int value, int offset) { + this.buffer[offset] = value & 0xFF; + } + void WriteShort(int value) { this.buffer[this.offset++] = value & 0xFF; this.buffer[this.offset++] = (value >> 8) & 0xFF; @@ -1040,15 +1211,14 @@ enum struct Buffer { this.offset += written + 1; } - void Finish() { - // Set newline - this.buffer[this.offset++] = '\n'; - } - int ReadByte() { return this.buffer[this.offset++] & 0xFF; } + int ReadByteAt(int offset) { + return this.buffer[offset] & 0xFF; + } + int ReadShort() { int value = this.buffer[this.offset++]; value += this.buffer[this.offset++] << 8; diff --git a/scripting/include/anymap.inc b/scripting/include/anymap.inc new file mode 100644 index 0000000..ce113f9 --- /dev/null +++ b/scripting/include/anymap.inc @@ -0,0 +1,206 @@ +#if defined _anymap_included + #endinput +#endif +#define _anymap_included + +// An AnyMapSnapshot is created via AnyMap.Snapshot(). It captures the +// keys on a map so they can be read. Snapshots must be freed with delete or +// CloseHandle(). +methodmap AnyMapSnapshot < Handle +{ + public static any DecodeKey(char key[6]) + { + return ((key[0] & ~0x80) << 28) | ((key[1] & ~0x80) << 21) | + ((key[2] & ~0x80) << 14) | ((key[3] & ~0x80) << 7) | (key[4] & ~0x80); + } + + // Returns the number of keys in the map snapshot. + property int Length + { + public get() + { + return view_as(this).Length; + } + } + + // Retrieves the value of a given key in a map snapshot. + // + // @param index Key index (starting from 0). + // @return Value used as the key. + // @error Index out of range. + public int GetKey(int index) + { + char encoded[6]; + view_as(this).GetKey(index, encoded, sizeof(encoded)); + return AnyMapSnapshot.DecodeKey(encoded); + } +}; + +methodmap AnyMap < Handle +{ + // Creates a hash map. A hash map is a container that can map values (called + // "keys") to arbitrary values (cells, arrays, or strings). Keys in a hash map + // are unique. That is, there is at most one entry in the map for a given key. + // + // Insertion, deletion, and lookup in a hash map are all considered to be fast + // operations, amortized to O(1), or constant time. + // + // The StringMap must be freed via delete or CloseHandle(). + + public AnyMap() + { + return view_as(new StringMap()); + } + + public static void EncodeKey(any value, char dest[6]) + { + dest[0] = view_as(((value >>> 28) & 0x7F) | 0x80); + dest[1] = view_as(((value >>> 21) & 0x7F) | 0x80); + dest[2] = view_as(((value >>> 14) & 0x7F) | 0x80); + dest[3] = view_as(((value >>> 7) & 0x7F) | 0x80); + dest[4] = view_as((value & 0x7F) | 0x80); + dest[5] = '\0'; + } + +#if SOURCEMOD_V_MINOR > 10 && SOURCEMOD_V_REV > 6597 + + // Clones a map, returning a new handle with the same size and data. + // This should NOT be confused with CloneHandle. This is a completely new + // handle with the same data but no relation to the original. It should be + // closed when no longer needed with delete or CloseHandle(). + // + // @return New handle to the cloned map + public AnyMap Clone() + { + return view_as(view_as(this).Clone()); + } +#endif + + // Sets a value in a map, either inserting a new entry or replacing an old one. + // + // @param key Value to use as the key. + // @param value Value to store at this key. + // @param replace If false, operation will fail if the key is already set. + // @return True on success, false on failure. + public bool SetValue(const any key, any value, bool replace=true) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).SetValue(encoded, value, replace); + } + + // Sets an array value in a map, either inserting a new entry or replacing an old one. + // + // @param key Value to use as the key. + // @param array Array to store. + // @param num_items Number of items in the array. + // @param replace If false, operation will fail if the key is already set. + // @return True on success, false on failure. + public bool SetArray(const any key, const any[] array, int num_items, bool replace=true) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).SetArray(encoded, array, num_items, replace); + } + + // Sets a string value in a map, either inserting a new entry or replacing an old one. + // + // @param key Value to use as the key. + // @param value String to store. + // @param replace If false, operation will fail if the key is already set. + // @return True on success, false on failure. + public bool SetString(const any key, const char[] value, bool replace=true) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).SetString(encoded, value, replace); + } + + // Retrieves a value in a map. + // + // @param key Value to use as the key. + // @param value Variable to store value. + // @return True on success. False if the key is not set, or the key is set + // as an array or string (not a value). + public bool GetValue(const any key, any &value) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).GetValue(encoded, value); + } + + // Retrieves an array in a map. + // + // @param key Value to use as the key. + // @param array Buffer to store array. + // @param max_size Maximum size of array buffer. + // @param size Optional parameter to store the number of elements written to the buffer. + // @return True on success. False if the key is not set, or the key is set + // as a value or string (not an array). + public bool GetArray(const any key, any[] array, int max_size, int &size=0) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).GetArray(encoded, array, max_size, size); + } + + // Retrieves a string in a map. + // + // @param key Value to use as the key. + // @param value Buffer to store value. + // @param max_size Maximum size of string buffer. + // @param size Optional parameter to store the number of bytes written to the buffer. + // @return True on success. False if the key is not set, or the key is set + // as a value or array (not a string). + public bool GetString(const any key, char[] value, int max_size, int &size=0) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).GetString(encoded, value, max_size, size); + } +#if SOURCEMOD_V_MINOR > 10 && SOURCEMOD_V_REV > 6645 + + // Checks whether a key is present in a map. + // + // @param key Value to use as the key. + // @return True if the key has been found, else false. + public bool ContainsKey(const any key) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).ContainsKey(encoded); + } +#endif + + // Removes a key entry from a Map. + // + // @param key Value to use as the key. + // @return True on success, false if the value was never set. + public bool Remove(const any key) + { + char encoded[6]; + AnyMap.EncodeKey(key, encoded); + return view_as(this).Remove(encoded); + } + + // Clears all entries from a map. + public void Clear() + { + view_as(this).Clear(); + } + + // Create a snapshot of the map's keys. See AnyMapSnapshot. + public AnyMapSnapshot Snapshot() + { + return view_as(view_as(this).Snapshot()); + } + + // Retrieves the number of elements in a map. + property int Size + { + public get() + { + return view_as(this).Size; + } + } +}; diff --git a/scripting/include/hats/hats.sp b/scripting/include/hats/hats.sp index 83fa789..2a92105 100644 --- a/scripting/include/hats/hats.sp +++ b/scripting/include/hats/hats.sp @@ -745,12 +745,14 @@ void EquipHat(int client, int entity, const char[] classname = "", int flags = H hatData[client].offset[2] += 4.2; } } else { - float mins[3]; + float mins[3], maxs[3]; + GetEntPropVector(modifyEntity, Prop_Send, "m_vecMaxs", maxs); GetEntPropVector(modifyEntity, Prop_Send, "m_vecMins", mins); + PrintToServer("%s mins: %f height:%f", classname, mins[2], maxs[2] - mins[2]); if(StrContains(classname, "weapon_molotov") > -1 || StrContains(classname, "weapon_pipe_bomb") > -1 || StrContains(classname, "weapon_vomitjar") > -1) { - hatData[client].offset[2] += 7.2; + hatData[client].offset[2] += 10.0 + 1.0; } else { - hatData[client].offset[2] += mins[2]; + hatData[client].offset[2] += 10.0 + mins[2]; } } diff --git a/scripting/l4d2_extraplayeritems.sp b/scripting/l4d2_extraplayeritems.sp index b30fe8f..e487f3f 100644 --- a/scripting/l4d2_extraplayeritems.sp +++ b/scripting/l4d2_extraplayeritems.sp @@ -1106,10 +1106,13 @@ Action Timer_SetupNewClient(Handle h, int userid) { } if(lowestClient > 0) { - // Get a position behind the player, but not too far to put them in a wall. - // Hopefully reducing the chance they shot for "appearing" infront of a player + // Gets the nav area of lowest client, and finds a random spot inside float pos[3]; - GetHorizontalPositionFromClient(lowestClient, -40.0, pos); + GetClientAbsOrigin(lowestClient, pos); + int nav = L4D_GetNearestNavArea(pos); + if(nav > 0) { + L4D_FindRandomSpot(nav, pos); + } TeleportEntity(client, pos, NULL_VECTOR, NULL_VECTOR); // Just incase they _are_ in a wall, let the game check: L4D_WarpToValidPositionIfStuck(client); @@ -1130,8 +1133,6 @@ void GiveWeapon(int client, const char[] weaponName, float delay = 0.3, int clea AcceptEntityInput(oldWpn, "Kill"); } } - PrintToServer("%N: Giving %s", client, weaponName); - PrintToConsoleAll("%N: Giving %s", client, weaponName); DataPack pack; CreateDataTimer(delay, Timer_GiveWeapon, pack); pack.WriteCell(GetClientUserId(client)); @@ -1187,7 +1188,6 @@ int SpawnItem(const char[] itemName, float pos[3], float ang[3] = NULL_VECTOR) { int spawner = CreateEntityByName(classname); if(spawner == -1) return -1; DispatchKeyValue(spawner, "solid", "6"); - // DispatchKeyValue(entity_weapon, "model", g_bLeft4Dead2 ? g_sWeaponModels2[model] : g_sWeaponModels[model]); DispatchKeyValue(spawner, "rendermode", "3"); DispatchKeyValue(spawner, "disableshadows", "1"); TeleportEntity(spawner, pos, ang, NULL_VECTOR); @@ -1288,6 +1288,12 @@ void Debug_GetAttributes(int attributes, char[] output, int maxlen) { } } +public void L4D2_OnChangeFinaleStage_Post(int stage) { + if(stage == 1) { + IncreaseKits(true); + } +} + public void OnMapStart() { char map[32]; GetCurrentMap(map, sizeof(map)); @@ -1729,9 +1735,9 @@ void PopulateItems() { g_areItemsPopulated = true; - if(L4D_IsMissionFinalMap(true)) { - IncreaseKits(true); - } + // if(L4D_IsMissionFinalMap(true)) { + // IncreaseKits(true); + // } //Generic Logic float percentage = hExtraItemBasePercentage.FloatValue * (g_survivorCount - 4); diff --git a/scripting/l4d2_hats.sp b/scripting/l4d2_hats.sp index 84f6bf1..bcd783f 100644 --- a/scripting/l4d2_hats.sp +++ b/scripting/l4d2_hats.sp @@ -547,8 +547,8 @@ public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3 if(entity > 0) { if(buttons & IN_ZOOM) { // Offset controls: - if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; - if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; + // if(buttons & IN_JUMP) hatData[client].offset[2] += 1.0; + // if(buttons & IN_DUCK) hatData[client].offset[2] -= 1.0; if(buttons & IN_FORWARD) hatData[client].offset[0] += 1.0; if(buttons & IN_BACK) hatData[client].offset[0] -= 1.0; if(buttons & IN_MOVELEFT) hatData[client].offset[1] += 1.0;