This commit is contained in:
Jackzie 2024-04-27 08:02:36 -05:00
parent f3ff80c5ab
commit 9a5aa5dd5f
24 changed files with 478 additions and 101 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
plugins/l4d2_mimic.smx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -21,6 +21,7 @@ char PRECACHE_SOUNDS[PRECACHE_SOUNDS_COUNT][] = {
#tryinclude <sceneprocessor> #tryinclude <sceneprocessor>
#include <multicolors> #include <multicolors>
#include "l4d_survivor_identity_fix.inc" #include "l4d_survivor_identity_fix.inc"
#include <anymap>
char MODELS[8][] = { char MODELS[8][] = {
"models/survivors/survivor_gambler.mdl", "models/survivors/survivor_gambler.mdl",
@ -51,6 +52,7 @@ static bool isHighPingIdle[MAXPLAYERS+1], isL4D1Survivors;
static Handle hGoAwayFromKeyboard; static Handle hGoAwayFromKeyboard;
static StringMap SteamIDs; static StringMap SteamIDs;
static char lastSound[MAXPLAYERS+1][64], gamemode[32]; static char lastSound[MAXPLAYERS+1][64], gamemode[32];
AnyMap disabledItems;
static float OUT_OF_BOUNDS[3] = {0.0, -1000.0, 0.0}; static float OUT_OF_BOUNDS[3] = {0.0, -1000.0, 0.0};
@ -108,6 +110,7 @@ public void OnPluginStart() {
} }
LasersUsed = new ArrayList(1, 0); LasersUsed = new ArrayList(1, 0);
disabledItems = new AnyMap();
SteamIDs = new StringMap(); SteamIDs = new StringMap();
hGamemode = FindConVar("mp_gamemode"); hGamemode = FindConVar("mp_gamemode");
@ -462,7 +465,7 @@ void SetCharacter(int target, int survivorIndex, L4DModelId modelIndex, bool kee
if (IsFakeClient(target)) { if (IsFakeClient(target)) {
char name[32]; char name[32];
GetSurvivorName(target, name, sizeof(name)); GetSurvivorName(target, name, sizeof(name));
// SetClientInfo(target, "name", name); SetClientInfo(target, "name", name);
} }
UpdatePlayerIdentity(target, view_as<Character>(survivorIndex), keepModel); UpdatePlayerIdentity(target, view_as<Character>(survivorIndex), keepModel);
@ -595,7 +598,6 @@ public void Event_PlayerDisconnect(Event event, const char[] name, bool dontBroa
} }
} }
int disabledItem[2048];
//Can also probably prevent kit drop to pick them up //Can also probably prevent kit drop to pick them up
public void Event_WeaponDrop(Event event, const char[] name, bool dontBroadcast) { public void Event_WeaponDrop(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
@ -604,16 +606,18 @@ public void Event_WeaponDrop(Event event, const char[] name, bool dontBroadcast)
GetEntityClassname(client, newWpn, sizeof(newWpn)); GetEntityClassname(client, newWpn, sizeof(newWpn));
if(StrEqual(newWpn, "weapon_ammo_pack")) { if(StrEqual(newWpn, "weapon_ammo_pack")) {
// prevent weapon from being picked up? // prevent weapon from being picked up?
disabledItem[weapon] = client; disabledItems.SetValue(weapon, GetClientUserId(client));
CreateTimer(10.0, Timer_AllowKitPickup, weapon); CreateTimer(10.0, Timer_AllowKitPickup, weapon);
} }
} }
public Action Event_OnWeaponEquip(int client, int weapon) { public Action Event_OnWeaponEquip(int client, int weapon) {
if(disabledItem[weapon] > 0 && disabledItem[weapon] != client) return Plugin_Handled; int userid;
return Plugin_Continue; if(disabledItems.GetValue(weapon, userid) && GetClientUserId(client) == userid)
return Plugin_Handled;
else return Plugin_Continue;
} }
public Action Timer_AllowKitPickup(Handle h, int entity) { public Action Timer_AllowKitPickup(Handle h, int entity) {
disabledItem[entity] = 0; disabledItems.Remove(entity);
return Plugin_Handled; return Plugin_Handled;
} }
public void OnMapStart() { public void OnMapStart() {
@ -629,6 +633,9 @@ public void OnMapStart() {
HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("info_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom); HookEntityOutput("trigger_changelevel", "OnStartTouch", EntityOutput_OnStartTouchSaferoom);
} }
public void OnMapEnd() {
disabledItems.Clear();
}
public void OnConfigsExecuted() { public void OnConfigsExecuted() {
isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1; isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1;
if(hSVMaxPlayers != null && hPlayerLimit.IntValue > 0) { if(hSVMaxPlayers != null && hPlayerLimit.IntValue > 0) {
@ -638,14 +645,19 @@ public void OnConfigsExecuted() {
#if defined _sceneprocessor_included #if defined _sceneprocessor_included
public void OnSceneStageChanged(int scene, SceneStages stage) { public void OnSceneStageChanged(int scene, SceneStages stage) {
if(stage == SceneStage_Started) { if(stage == SceneStage_SpawnedPost) {
int activator = GetSceneInitiator(scene); int activator = GetSceneInitiator(scene);
// int actor = GetActorFromScene(scene);
// PrintToServer("activator=%N actor=%N %s", activator, actor, sceneFile);
if(activator == 0) { if(activator == 0) {
static char sceneFile[64]; static char sceneFile[64];
GetSceneFile(scene, sceneFile, sizeof(sceneFile)); GetSceneFile(scene, sceneFile, sizeof(sceneFile));
if(StrContains(sceneFile, "scenes/mechanic/dlc1_c6m1_initialmeeting") > -1 || StrEqual(sceneFile, "scenes/teengirl/dlc1_c6m1_initialmeeting07.vcd")) { if(StrContains(sceneFile, "scenes/mechanic/dlc1_c6m1_initialmeeting") > -1 || StrEqual(sceneFile, "scenes/teengirl/dlc1_c6m1_initialmeeting07.vcd")) {
CancelScene(scene); CancelScene(scene);
}else if(StrEqual(sceneFile, "scenes/teengirl/dlc1_c6m1_initialmeeting13.vcd") && activator == 0) { } else if(StrEqual(sceneFile, "scenes/teengirl/dlc1_c6m1_initialmeeting13.vcd")) {
CancelScene(scene);
} else if(StrEqual(sceneFile, "scenes/coach/worldc1m3b04.vcd")) {
CancelScene(scene); CancelScene(scene);
} }
} }

View file

@ -52,6 +52,7 @@ int playerJoinTime[MAXPLAYERS+1];
Handle updateHealthTimer[MAXPLAYERS+1]; Handle updateHealthTimer[MAXPLAYERS+1];
Handle updateItemTimer[MAXPLAYERS+1]; Handle updateItemTimer[MAXPLAYERS+1];
Handle receiveTimeoutTimer = null; Handle receiveTimeoutTimer = null;
int pendingTries = 3;
bool lateLoaded; bool lateLoaded;
@ -72,16 +73,12 @@ GameState g_gameState;
Buffer sendBuffer; Buffer sendBuffer;
Buffer receiveBuffer; // Unfortunately there's no easy way to have this not be the same as BUFFER_SIZE 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) public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
{
lateLoaded = late; lateLoaded = late;
return APLRes_Success; return APLRes_Success;
} }
public void OnPluginStart() public void OnPluginStart() {
{
// TODO: periodic reconnect
g_socket = new Socket(SOCKET_TCP, OnSocketError); g_socket = new Socket(SOCKET_TCP, OnSocketError);
g_socket.SetOption(SocketKeepAlive, 1); g_socket.SetOption(SocketKeepAlive, 1);
g_socket.SetOption(SocketReuseAddr, 1); g_socket.SetOption(SocketReuseAddr, 1);
@ -118,17 +115,20 @@ public void OnPluginStart()
HookEvent("pills_used", Event_ItemUsed); HookEvent("pills_used", Event_ItemUsed);
HookEvent("adrenaline_used", Event_ItemUsed); HookEvent("adrenaline_used", Event_ItemUsed);
HookEvent("weapon_drop", Event_WeaponDrop); HookEvent("weapon_drop", Event_WeaponDrop);
HookEvent("player_first_spawn", Event_PlayerFirstSpawn); HookEvent("player_spawn", Event_PlayerSpawn);
HookEvent("map_transition", Event_MapTransition); HookEvent("map_transition", Event_MapTransition);
HookEvent("player_death", Event_PlayerDeath); HookEvent("player_death", Event_PlayerDeath);
HookEvent("player_bot_replace", Event_PlayerToBot); HookEvent("player_bot_replace", Event_PlayerToBot);
HookEvent("bot_player_replace", Event_BotToPlayer); HookEvent("bot_player_replace", Event_BotToPlayer);
campaignStartTime = GetTime(); campaignStartTime = GetTime();
char auth[32];
for(int i = 1; i <= MaxClients; i++) { for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) { if(IsClientInGame(i)) {
playerJoinTime[i] = GetTime(); if(GetClientAuthId(i, AuthId_Steam2, auth, sizeof(auth))) {
OnClientPutInServer(i); OnClientAuthorized(i, auth);
OnClientPutInServer(i);
}
} }
} }
@ -138,6 +138,15 @@ public void OnPluginStart()
RegAdminCmd("sm_panel_request_stop", Command_RequestStop, ADMFLAG_GENERIC); RegAdminCmd("sm_panel_request_stop", Command_RequestStop, ADMFLAG_GENERIC);
CommandArgRegex = new Regex("(?:[^\\s\"]+|\"[^\"]*\")+", 0); CommandArgRegex = new Regex("(?:[^\\s\"]+|\"[^\"]*\")+", 0);
CreateTimer(300.0, Timer_FullSync, _, TIMER_REPEAT);
}
Action Timer_FullSync(Handle h) {
if(CanSendPayload(true)) {
SendFullSync();
}
return Plugin_Continue;
} }
void TriggerHealthUpdate(int client, bool instant = false) { void TriggerHealthUpdate(int client, bool instant = false) {
@ -187,6 +196,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
if(authState == Auth_Pending) { if(authState == Auth_Pending) {
if(response == Live_OK) { if(response == Live_OK) {
authState = Auth_Success; authState = Auth_Success;
pendingTries = 0;
PrintToServer("[AdminPanel] Authenticated with server successfully."); PrintToServer("[AdminPanel] Authenticated with server successfully.");
SendFullSync(); SendFullSync();
} else if(response == Live_Error) { } else if(response == Live_Error) {
@ -203,7 +213,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
lastReceiveTime = GetTime(); lastReceiveTime = GetTime();
switch(response) { switch(response) {
case Live_RunComand: { case Live_RunCommand: {
char command[128]; char command[128];
char cmdNamespace[32]; char cmdNamespace[32];
int id = receiveBuffer.ReadByte(); int id = receiveBuffer.ReadByte();
@ -223,8 +233,18 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
case Live_Reconnect: case Live_Reconnect:
CreateTimer(5.0, Timer_Reconnect); CreateTimer(5.0, Timer_Reconnect);
case Live_Refresh: { case Live_Refresh: {
PrintToServer("[AdminPanel] Sync requested, performing"); int userid = receiveBuffer.ReadByte();
// SendFullSync(); if(userid > 0) {
int client = GetClientOfUserId(userid);
if(client > 0 && StartPayload(true)) {
PrintToServer("[AdminPanel] Sync requested for #%d, performing", userid);
AddPlayerRecord(client);
SendPayload();
}
} else {
PrintToServer("[AdminPanel] Sync requested, performing");
SendFullSync();
}
} }
} }
if(receiveTimeoutTimer != null) { if(receiveTimeoutTimer != null) {
@ -481,6 +501,7 @@ void ConnectSocket() {
if(authToken[0] == '\0') return; if(authToken[0] == '\0') return;
// Do not try to reconnect on auth failure, until token has changed // Do not try to reconnect on auth failure, until token has changed
if(authState == Auth_Fail) return; if(authState == Auth_Fail) return;
authState = Auth_Pending;
g_socket.Connect(OnSocketConnect, OnSocketReceive, OnSocketDisconnect, serverIp, serverPort); g_socket.Connect(OnSocketConnect, OnSocketReceive, OnSocketDisconnect, serverIp, serverPort);
} }
@ -499,6 +520,11 @@ Action Command_PanelDebug(int client, int args) {
ReplyToCommand(client, "Target Host: %s:%d", serverIp, serverPort); ReplyToCommand(client, "Target Host: %s:%d", serverIp, serverPort);
ReplyToCommand(client, "Buffer Size: %d", BUFFER_SIZE); ReplyToCommand(client, "Buffer Size: %d", BUFFER_SIZE);
ReplyToCommand(client, "Can Send: %b\tCan Force-Send: %b", CanSendPayload(), CanSendPayload(true)); ReplyToCommand(client, "Can Send: %b\tCan Force-Send: %b", CanSendPayload(), CanSendPayload(true));
} else if(StrEqual(arg, "cansend")) {
if(!g_socket.Connected) ReplyToCommand(client, "Socket Not Connected");
else if(authState != Auth_Success) ReplyToCommand(client, "Socket Not Authenticated (State=%d)", authState);
else if(numberOfViewers == 0 || numberOfPlayers == 0) ReplyToCommand(client, "Can send forefully, but no players(%d)/viewers(%d)", numberOfPlayers, numberOfViewers);
ReplyToCommand(client, "Can Send!");
} else if(StrEqual(arg, "builtin")) { } else if(StrEqual(arg, "builtin")) {
if(args < 2) { if(args < 2) {
ReplyToCommand(client, "Usage: builtin <command>"); ReplyToCommand(client, "Usage: builtin <command>");
@ -561,6 +587,7 @@ void Event_GameStart(Event event, const char[] name, bool dontBroadcast) {
} }
void Event_GameEnd(Event event, const char[] name, bool dontBroadcast) { void Event_GameEnd(Event event, const char[] name, bool dontBroadcast) {
campaignStartTime = 0; campaignStartTime = 0;
CreateTimer(10.0, Timer_FullSync);
} }
void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) { void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) {
@ -571,6 +598,12 @@ void Event_MapTransition(Event event, const char[] name, bool dontBroadcast) {
} }
} }
public void L4D_OnFirstSurvivorLeftSafeArea_Post(int client) {
if(CanSendPayload()) {
SendPlayers();
}
}
void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerDeath(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) { if(client > 0) {
@ -590,6 +623,8 @@ public void Event_BotToPlayer(Handle event, char[] name, bool dontBroadcast) {
int player = GetClientOfUserId(GetEventInt(event, "player")); int player = GetClientOfUserId(GetEventInt(event, "player"));
int bot = GetClientOfUserId(GetEventInt(event, "bot")); int bot = GetClientOfUserId(GetEventInt(event, "bot"));
if(player > 0 && !IsFakeClient(player) && StartPayload(true)) { if(player > 0 && !IsFakeClient(player) && StartPayload(true)) {
// Bot is going away, remove it: (prob unnecessary OnClientDisconnect happens)
AddPlayerRecord(bot, false);
AddPlayerRecord(player); AddPlayerRecord(player);
SendPayload(); SendPayload();
} }
@ -616,10 +651,13 @@ void Event_HealInterrupted(Event event, const char[] name, bool dontBroadcast) {
g_icBeingHealed[subject] = false; g_icBeingHealed[subject] = false;
} }
void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) { void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
playerJoinTime[client] = GetTime();
RecalculatePlayerCount(); RecalculatePlayerCount();
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0 && StartPayload()) {
AddPlayerRecord(client);
SendPayload();
}
} }
void RecalculatePlayerCount() { void RecalculatePlayerCount() {
@ -662,6 +700,21 @@ public void OnMapStart() {
public void OnConfigsExecuted() { public void OnConfigsExecuted() {
isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1; isL4D1Survivors = L4D2_GetSurvivorSetMap() == 1;
} }
public void OnClientAuthorized(int client, const char[] auth) {
if(!IsFakeClient(client)) {
strcopy(steamidCache[client], 32, auth);
numberOfPlayers++;
} else {
// Check if they are not a real survivor bot, such as ABMBot or EPIBot, etc
if(StrContains(nameCache[client], "bot", false) > -1) {
return;
}
strcopy(steamidCache[client], 32, "BOT");
}
GetClientName(client, nameCache[client], MAX_NAME_LENGTH);
playerJoinTime[client] = GetTime();
RequestFrame(SendNewClient, client);
}
// Player counts // Player counts
public void OnClientPutInServer(int client) { public void OnClientPutInServer(int client) {
if(g_gameState == State_Transitioning) { if(g_gameState == State_Transitioning) {
@ -671,21 +724,9 @@ public void OnClientPutInServer(int client) {
SendPayload(); SendPayload();
} }
} }
GetClientName(client, nameCache[client], MAX_NAME_LENGTH);
if(!IsFakeClient(client)) {
GetClientAuthId(client, AuthId_SteamID64, steamidCache[client], 32);
numberOfPlayers++;
} else {
// Check if they are not a bot, such as ABMBot or EPIBot, etc
if(StrContains(nameCache[client], "bot", false) > -1) {
return;
}
strcopy(steamidCache[client], 32, "BOT");
}
SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponPickUp); SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponPickUp);
SDKHook(client, SDKHook_OnTakeDamageAlivePost, OnTakeDamagePost); SDKHook(client, SDKHook_OnTakeDamageAlivePost, OnTakeDamagePost);
// We wait a frame because Event_PlayerFirstSpawn sets their join time // We wait a frame because Event_PlayerFirstSpawn sets their join time
RequestFrame(SendNewClient, client);
} }
void OnWeaponPickUp(int client, int weapon) { void OnWeaponPickUp(int client, int weapon) {
@ -780,6 +821,7 @@ public void OnClientDisconnect(int client) {
// Incase somehow we lost track // Incase somehow we lost track
if(numberOfPlayers < 0) { if(numberOfPlayers < 0) {
numberOfPlayers = 0; numberOfPlayers = 0;
CreateTimer(1.0, Timer_FullSync);
} }
} }
if(updateHealthTimer[client] != null) { if(updateHealthTimer[client] != null) {
@ -806,7 +848,6 @@ void OnCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
if(serverPort == 0) serverPort = DEFAULT_SERVER_PORT; if(serverPort == 0) serverPort = DEFAULT_SERVER_PORT;
} }
PrintToServer("[AdminPanel] Sending data to %s:%d", serverIp, serverPort); PrintToServer("[AdminPanel] Sending data to %s:%d", serverIp, serverPort);
authState = Auth_Pending;
ConnectSocket(); ConnectSocket();
} }
} else if(cvar_gamemode == convar) { } else if(cvar_gamemode == convar) {
@ -823,7 +864,6 @@ void OnCvarChanged(ConVar convar, const char[] oldValue, const char[] newValue)
} }
} else if(cvar_authToken == convar) { } else if(cvar_authToken == convar) {
strcopy(authToken, sizeof(authToken), newValue); strcopy(authToken, sizeof(authToken), newValue);
authState = Auth_Pending;
// Token changed, re-try authentication // Token changed, re-try authentication
ConnectSocket(); ConnectSocket();
} }
@ -931,15 +971,15 @@ enum CommandResultType {
} }
enum LiveRecordType { enum LiveRecordType {
Live_Game, Live_Game = 0,
Live_Player, Live_Player = 1,
Live_Survivor, Live_Survivor = 2,
Live_Infected, Live_Infected = 3,
Live_Finale, Live_Finale = 4,
Live_SurvivorItems, Live_SurvivorItems = 5,
Live_CommandResponse, Live_CommandResponse = 6,
Live_Auth, Live_Auth = 7,
Live_Meta Live_Meta = 8
} }
char LIVE_RECORD_NAMES[view_as<int>(Live_Meta)+1][] = { char LIVE_RECORD_NAMES[view_as<int>(Live_Meta)+1][] = {
"Game", "Game",
@ -958,13 +998,21 @@ enum LiveRecordResponse {
Live_Reconnect, Live_Reconnect,
Live_Error, Live_Error,
Live_Refresh, Live_Refresh,
Live_RunComand Live_RunCommand
} }
char pendingRecords[64]; char pendingRecords[64];
bool CanSendPayload(bool ignorePause = false) { bool CanSendPayload(bool ignorePause = false) {
if(!g_socket.Connected || authState != Auth_Success) return false; if(!g_socket.Connected) return false;
if(authState != Auth_Success) {
if(authState == Auth_Pending && pendingTries > 0) {
pendingTries--;
PrintToServer("[AdminPanel] Auth state is pending. Too early?");
ConnectSocket();
}
return false;
}
if(!ignorePause && (numberOfViewers == 0 || numberOfPlayers == 0)) return false; if(!ignorePause && (numberOfViewers == 0 || numberOfPlayers == 0)) return false;
return true; return true;
} }
@ -978,34 +1026,45 @@ bool StartPayload(bool ignorePause = false) {
/// Starts payload, ignoring if the payload can even be sent /// Starts payload, ignoring if the payload can even be sent
bool hasRecord; bool hasRecord;
int recordStart; int recordStart;
bool pendingRecord;
void StartPayloadEx() { void StartPayloadEx() {
if(pendingRecord) {
LogError("StartPayloadEx called before EndRecord()");
}
sendBuffer.Reset(); sendBuffer.Reset();
hasRecord = false; hasRecord = false;
pendingRecords[0] = '\0'; pendingRecords[0] = '\0';
recordStart = 0; recordStart = 0;
pendingRecord = false;
} }
void StartRecord(LiveRecordType type) { void StartRecord(LiveRecordType type) {
if(pendingRecord) {
LogError("StartRecord called before EndRecord()");
}
if(hasRecord) { if(hasRecord) {
sendBuffer.WriteChar('\x1e'); sendBuffer.WriteChar('\x1e');
} }
if(cvar_debug.BoolValue) if(cvar_debug.BoolValue)
Format(pendingRecords, sizeof(pendingRecords), "%s%s ", pendingRecords, LIVE_RECORD_NAMES[view_as<int>(type)]); Format(pendingRecords, sizeof(pendingRecords), "%s%s ", pendingRecords, LIVE_RECORD_NAMES[view_as<int>(type)]);
recordStart = sendBuffer.offset; recordStart = sendBuffer.offset;
sendBuffer.WriteByte(0); // write temp NULL to be replaced sendBuffer.WriteShort(-1); // write temp value to be replaced when record ends
sendBuffer.WriteByte(view_as<int>(type)); sendBuffer.WriteByte(view_as<int>(type));
hasRecord = true; pendingRecord = true;
} }
void EndRecord() { void EndRecord() {
int length = sendBuffer.offset - recordStart - 1; // subtract 1, as don't count length inside int length = sendBuffer.offset - recordStart - 2; // subtract 1, as don't count length inside
sendBuffer.WriteByteAt(length, recordStart); sendBuffer.WriteShortAt(length, recordStart);
// if(cvar_debug.BoolValue) { // if(cvar_debug.BoolValue) {
// int type = sendBuffer.ReadByteAt(recordStart + 1); // int type = sendBuffer.ReadByteAt(recordStart + 2);
// PrintToServer("End record %s(%d) (start: %d, end: %d) length: %d", LIVE_RECORD_NAMES[view_as<int>(type)], type, recordStart, sendBuffer.offset, length); // PrintToServer("End record %s(%d) (start: %d, end: %d) length: %d", LIVE_RECORD_NAMES[view_as<int>(type)], type, recordStart, sendBuffer.offset, length);
// } // }
hasRecord = true;
pendingRecord = false;
} }
void AddGameRecord() { void AddGameRecord() {
@ -1020,13 +1079,6 @@ void AddGameRecord() {
EndRecord(); 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) { void AddFinaleRecord(int stage) {
StartRecord(Live_Finale); StartRecord(Live_Finale);
sendBuffer.WriteByte(stage); // finale stage sendBuffer.WriteByte(stage); // finale stage
@ -1127,6 +1179,9 @@ void AddCommandResponseRecord(int id, CommandResultType resultType = Result_None
} }
void AddAuthRecord() { void AddAuthRecord() {
if(authToken[0] == '\0') {
LogError("AddAuthRecord called with missing auth token");
}
StartRecord(Live_Auth); StartRecord(Live_Auth);
sendBuffer.WriteByte(LIVESTATUS_VERSION); sendBuffer.WriteByte(LIVESTATUS_VERSION);
sendBuffer.WriteString(authToken); sendBuffer.WriteString(authToken);
@ -1141,10 +1196,11 @@ void AddMetaRecord(bool state) {
void SendPayload() { void SendPayload() {
if(sendBuffer.offset == 0) return; if(sendBuffer.offset == 0) return;
int len = sendBuffer.Finish();
if(cvar_debug.BoolValue) { if(cvar_debug.BoolValue) {
PrintToServer("[AdminPanel] Sending %d bytes of data (records = %s)", sendBuffer.offset, pendingRecords); PrintToServer("[AdminPanel] Sending %d bytes of data (records = %s)", len, pendingRecords);
} }
g_socket.Send(sendBuffer.buffer, sendBuffer.offset); g_socket.Send(sendBuffer.buffer, len);
} }
enum struct Buffer { enum struct Buffer {
@ -1194,19 +1250,30 @@ enum struct Buffer {
this.buffer[this.offset++] = (value >> 8) & 0xFF; this.buffer[this.offset++] = (value >> 8) & 0xFF;
} }
void WriteInt(int value, int bytes = 4) { void WriteShortAt(int value, int offset) {
this.buffer[this.offset++] = value & 0xFF; this.buffer[offset] = value & 0xFF;
this.buffer[this.offset++] = (value >> 8) & 0xFF; this.buffer[offset+1] = (value >> 8) & 0xFF;
this.buffer[this.offset++] = (value >> 16) & 0xFF; }
this.buffer[this.offset++] = (value >> 24) & 0xFF;
void WriteInt(int value) {
this.WriteIntAt(value, this.offset);
this.offset += 4;
} }
void WriteIntAt(int value, int offset) {
this.buffer[offset] = value & 0xFF;
this.buffer[offset+1] = (value >> 8) & 0xFF;
this.buffer[offset+2] = (value >> 16) & 0xFF;
this.buffer[offset+3] = (value >> 24) & 0xFF;
}
void WriteFloat(float value) { void WriteFloat(float value) {
this.WriteInt(view_as<int>(value)); this.WriteInt(view_as<int>(value));
} }
// Writes a null-terminated length string, strlen > size is truncated. // Writes a null-terminated length string, strlen > size is truncated.
void WriteString(const char[] string) { void WriteString(const char[] string) {
this.buffer[this.offset] = '\0';
int written = strcopy(this.buffer[this.offset], BUFFER_SIZE, string); int written = strcopy(this.buffer[this.offset], BUFFER_SIZE, string);
this.offset += written + 1; this.offset += written + 1;
} }
@ -1250,4 +1317,15 @@ enum struct Buffer {
bool EOF() { bool EOF() {
return this.offset >= BUFFER_SIZE; return this.offset >= BUFFER_SIZE;
} }
int Finish() {
this.buffer[this.offset++] = '\x0A';
return this.offset;
}
}
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;
} }

View file

@ -169,8 +169,7 @@ void OnTankBotSpawn(int client) {
SetEntProp(client, Prop_Send, "m_iHealth", health); SetEntProp(client, Prop_Send, "m_iHealth", health);
g_finaleStage = Stage_FirstTankSpawned; g_finaleStage = Stage_FirstTankSpawned;
return; return;
} else if(g_realSurvivorCount < 6 && g_finaleStage == Stage_FirstTankSpawned) { } else if(g_realSurvivorCount >= 6 && g_finaleStage == Stage_FirstTankSpawned) {
// 2nd tank spawned
PrintDebug(DEBUG_SPAWNLOGIC, "OnTankBotSpawn: [FINALE] 2nd tank spawned"); PrintDebug(DEBUG_SPAWNLOGIC, "OnTankBotSpawn: [FINALE] 2nd tank spawned");
float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC); float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC);
// Pass it 0, which doesnt make it a split tank, has default health // Pass it 0, which doesnt make it a split tank, has default health
@ -215,6 +214,7 @@ int CalculateExtraTankHealth(int client) {
int health = GetEntProp(client, Prop_Send, "m_iHealth"); int health = GetEntProp(client, Prop_Send, "m_iHealth");
float additionalHealth = float(g_survivorCount - 4) * cvEPITankHealth.FloatValue; float additionalHealth = float(g_survivorCount - 4) * cvEPITankHealth.FloatValue;
health += RoundFloat(additionalHealth); health += RoundFloat(additionalHealth);
if(health <= 0) PrintToServer("CalculateExtraTankHealth: returning 0?");
return health; return health;
} }

View file

@ -707,15 +707,15 @@ Action Command_SetReverseFF(int client, int args) {
} else if(StrEqual(arg, "1")) { } else if(StrEqual(arg, "1")) {
flag = 1; flag = 1;
} else { } else {
ReplyToCommand(client, "Unsupported amount. Possible values: 0, 2, 0.5, 3, 1"); ReplyToCommand(client, "Unsupported amount. Possible values: 0.5, 1, 2, 3");
return Plugin_Handled; return Plugin_Handled;
} }
// args are 1-indexed so <= // args are 1-indexed so <=
for(int i = 3; i <= args; i++) { for(int i = 3; i <= args; i++) {
GetCmdArg(i, arg, sizeof(arg)); GetCmdArg(i, arg, sizeof(arg));
if(arg[0] == 'f') { if(arg[0] == 'f') { // [f]ire
flag |= 32; flag |= 32;
} else if(arg[0] == 'e') { } else if(arg[0] == 'e' || arg[0] == 'b') { //[]blast or [e]xplode
flag |= 64; flag |= 64;
} else { } else {
ReplyToCommand(client, "Unknown arg: %s", arg); ReplyToCommand(client, "Unknown arg: %s", arg);

View file

@ -86,9 +86,14 @@ public Plugin myinfo =
}; };
ConVar hExtraItemBasePercentage, hExtraSpawnBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode, cvEPITankHealth, cvEPIEnabledMode; ConVar hExtraItemBasePercentage, hExtraSpawnBasePercentage, hAddExtraKits, hMinPlayers, hUpdateMinPlayers, hMinPlayersSaferoomDoor, hSaferoomDoorWaitSeconds, hSaferoomDoorAutoOpen, hEPIHudState, hExtraFinaleTank, cvDropDisconnectTime, hSplitTankChance, cvFFDecreaseRate, cvZDifficulty, cvEPIHudFlags, cvEPISpecialSpawning, cvEPIGamemodes, hGamemode, cvEPITankHealth, cvEPIEnabledMode;
ConVar cvEPICommonCountScale, cvEPICommonCountScaleMax;
ConVar g_ffFactorCvar, hExtraTankThreshold; ConVar g_ffFactorCvar, hExtraTankThreshold;
ConVar cvZCommonLimit; int zCommonLimitPrevValue;
int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount; int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
bool g_forcedSurvivorCount; bool g_forcedSurvivorCount, g_extraKitsSpawnedFinale;
static int g_currentChapter; static int g_currentChapter;
bool g_isCheckpointReached, g_isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated; bool g_isCheckpointReached, g_isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated;
static ArrayList g_ammoPacks; static ArrayList g_ammoPacks;
@ -317,6 +322,10 @@ public void OnPluginStart() {
cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0); cvEPITankHealth = CreateConVar("epi_tank_chunkhp", "2500", "The amount of health added to tank, for each extra player", FCVAR_NONE, true, 0.0);
cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE); cvEPIGamemodes = CreateConVar("epi_gamemodes", "coop,realism,versus", "Gamemodes where plugin is active. Comma-separated", FCVAR_NONE);
cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0); cvEPIEnabledMode = CreateConVar("epi_enabled", "1", "Is EPI enabled?\n0=OFF\n1=Auto (Official Maps Only)(5+)\n2=Auto (Any map) (5+)\n3=Forced on", FCVAR_NONE, true, 0.0, true, 3.0);
cvEPICommonCountScale = CreateConVar("epi_commons_scale_multiplier", "0", "This value is multiplied by the number of extra players playing. It's then added to z_common_limit. 5 players with value 5 would be z_common_limit + ", FCVAR_NONE, true, 0.0);
cvEPICommonCountScaleMax = CreateConVar("epi_commons_scale_max", "60", "The maximum amount that z_common_limit can be scaled to.", FCVAR_NONE, true, 0.0);
cvZCommonLimit = FindConVar("z_common_limit");
// TODO: hook flags, reset name index / ping mode // TODO: hook flags, reset name index / ping mode
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange); cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange); cvEPISpecialSpawning.AddChangeHook(Cvar_SpecialSpawningChange);
@ -1001,6 +1010,19 @@ Action Event_Pickup(int client, int weapon) {
void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) { void Event_ItemPickup(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid")); int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0 && GetClientTeam(client) == 2 && !IsFakeClient(client)) { if(client > 0 && GetClientTeam(client) == 2 && !IsFakeClient(client)) {
if(!g_extraKitsSpawnedFinale && L4D_IsMissionFinalMap(true)) {
float pos[3];
GetAbsOrigin(client, pos);
Address address = L4D_GetNearestNavArea(pos);
if(address != Address_Null) {
int attributes = L4D_GetNavArea_SpawnAttributes(address);
if(attributes & NAV_SPAWN_FINALE) {
IncreaseKits(true);
}
}
}
// TODO: trigger increase kits finale on kit pickup
UpdatePlayerInventory(client); UpdatePlayerInventory(client);
QueueSaveInventory(client); QueueSaveInventory(client);
} }
@ -1197,6 +1219,11 @@ int SpawnItem(const char[] itemName, float pos[3], float ang[3] = NULL_VECTOR) {
} }
void IncreaseKits(bool inFinale) { void IncreaseKits(bool inFinale) {
if(inFinale) {
if(g_extraKitsSpawnedFinale) return;
g_extraKitsSpawnedFinale = true;
g_extraKitsAmount = g_realSurvivorCount - 4;
}
float pos[3]; float pos[3];
int entity = FindEntityByClassname(-1, "weapon_first_aid_kit_spawn"); int entity = FindEntityByClassname(-1, "weapon_first_aid_kit_spawn");
if(entity == INVALID_ENT_REFERENCE) { if(entity == INVALID_ENT_REFERENCE) {
@ -1289,12 +1316,13 @@ void Debug_GetAttributes(int attributes, char[] output, int maxlen) {
} }
public void L4D2_OnChangeFinaleStage_Post(int stage) { public void L4D2_OnChangeFinaleStage_Post(int stage) {
if(stage == 1) { if(stage == 1 && IsEPIActive()) {
IncreaseKits(true); IncreaseKits(true);
} }
} }
public void OnMapStart() { public void OnMapStart() {
g_extraKitsSpawnedFinale = false;
char map[32]; char map[32];
GetCurrentMap(map, sizeof(map)); GetCurrentMap(map, sizeof(map));
// If map starts with c#m#, 98% an official map // If map starts with c#m#, 98% an official map
@ -2031,14 +2059,7 @@ bool DoesInventoryDiffer(int client) {
bool IsEPIActive() { bool IsEPIActive() {
return g_epiEnabled; return g_epiEnabled;
} }
/* bool wasActive;
[Debug] UpdateSurvivorCount: total=4 real=4 active=4
[Debug] UpdateSurvivorCount: total=4 real=4 active=4
Player no longer idle
[Debug] UpdateSurvivorCount: total=5 real=4 active=5
[Debug] UpdateSurvivorCount: total=4 real=4 active=4
Player no longer idle
*/
void UpdateSurvivorCount() { void UpdateSurvivorCount() {
#if defined DEBUG_FORCE_PLAYERS #if defined DEBUG_FORCE_PLAYERS
g_survivorCount = DEBUG_FORCE_PLAYERS; g_survivorCount = DEBUG_FORCE_PLAYERS;
@ -2075,9 +2096,35 @@ void UpdateSurvivorCount() {
isActive = (g_isOfficialMap || cvEPIEnabledMode.IntValue == 2) && g_realSurvivorCount > 4; isActive = (g_isOfficialMap || cvEPIEnabledMode.IntValue == 2) && g_realSurvivorCount > 4;
} }
g_epiEnabled = isActive; g_epiEnabled = isActive;
if(g_epiEnabled && !wasActive) {
OnEPIActive();
wasActive = true;
} else if(wasActive) {
OnEPIInactive();
}
if(isActive)
SetFFFactor(g_epiEnabled); SetFFFactor(g_epiEnabled);
} }
void OnEPIActive() {
zCommonLimitPrevValue = cvZCommonLimit.IntValue;
// TODO: lag check for common limit
if(cvEPICommonCountScale.IntValue > 0) {
int newLimit = zCommonLimitPrevValue + RoundFloat(cvEPICommonCountScale.FloatValue * float(g_realSurvivorCount));
if(newLimit > 0) {
if(newLimit > cvEPICommonCountScaleMax.IntValue) {
newLimit = cvEPICommonCountScaleMax.IntValue;
}
}
cvZCommonLimit.IntValue = newLimit;
}
}
void OnEPIInactive() {
cvZCommonLimit.IntValue = zCommonLimitPrevValue;
}
void SetFFFactor(bool enabled) { void SetFFFactor(bool enabled) {
static float prevValue; static float prevValue;
// Restore the previous value (we use the value for the calculations of new value) // Restore the previous value (we use the value for the calculations of new value)

View file

@ -343,6 +343,9 @@ public void OnMapStart() {
} }
Game.State = State_Unknown; Game.State = State_Unknown;
} }
public void OnMapEnd() {
Game.Cleanup();
}
public void ThinkPost(int entity) { public void ThinkPost(int entity) {
static int iTeamNum[MAXPLAYERS+1]; static int iTeamNum[MAXPLAYERS+1];

View file

@ -248,6 +248,9 @@ public void OnMapStart() {
lateLoaded = false; lateLoaded = false;
} }
} }
public void OnMapEnd() {
Game.Cleanup();
}
public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) { public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
if(isEnabled) { if(isEnabled) {

225
scripting/l4d2_mimic.sp Normal file
View file

@ -0,0 +1,225 @@
#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#include <jutils>
#include <left4dhooks>
int g_mimicBot;
int g_mimicController;
int g_mimicCamera;
public Plugin myinfo =
{
name = "L4D2 Mimic",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D/L4D2 only.");
}
RegAdminCmd("sm_mimic", Command_Mimic, ADMFLAG_CHEATS);
HookEvent("player_bot_replace", Event_BotToIdle);
AddCommandListener(OnCommand);
}
public void OnPluginEnd() {
StopMimic();
}
#define PASSTHROUGH_COMMANDS_MAX 10
char PASSTHROUGH_COMMANDS[PASSTHROUGH_COMMANDS_MAX][] = {
"say",
"vocalize",
"sm_give",
"give",
"sm_say",
"sm_chat",
"use",
"sm_slay",
"sm_model",
"sm_surv",
};
Action OnCommand(int client, const char[] command, int argc) {
if(g_mimicController == 0 || client != g_mimicController) return Plugin_Continue;
for(int i = 0; i < PASSTHROUGH_COMMANDS_MAX; i++) {
if(StrEqual(command, PASSTHROUGH_COMMANDS[i])) {
char args[256];
GetCmdArgString(args, sizeof(args));
// PrintToServer("pass: %s %s", command, args);
FakeClientCommandEx(g_mimicBot, "%s %s", command, args);
return Plugin_Handled;
}
}
// PrintToServer("ignore: %s", command);
return Plugin_Continue;
}
void Event_BotToIdle(Event event, const char[] name, bool dontBroadcast) {
int player = GetClientOfUserId(event.GetInt("player"));
int bot = GetClientOfUserId(event.GetInt("bot"));
if(g_mimicBot == player) {
KickClient(bot);
RequestFrame(SetupMimic);
}
}
public void OnClientDisconnect(int client) {
if(client == g_mimicBot || client == g_mimicController) {
StopMimic();
}
}
Action Command_Mimic(int client, int args) {
if(g_mimicController != 0) {
if(g_mimicController == client) {
StopMimic();
} else {
ReplyToCommand(client, "Mimic is currently active by another player.");
}
} else if(args > 0) {
char name[32], id[4];
GetCmdArg(1, name, sizeof(name));
int survivorId = -1;
if(args > 1) {
GetCmdArg(2, id, sizeof(id));
survivorId = GetSurvivorId(id, L4D2_GetSurvivorSetMap() == 0);
}
StartMimic(client, name, survivorId);
} else {
ReplyToCommand(client, "Enter name");
}
return Plugin_Handled;
}
void StartMimic(int controller, const char[] name, int survivorId = -1) {
int bot = CreateFakeClient(name);
if(bot == -1) {
PrintToChat(controller, "Could not spawn fake client");
return;
}
DispatchKeyValue(bot, "classname", "SurvivorBot");
ChangeClientTeam(bot, 2);
if(!DispatchSpawn(bot)) {
PrintToChat(controller, "Could not dispatch spawn");
return;
}
L4D_RespawnPlayer(bot);
char model[128];
GetEntPropString(controller, Prop_Data, "m_ModelName", model, sizeof(model));
SetEntityModel(bot, model);
int camera = CreateEntityByName("point_viewcontrol_survivor");
DispatchKeyValue(camera, "targetname", "mimic_cam");
DispatchSpawn(camera);
g_mimicBot = bot;
g_mimicController = controller;
g_mimicCamera = camera;
SDKHook(controller, SDKHook_WeaponSwitchPost, OnWeaponSwitchPost);
PrintToServer("controller: %N | bot: %N(#%d) | camera: %d", controller, bot, GetClientUserId(bot), camera);
RequestFrame(SetupMimic, survivorId);
}
void SetupMimic(int survivorId = -1) {
if(g_mimicController == 0) return;
float pos[3], ang[3];
GetClientAbsOrigin(g_mimicController, pos);
int nav = L4D_GetNearestNavArea(pos);
if(nav > 0) {
L4D_FindRandomSpot(nav, pos);
}
TeleportEntity(g_mimicBot, pos, NULL_VECTOR, NULL_VECTOR);
CheatCommand(g_mimicBot, "give", "rifle_ak47", "");
CheatCommand(g_mimicBot, "give", "first_aid_kit", "");
if(survivorId == -1) survivorId = GetEntProp(g_mimicController, Prop_Send, "m_survivorCharacter");
SetEntProp(g_mimicBot, Prop_Send, "m_survivorCharacter", survivorId);
AcceptEntityInput(g_mimicCamera, "Disable", g_mimicController);
GetClientEyePosition(g_mimicBot, pos);
GetClientEyeAngles(g_mimicBot, ang);
ClearParent(g_mimicCamera);
TeleportEntity(g_mimicCamera, pos, NULL_VECTOR, NULL_VECTOR);
SetParent(g_mimicCamera, g_mimicBot);
AcceptEntityInput(g_mimicCamera, "Enable", g_mimicController);
}
void OnWeaponSwitchPost(int client, int weapon) {
if(g_mimicBot == 0) return;
for(int slot = 0; slot < 5; slot++) {
int slotWpn = GetPlayerWeaponSlot(client, slot);
if(slotWpn == weapon) {
ClientCommand(g_mimicBot, "slot%d", slot);
return;
}
}
}
void StopMimic() {
if(g_mimicBot > 0 && IsClientInGame(g_mimicBot)) {
if(L4D_GoAwayFromKeyboard(g_mimicBot)) {
int bot = L4D_GetBotOfIdlePlayer(g_mimicBot);
if(bot > 0) {
KickClient(bot);
}
}
KickClient(g_mimicBot);
}
if(g_mimicCamera > 0 && IsValidEntity(g_mimicCamera)) {
AcceptEntityInput(g_mimicCamera, "Disable");
RemoveEntity(g_mimicCamera);
}
if(g_mimicController > 0 && IsClientConnected(g_mimicController)) {
SDKUnhook(g_mimicController, SDKHook_WeaponSwitchPost, OnWeaponSwitchPost);
}
g_mimicBot = 0;
g_mimicController = 0;
g_mimicCamera = 0;
}
int prevButtons;
float prevAngles[3], prevVel[3];
public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon) {
if(client == g_mimicController) {
if(!IsPlayerAlive(client)) {
StopMimic();
}
prevAngles = angles;
prevButtons = buttons;
prevVel = vel;
SetEntPropFloat(client, Prop_Send, "m_flNextAttack", GetGameTime() + 1.0);
// TeleportEntity(client, NULL_VECTOR, angles, NULL_VECTOR);
return Plugin_Handled;
} else if(client == g_mimicBot) {
if(!IsPlayerAlive(client)) {
StopMimic();
}
buttons = prevButtons | IN_BULLRUSH;
angles = prevAngles;
vel = prevVel;
TeleportEntity(client, NULL_VECTOR, angles, NULL_VECTOR);
return Plugin_Changed;
}
return Plugin_Continue;
}

View file

@ -10,6 +10,7 @@
#include <sourcemod> #include <sourcemod>
#include <sdktools> #include <sdktools>
#include <sdkhooks> #include <sdkhooks>
#include <anymap>
public Plugin myinfo = public Plugin myinfo =
{ {
@ -33,7 +34,7 @@ static ConVar hZCommonLimit;
static bool IsDoneLoading, clownMusicPlayed; static bool IsDoneLoading, clownMusicPlayed;
static int iCurrentCommons, commonLimit, clownCommonsSpawned; static int iCurrentCommons, commonLimit, clownCommonsSpawned;
static int commonType[2048]; static AnyMap commonType;
#define COMMON_MODELS_COUNT 6 #define COMMON_MODELS_COUNT 6
static char INFECTED_MODELS[COMMON_MODELS_COUNT][] = { static char INFECTED_MODELS[COMMON_MODELS_COUNT][] = {
@ -50,13 +51,15 @@ static char WORKER_MODELS[3][] = {
"models/infected/common_male_roadcrew.mdl", "models/infected/common_male_roadcrew.mdl",
"models/infected/common_male_roadcrew_rain.mdl" "models/infected/common_male_roadcrew_rain.mdl"
}; };
enum CommonTypes { enum CommonType {
Common_Worker = -2,
Common_Any = -1,
Common_Clown, Common_Clown,
Common_Mud, Common_Mud,
Common_Ceda, Common_Ceda,
Common_Riot, Common_Riot,
Common_Jimmy, Common_Jimmy,
Common_Worker = -1, Common_Fallen,
}; };
//TODO: Add back survivor zombie, inc z_fallen_max_count //TODO: Add back survivor zombie, inc z_fallen_max_count
@ -67,6 +70,8 @@ public void OnPluginStart() {
SetFailState("This plugin is for L4D2 only."); SetFailState("This plugin is for L4D2 only.");
} }
commonType = new AnyMap();
HookEvent("game_start", OnGameStart); HookEvent("game_start", OnGameStart);
hPercentTotal = CreateConVar("l4d2_population_global_chance", "1.0", "The % chance that any the below chances occur.\n0.0 = NEVER, 1.0: ALWAYS"); hPercentTotal = CreateConVar("l4d2_population_global_chance", "1.0", "The % chance that any the below chances occur.\n0.0 = NEVER, 1.0: ALWAYS");
@ -114,6 +119,7 @@ public void OnMapEnd() {
IsDoneLoading = false; IsDoneLoading = false;
iCurrentCommons = 0; iCurrentCommons = 0;
clownCommonsSpawned = 0; clownCommonsSpawned = 0;
commonType.Clear();
} }
public void CVAR_hTotalZombiesChanged(ConVar convar, const char[] oldValue, const char[] newValue) { public void CVAR_hTotalZombiesChanged(ConVar convar, const char[] oldValue, const char[] newValue) {
@ -137,28 +143,28 @@ public void OnEntityCreated(int entity, const char[] classname) {
if(GetRandomFloat() <= hPercentTotal.FloatValue) { if(GetRandomFloat() <= hPercentTotal.FloatValue) {
if(GetRandomFloat() <= hPercentClown.FloatValue) { if(GetRandomFloat() <= hPercentClown.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Clown]); SetEntityModel(entity, INFECTED_MODELS[Common_Clown]);
commonType[entity] = 2; commonType.SetValue(entity, Common_Clown);
}else if(GetRandomFloat() <= hPercentMud.FloatValue) { }else if(GetRandomFloat() <= hPercentMud.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Mud]); SetEntityModel(entity, INFECTED_MODELS[Common_Mud]);
commonType[entity] = 3; commonType.SetValue(entity, Common_Mud);
}else if(GetRandomFloat() <= hPercentCeda.FloatValue) { }else if(GetRandomFloat() <= hPercentCeda.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Ceda]); SetEntityModel(entity, INFECTED_MODELS[Common_Ceda]);
commonType[entity] = 4; commonType.SetValue(entity, Common_Ceda);
}else if(GetRandomFloat() <= hPercentWorker.FloatValue) { }else if(GetRandomFloat() <= hPercentWorker.FloatValue) {
//worker has multiple models: //worker has multiple models:
SetEntityModel(entity, WORKER_MODELS[GetRandomInt(0,2)]); SetEntityModel(entity, WORKER_MODELS[GetRandomInt(0,2)]);
commonType[entity] = 5; commonType.SetValue(entity, Common_Worker);
}else if(GetRandomFloat() <= hPercentRiot.FloatValue) { }else if(GetRandomFloat() <= hPercentRiot.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Riot]); SetEntityModel(entity, INFECTED_MODELS[Common_Riot]);
commonType[entity] = 6; commonType.SetValue(entity, Common_Riot);
}else if(GetRandomFloat() <= hPercentJimmy.FloatValue) { }else if(GetRandomFloat() <= hPercentJimmy.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Jimmy]); SetEntityModel(entity, INFECTED_MODELS[Common_Jimmy]);
commonType[entity] = 7; commonType.SetValue(entity, Common_Jimmy);
}else{ }else{
commonType[entity] = 1; commonType.SetValue(entity, Common_Any);
} }
}else{ }else{
commonType[entity] = 1; commonType.SetValue(entity, Common_Any);
} }
} }
} }
@ -171,7 +177,8 @@ public Action Hook_SpawnPost(int entity) {
} }
} }
++iCurrentCommons; ++iCurrentCommons;
if(commonType[entity] == 2) { CommonType type;
if(commonType.GetValue(entity, type) && type == Common_Clown) {
if(++clownCommonsSpawned > CLOWN_MUSIC_THRESHOLD && !clownMusicPlayed) { if(++clownCommonsSpawned > CLOWN_MUSIC_THRESHOLD && !clownMusicPlayed) {
clownMusicPlayed = true; clownMusicPlayed = true;
EmitSoundToAll("custom/clown.mp3"); EmitSoundToAll("custom/clown.mp3");
@ -189,11 +196,12 @@ public Action Hook_SpawnPost(int entity) {
// } // }
public void OnEntityDestroyed(int entity) { public void OnEntityDestroyed(int entity) {
if(entity > 0 && entity <= 2048 && commonType[entity] > 0) { if(entity > 0 && entity <= 2048 && commonType.ContainsKey(entity)) {
commonType[entity] = 0; CommonType type;
if(commonType[entity] == 2) { if(commonType.GetValue(entity, type) && type == Common_Clown) {
--clownCommonsSpawned; --clownCommonsSpawned;
} }
commonType.Remove(entity);
if(--iCurrentCommons < CLOWN_MUSIC_THRESHOLD - 10) { if(--iCurrentCommons < CLOWN_MUSIC_THRESHOLD - 10) {
clownMusicPlayed = false; clownMusicPlayed = false;
} }

View file

@ -273,6 +273,7 @@ public void OnMapEnd() {
Game.UnsetupPlayer(i); Game.UnsetupPlayer(i);
} }
} }
Game.Cleanup();
} }
void ClearInventory(int client) { void ClearInventory(int client) {
@ -347,7 +348,7 @@ stock void GlowEntity(int entity, int client, float lifetime = 5.0) {
GetEntPropVector(entity, Prop_Data, "m_vecMaxs", maxs); GetEntPropVector(entity, Prop_Data, "m_vecMaxs", maxs);
GetEntPropVector(entity, Prop_Data, "m_angRotation", ang); GetEntPropVector(entity, Prop_Data, "m_angRotation", ang);
Effect_DrawBeamBoxRotatableToClient(client, pos, mins, maxs, ang, g_iLaserIndex, 0, 0, 1, lifetime, 1.0, 1.0, 100, 0.1, COLOR_PROPFINDER, 0.0); Effect_DrawBeamBoxRotatableToClient(client, pos, mins, maxs, ang, g_iLaserIndex, 0, 0, 1, lifetime, 1.0, 1.0, 100, 0.1, COLOR_PROPFINDER, 0);
} }
public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) { public Action OnPlayerRunCmd(int client, int& buttons, int& impulse, float vel[3], float angles[3], int& weapon, int& subtype, int& cmdnum, int& tickcount, int& seed, int mouse[2]) {

View file

@ -181,7 +181,7 @@ public void Event_PlayerToBot(Handle event, char[] name, bool dontBroadcast)
} }
for(int i = 0; i < 8; i++) { for(int i = 0; i < 8; i++) {
if (strcmp(g_Models[player], survivor_models[i], false) == 0) { if (strcmp(g_Models[player], survivor_models[i], false) == 0) {
// SetClientInfo(bot, "name", survivor_names[i]); SetClientInfo(bot, "name", survivor_names[i]);
break; break;
} }
} }

View file

@ -250,7 +250,7 @@ public Action OnClientSayCommand(int client, const char[] command, const char[]
// TODO: escape content // TODO: escape content
DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgsTrimmed); DB.Format(query, sizeof(query), "INSERT INTO `notes` (steamid, markedBy, content) VALUES ('%s', '%s', '%s')", menuNoteTarget, buffer, sArgsTrimmed);
DB.Query(DB_AddNote, query); DB.Query(DB_AddNote, query);
LogAction(client, -1, "\"%L\" added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgsTrimmed); LogAction(client, -1, "added note for \"%s\" (%s): \"%s\"", client, menuNoteTargetName, menuNoteTarget, sArgsTrimmed);
Format(buffer, sizeof(buffer), "%N: ", client); Format(buffer, sizeof(buffer), "%N: ", client);
CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTargetName, sArgs); CShowActivity2(client, buffer, "added a note for {green}%s: {default}\"%s\"", menuNoteTargetName, sArgs);
} }