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

View file

@ -52,6 +52,7 @@ int playerJoinTime[MAXPLAYERS+1];
Handle updateHealthTimer[MAXPLAYERS+1];
Handle updateItemTimer[MAXPLAYERS+1];
Handle receiveTimeoutTimer = null;
int pendingTries = 3;
bool lateLoaded;
@ -72,16 +73,12 @@ GameState g_gameState;
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)
{
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) {
lateLoaded = late;
return APLRes_Success;
}
public void OnPluginStart()
{
// TODO: periodic reconnect
public void OnPluginStart() {
g_socket = new Socket(SOCKET_TCP, OnSocketError);
g_socket.SetOption(SocketKeepAlive, 1);
g_socket.SetOption(SocketReuseAddr, 1);
@ -118,19 +115,22 @@ public void OnPluginStart()
HookEvent("pills_used", Event_ItemUsed);
HookEvent("adrenaline_used", Event_ItemUsed);
HookEvent("weapon_drop", Event_WeaponDrop);
HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
HookEvent("player_spawn", Event_PlayerSpawn);
HookEvent("map_transition", Event_MapTransition);
HookEvent("player_death", Event_PlayerDeath);
HookEvent("player_bot_replace", Event_PlayerToBot);
HookEvent("bot_player_replace", Event_BotToPlayer);
campaignStartTime = GetTime();
char auth[32];
for(int i = 1; i <= MaxClients; i++) {
if(IsClientConnected(i) && IsClientInGame(i)) {
playerJoinTime[i] = GetTime();
if(IsClientInGame(i)) {
if(GetClientAuthId(i, AuthId_Steam2, auth, sizeof(auth))) {
OnClientAuthorized(i, auth);
OnClientPutInServer(i);
}
}
}
AutoExecConfig(true, "adminpanel");
@ -138,6 +138,15 @@ public void OnPluginStart()
RegAdminCmd("sm_panel_request_stop", Command_RequestStop, ADMFLAG_GENERIC);
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) {
@ -187,6 +196,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
if(authState == Auth_Pending) {
if(response == Live_OK) {
authState = Auth_Success;
pendingTries = 0;
PrintToServer("[AdminPanel] Authenticated with server successfully.");
SendFullSync();
} else if(response == Live_Error) {
@ -203,7 +213,7 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
lastReceiveTime = GetTime();
switch(response) {
case Live_RunComand: {
case Live_RunCommand: {
char command[128];
char cmdNamespace[32];
int id = receiveBuffer.ReadByte();
@ -223,8 +233,18 @@ void OnSocketReceive(Socket socket, const char[] receiveData, int dataSize, int
case Live_Reconnect:
CreateTimer(5.0, Timer_Reconnect);
case Live_Refresh: {
int userid = receiveBuffer.ReadByte();
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();
SendFullSync();
}
}
}
if(receiveTimeoutTimer != null) {
@ -481,6 +501,7 @@ void ConnectSocket() {
if(authToken[0] == '\0') return;
// Do not try to reconnect on auth failure, until token has changed
if(authState == Auth_Fail) return;
authState = Auth_Pending;
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, "Buffer Size: %d", BUFFER_SIZE);
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")) {
if(args < 2) {
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) {
campaignStartTime = 0;
CreateTimer(10.0, Timer_FullSync);
}
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) {
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0) {
@ -590,6 +623,8 @@ 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)) {
// Bot is going away, remove it: (prob unnecessary OnClientDisconnect happens)
AddPlayerRecord(bot, false);
AddPlayerRecord(player);
SendPayload();
}
@ -616,10 +651,13 @@ void Event_HealInterrupted(Event event, const char[] name, bool dontBroadcast) {
g_icBeingHealed[subject] = false;
}
void Event_PlayerFirstSpawn(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
playerJoinTime[client] = GetTime();
void Event_PlayerSpawn(Event event, const char[] name, bool dontBroadcast) {
RecalculatePlayerCount();
int client = GetClientOfUserId(event.GetInt("userid"));
if(client > 0 && StartPayload()) {
AddPlayerRecord(client);
SendPayload();
}
}
void RecalculatePlayerCount() {
@ -662,6 +700,21 @@ public void OnMapStart() {
public void OnConfigsExecuted() {
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
public void OnClientPutInServer(int client) {
if(g_gameState == State_Transitioning) {
@ -671,21 +724,9 @@ public void OnClientPutInServer(int client) {
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_OnTakeDamageAlivePost, OnTakeDamagePost);
// We wait a frame because Event_PlayerFirstSpawn sets their join time
RequestFrame(SendNewClient, client);
}
void OnWeaponPickUp(int client, int weapon) {
@ -780,6 +821,7 @@ public void OnClientDisconnect(int client) {
// Incase somehow we lost track
if(numberOfPlayers < 0) {
numberOfPlayers = 0;
CreateTimer(1.0, Timer_FullSync);
}
}
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;
}
PrintToServer("[AdminPanel] Sending data to %s:%d", serverIp, serverPort);
authState = Auth_Pending;
ConnectSocket();
}
} else if(cvar_gamemode == convar) {
@ -823,7 +864,6 @@ 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();
}
@ -931,15 +971,15 @@ enum CommandResultType {
}
enum LiveRecordType {
Live_Game,
Live_Player,
Live_Survivor,
Live_Infected,
Live_Finale,
Live_SurvivorItems,
Live_CommandResponse,
Live_Auth,
Live_Meta
Live_Game = 0,
Live_Player = 1,
Live_Survivor = 2,
Live_Infected = 3,
Live_Finale = 4,
Live_SurvivorItems = 5,
Live_CommandResponse = 6,
Live_Auth = 7,
Live_Meta = 8
}
char LIVE_RECORD_NAMES[view_as<int>(Live_Meta)+1][] = {
"Game",
@ -958,13 +998,21 @@ enum LiveRecordResponse {
Live_Reconnect,
Live_Error,
Live_Refresh,
Live_RunComand
Live_RunCommand
}
char pendingRecords[64];
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;
return true;
}
@ -978,34 +1026,45 @@ bool StartPayload(bool ignorePause = false) {
/// Starts payload, ignoring if the payload can even be sent
bool hasRecord;
int recordStart;
bool pendingRecord;
void StartPayloadEx() {
if(pendingRecord) {
LogError("StartPayloadEx called before EndRecord()");
}
sendBuffer.Reset();
hasRecord = false;
pendingRecords[0] = '\0';
recordStart = 0;
pendingRecord = false;
}
void StartRecord(LiveRecordType type) {
if(pendingRecord) {
LogError("StartRecord called before EndRecord()");
}
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.WriteShort(-1); // write temp value to be replaced when record ends
sendBuffer.WriteByte(view_as<int>(type));
hasRecord = true;
pendingRecord = true;
}
void EndRecord() {
int length = sendBuffer.offset - recordStart - 1; // subtract 1, as don't count length inside
sendBuffer.WriteByteAt(length, recordStart);
int length = sendBuffer.offset - recordStart - 2; // subtract 1, as don't count length inside
sendBuffer.WriteShortAt(length, recordStart);
// 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);
// }
hasRecord = true;
pendingRecord = false;
}
void AddGameRecord() {
@ -1020,13 +1079,6 @@ void AddGameRecord() {
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
@ -1127,6 +1179,9 @@ void AddCommandResponseRecord(int id, CommandResultType resultType = Result_None
}
void AddAuthRecord() {
if(authToken[0] == '\0') {
LogError("AddAuthRecord called with missing auth token");
}
StartRecord(Live_Auth);
sendBuffer.WriteByte(LIVESTATUS_VERSION);
sendBuffer.WriteString(authToken);
@ -1141,10 +1196,11 @@ void AddMetaRecord(bool state) {
void SendPayload() {
if(sendBuffer.offset == 0) return;
int len = sendBuffer.Finish();
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 {
@ -1194,11 +1250,21 @@ enum struct Buffer {
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 WriteShortAt(int value, int offset) {
this.buffer[offset] = value & 0xFF;
this.buffer[offset+1] = (value >> 8) & 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) {
@ -1207,6 +1273,7 @@ enum struct Buffer {
// Writes a null-terminated length string, strlen > size is truncated.
void WriteString(const char[] string) {
this.buffer[this.offset] = '\0';
int written = strcopy(this.buffer[this.offset], BUFFER_SIZE, string);
this.offset += written + 1;
}
@ -1250,4 +1317,15 @@ enum struct Buffer {
bool EOF() {
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);
g_finaleStage = Stage_FirstTankSpawned;
return;
} else if(g_realSurvivorCount < 6 && g_finaleStage == Stage_FirstTankSpawned) {
// 2nd tank spawned
} else if(g_realSurvivorCount >= 6 && g_finaleStage == Stage_FirstTankSpawned) {
PrintDebug(DEBUG_SPAWNLOGIC, "OnTankBotSpawn: [FINALE] 2nd tank spawned");
float duration = GetRandomFloat(EXTRA_TANK_MIN_SEC, EXTRA_TANK_MAX_SEC);
// 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");
float additionalHealth = float(g_survivorCount - 4) * cvEPITankHealth.FloatValue;
health += RoundFloat(additionalHealth);
if(health <= 0) PrintToServer("CalculateExtraTankHealth: returning 0?");
return health;
}

View file

@ -707,15 +707,15 @@ Action Command_SetReverseFF(int client, int args) {
} else if(StrEqual(arg, "1")) {
flag = 1;
} 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;
}
// args are 1-indexed so <=
for(int i = 3; i <= args; i++) {
GetCmdArg(i, arg, sizeof(arg));
if(arg[0] == 'f') {
if(arg[0] == 'f') { // [f]ire
flag |= 32;
} else if(arg[0] == 'e') {
} else if(arg[0] == 'e' || arg[0] == 'b') { //[]blast or [e]xplode
flag |= 64;
} else {
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 cvEPICommonCountScale, cvEPICommonCountScaleMax;
ConVar g_ffFactorCvar, hExtraTankThreshold;
ConVar cvZCommonLimit; int zCommonLimitPrevValue;
int g_extraKitsAmount, g_extraKitsStart, g_saferoomDoorEnt, g_prevPlayerCount;
bool g_forcedSurvivorCount;
bool g_forcedSurvivorCount, g_extraKitsSpawnedFinale;
static int g_currentChapter;
bool g_isCheckpointReached, g_isLateLoaded, g_startCampaignGiven, g_isFailureRound, g_areItemsPopulated;
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);
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);
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
cvEPIHudFlags.AddChangeHook(Cvar_HudStateChange);
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) {
int client = GetClientOfUserId(event.GetInt("userid"));
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);
QueueSaveInventory(client);
}
@ -1197,6 +1219,11 @@ int SpawnItem(const char[] itemName, float pos[3], float ang[3] = NULL_VECTOR) {
}
void IncreaseKits(bool inFinale) {
if(inFinale) {
if(g_extraKitsSpawnedFinale) return;
g_extraKitsSpawnedFinale = true;
g_extraKitsAmount = g_realSurvivorCount - 4;
}
float pos[3];
int entity = FindEntityByClassname(-1, "weapon_first_aid_kit_spawn");
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) {
if(stage == 1) {
if(stage == 1 && IsEPIActive()) {
IncreaseKits(true);
}
}
public void OnMapStart() {
g_extraKitsSpawnedFinale = false;
char map[32];
GetCurrentMap(map, sizeof(map));
// If map starts with c#m#, 98% an official map
@ -2031,14 +2059,7 @@ bool DoesInventoryDiffer(int client) {
bool IsEPIActive() {
return g_epiEnabled;
}
/*
[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
*/
bool wasActive;
void UpdateSurvivorCount() {
#if defined DEBUG_FORCE_PLAYERS
g_survivorCount = DEBUG_FORCE_PLAYERS;
@ -2075,9 +2096,35 @@ void UpdateSurvivorCount() {
isActive = (g_isOfficialMap || cvEPIEnabledMode.IntValue == 2) && g_realSurvivorCount > 4;
}
g_epiEnabled = isActive;
if(g_epiEnabled && !wasActive) {
OnEPIActive();
wasActive = true;
} else if(wasActive) {
OnEPIInactive();
}
if(isActive)
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) {
static float prevValue;
// 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;
}
public void OnMapEnd() {
Game.Cleanup();
}
public void ThinkPost(int entity) {
static int iTeamNum[MAXPLAYERS+1];

View file

@ -248,6 +248,9 @@ public void OnMapStart() {
lateLoaded = false;
}
}
public void OnMapEnd() {
Game.Cleanup();
}
public Action L4D2_OnChangeFinaleStage(int &finaleType, const char[] arg) {
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 <sdktools>
#include <sdkhooks>
#include <anymap>
public Plugin myinfo =
{
@ -33,7 +34,7 @@ static ConVar hZCommonLimit;
static bool IsDoneLoading, clownMusicPlayed;
static int iCurrentCommons, commonLimit, clownCommonsSpawned;
static int commonType[2048];
static AnyMap commonType;
#define COMMON_MODELS_COUNT 6
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_rain.mdl"
};
enum CommonTypes {
enum CommonType {
Common_Worker = -2,
Common_Any = -1,
Common_Clown,
Common_Mud,
Common_Ceda,
Common_Riot,
Common_Jimmy,
Common_Worker = -1,
Common_Fallen,
};
//TODO: Add back survivor zombie, inc z_fallen_max_count
@ -67,6 +70,8 @@ public void OnPluginStart() {
SetFailState("This plugin is for L4D2 only.");
}
commonType = new AnyMap();
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");
@ -114,6 +119,7 @@ public void OnMapEnd() {
IsDoneLoading = false;
iCurrentCommons = 0;
clownCommonsSpawned = 0;
commonType.Clear();
}
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() <= hPercentClown.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Clown]);
commonType[entity] = 2;
commonType.SetValue(entity, Common_Clown);
}else if(GetRandomFloat() <= hPercentMud.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Mud]);
commonType[entity] = 3;
commonType.SetValue(entity, Common_Mud);
}else if(GetRandomFloat() <= hPercentCeda.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Ceda]);
commonType[entity] = 4;
commonType.SetValue(entity, Common_Ceda);
}else if(GetRandomFloat() <= hPercentWorker.FloatValue) {
//worker has multiple models:
SetEntityModel(entity, WORKER_MODELS[GetRandomInt(0,2)]);
commonType[entity] = 5;
commonType.SetValue(entity, Common_Worker);
}else if(GetRandomFloat() <= hPercentRiot.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Riot]);
commonType[entity] = 6;
commonType.SetValue(entity, Common_Riot);
}else if(GetRandomFloat() <= hPercentJimmy.FloatValue) {
SetEntityModel(entity, INFECTED_MODELS[Common_Jimmy]);
commonType[entity] = 7;
commonType.SetValue(entity, Common_Jimmy);
}else{
commonType[entity] = 1;
commonType.SetValue(entity, Common_Any);
}
}else{
commonType[entity] = 1;
commonType.SetValue(entity, Common_Any);
}
}
}
@ -171,7 +177,8 @@ public Action Hook_SpawnPost(int entity) {
}
}
++iCurrentCommons;
if(commonType[entity] == 2) {
CommonType type;
if(commonType.GetValue(entity, type) && type == Common_Clown) {
if(++clownCommonsSpawned > CLOWN_MUSIC_THRESHOLD && !clownMusicPlayed) {
clownMusicPlayed = true;
EmitSoundToAll("custom/clown.mp3");
@ -189,11 +196,12 @@ public Action Hook_SpawnPost(int entity) {
// }
public void OnEntityDestroyed(int entity) {
if(entity > 0 && entity <= 2048 && commonType[entity] > 0) {
commonType[entity] = 0;
if(commonType[entity] == 2) {
if(entity > 0 && entity <= 2048 && commonType.ContainsKey(entity)) {
CommonType type;
if(commonType.GetValue(entity, type) && type == Common_Clown) {
--clownCommonsSpawned;
}
commonType.Remove(entity);
if(--iCurrentCommons < CLOWN_MUSIC_THRESHOLD - 10) {
clownMusicPlayed = false;
}

View file

@ -273,6 +273,7 @@ public void OnMapEnd() {
Game.UnsetupPlayer(i);
}
}
Game.Cleanup();
}
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_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]) {

View file

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

View file

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