From b14a467922a163a58bf611726b40e8f81aa9f15c Mon Sep 17 00:00:00 2001 From: Altair Sossai Date: Fri, 5 Jul 2024 09:26:13 -0300 Subject: [PATCH] Add l4d2_playstats_sync.smx plugin --- .gitignore | 5 +- .../plugins/optional/l4d2_playstats_sync.smx | Bin 0 -> 13052 bytes .../scripting/l4d2_playstats_sync.sp | 572 ++++++++++++++++++ cfg/cfgogl/zonemod/shared_plugins.cfg | 1 + cfg/cfgogl/zonemod/zonemod.cfg | 2 +- cfg/server.cfg | 1 + 6 files changed, 579 insertions(+), 2 deletions(-) create mode 100644 addons/sourcemod/plugins/optional/l4d2_playstats_sync.smx create mode 100644 addons/sourcemod/scripting/l4d2_playstats_sync.sp diff --git a/.gitignore b/.gitignore index 6e49244f3..a2d190003 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,7 @@ $RECYCLE.BIN/ # ========================= # Visual Studio Code files -/.vscode \ No newline at end of file +/.vscode + +# Ignore secrets file +cfg/server_secrets.cfg diff --git a/addons/sourcemod/plugins/optional/l4d2_playstats_sync.smx b/addons/sourcemod/plugins/optional/l4d2_playstats_sync.smx new file mode 100644 index 0000000000000000000000000000000000000000..2299f43a30cc16f1e661ed18e76ffe9acbf2dacd GIT binary patch literal 13052 zcmYkC1y~zf)UJcm;`HDyMGLgJYjFu)T#9>fOOT?)tx&vpaSQJ5?(Ri{2bY_3?*Bjc z=E<7%t~Fn_%$}J&$&QSSssyLjY$5TL2&q?kVB64Ch|BXM|gp9RT2n0RXVW8PgM9I9$&UXEbjBKnW25 zkcP8{F947P&#S|E9|Qn^;A{wIGe3Ak;2vHCz!T2&a1Z|&ARW$d86X^PME(Fk8Qdqq ztq0E3aGwpg9XL01aon}tv z765j0Q#Vrp`#1Nm_BLj&|AB|83mhCx-E2H8{sRZNb8&OC`M=u#$ic$R+R6NX(*G;{ zZ;yqeyTkwF-Mqh9m|Ix>f03(&+y6vn_NK0`|8J+IjfFkDICk@|R_vBG_W#!VFWTEU z{x8}%S~~sT8q*z|H_@r{$;P_HZT@QbHRPzcUq1x9A{~eTD&s=CA@M9 z4H5aLgAKiDJbD=|Nn-G4%rps7F5@hcz;A=0%5p*a)9&v)q?+>!-au=Z<>eK~crO?r z3~V+w3VbRlh4v-$+=W=_OvTGbDQ|sW_p`44u6c7^(Vw{8JG-9>?fY%GP6*t#3f-=) zI1*-Wymm!nhR|GsB=WBob9`>vUUWn_y)a&iRc5OPO#onTyODHrb+Lg+FH)>et>(=OBLVOh} zhjP+gRcev#%uPGJ^{lH`Y43viCfH9kd2FCN)#ZJ@bf9fgr&caDJB_@&PC0jyTeYqI z`AT!R6N#JW7i^HpmVon^9rG;q0~r z2DHZNEtSAX+4QitLC+UE?-8CC<|NO$&`NM;t3D6NHLz81Lj#mb=rngZik}6p?#%cw zH88`!V7ZqL{WFrmdFJll-g@ulUpE+mnqZgN)Myr~!r}B~KWOfgmSE|+3ketBz2gx7 zuxH6b`Cd!?cKM!=_I_6EUz3T$pu_N-u>GRbuW}D|Gw7~RMf$WbuW50 zw&d;d6tC>cp4$9(;2>cRah+2gK0>G~1ZS#$rA94zU)RbL0(+`D7$jY@s$vp~T~`^t z3LjNe&Z_?96*Q3R6Te1S$xGM)oo2KkvvXGoH_C68fjsQX8*TS3quiv?W$JcxX3a#R z+p(>6Q2V~PE&Z%y8-Iwkfqk)s8OOPBC_FFxU2%2XTky8Gm+;UdZQ)FG4l!2{HDcv% zS4l7MiKnR4P4drxHGwtnwxE}61;!mrDYt2adzGH;yK@fuq^_hp5g|**m%&n}1{9ms zHY9MH?=Sr7Ucc(CaSS}xZDC8|+c4mmeE2+}rS7O4c;RSdzT!KEEt^w@`^Lny?8V3zg|LUg`jy3VJwXQ4+r^@ypyR#+w>0b^h^9sOTmZZgm+JJi-&V!<* zPhlsvE+@k-#Vri6hlU-Odb!+7#BgwZDH?<^~^tcMeJ*;$2{4{ix&BVtT#O z?A5|H;b1q*)a8d`O&TI8c_{;6pE!LJG%tho$!4)Ska+aw~#kkme3+gI)WH?WY&oB4P4{U2|aI zgIlU&birPV#?6k4@~N%e8jEjzf8{V=EB``~K_XI&u<|*1%9($)43d+FntR`HL1hW|LexVcflJ%#oFal`{GFQai9- zz@hO|v0cZkyX?{BZuKV?Nb`-Ido(bQ9o%-%I<04Qm9Xvybl-9>JY&4}&{Uy|)ff;i zya2{C+@6+CaOAx`9B~PI$z*Yeo}UMMyJcm0v?Y5~D(dClT0gV_6N@>@>j%nY6J5c} zcRWo|*|O$Visgo1vbDd&YA@_^A07DyG_MZJ1JzXiK7S3o7;n&?AS6B`VCPjA&C=$a zD*#J-+c!HBrB_JR9ONi|?>9M*p>UBu@rEwWOC*w*(dmjlaD=35dZP^R~G zE`)I6JnYkKU>+K+@{xRY7lh?biE|pqo`SN)vA#;%$!)$3yesRO*x8zF-XBE|(-^W> zjk2?W&6IIK!DM0nY_|`ol-X_^sV}Q%-~dS1`&U0ug3jg5eg0R&8{hE~o6E>?G&6Qk z6>ta1Dbw&qG#aPBa8jcvSqXUihYM{+sr2eg?D;~7&E=O^UNoY&vklx6EfR*ZQ%0A$ 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 literal 0 HcmV?d00001 diff --git a/addons/sourcemod/scripting/l4d2_playstats_sync.sp b/addons/sourcemod/scripting/l4d2_playstats_sync.sp new file mode 100644 index 000000000..7b6c46bb5 --- /dev/null +++ b/addons/sourcemod/scripting/l4d2_playstats_sync.sp @@ -0,0 +1,572 @@ +#include +#include +#include +#include +#include + +#define NEEDED_FOR_THE_MIX 2 +#define L4D2_TEAM_SPECTATOR 1 +#define L4D2_TEAM_SURVIVOR 2 +#define L4D2_TEAM_INFECTED 3 + +public Plugin myinfo = +{ + name = "L4D2 - Player Statistics Sync", + author = "Altair Sossai", + description = "Sends the information generated by plugin l4d2_playstats.smx to the API of l4d2_playstats", + version = "1.0.0", + url = "https://github.com/altair-sossai/l4d2-zone-server" +}; + +ConVar cvar_playstats_endpoint; +ConVar cvar_playstats_server; +ConVar cvar_playstats_access_token; + +int mixVotes = 0; +bool mixBlocked = false; + +public void OnPluginStart() +{ + cvar_playstats_endpoint = CreateConVar("playstats_endpoint", "https://l4d2-playstats-api.azurewebsites.net", "Play Stats endpoint", FCVAR_PROTECTED); + cvar_playstats_server = CreateConVar("playstats_server", "", "l4d2-zone-server", FCVAR_PROTECTED); + cvar_playstats_access_token = CreateConVar("playstats_access_token", "", "Play Stats Access Token", FCVAR_PROTECTED); + + RegAdminCmd("sm_syncstats", SyncStatsCmd, ADMFLAG_BAN); + RegConsoleCmd("sm_ranking", ShowRankingCmd); + RegConsoleCmd("sm_lastmatch", LastMatchCmd); + RegConsoleCmd("sm_rmix", RankingMixCmd); + + HookEvent("round_start", Event_RoundStart, EventHookMode_PostNoCopy); + + CreateTimer(20.0, DisplayStatsUrlTick); + CreateTimer(100.0, DisplayStatsUrlTick, _, TIMER_REPEAT); +} + +public void Event_RoundStart(Event hEvent, const char[] eName, bool dontBroadcast) +{ + ClearMixVotes(); + Sync(); +} + +public void OnRoundIsLive() +{ + BlockMixVotes(); +} + +public void OnMapStart() +{ + ClearMixVotes(); +} + +public Action SyncStatsCmd(int client, int args) +{ + Sync(); + return Plugin_Handled; +} + +public Action ShowRankingCmd(int client, int args) +{ + ShowRanking(client); + return Plugin_Handled; +} + +public Action LastMatchCmd(int client, int args) +{ + LastMatch(client); + return Plugin_Handled; +} + +public Action RankingMixCmd(int client, int args) +{ + RankingMix(client); + return Plugin_Handled; +} + +public Action DisplayStatsUrlTick(Handle timer) +{ + if (!IsInReady()) + return Plugin_Continue; + + new String:server[100]; + GetConVarString(cvar_playstats_server, server, sizeof(server)); + + PrintToChatAll("Estatísticas/ranking disponível em:"); + PrintToChatAll("\x03https://l4d2-playstats.azurewebsites.net/server/%s", server); + PrintToChatAll("\x01Use \x04!ranking \x01para consultar sua posição"); + + return Plugin_Continue; +} + +public void Sync() +{ + char logsPath[128] = "logs/"; + BuildPath(Path_SM, logsPath, PLATFORM_MAX_PATH, logsPath); + + Regex regex = new Regex("^\\w{4}-\\w{2}-\\w{2}_\\w{2}-\\w{2}_\\d{4}.*\\.txt$"); + DirectoryListing directoryListing = OpenDirectory(logsPath); + + char fileName[128]; + while (directoryListing.GetNext(fileName, sizeof(fileName))) + { + if (!regex.Match(fileName)) + continue; + + SyncFile(fileName); + } +} + +public void SyncFile(String:fileName[]) +{ + char filePath[128]; + FormatEx(filePath, sizeof(filePath), "%s%s", "logs/", fileName); + BuildPath(Path_SM, filePath, PLATFORM_MAX_PATH, filePath); + + File file = OpenFile(filePath, "r"); + if (!file) + return; + + char content[40000]; + file.ReadString(content, sizeof(content), -1); + + JSONObject command = new JSONObject(); + + command.SetString("fileName", fileName); + command.SetString("content", content); + + HTTPRequest request = BuildHTTPRequest("/api/statistics"); + request.Post(command, SyncFileResponse); +} + +void SyncFileResponse(HTTPResponse httpResponse, any value) +{ + if (httpResponse.Status != HTTPStatus_OK) + return; + + JSONObject response = view_as(httpResponse.Data); + + bool mustBeDeleted = response.GetBool("mustBeDeleted"); + if (!mustBeDeleted) + return; + + char fileName[128]; + response.GetString("fileName", fileName, sizeof(fileName)); + + char filePath[128]; + FormatEx(filePath, sizeof(filePath), "%s%s", "logs/", fileName); + BuildPath(Path_SM, filePath, PLATFORM_MAX_PATH, filePath); + + DeleteFile(filePath); +} + +public void ShowRanking(int client) +{ + new String:server[100]; + GetConVarString(cvar_playstats_server, server, sizeof(server)); + + new String:communityId[25]; + GetClientAuthId(client, AuthId_SteamID64, communityId, sizeof(communityId)); + + char path[128]; + FormatEx(path, sizeof(path), "/api/ranking/%s/place/%s", server, communityId); + + HTTPRequest request = BuildHTTPRequest(path); + request.Get(ShowRankingResponse, client); +} + +void ShowRankingResponse(HTTPResponse httpResponse, int client) +{ + if (httpResponse.Status != HTTPStatus_OK) + return; + + JSONObject response = view_as(httpResponse.Data); + JSONArray top3 = view_as(response.Get("top3")); + JSONObject me = view_as(response.Get("me")); + + for (int i = 0; i < top3.Length; i++) + { + JSONObject player = view_as(top3.Get(i)); + PrintPlayerInfo(player, client); + } + + if (me.GetInt("position") >= 4) + PrintPlayerInfo(me, client); + + PrintToChatAll("\x01Use \x04!lastmatch \x01para visualizar os resultados do último jogo"); +} + +void PrintPlayerInfo(JSONObject player, int client) +{ + int position = player.GetInt("position"); + int points = player.GetInt("points"); + int lastMatchPoints = player.GetInt("lastMatchPoints"); + + char name[256]; + player.GetString("name", name, sizeof(name)); + + if (lastMatchPoints == 0) + PrintToChat(client, "\x04%dº \x01%s: \x03%d \x01pts", position, name, points); + else if (lastMatchPoints > 0) + PrintToChat(client, "\x04%dº \x01%s: \x03%d \x01pts \x03(+%d)", position, name, points, lastMatchPoints); + else + PrintToChat(client, "\x04%dº \x01%s: \x03%d \x01pts \x04(%d)", position, name, points, lastMatchPoints); +} + +public void LastMatch(int client) +{ + new String:server[100]; + GetConVarString(cvar_playstats_server, server, sizeof(server)); + + char path[128]; + FormatEx(path, sizeof(path), "/api/ranking/%s/last-match", server); + + HTTPRequest request = BuildHTTPRequest(path); + request.Get(LastMatchResponse, client); +} + +void LastMatchResponse(HTTPResponse httpResponse, int client) +{ + if (httpResponse.Status != HTTPStatus_OK) + return; + + JSONObject response = view_as(httpResponse.Data); + JSONObject match = view_as(response.Get("match")); + JSONArray teams = view_as(match.Get("teams")); + JSONArray players = view_as(response.Get("players")); + + char campaign[128]; + match.GetString("campaign", campaign, sizeof(campaign)); + + char matchElapsed[16]; + match.GetString("matchElapsed", matchElapsed, sizeof(matchElapsed)); + + PrintDivider(client); + PrintToChat(client, "\x01Campanha: \x04%s", campaign); + PrintToChat(client, "\x01Duração: \x04%s", matchElapsed); + + if (teams.Length == 2) + { + JSONObject teamA = view_as(teams.Get(0)); + JSONObject teamB = view_as(teams.Get(1)); + + int scoreTeamA = teamA.GetInt("score"); + int scoreTeamB = teamB.GetInt("score"); + + if (scoreTeamA > scoreTeamB) + PrintToChat(client, "\x01Equipe A \x03%d \x01x \x04%d \x01Equipe B", scoreTeamA, scoreTeamB); + else if (scoreTeamB > scoreTeamA) + PrintToChat(client, "\x01Equipe A \x04%d \x01x \x03%d \x01Equipe B", scoreTeamA, scoreTeamB); + else + PrintToChat(client, "\x01Equipe A \x04%d \x01x \x04%d \x01Equipe B", scoreTeamA, scoreTeamB); + + JSONArray playersTeamA = view_as(teamA.Get("players")); + JSONArray playersTeamB = view_as(teamB.Get("players")); + + PrintDivider(client); + PrintToChat(client, "\x01Equipe A (\x04MVP SI\x03 | \x04MVP CM\x03 | \x04LVP FF\x01):"); + for (int i = 0; i < playersTeamA.Length; i++) + { + JSONObject player = view_as(playersTeamA.Get(i)); + + char name[256]; + player.GetString("name", name, sizeof(name)); + + int mvpSiDamage = player.GetInt("mvpSiDamage"); + int mvpCommon = player.GetInt("mvpCommon"); + int lvpFfGiven = player.GetInt("lvpFfGiven"); + + PrintToChat(client, "\x01[\x04%d\x03 | \x04%d\x03 | \x04%d\x01] - \x01%s", mvpSiDamage, mvpCommon, lvpFfGiven, name); + } + + PrintDivider(client); + PrintToChat(client, "\x01Equipe B (\x04MVP SI\x03 | \x04MVP CM\x03 | \x04LVP FF\x01):"); + for (int i = 0; i < playersTeamB.Length; i++) + { + JSONObject player = view_as(playersTeamB.Get(i)); + + char name[256]; + player.GetString("name", name, sizeof(name)); + + int mvpSiDamage = player.GetInt("mvpSiDamage"); + int mvpCommon = player.GetInt("mvpCommon"); + int lvpFfGiven = player.GetInt("lvpFfGiven"); + + PrintToChat(client, "\x01[\x04%d\x03 | \x04%d\x03 | \x04%d\x01] - \x01%s", mvpSiDamage, mvpCommon, lvpFfGiven, name); + } + } + + PrintDivider(client); + for (int i = 0; i < players.Length; i++) + { + JSONObject player = view_as(players.Get(i)); + + int position = player.GetInt("position"); + int lastMatchPoints = player.GetInt("lastMatchPoints"); + + char name[256]; + player.GetString("name", name, sizeof(name)); + + if (lastMatchPoints >= 0) + PrintToChat(client, "\x04%dº \x01%s: \x03+%d pts", position, name, lastMatchPoints); + else + PrintToChat(client, "\x04%dº \x01%s: \x04%d pts", position, name, lastMatchPoints); + } +} + +void PrintDivider(int client) +{ + PrintToChat(client, "_________________"); +} + +void RankingMix(int client) +{ + if (mixBlocked || !SurvivorOrInfected(client) || GameInProgress()) + return; + + if (NumberOfPlayersInTeams() != 8) + { + PrintToChat(client, "\x01É necessário \x048 jogadores \x01para iniciar o mix"); + return; + } + + mixVotes++; + + if (!CanRunMix(client)) + { + PrintToChatAll("\x03%N \x01quer iniciar um mix baseado no ranking, digite \x04!rmix \x01 para iniciar", client); + return; + } + + ClearMixVotes(); + PrintToChatAll("\x01Iniciando mix baseado no ranking..."); + + JSONObject command = new JSONObject(); + + int player = 1 + for (int iClient = 1; iClient <= MaxClients; iClient++) + { + if (!IsClientInGame(iClient) || IsFakeClient(iClient) || !SurvivorOrInfected(iClient)) + continue; + + char property[8]; + FormatEx(property, sizeof(property), "player%d", player++); + + new String:communityId[25]; + GetClientAuthId(iClient, AuthId_SteamID64, communityId, sizeof(communityId)); + + command.SetString(property, communityId); + } + + HTTPRequest request = BuildHTTPRequest("/api/mix"); + request.Post(command, RankingMixResponse); +} + +void RankingMixResponse(HTTPResponse httpResponse, int any) +{ + if (httpResponse.Status == HTTPStatus_BadRequest) + { + JSONObject response = view_as(httpResponse.Data); + + new String:message[256]; + response.GetString("message", message, sizeof(message)); + + PrintToChatAll("\x04Erro: \x01%s", message); + return; + } + + if (httpResponse.Status != HTTPStatus_OK) + { + PrintToChatAll("\x04Erro ao gerar o mix"); + return; + } + + MoveAllPlayersToSpectated(); + + JSONObject response = view_as(httpResponse.Data); + JSONArray survivors = view_as(response.Get("survivors")); + JSONArray infecteds = view_as(response.Get("infecteds")); + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || IsFakeClient(client)) + continue; + + new String:clientCommunityId[25]; + GetClientAuthId(client, AuthId_SteamID64, clientCommunityId, sizeof(clientCommunityId)); + + bool found = false; + + for (int i = 0; !found && i < survivors.Length; i++) + { + JSONObject survivor = view_as(survivors.Get(i)); + + new String:communityId[25]; + survivor.GetString("communityId", communityId, sizeof(communityId)); + + if(!StrEqual(communityId, clientCommunityId)) + continue; + + MovePlayerToSurvivor(client); + found = true; + } + + for (int i = 0; !found && i < infecteds.Length; i++) + { + JSONObject infected = view_as(infecteds.Get(i)); + + new String:communityId[25]; + infected.GetString("communityId", communityId, sizeof(communityId)); + + if(!StrEqual(communityId, clientCommunityId)) + continue; + + MovePlayerToInfected(client); + found = true; + } + } + + char survivorTeam[256]; + + for (int i = 0; i < survivors.Length; i++) + { + JSONObject survivor = view_as(survivors.Get(i)); + + int position = survivor.GetInt("position"); + + char name[256]; + survivor.GetString("name", name, sizeof(name)); + + if(strlen(survivorTeam) == 0) + FormatEx(survivorTeam, sizeof(survivorTeam), "\x04#%d \x01%s", position, name); + else + FormatEx(survivorTeam, sizeof(survivorTeam), "%s \x03| \x04#%d \x01%s", survivorTeam, position, name); + } + + char infectedTeam[256]; + + for (int i = 0; i < infecteds.Length; i++) + { + JSONObject infected = view_as(infecteds.Get(i)); + + int position = infected.GetInt("position"); + + char name[256]; + infected.GetString("name", name, sizeof(name)); + + if(strlen(infectedTeam) == 0) + FormatEx(infectedTeam, sizeof(infectedTeam), "\x04#%d \x01%s", position, name); + else + FormatEx(infectedTeam, sizeof(infectedTeam), "%s \x03| \x04#%d \x01%s", infectedTeam, position, name); + } + + PrintToChatAll(survivorTeam); + PrintToChatAll("\x04---VS---"); + PrintToChatAll(infectedTeam); +} + +public void MoveAllPlayersToSpectated() +{ + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || IsFakeClient(client) || !SurvivorOrInfected(client)) + continue; + + MovePlayerToSpectator(client); + } +} + +public void MovePlayerToSpectator(int client) +{ + ChangeClientTeam(client, L4D2_TEAM_SPECTATOR); +} + +public void MovePlayerToSurvivor(int client) +{ + int bot = FindSurvivorBot(); + if (bot <= 0) + return; + + int flags = GetCommandFlags("sb_takecontrol"); + SetCommandFlags("sb_takecontrol", flags & ~FCVAR_CHEAT); + FakeClientCommand(client, "sb_takecontrol"); + SetCommandFlags("sb_takecontrol", flags); +} + +public void MovePlayerToInfected(int client) +{ + ChangeClientTeam(client, L4D2_TEAM_INFECTED); +} + +public int FindSurvivorBot() +{ + for (int client = 1; client <= MaxClients; client++) + if(IsClientInGame(client) && IsFakeClient(client) && GetClientTeam(client) == L4D2_TEAM_SURVIVOR) + return client; + + return -1; +} + +public bool SurvivorOrInfected(int client) +{ + int clientTeam = GetClientTeam(client); + + return clientTeam == L4D2_TEAM_SURVIVOR || clientTeam == L4D2_TEAM_INFECTED; +} + +public bool GameInProgress() +{ + int teamAScore = L4D2Direct_GetVSCampaignScore(0); + int teamBScore = L4D2Direct_GetVSCampaignScore(1); + + return teamAScore != 0 || teamBScore != 0; +} + +public int NumberOfPlayersInTeams() +{ + int count = 0; + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || IsFakeClient(client) || !SurvivorOrInfected(client)) + continue; + + count++; + } + + return count; +} + +public bool CanRunMix(int client) +{ + bool admin = GetAdminFlag(GetUserAdmin(client), Admin_Changemap); + if (admin) + return true; + + return mixVotes >= NEEDED_FOR_THE_MIX; +} + +public void ClearMixVotes() +{ + mixVotes = 0; + mixBlocked = false; +} + +public void BlockMixVotes() +{ + mixVotes = 0; + mixBlocked = true; +} + +HTTPRequest BuildHTTPRequest(char[] path) +{ + new String:endpoint[255]; + GetConVarString(cvar_playstats_endpoint, endpoint, sizeof(endpoint)); + StrCat(endpoint, sizeof(endpoint), path); + + new String:access_token[100]; + GetConVarString(cvar_playstats_access_token, access_token, sizeof(access_token)); + + HTTPRequest request = new HTTPRequest(endpoint); + request.SetHeader("Authorization", access_token); + + return request; +} \ No newline at end of file diff --git a/cfg/cfgogl/zonemod/shared_plugins.cfg b/cfg/cfgogl/zonemod/shared_plugins.cfg index b14e4d273..35c392bda 100644 --- a/cfg/cfgogl/zonemod/shared_plugins.cfg +++ b/cfg/cfgogl/zonemod/shared_plugins.cfg @@ -163,6 +163,7 @@ sm plugins load optional/l4d2_connect_announce.smx sm plugins load optional/l4d2_tank_is_comming.smx sm plugins load optional/l4d_sm_respawn.smx sm plugins load optional/l4d_death_item_glow.smx +sm plugins load optional/l4d2_playstats_sync.smx // Letzzzz go. sm plugins load confoglcompmod.smx diff --git a/cfg/cfgogl/zonemod/zonemod.cfg b/cfg/cfgogl/zonemod/zonemod.cfg index 60c063ab0..598f8b0ec 100644 --- a/cfg/cfgogl/zonemod/zonemod.cfg +++ b/cfg/cfgogl/zonemod/zonemod.cfg @@ -60,7 +60,7 @@ confogl_addcvar l4d2_melee_drop_method 2 confogl_addcvar sm_survivor_mvp_brevity 0 confogl_addcvar sm_survivor_mvp_brevity_latest 111 confogl_addcvar sm_stats_autoprint_vs_round 8372 -confogl_addcvar sm_stats_writestats 0 +confogl_addcvar sm_stats_writestats 1 // [l4d2_skill_detect.smx] confogl_addcvar sm_skill_report_enable 1 diff --git a/cfg/server.cfg b/cfg/server.cfg index 5905adaf1..737ebb2c7 100644 --- a/cfg/server.cfg +++ b/cfg/server.cfg @@ -4,6 +4,7 @@ motdfile "motd.txt" hostfile "host.txt" exec banned_user.cfg +exec server_secrets.cfg sv_contact "altairsossai@gmail.com"