mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-05 18:43:21 +00:00
New socket system
This commit is contained in:
parent
001e8cdb52
commit
9007092afd
2 changed files with 647 additions and 216 deletions
Binary file not shown.
|
@ -2,12 +2,10 @@
|
|||
|
||||
#define DEBUG
|
||||
|
||||
// Update intervals (only sends when > 0 players)
|
||||
// The update interval when there are active viewers
|
||||
#define UPDATE_INTERVAL 5.0
|
||||
// The update interval when there are no viewers on.
|
||||
// We still need to poll to know how many viewers are watching
|
||||
#define UPDATE_INTERVAL_SLOW 20.0
|
||||
// Every attempt waits exponentionally longer, up to this value.
|
||||
#define MAX_ATTEMPT_TIMEOUT 120.0
|
||||
#define DEFAULT_SERVER_PORT 7888
|
||||
#define SOCKET_TIMEOUT_DURATION 90.0
|
||||
|
||||
#include <sourcemod>
|
||||
#include <sdktools>
|
||||
|
@ -15,6 +13,7 @@
|
|||
#include <left4dhooks>
|
||||
#include <multicolors>
|
||||
#include <jutils>
|
||||
#include <socket>
|
||||
|
||||
#pragma newdecls required
|
||||
|
||||
|
@ -27,102 +26,277 @@ public Plugin myinfo =
|
|||
url = "https://github.com/jackzmc/l4d2-admin-dash"
|
||||
};
|
||||
|
||||
int LIVESTATUS_VERSION = 0;
|
||||
|
||||
|
||||
ConVar cvar_debug;
|
||||
ConVar cvar_postAddress; char postAddress[128];
|
||||
ConVar cvar_authKey; char authKey[512];
|
||||
ConVar cvar_gamemode; char gamemode[32];
|
||||
ConVar cvar_difficulty; int gameDifficulty;
|
||||
ConVar cvar_id; char serverId[32];
|
||||
ConVar cvar_address; char serverIp[16] = "127.0.0.1"; int serverPort = DEFAULT_SERVER_PORT;
|
||||
|
||||
char currentMap[64];
|
||||
int numberOfPlayers = 0;
|
||||
int lastSuccessTime;
|
||||
int campaignStartTime;
|
||||
int lastErrorCode;
|
||||
int uptime;
|
||||
bool fastUpdateMode = false;
|
||||
|
||||
Handle updateTimer = null;
|
||||
bool g_inTransition;
|
||||
bool isL4D1Survivors;
|
||||
int lastReceiveTime;
|
||||
|
||||
char steamidCache[MAXPLAYERS+1][32];
|
||||
char nameCache[MAXPLAYERS+1][MAX_NAME_LENGTH];
|
||||
int g_icBeingHealed[MAXPLAYERS+1];
|
||||
int playerJoinTime[MAXPLAYERS+1];
|
||||
Handle updateHealthTimer[MAXPLAYERS+1];
|
||||
Handle updateItemTimer[MAXPLAYERS+1];
|
||||
Handle receiveTimeoutTimer = null;
|
||||
|
||||
bool lateLoaded;
|
||||
|
||||
Socket g_socket;
|
||||
bool g_isPaused;
|
||||
#define BUFFER_SIZE 2048
|
||||
Buffer sendBuffer;
|
||||
Buffer receiveBuffer; // Unfortunately there's no easy way to have this not be the same as BUFFER_SIZE
|
||||
|
||||
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
|
||||
{
|
||||
lateLoaded = late;
|
||||
return APLRes_Success;
|
||||
}
|
||||
|
||||
public void OnPluginStart()
|
||||
{
|
||||
// TODO: periodic reconnect
|
||||
g_socket = new Socket(SOCKET_TCP, OnSocketError);
|
||||
g_socket.SetOption(SocketKeepAlive, 1);
|
||||
g_socket.SetOption(SocketReuseAddr, 1);
|
||||
|
||||
uptime = GetTime();
|
||||
cvar_debug = CreateConVar("sm_adminpanel_debug", "0", "Turn on debug mode", FCVAR_DONTRECORD, true, 0.0, true, 1.0);
|
||||
|
||||
cvar_postAddress = CreateConVar("sm_adminpanel_url", "", "The base address to post updates to", FCVAR_NONE);
|
||||
cvar_postAddress.AddChangeHook(OnCvarChanged);
|
||||
cvar_postAddress.GetString(postAddress, sizeof(postAddress));
|
||||
cvar_authKey = CreateConVar("sm_adminpanel_key", "", "The authentication key", FCVAR_NONE);
|
||||
cvar_authKey.AddChangeHook(OnCvarChanged);
|
||||
cvar_authKey.GetString(authKey, sizeof(authKey));
|
||||
cvar_id = CreateConVar("sm_adminpanel_id", "", "The server ID to post updates for", FCVAR_NONE);
|
||||
cvar_id.AddChangeHook(OnCvarChanged);
|
||||
cvar_id.GetString(serverId, sizeof(serverId));
|
||||
|
||||
cvar_address = CreateConVar("sm_adminpanel_host", "100.108.152.125:7888", "The IP and port to connect to, default is 7888", FCVAR_NONE);
|
||||
cvar_address.AddChangeHook(OnCvarChanged);
|
||||
cvar_address.GetString(serverIp, sizeof(serverIp));
|
||||
OnCvarChanged(cvar_address, "", serverIp);
|
||||
|
||||
cvar_gamemode = FindConVar("mp_gamemode");
|
||||
cvar_gamemode.AddChangeHook(OnCvarChanged);
|
||||
cvar_gamemode.GetString(gamemode, sizeof(gamemode));
|
||||
|
||||
cvar_difficulty = FindConVar("z_difficulty");
|
||||
cvar_difficulty.AddChangeHook(OnCvarChanged);
|
||||
gameDifficulty = GetDifficultyInt();
|
||||
|
||||
HookEvent("game_init", Event_GameStart);
|
||||
HookEvent("game_end", Event_GameEnd);
|
||||
HookEvent("heal_success", Event_HealStop);
|
||||
HookEvent("heal_interrupted", Event_HealStop);
|
||||
HookEvent("heal_begin", Event_HealStart);
|
||||
HookEvent("heal_success", Event_HealSuccess);
|
||||
HookEvent("heal_interrupted", Event_HealInterrupted);
|
||||
HookEvent("pills_used", Event_ItemUsed);
|
||||
HookEvent("adrenaline_used", Event_ItemUsed);
|
||||
HookEvent("weapon_drop", Event_WeaponDrop);
|
||||
HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
|
||||
HookEvent("map_transition", Event_MapTransition);
|
||||
HookEvent("player_death", Event_PlayerDeath);
|
||||
|
||||
campaignStartTime = GetTime();
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(IsClientConnected(i) && IsClientInGame(i)) {
|
||||
playerJoinTime[i] = GetTime();
|
||||
OnClientPutInServer(i);
|
||||
}
|
||||
}
|
||||
|
||||
TryStartTimer(true);
|
||||
|
||||
AutoExecConfig(true, "adminpanel");
|
||||
|
||||
RegAdminCmd("sm_panel_status", Command_PanelStatus, ADMFLAG_GENERIC);
|
||||
|
||||
RegAdminCmd("sm_panel_debug", Command_PanelDebug, ADMFLAG_GENERIC);
|
||||
RegAdminCmd("sm_panel_request_stop", Command_RequestStop, ADMFLAG_GENERIC);
|
||||
}
|
||||
|
||||
#define DATE_FORMAT "%F at %I:%M %p"
|
||||
Action Command_PanelStatus(int client, int args) {
|
||||
ReplyToCommand(client, "Active: %b", updateTimer != null);
|
||||
ReplyToCommand(client, "#Players: %d", numberOfPlayers);
|
||||
ReplyToCommand(client, "Update Interval: %0f s", fastUpdateMode ? UPDATE_INTERVAL : UPDATE_INTERVAL_SLOW);
|
||||
char buffer[32];
|
||||
ReplyToCommand(client, "Last Error Code: %d", lastErrorCode);
|
||||
if(lastSuccessTime > 0)
|
||||
FormatTime(buffer, sizeof(buffer), DATE_FORMAT, lastSuccessTime);
|
||||
else
|
||||
Format(buffer, sizeof(buffer), "(none)");
|
||||
ReplyToCommand(client, "Last Success: %s", buffer);
|
||||
return Plugin_Handled;
|
||||
void TriggerHealthUpdate(int client, bool instant = false) {
|
||||
if(updateHealthTimer[client] != null) {
|
||||
delete updateHealthTimer[client];
|
||||
}
|
||||
updateHealthTimer[client] = CreateTimer(instant ? 0.1 : 1.0, Timer_UpdateHealth, client);
|
||||
}
|
||||
|
||||
void TryStartTimer(bool fast = true) {
|
||||
if(numberOfPlayers > 0 && updateTimer == null && postAddress[0] != '\0' && authKey[0] != 0) {
|
||||
fastUpdateMode = fast;
|
||||
float interval = fast ? UPDATE_INTERVAL : UPDATE_INTERVAL_SLOW;
|
||||
updateTimer = CreateTimer(interval, Timer_PostStatus, _, TIMER_REPEAT);
|
||||
PrintToServer("[AdminPanel] Updating every %.1f seconds", interval);
|
||||
void TriggerItemUpdate(int client) {
|
||||
if(updateItemTimer[client] != null) {
|
||||
delete updateItemTimer[client];
|
||||
}
|
||||
updateItemTimer[client] = CreateTimer(1.0, Timer_UpdateItems, client);
|
||||
}
|
||||
|
||||
void OnSocketError(Socket socket, int errorType, int errorNumber, int any) {
|
||||
PrintToServer("[AdminPanel] Socket Error %d %d", errorType, errorNumber);
|
||||
if(!socket.Connected) {
|
||||
PrintToServer("[AdminPanel] Lost connection to socket, reconnecting", errorType, errorNumber);
|
||||
ConnectSocket();
|
||||
}
|
||||
}
|
||||
|
||||
void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int arg) {
|
||||
receiveBuffer.FromArray(receiveData, dataSize);
|
||||
LiveRecordResponse response = view_as<LiveRecordResponse>(receiveBuffer.ReadByte());
|
||||
if(cvar_debug.BoolValue) {
|
||||
PrintToServer("[AdminPanel] Received: %d", response);
|
||||
}
|
||||
lastReceiveTime = GetTime();
|
||||
switch(response) {
|
||||
case Live_OK: {
|
||||
int viewerCount = receiveBuffer.ReadByte();
|
||||
g_isPaused = viewerCount == 0;
|
||||
}
|
||||
case Live_Reconnect:
|
||||
CreateTimer(5.0, Timer_Reconnect);
|
||||
case Live_Refresh: {
|
||||
PrintToServer("[AdminPanel] Refresh requested, performing");
|
||||
StartPayload();
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
|
||||
SendPlayers();
|
||||
}
|
||||
}
|
||||
if(receiveTimeoutTimer != null) {
|
||||
delete receiveTimeoutTimer;
|
||||
}
|
||||
receiveTimeoutTimer = CreateTimer(SOCKET_TIMEOUT_DURATION, Timer_Reconnect, 1);
|
||||
}
|
||||
|
||||
void OnSocketConnect(Socket socket, int any) {
|
||||
if(cvar_debug.BoolValue)
|
||||
PrintToServer("[AdminPanel] Connected to %s:%d", serverIp, serverPort);
|
||||
g_socket.SetArg(0);
|
||||
// Late loads / first setup we can't send
|
||||
if(currentMap[0] != '\0' && StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
// Resend all players
|
||||
SendPlayers();
|
||||
}
|
||||
}
|
||||
|
||||
void OnSocketDisconnect(Socket socket, int attempt) {
|
||||
g_socket.SetArg(attempt + 1);
|
||||
float nextAttempt = Exponential(float(attempt) / 2.0) + 2.0;
|
||||
if(nextAttempt > MAX_ATTEMPT_TIMEOUT) nextAttempt = MAX_ATTEMPT_TIMEOUT;
|
||||
PrintToServer("[AdminPanel] Disconnected, retrying in %.0f seconds", nextAttempt);
|
||||
CreateTimer(nextAttempt, Timer_Reconnect);
|
||||
}
|
||||
|
||||
Action Timer_Reconnect(Handle h, int type) {
|
||||
if(type == 1) {
|
||||
PrintToServer("[AdminPanel] No response after %f seconds, attempting reconnect", SOCKET_TIMEOUT_DURATION);
|
||||
}
|
||||
ConnectSocket();
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
void ConnectSocket() {
|
||||
if(g_socket == null) LogError("Socket is invalid");
|
||||
if(g_socket.Connected)
|
||||
g_socket.Disconnect();
|
||||
if(serverId[0] == '\0') return;
|
||||
g_socket.SetOption(DebugMode, cvar_debug.BoolValue);
|
||||
g_socket.Connect(OnSocketConnect, OnSocketReceive, OnSocketDisconnect, serverIp, serverPort);
|
||||
}
|
||||
|
||||
#define DATE_FORMAT "%F at %I:%M %p"
|
||||
Action Command_PanelDebug(int client, int args) {
|
||||
char arg[32];
|
||||
GetCmdArg(1, arg, sizeof(arg));
|
||||
if(StrEqual(arg, "connect")) {
|
||||
if(serverId[0] == '\0')
|
||||
ReplyToCommand(client, "No server id.");
|
||||
else
|
||||
ConnectSocket();
|
||||
} else if(StrEqual(arg, "info")) {
|
||||
ReplyToCommand(client, "Connected: %b\tPaused: %b\t#Player: %d", g_socket.Connected, g_isPaused, numberOfPlayers);
|
||||
ReplyToCommand(client, "ID: %s", serverId);
|
||||
ReplyToCommand(client, "Target Host: %s:%d", serverIp, serverPort);
|
||||
ReplyToCommand(client, "Buffer Size: %d", BUFFER_SIZE);
|
||||
} else if(g_socket.Connected) {
|
||||
if(StrEqual(arg, "game")) {
|
||||
StartPayload();
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
} else if(StrEqual(arg, "players")) {
|
||||
SendPlayers();
|
||||
} else {
|
||||
ReplyToCommand(client, "Unknown type");
|
||||
return Plugin_Handled;
|
||||
}
|
||||
} else {
|
||||
ReplyToCommand(client, "Not connected");
|
||||
}
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
Action Command_RequestStop(int client, int args) {
|
||||
if(GetClientCount(false) > 0) {
|
||||
ReplyToCommand(client, "There are still %d players online.", GetClientCount(false));
|
||||
} else {
|
||||
ReplyToCommand(client, "Stopping...");
|
||||
RequestFrame(StopServer);
|
||||
}
|
||||
return Plugin_Handled;
|
||||
}
|
||||
void StopServer() {
|
||||
ServerCommand("exit");
|
||||
}
|
||||
|
||||
void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
|
||||
campaignStartTime = GetTime();
|
||||
if(StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
void Event_GameEnd(Event event, const char[] name, bool dontBroadcast) {
|
||||
campaignStartTime = 0;
|
||||
}
|
||||
|
||||
void Event_HealStart(Event event, const char[] name, bool dontBroadcast) {
|
||||
int healing = GetClientOfUserId(event.GetInt("subject"));
|
||||
g_icBeingHealed[healing] = true;
|
||||
void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) {
|
||||
g_inTransition = true;
|
||||
if(StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
void Event_HealStop(Event event, const char[] name, bool dontBroadcast) {
|
||||
int healing = GetClientOfUserId(event.GetInt("subject"));
|
||||
g_icBeingHealed[healing] = false;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void Event_HealStart(Event event, const char[] name, bool dontBroadcast) {
|
||||
int subject = GetClientOfUserId(event.GetInt("subject"));
|
||||
g_icBeingHealed[subject] = true;
|
||||
}
|
||||
void Event_HealSuccess(Event event, const char[] name, bool dontBroadcast) {
|
||||
int healer = GetClientOfUserId(event.GetInt("userid"));
|
||||
int subject = GetClientOfUserId(event.GetInt("subject"));
|
||||
if(subject > 0 && StartPayload()) {
|
||||
g_icBeingHealed[subject] = false;
|
||||
// Update the subject's health:
|
||||
AddSurvivorRecord(subject);
|
||||
// Update the teammate who healed subject:
|
||||
AddSurvivorItemsRecord(healer);
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
void Event_HealInterrupted(Event event, const char[] name, bool dontBroadcast) {
|
||||
int subject = GetClientOfUserId(event.GetInt("subject"));
|
||||
g_icBeingHealed[subject] = false;
|
||||
}
|
||||
|
||||
void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
|
||||
|
@ -141,24 +315,155 @@ void RecalculatePlayerCount() {
|
|||
numberOfPlayers = players;
|
||||
}
|
||||
|
||||
void SendPlayers() {
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(IsClientInGame(i)) {
|
||||
StartPayload();
|
||||
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 OnMapStart() {
|
||||
GetCurrentMap(currentMap, sizeof(currentMap));
|
||||
numberOfPlayers = 0;
|
||||
if(lateLoaded) {
|
||||
StartPayload();
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
|
||||
SendPlayers();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnConfigsExecuted() {
|
||||
isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1;
|
||||
}
|
||||
// Player counts
|
||||
public void OnClientPutInServer(int client) {
|
||||
if(g_inTransition) {
|
||||
g_inTransition = false;
|
||||
if(StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
GetClientName(client, nameCache[client], MAX_NAME_LENGTH);
|
||||
if(!IsFakeClient(client)) {
|
||||
GetClientAuthId(client, AuthId_SteamID64, steamidCache[client], 32);
|
||||
numberOfPlayers++;
|
||||
TryStartTimer(true);
|
||||
} 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) {
|
||||
return;
|
||||
}
|
||||
strcopy(steamidCache[client], 32, "BOT");
|
||||
}
|
||||
SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponPickUp);
|
||||
SDKHook(client, SDKHook_OnTakeDamageAlivePost, OnTakeDamagePost);
|
||||
// We wait a frame because Event_PlayerFirstSpawn sets their join time
|
||||
RequestFrame(SendNewClient, client);
|
||||
}
|
||||
|
||||
void OnWeaponPickUp(int client, int weapon) {
|
||||
// float time = GetGameTime();
|
||||
// if(time - lastUpdateTime[client] > 3.0 && StartPayload()) {
|
||||
// lastUpdateTime[client] = time;
|
||||
// AddSurvivorItemsRecord(client);
|
||||
// SendPayload();
|
||||
// }
|
||||
if(GetClientTeam(client) == 2)
|
||||
TriggerItemUpdate(client);
|
||||
}
|
||||
|
||||
// Tracks the inventories for pills/adr used, kit used, ammo pack used, etc
|
||||
void Event_WeaponDrop(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
if(client > 0) {
|
||||
if(GetClientTeam(client) == 2)
|
||||
TriggerItemUpdate(client);
|
||||
// if(StartPayload()) {
|
||||
// AddSurvivorItemsRecord(client);
|
||||
// SendPayload();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void Event_ItemUsed(Event event, const char[] name, bool dontBroadcast) {
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
if(client > 0) {
|
||||
// if(StartPayload()) {
|
||||
// AddSurvivorRecord(client);
|
||||
// SendPayload();
|
||||
// }
|
||||
if(GetClientTeam(client) == 2)
|
||||
TriggerHealthUpdate(client);
|
||||
}
|
||||
}
|
||||
|
||||
void OnTakeDamagePost(int victim, int attacker, int inflictor, float damage, int damagetype, int weapon, const float damageForce[3], const float damagePosition[3], int damagecustom) {
|
||||
if(damage > 1.0 && victim > 0 && victim <= MaxClients) {
|
||||
TriggerHealthUpdate(victim);
|
||||
// if(GetGameTime() - lastUpdateTime[victim] > 0.3 && StartPayload()) {
|
||||
// lastUpdateTime[victim] = GetGameTime();
|
||||
// if(GetClientTeam(victim) == 2)
|
||||
// AddSurvivorRecord(victim);
|
||||
// else
|
||||
// AddInfectedRecord(victim);
|
||||
// SendPayload();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
Action Timer_UpdateHealth(Handle h, int client) {
|
||||
if(IsClientInGame(client) && StartPayload()) {
|
||||
if(GetClientTeam(client) == 2)
|
||||
AddSurvivorRecord(client);
|
||||
else
|
||||
AddInfectedRecord(client);
|
||||
SendPayload();
|
||||
}
|
||||
updateHealthTimer[client] = null;
|
||||
return Plugin_Handled;
|
||||
}
|
||||
Action Timer_UpdateItems(Handle h, int client) {
|
||||
if(IsClientInGame(client) && StartPayload()) {
|
||||
AddSurvivorItemsRecord(client);
|
||||
SendPayload();
|
||||
}
|
||||
updateItemTimer[client] = null;
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
void SendNewClient(int client) {
|
||||
if(!IsClientInGame(client)) return;
|
||||
if(StartPayload()) {
|
||||
PrintToServer("SendNewClient(%N)", client);
|
||||
AddPlayerRecord(client);
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClientDisconnect(int client) {
|
||||
if(StartPayload()) {
|
||||
// hopefully userid is valid here?
|
||||
AddPlayerRecord(client, false);
|
||||
SendPayload();
|
||||
}
|
||||
steamidCache[client][0] = '\0';
|
||||
nameCache[client][0] = '\0';
|
||||
if(!IsFakeClient(client)) {
|
||||
|
@ -167,134 +472,58 @@ public void OnClientDisconnect(int client) {
|
|||
if(numberOfPlayers < 0) {
|
||||
numberOfPlayers = 0;
|
||||
}
|
||||
if(numberOfPlayers == 0 && updateTimer != null) {
|
||||
delete updateTimer;
|
||||
}
|
||||
}
|
||||
if(updateHealthTimer[client] != null) {
|
||||
delete updateHealthTimer[client];
|
||||
}
|
||||
if(updateItemTimer[client] != null) {
|
||||
delete updateItemTimer[client];
|
||||
}
|
||||
}
|
||||
|
||||
// Cvar updates
|
||||
void OnCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue) {
|
||||
if(cvar_postAddress == convar) {
|
||||
strcopy(postAddress, sizeof(postAddress), newValue);
|
||||
PrintToServer("[AdminPanel] Update Url has updated");
|
||||
} else if(cvar_authKey == convar) {
|
||||
strcopy(authKey, sizeof(authKey), newValue);
|
||||
PrintToServer("[AdminPanel] Auth key has been updated");
|
||||
if(cvar_id == convar) {
|
||||
strcopy(serverId, sizeof(serverId), newValue);
|
||||
PrintToServer("[AdminPanel] Server ID changed to: %s", serverId);
|
||||
} else if(cvar_address == convar) {
|
||||
if(newValue[0] == '\0') {
|
||||
if(g_socket.Connected)
|
||||
g_socket.Disconnect();
|
||||
serverPort = DEFAULT_SERVER_PORT;
|
||||
serverIp = "127.0.0.1";
|
||||
PrintToServer("[AdminPanel] Deactivated");
|
||||
} else {
|
||||
int index = SplitString(newValue, ":", serverIp, sizeof(serverIp));
|
||||
if(index > -1) {
|
||||
serverPort = StringToInt(newValue[index]);
|
||||
if(serverPort == 0) serverPort = DEFAULT_SERVER_PORT;
|
||||
}
|
||||
PrintToServer("[AdminPanel] Sending data to %s:%d", serverIp, serverPort);
|
||||
ConnectSocket();
|
||||
}
|
||||
} else if(cvar_gamemode == convar) {
|
||||
strcopy(gamemode, sizeof(gamemode), newValue);
|
||||
}
|
||||
TryStartTimer(true);
|
||||
}
|
||||
|
||||
bool isSubmitting;
|
||||
Action Timer_PostStatus(Handle h) {
|
||||
if(isSubmitting) return Plugin_Continue;
|
||||
isSubmitting = true;
|
||||
// TODO: optimize only if someone is requesting live
|
||||
HTTPRequest req = new HTTPRequest(postAddress);
|
||||
JSONObject obj = GetObject();
|
||||
req.SetHeader("x-authtoken", authKey);
|
||||
// req.AppendFormParam("playerCount", "%d", numberOfPlayers);
|
||||
// req.AppendFormParam("map", currentMap);
|
||||
if(cvar_debug.BoolValue) PrintToServer("[AdminPanel] Submitting");
|
||||
req.Post(obj, Callback_PostStatus);
|
||||
delete obj;
|
||||
// req.PostForm(Callback_PostStatus);
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
void Callback_PostStatus(HTTPResponse response, any value, const char[] error) {
|
||||
isSubmitting = false;
|
||||
if(response.Status == HTTPStatus_NoContent || response.Status == HTTPStatus_OK) {
|
||||
lastErrorCode = 0;
|
||||
lastSuccessTime = GetTime();
|
||||
if(cvar_debug.BoolValue)
|
||||
PrintToServer("[AdminPanel] Response: OK/204");
|
||||
// We have subscribers, kill timer and recreate it in fast mode (if not already):
|
||||
if(!fastUpdateMode) {
|
||||
PrintToServer("[AdminPanel] Switching to fast update interval for active viewers.");
|
||||
if(updateTimer != null)
|
||||
delete updateTimer;
|
||||
TryStartTimer(true);
|
||||
if(StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
}
|
||||
|
||||
} else if(response.Status == HTTPStatus_Gone) {
|
||||
lastErrorCode = 0;
|
||||
// We have no subscribers, kill timer and recreate it in slow mode (if not already):
|
||||
if(fastUpdateMode) {
|
||||
PrintToServer("[AdminPanel] Switching to slow update interval, no viewers");
|
||||
if(updateTimer != null)
|
||||
delete updateTimer;
|
||||
TryStartTimer(false);
|
||||
}
|
||||
} else {
|
||||
lastErrorCode = view_as<int>(response.Status);
|
||||
lastSuccessTime = 0;
|
||||
// TODO: backoff
|
||||
PrintToServer("[AdminPanel] Getting response: %d", response.Status);
|
||||
if(cvar_debug.BoolValue) {
|
||||
char buffer[64];
|
||||
JSONObject json = view_as<JSONObject>(response.Data);
|
||||
if(json.GetString("error", buffer, sizeof(buffer))) {
|
||||
PrintToServer("[AdminPanel] Got %d response from server: \"%s\"", view_as<int>(response.Status), buffer);
|
||||
json.GetString("message", buffer, sizeof(buffer));
|
||||
PrintToServer("[AdminPanel] Error message: \"%s\"", buffer);
|
||||
} else {
|
||||
PrintToServer("[AdminPanel] Got %d response from server: <unknown json>\n%s", view_as<int>(response.Status), error);
|
||||
}
|
||||
}
|
||||
if(view_as<int>(response.Status) == 0 || response.Status == HTTPStatus_Unauthorized || response.Status == HTTPStatus_Forbidden) {
|
||||
PrintToServer("[AdminPanel] API Key seems to be invalid, killing timer.");
|
||||
if(updateTimer != null)
|
||||
delete updateTimer;
|
||||
} else if(cvar_difficulty == convar) {
|
||||
gameDifficulty = GetDifficultyInt();
|
||||
if(StartPayload()) {
|
||||
AddGameRecord();
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject GetObject() {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.SetInt("playerCount", numberOfPlayers);
|
||||
obj.SetString("map", currentMap);
|
||||
obj.SetString("gamemode", gamemode);
|
||||
obj.SetInt("startTime", uptime);
|
||||
obj.SetFloat("fps", 1.0 / GetGameFrameTime());
|
||||
AddFinaleInfo(obj);
|
||||
JSONArray players = GetPlayers();
|
||||
obj.Set("players", players);
|
||||
delete players;
|
||||
obj.SetFloat("refreshInterval", UPDATE_INTERVAL);
|
||||
obj.SetInt("lastUpdateTime", GetTime());
|
||||
obj.SetInt("campaignStartTime", campaignStartTime);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void AddFinaleInfo(JSONObject parentObj) {
|
||||
if(L4D_IsMissionFinalMap()) {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.SetBool("escapeLeaving", L4D_IsFinaleEscapeInProgress());
|
||||
obj.SetInt("finaleStage", L4D2_GetCurrentFinaleStage());
|
||||
parentObj.Set("finaleInfo", obj);
|
||||
delete obj;
|
||||
public void L4D2_OnChangeFinaleStage_Post(int finaleType, const char[] arg) {
|
||||
if(StartPayload()) {
|
||||
AddFinaleRecord(finaleType);
|
||||
SendPayload();
|
||||
}
|
||||
}
|
||||
|
||||
JSONArray GetPlayers() {
|
||||
JSONArray players = new JSONArray();
|
||||
for(int i = 1; i <= MaxClients; i++) {
|
||||
if(IsClientConnected(i) && IsClientInGame(i)) {
|
||||
int team = GetClientTeam(i);
|
||||
if( team == 2 || team == 3) {
|
||||
JSONObject player = GetPlayer(i);
|
||||
players.Push(player);
|
||||
delete player;
|
||||
}
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
|
||||
enum {
|
||||
Action_BeingHealed = -1,
|
||||
Action_None = 0, // No use action active
|
||||
|
@ -351,7 +580,9 @@ enum {
|
|||
pState_BlackAndWhite = 1,
|
||||
pState_InSaferoom = 2,
|
||||
pState_IsCalm = 4,
|
||||
pState_IsBoomed = 8
|
||||
pState_IsBoomed = 8,
|
||||
pState_IsPinned = 16,
|
||||
pState_IsAlive = 32,
|
||||
}
|
||||
|
||||
stock bool IsPlayerBoomed(int client) {
|
||||
|
@ -365,62 +596,262 @@ int GetPlayerStates(int client) {
|
|||
if(GetEntProp(client, Prop_Send, "m_bIsOnThirdStrike", 1)) state |= pState_BlackAndWhite;
|
||||
if(GetEntProp(client, Prop_Send, "m_isCalm")) state |= pState_IsCalm;
|
||||
if(IsPlayerBoomed(client)) state |= pState_IsBoomed;
|
||||
if(IsPlayerAlive(client)) state |= pState_IsAlive;
|
||||
if(L4D2_GetInfectedAttacker(client) > 0) state |= pState_IsPinned;
|
||||
return state;
|
||||
}
|
||||
|
||||
JSONObject GetPlayer(int client) {
|
||||
int team = GetClientTeam(client);
|
||||
JSONObject player = new JSONObject();
|
||||
player.SetString("steamid", steamidCache[client]);
|
||||
player.SetInt("userId", GetClientUserId(client));
|
||||
player.SetString("name", nameCache[client]);
|
||||
player.SetInt("team", team);
|
||||
player.SetBool("isAlive", IsPlayerAlive(client));
|
||||
player.SetInt("joinTime", playerJoinTime[client]);
|
||||
player.SetInt("permHealth", GetEntProp(client, Prop_Send, "m_iHealth"));
|
||||
if(team == 2) {
|
||||
// Include idle players (player here is their idle bot)
|
||||
if(IsFakeClient(client)) {
|
||||
int idlePlayer = L4D_GetIdlePlayerOfBot(client);
|
||||
if(idlePlayer > 0) {
|
||||
player.SetString("idlePlayerId", steamidCache[idlePlayer]);
|
||||
if(IsClientInGame(idlePlayer)) {
|
||||
JSONObject idlePlayerObj = GetPlayer(idlePlayer);
|
||||
player.Set("idlePlayer", idlePlayerObj);
|
||||
delete idlePlayerObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
player.SetInt("action", GetAction(client));
|
||||
player.SetInt("flowProgress", L4D2_GetVersusCompletionPlayer(client));
|
||||
player.SetFloat("flow", L4D2Direct_GetFlowDistance(client));
|
||||
player.SetBool("isPinned", L4D2_GetInfectedAttacker(client) > 0);
|
||||
player.SetInt("tempHealth", L4D_GetPlayerTempHealth(client));
|
||||
player.SetInt("states", GetPlayerStates(client));
|
||||
player.SetInt("move", GetPlayerMovement(client));
|
||||
player.SetInt("survivor", GetEntProp(client, Prop_Send, "m_survivorCharacter"));
|
||||
JSONArray weapons = GetPlayerWeapons(client);
|
||||
player.Set("weapons", weapons);
|
||||
delete weapons;
|
||||
} else if(team == 3) {
|
||||
player.SetInt("class", L4D2_GetPlayerZombieClass(client));
|
||||
player.SetInt("maxHealth", GetEntProp(client, Prop_Send, "m_iMaxHealth"));
|
||||
int victim = L4D2_GetSurvivorVictim(client);
|
||||
if(victim > 0)
|
||||
player.SetString("pinnedSurvivorId", steamidCache[victim]);
|
||||
}
|
||||
return player;
|
||||
stock int GetDifficultyInt() {
|
||||
char diff[16];
|
||||
cvar_difficulty.GetString(diff, sizeof(diff));
|
||||
if(StrEqual(diff, "easy", false)) return 0;
|
||||
else if(StrEqual(diff, "hard", false)) return 2;
|
||||
else if(StrEqual(diff, "impossible", false)) return 3;
|
||||
else return 1;
|
||||
}
|
||||
|
||||
JSONArray GetPlayerWeapons(int client) {
|
||||
JSONArray weapons = new JSONArray();
|
||||
static char buffer[64];
|
||||
for(int slot = 0; slot < 6; slot++) {
|
||||
if(GetClientWeaponNameSmart(client, slot, buffer, sizeof(buffer))) {
|
||||
weapons.PushString(buffer);
|
||||
} else {
|
||||
weapons.PushNull();
|
||||
enum LiveRecordType {
|
||||
Live_Game,
|
||||
Live_Player,
|
||||
Live_Survivor,
|
||||
Live_Infected,
|
||||
Live_Finale,
|
||||
Live_SurvivorItems,
|
||||
Live_CommandResponse,
|
||||
Live_Auth
|
||||
}
|
||||
|
||||
enum LiveRecordResponse {
|
||||
Live_OK,
|
||||
Live_Reconnect,
|
||||
Live_Error,
|
||||
Live_Refresh,
|
||||
Live_RunComand
|
||||
}
|
||||
|
||||
bool StartPayload() {
|
||||
if(!cvar_debug.BoolValue && (g_isPaused || numberOfPlayers == 0)) return false;
|
||||
sendBuffer.Reset();
|
||||
sendBuffer.WriteByte(LIVESTATUS_VERSION);
|
||||
sendBuffer.WriteString(serverId);
|
||||
return g_socket.Connected;
|
||||
}
|
||||
|
||||
void StartRecord(LiveRecordType type) {
|
||||
sendBuffer.WriteChar('\x1e'); // record separator
|
||||
sendBuffer.WriteByte(view_as<int>(type));
|
||||
}
|
||||
|
||||
void AddGameRecord() {
|
||||
PrintToServer("pushing Live_Game");
|
||||
StartRecord(Live_Game);
|
||||
sendBuffer.WriteInt(uptime);
|
||||
sendBuffer.WriteInt(campaignStartTime);
|
||||
sendBuffer.WriteByte(gameDifficulty);
|
||||
sendBuffer.WriteByte(g_inTransition);
|
||||
sendBuffer.WriteString(gamemode);
|
||||
sendBuffer.WriteString(currentMap);
|
||||
}
|
||||
|
||||
void AddFinaleRecord(int stage) {
|
||||
StartRecord(Live_Finale);
|
||||
sendBuffer.WriteByte(stage); // finale stage
|
||||
sendBuffer.WriteByte(L4D_IsFinaleEscapeInProgress()); // escape or not
|
||||
}
|
||||
|
||||
void AddPlayerRecord(int client, bool connected = true) {
|
||||
// fake bots are ignored:
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
return weapons;
|
||||
StartRecord(Live_Player);
|
||||
sendBuffer.WriteInt(GetClientUserId(client));
|
||||
sendBuffer.WriteString(steamidCache[client]);
|
||||
if(connected) {
|
||||
sendBuffer.WriteByte(isIdle);
|
||||
sendBuffer.WriteInt(playerJoinTime[client]);
|
||||
sendBuffer.WriteString(nameCache[client]);
|
||||
|
||||
if(GetClientTeam(originalClient) == 2) {
|
||||
AddSurvivorRecord(originalClient, client);
|
||||
AddSurvivorItemsRecord(originalClient, client);
|
||||
} else if(GetClientTeam(client) == 3) {
|
||||
AddInfectedRecord(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AddSurvivorRecord(int client, int forClient = 0) {
|
||||
if(forClient == 0) forClient = client;
|
||||
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;
|
||||
StartRecord(Live_Survivor);
|
||||
sendBuffer.WriteInt(GetClientUserId(forClient));
|
||||
sendBuffer.WriteByte(survivor);
|
||||
sendBuffer.WriteByte(L4D_GetPlayerTempHealth(client)); //temp health
|
||||
sendBuffer.WriteByte(GetEntProp(client, Prop_Send, "m_iHealth")); //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
|
||||
}
|
||||
void AddSurvivorItemsRecord(int client, int forClient = 0) {
|
||||
if(forClient == 0) forClient = client;
|
||||
StartRecord(Live_SurvivorItems);
|
||||
sendBuffer.WriteInt(GetClientUserId(client));
|
||||
char name[32];
|
||||
for(int slot = 0; slot < 6; slot++) {
|
||||
name[0] = '\0';
|
||||
GetClientWeaponNameSmart2(client, slot, name, sizeof(name));
|
||||
sendBuffer.WriteString(name);
|
||||
}
|
||||
}
|
||||
void AddInfectedRecord(int client) {
|
||||
StartRecord(Live_Infected);
|
||||
sendBuffer.WriteInt(GetClientUserId(client));
|
||||
sendBuffer.WriteShort(GetEntProp(client, Prop_Send, "m_iHealth")); //cur health
|
||||
sendBuffer.WriteShort(GetEntProp(client, Prop_Send, "m_iMaxHealth")); //max health
|
||||
sendBuffer.WriteByte(L4D2_GetPlayerZombieClass(client)); // class
|
||||
int victim = L4D2_GetSurvivorVictim(client);
|
||||
if(victim > 0)
|
||||
sendBuffer.WriteInt(GetClientUserId(victim));
|
||||
else
|
||||
sendBuffer.WriteInt(0);
|
||||
}
|
||||
|
||||
void SendPayload() {
|
||||
sendBuffer.Finish();
|
||||
if(cvar_debug.BoolValue)
|
||||
PrintToServer("[AdminPanel] Sending %d bytes of data", sendBuffer.offset);
|
||||
g_socket.Send(sendBuffer.buffer, sendBuffer.offset);
|
||||
}
|
||||
|
||||
enum struct Buffer {
|
||||
char buffer[BUFFER_SIZE];
|
||||
int offset;
|
||||
|
||||
void Reset() {
|
||||
this.buffer[0] = '\0';
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
void FromArray(const char[] input, int size) {
|
||||
this.Reset();
|
||||
int max = BUFFER_SIZE;
|
||||
if(size < max) max = size;
|
||||
for(int i = 0; i < max; i++) {
|
||||
this.buffer[i] = input[i];
|
||||
}
|
||||
}
|
||||
|
||||
void Print() {
|
||||
char[] output = new char[BUFFER_SIZE+100];
|
||||
for(int i = 0; i < BUFFER_SIZE; i++) {
|
||||
if(this.buffer[i] == '\0') {
|
||||
Format(output, BUFFER_SIZE, "%s \\0", output);
|
||||
} else {
|
||||
Format(output, BUFFER_SIZE, "%s %c", output, this.buffer[i]);
|
||||
}
|
||||
}
|
||||
PrintToServer("%s", output);
|
||||
}
|
||||
|
||||
void WriteChar(char c) {
|
||||
this.buffer[this.offset++] = c;
|
||||
}
|
||||
|
||||
void WriteByte(int value) {
|
||||
this.buffer[this.offset++] = value & 0xFF;
|
||||
}
|
||||
|
||||
void WriteShort(int value) {
|
||||
this.buffer[this.offset++] = value & 0xFF;
|
||||
this.buffer[this.offset++] = (value >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
void WriteInt(int value, int bytes = 4) {
|
||||
this.buffer[this.offset++] = value & 0xFF;
|
||||
this.buffer[this.offset++] = (value >> 8) & 0xFF;
|
||||
this.buffer[this.offset++] = (value >> 16) & 0xFF;
|
||||
this.buffer[this.offset++] = (value >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
void WriteFloat(float value) {
|
||||
this.WriteInt(view_as<int>(value));
|
||||
}
|
||||
|
||||
/// Writes a variable-width string, with the size being prepended. Only supports strings up to 2^15 in size
|
||||
/// @param lenHint - optional, but the length of the string, to avoid strlen() twice
|
||||
void WriteVarString(const char[] string, int lenHint = -1) {
|
||||
if(lenHint < 0) lenHint = strlen(string);
|
||||
this.WriteShort(lenHint);
|
||||
// null term written will just get overwritten
|
||||
strcopy(this.buffer[this.offset], BUFFER_SIZE, string);
|
||||
this.offset += lenHint;
|
||||
}
|
||||
|
||||
// Writes a null-terminated length string, strlen > size is truncated.
|
||||
void WriteString(const char[] string) {
|
||||
int written = strcopy(this.buffer[this.offset], BUFFER_SIZE, string);
|
||||
this.offset += written + 1;
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
// Set newline
|
||||
this.buffer[this.offset++] = '\n';
|
||||
}
|
||||
|
||||
int ReadByte() {
|
||||
return this.buffer[this.offset++] & 0xFF;
|
||||
}
|
||||
|
||||
int ReadShort() {
|
||||
int value = this.buffer[this.offset++];
|
||||
value += this.buffer[this.offset++] << 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
int ReadInt() {
|
||||
int value = this.buffer[this.offset++];
|
||||
value += this.buffer[this.offset++] << 8;
|
||||
value += this.buffer[this.offset++] << 16;
|
||||
value += this.buffer[this.offset++] << 32;
|
||||
return value;
|
||||
}
|
||||
|
||||
float ReadFloat() {
|
||||
return view_as<float>(this.ReadInt());
|
||||
}
|
||||
|
||||
int ReadString(char[] output, int maxlen) {
|
||||
int len = strcopy(output, maxlen, this.buffer[this.offset]) + 1;
|
||||
this.offset += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
char ReadChar() {
|
||||
return this.buffer[this.offset++];
|
||||
}
|
||||
|
||||
bool EOF() {
|
||||
return this.offset >= BUFFER_SIZE;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue