Fixes / Updates

This commit is contained in:
Jackzie 2024-04-03 11:16:15 -05:00
parent 2e1bff4f86
commit f3ff80c5ab
6 changed files with 488 additions and 104 deletions

Binary file not shown.

View file

@ -14,6 +14,7 @@
#include <multicolors>
#include <jutils>
#include <socket>
#include <SteamWorks>
#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<int>(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 <command>");
@ -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<int>(type));
if(type == Result_Float)
ReplyToCommand(client, "Result: %f (type=%d)", result, view_as<int>(type));
else
ReplyToCommand(client, "Result: %d (type=%d)", result, view_as<int>(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<int>(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<int>(type)]);
recordStart = sendBuffer.offset;
sendBuffer.WriteByte(0); // write temp NULL to be replaced
sendBuffer.WriteByte(view_as<int>(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<int>(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<int>(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<int>(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;

View file

@ -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<StringMapSnapshot>(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<StringMapSnapshot>(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<AnyMap>(new StringMap());
}
public static void EncodeKey(any value, char dest[6])
{
dest[0] = view_as<char>(((value >>> 28) & 0x7F) | 0x80);
dest[1] = view_as<char>(((value >>> 21) & 0x7F) | 0x80);
dest[2] = view_as<char>(((value >>> 14) & 0x7F) | 0x80);
dest[3] = view_as<char>(((value >>> 7) & 0x7F) | 0x80);
dest[4] = view_as<char>((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<AnyMap>(view_as<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(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<StringMap>(this).Remove(encoded);
}
// Clears all entries from a map.
public void Clear()
{
view_as<StringMap>(this).Clear();
}
// Create a snapshot of the map's keys. See AnyMapSnapshot.
public AnyMapSnapshot Snapshot()
{
return view_as<AnyMapSnapshot>(view_as<StringMap>(this).Snapshot());
}
// Retrieves the number of elements in a map.
property int Size
{
public get()
{
return view_as<StringMap>(this).Size;
}
}
};

View file

@ -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];
}
}

View file

@ -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);

View file

@ -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;