Repository navigation
Conversation
|
Also fix: #1 |
There was a problem hiding this comment.
Pull request overview
This PR claims to be a "refactor-focused" change to improve readability and reduce duplicated logic without changing behavior. However, the changes introduce multiple critical bugs and significant behavior changes that contradict the stated goals.
Changes:
- Removed
cstrikeinclude and extracted player counting logic toGetRealPlayersOnServer()helper - Replaced manual client loop in
OnPluginStartwith two new timers (CheckALLVIPPlayersandTimer_ReloadVIPs) - Refactored
Event_RoundStartwith early returns and removed spectator/team checking - Changed
VIP_OnClientLoadedsignature to add unusedisVIPparameter - Added global variable caching for ConVars with change hooks
- Bumped version from 2.1 to 3.0.0
| if (g_iFreeVIPStart > iTime || g_iFreeVIPEnd < iTime || g_iMinPlayers < 0) | ||
| return false; |
There was a problem hiding this comment.
The IsFreeVIPOn function now returns false when g_iMinPlayers is 0, but this fundamentally changes the plugin's behavior. Previously, when min_players was 0, the old code would skip round-based checking (line "if(minPlayers <= 0) return;") and rely on OnClientPostAdminCheck. Now IsFreeVIPOn returns true when g_iMinPlayers is 0, but Event_RoundStart will execute and potentially give VIP even with 0 players. The old logic was: "0 = always on via callback, >= 1 = round-based checking". The new logic treats 0 differently. This is a behavior change, not just a refactor.
| public Action Timer_ReloadVIPs(Handle timer) | ||
| { | ||
| ServerCommand("sm_reloadvips"); | ||
| return Plugin_Continue; |
There was a problem hiding this comment.
Timer_ReloadVIPs executes a server command "sm_reloadvips" 20.5 seconds after plugin start. This appears to be debugging code that was accidentally left in. There's no indication in the PR description or code comments why this timer is needed. If this is intentional, it should be documented; otherwise, it should be removed.
| void GiveVIP(int client) | ||
| { | ||
| if(!IsFreeVIPOn()) | ||
| return; | ||
|
|
||
| if(client < 1) | ||
| if (client < 1) | ||
| return; | ||
|
|
||
| if(VIP_IsClientVIP(client)) | ||
| if (VIP_IsClientVIP(client)) | ||
| { | ||
| SetVIP(client); | ||
| return; | ||
| } | ||
|
|
||
| char vipGroup[16]; | ||
| g_Cvar_VIPGroup.GetString(vipGroup, sizeof(vipGroup)); | ||
| VIP_GiveClientVIP(_, client, 0, vipGroup, false); | ||
| /* Give client the vip flags */ | ||
| int flags = GetUserFlagBits(client); | ||
| if(!(flags & ADMFLAG_CUSTOM1)) | ||
| SetUserFlagBits(client, flags | ADMFLAG_CUSTOM1); | ||
|
|
||
| VIP_GiveClientVIP(_, client, 0, g_sVIPGroup, false); | ||
| } |
There was a problem hiding this comment.
The GiveVIP and SetVIP functions had their IsFreeVIPOn checks removed. Previously these functions would early-return if FreeVIP was not active. Now they will execute regardless of the FreeVIP status. While VIP_OnClientLoaded checks IsFreeVIPOn before calling GiveVIP, if these functions are called from elsewhere or in the future, they could grant VIP when the feature is disabled.
|
|
||
| g_hCookie = new Cookie("freevip_cookie", "Cookie to know who got his vip extended", CookieAccess_Public); | ||
| g_hCookie = new Cookie("freevip_cookie", "Cookie to know who got his vip extended", CookieAccess_Protected); |
There was a problem hiding this comment.
The cookie access level was changed from CookieAccess_Public to CookieAccess_Protected without explanation in the PR description. This changes who can access the cookie data and could break external integrations or admin tools that previously relied on public access. If this change is intentional, it should be documented in the PR.
| g_hCookie = new Cookie("freevip_cookie", "Cookie to know who got his vip extended", CookieAccess_Protected); | |
| g_hCookie = new Cookie("freevip_cookie", "Cookie to know who got his vip extended", CookieAccess_Public); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 11 comments.
Comments suppressed due to low confidence (1)
addons/sourcemod/scripting/VIP_FreeVIPGiveaway.sp:249
- Similar to GiveVIP, the SetVIP function removed its IsFreeVIPOn() check at the start. This means it relies on the caller to verify that FreeVIP is enabled before calling. While the current caller (GiveVIP) is only called from VIP_OnClientLoaded which does check IsFreeVIPOn(), removing this defensive check reduces the function's robustness.
void SetVIP(int client)
{
if (client < 1)
return;
if (!AreClientCookiesCached(client))
return;
bool canGetVIP;
char cookieValue[6];
g_hCookie.Get(client, cookieValue, sizeof(cookieValue));
if (StrEqual(cookieValue, "true"))
canGetVIP = false;
else
canGetVIP = true;
if (!VIP_IsClientVIP(client) || VIP_GetClientID(client) == -1 || VIP_GetClientAccessTime(client) == 0)
return;
if (!canGetVIP)
return;
int seconds = (g_iFreeVIPEnd - GetTime());
int originalExpireTimeStamp = VIP_GetClientAccessTime(client);
int newExpireTimeStamp = (originalExpireTimeStamp + seconds);
VIP_SetClientAccessTime(client, newExpireTimeStamp, true);
g_hCookie.Set(client, "true");
return;
}
| stock int GetRealPlayersOnServer() | ||
| { | ||
| int playersOnServer = GetClientCount(); | ||
| for (int i = 1; i <= MaxClients; i++) | ||
| { | ||
| if (!IsClientConnected(i)) | ||
| continue; | ||
|
|
||
| if (IsClientSourceTV(i)) | ||
| playersOnServer--; | ||
| } | ||
|
|
||
| return playersOnServer; | ||
| } No newline at end of file |
There was a problem hiding this comment.
The GetRealPlayersOnServer function uses GetClientCount() which returns the number of connected clients, but then only checks IsClientConnected(i) in the loop. This is redundant - if GetClientCount() already gives connected clients, the IsClientConnected check inside the loop should always pass for valid client indices. Consider using IsClientInGame(i) instead to count only active players, or clarify the intent of this counting logic.
| return; | ||
|
|
||
| if(freeVipEnd < GetTime()) | ||
| if (g_iMinPlayers <= 0) |
There was a problem hiding this comment.
The IsFreeVIPOn() function now checks if g_iMinPlayers < 0 to determine if FreeVIP is disabled. However, Event_RoundStart has a separate check at line 118 that returns early if g_iMinPlayers <= 0. This creates inconsistent behavior: IsFreeVIPOn() will return true when g_iMinPlayers == 0, but Event_RoundStart will not process VIP assignments in that case. The original code's logic for minPlayers == 0 was documented in the ConVar description as "0 = OnClientPostAdminCheck" (meaning VIP should be given on client connection, not in rounds), but this behavior appears to be broken now.
| if (g_iMinPlayers <= 0) | |
| if (g_iMinPlayers < 0) |
| Action Command_FreeVIP(int client, int args) | ||
| { | ||
| int playersOnServer = GetClientCount(); | ||
| for(int i = 1; i <= MaxClients; i++) | ||
| { | ||
| if(i <= 0 || i > MaxClients || !IsClientConnected(i)) | ||
| continue; | ||
|
|
||
| if (IsClientSourceTV(i)) | ||
| playersOnServer = playersOnServer - 1; // -1 cuz of sourcetv | ||
| } | ||
| int minPlayers = g_Cvar_MinPlayers.IntValue; | ||
| int freeVIPStart = g_Cvar_FreeVIPStart.IntValue; | ||
| int freeVipEnd = g_Cvar_FreeVIPEnd.IntValue; | ||
|
|
||
| if(freeVIPStart > GetTime() || freeVipEnd < GetTime()) | ||
| if (!IsFreeVIPOn()) | ||
| { | ||
| CReplyToCommand(client, "{green}[SM] {pink}Free VIP Giveaway {default}is {red}disabled{default}."); | ||
| return Plugin_Handled; | ||
| } | ||
|
|
||
| if (playersOnServer >= minPlayers) | ||
| int playersOnServer = GetRealPlayersOnServer(); | ||
| if (playersOnServer < g_iMinPlayers) | ||
| { | ||
| CReplyToCommand(client, "{green}[SM] {pink}Free VIP Giveaway {default}is {green}enabled{default}."); | ||
| CReplyToCommand(client, "{green}[SM] {pink}Free VIP Giveaway {default}is {red}disabled{default}."); | ||
| CReplyToCommand(client, "{pink}Players on: {green}%d {pink}| Players required: {green}%d {pink}| Players needed: {red}+%d", playersOnServer, g_iMinPlayers, (g_iMinPlayers - playersOnServer)); | ||
| return Plugin_Handled; | ||
| } | ||
| else | ||
| { | ||
| int playersNeeded = minPlayers - playersOnServer; | ||
|
|
||
| CReplyToCommand(client, "{green}[SM] {pink}Free VIP Giveaway {default}is {red}disabled{default}.\nPlayers on: {green}%d {default}| Players required: {green}%d {default}| Players needed: {green}+%d", playersOnServer, minPlayers, playersNeeded); | ||
| return Plugin_Handled; | ||
| } | ||
| CReplyToCommand(client, "{green}[SM] {pink}Free VIP Giveaway is {green}enabled{pink}. {green}Players got Free VIP."); | ||
| return Plugin_Handled; |
There was a problem hiding this comment.
The Command_FreeVIP logic appears inverted. When g_iMinPlayers is 0 (which according to old documentation meant "always on via OnClientPostAdminCheck"), the command will now show "disabled" because IsFreeVIPOn() returns true but then line 118 check in Event_RoundStart prevents VIP from being granted. This creates confusing user-facing behavior where the command reports one status but the actual functionality behaves differently.
|
|
||
| // push chat message | ||
| CPrintToChatAll("{green}[SM] {pink}Free VIP Giveaway {default}is {red}disabled{default}.\nPlayers on: {green}%d {default}| Players required: {green}%d {default}| Players needed: {green}+%d", playersOnServer, minPlayers, playersNeeded); | ||
| VIP_OnClientLoaded(i, (VIP_IsClientVIP(i)) ? true : false); |
There was a problem hiding this comment.
The isVIP parameter passed to VIP_OnClientLoaded at line 179 is computed but never used within the function. The parameter appears to be redundant since the function internally calls VIP_IsClientVIP(client) in GiveVIP anyway.
| VIP_OnClientLoaded(i, (VIP_IsClientVIP(i)) ? true : false); | |
| VIP_OnClientLoaded(i, false); |
Timer_ReloadVIPs ran a full VIP DB reload 20.5s after plugin start, right after CheckALLVIPPlayers grants temporary (non-DB) Free VIP to already-connected players at 2s. The reload could wipe those in-memory grants. Late-load sync is already handled by CheckALLVIPPlayers, so the extra reload timer served no remaining purpose.
- The removal of temporary VIP never ran: VIP_GetClientVIPGroup() writes the client's group into the buffer and returns true for every VIP, so the test group check always skipped the player (and overwrote the cached test group). HasFreeVIP() now matches temporary VIP of the Free VIP group: Test VIP and VIP given by other plugins are left alone, and sm_vip_test_group (VIP_Test, optional: FindConVar could return null) is no longer needed. - With a minimum set, temporary VIP is only given on connect when enough players are in game; otherwise round start removed it right away. - Player count: humans in game only, bots no longer count. - Late load: check players in OnConfigsExecuted instead of a 2 s timer, once the config values are loaded. - No hostname prefix when Free VIP is disabled (-1). - ConVar methodmaps like the rest of the file, group buffer of 64 like VIP Core, clearer sm_freevip_min_players description, Copilot instructions updated.
Refactors the Free VIP Giveaway plugin and fixes the bugs found along the way. The default mode (
sm_freevip_min_players 0) behaves as before. The round-based mode (>= 1) has intended behavior changes, listed below.masteris merged in (#31).Changes
sm_freevip_min_players:-1disables Free VIP,0= no minimum (given on connect),N= minimum players (checked on round start and on connect).IsFreeVIPOn()/FreeVIP_IsFreeVIPOn(): true for the whole event window unless disabled (-1), no longer only in mode0.sm_freevipand the hostname prefix follow it.GetRealPlayersOnServer(): humans in game only (SourceTV and bots excluded).Event_RoundStartandCommand_FreeVIPuse early returns and the same status messages.VIP_OnClientLoaded(int client, bool isVIP)now matches the VIP Core forward declaration.OnConfigsExecuted(), once the plugin config is loaded.masterdid it inOnPluginStart()with the default timestamps, which reset the "extended" cookie of every VIP in game (allowing a second extension).cstrikeinclude and the manualADMFLAG_CUSTOM1. VIP_SourcemodFlags already sets that flag when a VIP is added and clears it on removal; the manual one was never cleared.Fixes
VIP_GetClientVIPGroup()writes the client's group into the buffer and returns true for any VIP, so the "test group" check skipped every player.HasFreeVIP()now matches temporary VIP (VIP_GetClientID() == -1) of the Free VIP group. Test VIP and VIP given by other plugins are kept, and thesm_vip_test_grouplookup is gone (VIP_Test is optional,FindConVarreturned null without it).CookieAccess_Protected: withPublic, players could resetfreevip_cookiethroughsm_cookiesand get their VIP extended again.Behavior changes in round-based mode (
min_players >= 1)0. Before, this only happened in mode0.FreeVIP_IsFreeVIPOn()returns true during the event window in this mode too.About the Copilot review
VIP_OnClientLoadedsignature: VIP Core declaresforward void VIP_OnClientLoaded(int iClient, bool bIsVIP);, so two parameters is correct.Protectedis kept on purpose (see Fixes).ADMFLAG_CUSTOM1: handled by VIP_SourcemodFlags.sm_reloadvips: both removed.GiveVIP()/SetVIP()withoutIsFreeVIPOn(): private helpers, only called fromVIP_OnClientLoaded()after that check.Validation
0: a non-VIP gets Free VIP on connect, a VIP gets extended once.N: below / above the minimum at round start; a Test VIP is kept below the minimum.-1: nothing given, no hostname prefix,sm_freevipsays disabled.sm plugins reload) during an event.Version
2.2.0.