From 8f755a8e656d3e3594ad50945713a40ccf5758c8 Mon Sep 17 00:00:00 2001 From: Rick Yao Date: Sun, 1 Feb 2026 22:13:20 +0800 Subject: [PATCH 1/2] feat: add idle detection to kill players with timer/freeze bomb - Hook player_bot_replace event to detect when players go idle - Kill players with timer bomb or freeze bomb immediately when replaced by bot - Remove position-based idle detection approach Closes #1 --- l4d2_tank_draw/scripting/l4d2_tank_draw.sp | 48 +++++++++++++++++++ .../translations/l4d2_tank_draw.phrases.txt | 12 +++++ 2 files changed, 60 insertions(+) diff --git a/l4d2_tank_draw/scripting/l4d2_tank_draw.sp b/l4d2_tank_draw/scripting/l4d2_tank_draw.sp index c3b49ba..241d27f 100644 --- a/l4d2_tank_draw/scripting/l4d2_tank_draw.sp +++ b/l4d2_tank_draw/scripting/l4d2_tank_draw.sp @@ -153,6 +153,7 @@ public void OnPluginStart() HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre); HookEvent("mission_lost", Event_Lost, EventHookMode_Pre); + HookEvent("player_bot_replace", Event_PlayerBotReplace); HookEvent("molotov_thrown", Event_Molotov); @@ -312,6 +313,53 @@ public void OnClientDisconnect(int client) return; } +public Action Event_PlayerBotReplace(Event event, const char[] name, bool dontBroadcast) +{ + if (g_iTankDrawEnable == 0) { return Plugin_Continue; } + DebugPrint("Event_PlayerBotReplace triggered."); + + // Get the player who was replaced (the human player who went idle) + int player = GetClientOfUserId(event.GetInt("player")); + + if (!IsValidClient(player)) + { + DebugPrint("Invalid player in player_bot_replace event."); + return Plugin_Continue; + } + + // Check if player has a timer bomb + if (g_hTimeBombTimer[player] != null) + { + char playerName[MAX_NAME_LENGTH]; + GetClientName(player, playerName, sizeof(playerName)); + + CPrintToChatAll("%t", "TankDraw_IdleKillTimerBomb", playerName); + PrintHintTextToAll("%t", "TankDraw_IdleKillTimerBomb_NoColor", playerName); + + KillTimeBomb(player); + ForcePlayerSuicide(player); + DebugPrint("Player %s (userid: %d) was killed for idling with timer bomb.", playerName, event.GetInt("player")); + return Plugin_Continue; + } + + // Check if player has a freeze bomb + if (g_hFreezeBombTimer[player] != null) + { + char playerName[MAX_NAME_LENGTH]; + GetClientName(player, playerName, sizeof(playerName)); + + CPrintToChatAll("%t", "TankDraw_IdleKillTimerBomb", playerName); + PrintHintTextToAll("%t", "TankDraw_IdleKillTimerBomb_NoColor", playerName); + + KillFreezeBombTimer(player); + ForcePlayerSuicide(player); + DebugPrint("Player %s (userid: %d) was killed for idling with freeze bomb.", playerName, event.GetInt("player")); + return Plugin_Continue; + } + + return Plugin_Continue; +} + public Action Event_Molotov(Event event, const char[] name, bool dontBroadcast) { DebugPrint("Event_Molotov triggered."); diff --git a/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt b/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt index f6ae0c4..292c556 100644 --- a/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt +++ b/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt @@ -670,5 +670,17 @@ { "en" "[Tank Draw] Player %s's lucky draw result: Drug all survivor for %d seconds." "chi" "[坦克抽奖] 玩家 %s 的幸运抽奖结果: 全体中毒 %d 秒" + }, + + "TankDraw_IdleKillTimerBomb" + { + "en" "{yellow}[Tank Draw] {lightgreen}Player {olive}%s{lightgreen} was detected idling with a timer bomb and has been {olive}executed." + "chi" "{yellow}[坦克抽奖] {lightgreen}玩家 {olive}%s{lightgreen} 被检测到闲置不用定时炸弹,已被{olive}处决" + }, + + "TankDraw_IdleKillTimerBomb_NoColor" + { + "en" "[Tank Draw] Player %s was detected idling with a timer bomb and has been executed." + "chi" "[坦克抽奖] 玩家 %s 被检测到闲置不用定时炸弹,已被处决" } } \ No newline at end of file From 92e6cd920811643b3e4db455e90079713b2b30fb Mon Sep 17 00:00:00 2001 From: Rick Date: Mon, 2 Feb 2026 19:52:04 +0800 Subject: [PATCH 2/2] feat: kill player if attempting to avoid punishment --- l4d2_tank_draw/scripting/l4d2_tank_draw.sp | 31 +++++++------------ .../translations/l4d2_tank_draw.phrases.txt | 16 +++++----- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/l4d2_tank_draw/scripting/l4d2_tank_draw.sp b/l4d2_tank_draw/scripting/l4d2_tank_draw.sp index 241d27f..cba978d 100644 --- a/l4d2_tank_draw/scripting/l4d2_tank_draw.sp +++ b/l4d2_tank_draw/scripting/l4d2_tank_draw.sp @@ -153,7 +153,7 @@ public void OnPluginStart() HookEvent("player_death", Event_PlayerDeath, EventHookMode_Pre); HookEvent("mission_lost", Event_Lost, EventHookMode_Pre); - HookEvent("player_bot_replace", Event_PlayerBotReplace); + HookEvent("player_bot_replace", Event_PlayerBotReplace, EventHookMode_Pre); HookEvent("molotov_thrown", Event_Molotov); @@ -320,6 +320,7 @@ public Action Event_PlayerBotReplace(Event event, const char[] name, bool dontBr // Get the player who was replaced (the human player who went idle) int player = GetClientOfUserId(event.GetInt("player")); + DebugPrint("player id : %d is replaced", player); if (!IsValidClient(player)) { @@ -328,32 +329,24 @@ public Action Event_PlayerBotReplace(Event event, const char[] name, bool dontBr } // Check if player has a timer bomb - if (g_hTimeBombTimer[player] != null) + if (g_hTimeBombTimer[player] != null || g_hFreezeBombTimer[player] != null || g_hDrugTimers[player] != null) { char playerName[MAX_NAME_LENGTH]; GetClientName(player, playerName, sizeof(playerName)); - CPrintToChatAll("%t", "TankDraw_IdleKillTimerBomb", playerName); - PrintHintTextToAll("%t", "TankDraw_IdleKillTimerBomb_NoColor", playerName); + CPrintToChatAll("%t", "TankDraw_Avoid_Punish", playerName); + PrintHintTextToAll("%t", "TankDraw_Avoid_Punish_NoColor", playerName); - KillTimeBomb(player); - ForcePlayerSuicide(player); - DebugPrint("Player %s (userid: %d) was killed for idling with timer bomb.", playerName, event.GetInt("player")); - return Plugin_Continue; - } - - // Check if player has a freeze bomb - if (g_hFreezeBombTimer[player] != null) - { - char playerName[MAX_NAME_LENGTH]; - GetClientName(player, playerName, sizeof(playerName)); - - CPrintToChatAll("%t", "TankDraw_IdleKillTimerBomb", playerName); - PrintHintTextToAll("%t", "TankDraw_IdleKillTimerBomb_NoColor", playerName); + int botId = GetClientOfUserId(event.GetInt("bot")); + DebugPrint("replace bot id: %d", botId); KillFreezeBombTimer(player); + KillFreezeBombTimer(botId); + ForcePlayerSuicide(player); - DebugPrint("Player %s (userid: %d) was killed for idling with freeze bomb.", playerName, event.GetInt("player")); + ForcePlayerSuicide(botId); + + DebugPrint("Player %s (userid: %d) was killed for avoiding punishment.", playerName, event.GetInt("player")); return Plugin_Continue; } diff --git a/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt b/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt index 292c556..aa19865 100644 --- a/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt +++ b/l4d2_tank_draw/translations/l4d2_tank_draw.phrases.txt @@ -670,17 +670,17 @@ { "en" "[Tank Draw] Player %s's lucky draw result: Drug all survivor for %d seconds." "chi" "[坦克抽奖] 玩家 %s 的幸运抽奖结果: 全体中毒 %d 秒" - }, + } - "TankDraw_IdleKillTimerBomb" + "TankDraw_Avoid_Punish" { - "en" "{yellow}[Tank Draw] {lightgreen}Player {olive}%s{lightgreen} was detected idling with a timer bomb and has been {olive}executed." - "chi" "{yellow}[坦克抽奖] {lightgreen}玩家 {olive}%s{lightgreen} 被检测到闲置不用定时炸弹,已被{olive}处决" - }, + "en" "{yellow}[Tank Draw] {lightgreen}Player {olive}%s{lightgreen} attempted to avoid punishment. {olive} Executed." + "chi" "{yellow}[坦克抽奖] {lightgreen}玩家 {olive}%s{lightgreen} 试图躲避惩罚,已被{olive}处决" + } - "TankDraw_IdleKillTimerBomb_NoColor" + "TankDraw_Avoid_Punish_NoColor" { - "en" "[Tank Draw] Player %s was detected idling with a timer bomb and has been executed." - "chi" "[坦克抽奖] 玩家 %s 被检测到闲置不用定时炸弹,已被处决" + "en" "[Tank Draw] Player %s attempted to avoid punishment. Executed." + "chi" "[坦克抽奖] 玩家 %s 视图躲避惩罚,已被处决" } } \ No newline at end of file