From 4a93867cd7ced7aa26d226b6cd49cf5076960a91 Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Thu, 24 Jul 2025 02:29:01 +0300 Subject: [PATCH 1/6] fix: OT backup incorrect score --- src/eBot/Match/Match.php | 282 ++++++++++++++++++--------------------- 1 file changed, 132 insertions(+), 150 deletions(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index d454441..9bee3d0 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -20,9 +20,11 @@ use eTools\Task\Taskable; use eTools\Rcon\CSGO as Rcon; -class Match implements Taskable +class Match implements + Taskable { + const STATUS_NOT_STARTED = 0; const STATUS_STARTING = 1; const STATUS_WU_KNIFE = 2; @@ -720,8 +722,7 @@ private function engageMap() $this->addLog("Detected a workshop map, loading it using host_workshop_map."); $this->rcon->send("host_workshop_map " . \eBot\Config\Config::getInstance() ->getWorkshopByMap($this->currentMap->getMapName())); - } - else { + } else { $this->rcon->send("changelevel " . $this->currentMap->getMapName()); } @@ -816,32 +817,32 @@ public function sendRotateMessage() } if ($this->mapIsEngaged && ($this->streamerReady || !$this->config_streamer)) { if ($this->getStatus() == self::STATUS_END_KNIFE) { - $messages [] = "Waiting for " . $this->formatText($this->winKnifeTeamName, "green") . " (" . $this->winKnife . ") to choose side (!stay/!switch)."; - $messages [] = "Available commands: !help, !rules, !stay, !switch, !restart."; + $messages[] = "Waiting for " . $this->formatText($this->winKnifeTeamName, "green") . " (" . $this->winKnife . ") to choose side (!stay/!switch)."; + $messages[] = "Available commands: !help, !rules, !stay, !switch, !restart."; } else { - $messages [] = "Please write " . $this->formatText("!ready", "yellow") . " when your team is ready!"; - $messages [] = "Available commands: !help, !rules, !ready, !notready."; + $messages[] = "Please write " . $this->formatText("!ready", "yellow") . " when your team is ready!"; + $messages[] = "Available commands: !help, !rules, !ready, !notready."; } } else if ($this->mapIsEngaged && (!$this->streamerReady || $this->config_streamer)) { - $messages [] = "Streamers are not ready yet!"; + $messages[] = "Streamers are not ready yet!"; } else { - $messages [] = "Please write " . $this->formatText("!map mapname", "yellow") . "to select the map!"; + $messages[] = "Please write " . $this->formatText("!map mapname", "yellow") . "to select the map!"; $maps = \eBot\Config\Config::getInstance()->getMaps(); foreach ($maps as $map) { $mapmessage .= "$map, "; } - $messages [] = substr($mapmessage, 0, -2); + $messages[] = substr($mapmessage, 0, -2); } if ($message) - $messages [] = "$message - " . $this->formatText($this->teamAName, "ltGreen") . " ($teamA) VS ($teamB) " . $this->formatText($this->teamBName, "ltGreen") . "."; + $messages[] = "$message - " . $this->formatText($this->teamAName, "ltGreen") . " ($teamA) VS ($teamB) " . $this->formatText($this->teamBName, "ltGreen") . "."; $adverts = \eBot\Config\Config::getInstance()->getAdvertising($this->season_id); for ($i = 0; $i < count($adverts['season_id']); $i++) { - $messages [] = $adverts['message'][$i]; + $messages[] = $adverts['message'][$i]; if ($message) - $messages [] = "$message - " . $this->formatText($this->teamAName, "ltGreen") . " ($teamA) VS ($teamB) " . $this->formatText($this->teamBName, "ltGreen") . "."; + $messages[] = "$message - " . $this->formatText($this->teamAName, "ltGreen") . " ($teamA) VS ($teamB) " . $this->formatText($this->teamBName, "ltGreen") . "."; } $message = $messages[$this->message++ % count($messages)]; @@ -862,12 +863,12 @@ public function formatText($text, $color) { $colors = [ "default" => "\001", - "red" => "\002", - "blue" => "\003", - "green" => "\004", + "red" => "\002", + "blue" => "\003", + "green" => "\004", "ltGreen" => "\005", - "yellow" => "\006", - "ltRed" => "\007", + "yellow" => "\006", + "ltRed" => "\007", ]; if ($colors["$color"]) { @@ -1229,7 +1230,7 @@ private function processPurchased(\eBot\Message\Type\Purchased $message) ('" . $this->match_id . "', '" . $this->currentMap->getMapId() . "', 'purchased', '$text', '" . $this->getRoundTime() . "', '" . $this->getNbRound() . "', NOW(), NOW()) "); -// $this->addLog($message->userName . " (" . $message->userTeam . ") purchased " . $message->object); + // $this->addLog($message->userName . " (" . $message->userTeam . ") purchased " . $message->object); } } @@ -1873,7 +1874,7 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) if ($playerBest != null) { $playerId = $playerBest->getId(); - $playerFirstKill = (int)$playerBest->gotFirstKill; + $playerFirstKill = (int) $playerBest->gotFirstKill; } else { $playerId = "NULL"; $playerFirstKill = "NULL"; @@ -2270,7 +2271,7 @@ private function processAttacked(\eBot\Message\Type\Attacked $message) $this->roundData[$this->getNbRound()][$message->victimTeam]["DAMAGE_TAKEN"][$message->victimName][$message->attackerName]["HITS"] += 1; } -// $this->say($message->attackerName . " (" . $message->attackerTeam . ") hit " . $message->victimName . " (" . $message->victimTeam . ") for " . $message->attackerDamage . " in round " . $this->getNbRound() . " (hp left: " . $this->roundData[$this->getNbRound()]["HEALTH_LEFT"][$message->victimName] . ") with " . $message->attackerWeapon . " hit in " . $message->attackerHitGroup); + // $this->say($message->attackerName . " (" . $message->attackerTeam . ") hit " . $message->victimName . " (" . $message->victimTeam . ") for " . $message->attackerDamage . " in round " . $this->getNbRound() . " (hp left: " . $this->roundData[$this->getNbRound()]["HEALTH_LEFT"][$message->victimName] . ") with " . $message->attackerWeapon . " hit in " . $message->attackerHitGroup); } } @@ -2284,7 +2285,7 @@ private function processKillAssist(\eBot\Message\Type\KillAssist $message) $killer->save(); } -// $this->addLog($message->userName . " assisted the kill of " . $message->killedUserName); + // $this->addLog($message->userName . " assisted the kill of " . $message->killedUserName); // $this->addMatchLog($this->getColoredUserNameHTML($message->userName, $message->userTeam) . " assisted the kill of " . $this->getColoredUserNameHTML($message->killedUserName, $message->killedUserTeam)); } @@ -2394,16 +2395,16 @@ private function processKill(\eBot\Message\Type\Kill $message) if ($this->isMatchRound()) { $sendToWebsocket = json_encode([ - 'type' => 'kill', - 'id' => $this->match_id, - 'killer' => $message->getUserName(), + 'type' => 'kill', + 'id' => $this->match_id, + 'killer' => $message->getUserName(), 'killerPosX' => $message->killerPosX, 'killerPosY' => $message->killerPosY, - 'weapon' => $message->getWeapon(), - 'killed' => $message->getKilledUserName(), + 'weapon' => $message->getWeapon(), + 'killed' => $message->getKilledUserName(), 'killedPosX' => $message->killedPosX, 'killedPosY' => $message->killedPosY, - 'headshot' => $message->getHeadshot(), + 'headshot' => $message->getHeadshot(), ]); $this->websocket['livemap']->sendData($sendToWebsocket); } @@ -2587,11 +2588,11 @@ private function processRoundStart(\eBot\Message\Type\RoundStart $message) "); if ($this->isMatchRound()) $this->websocket['livemap']->sendData(json_encode([ - 'type' => 'newRound', - 'id' => $this->match_id, + 'type' => 'newRound', + 'id' => $this->match_id, 'message' => "newRound", - 'round' => $this->getNbRound(), - 'status' => $this->getStatusText(false), + 'round' => $this->getNbRound(), + 'status' => $this->getStatusText(false), ])); $this->roundEndEvent = false; @@ -2799,9 +2800,9 @@ private function pauseMatch() $pauseMethod = \eBot\Config\Config::getInstance()->getPauseMethod(); $pauseMethods = [ - "instantConfirm" => ["text" => "The match is paused.", "method" => "pause"], + "instantConfirm" => ["text" => "The match is paused.", "method" => "pause"], "instantNoConfirm" => ["text" => "The match is paused.", "method" => "pause"], - "nextRound" => ["text" => "Match will be paused at the start of the next round!", "method" => "mp_pause_match"], + "nextRound" => ["text" => "Match will be paused at the start of the next round!", "method" => "mp_pause_match"], ]; if ($pauseMethod == "instantConfirm") { @@ -3070,7 +3071,7 @@ private function goLive($type) { $types = [ "KNIFE" => ["restartMethod" => "ko3", "eslAnnounce" => "KNIFE LIVE!"], - "LIVE" => ["restartMethod" => "lo3", "eslAnnounce" => "1st Side: LIVE!"], + "LIVE" => ["restartMethod" => "lo3", "eslAnnounce" => "1st Side: LIVE!"], ]; if (\eBot\Config\Config::getInstance()->getKo3Method() == "csay" && $this->pluginCsay) { @@ -3141,7 +3142,7 @@ private function startMatch($force_ready = false) // Undo changes from warmup (turn back to default values) and go live with first side of regulation $this->undoWarmupConfig()->executeMatchConfig()->goLive("LIVE"); break; - case Map::STATUS_WU_2_SIDE : + case Map::STATUS_WU_2_SIDE: $this->currentMap->setStatus(Map::STATUS_SECOND_SIDE, true); $this->setStatus(self::STATUS_SECOND_SIDE, true); @@ -3153,7 +3154,7 @@ private function startMatch($force_ready = false) } $this->say("2nd Side: LIVE!"); break; - case Map::STATUS_WU_OT_1_SIDE : + case Map::STATUS_WU_OT_1_SIDE: $this->currentMap->setStatus(Map::STATUS_OT_FIRST_SIDE, true); $this->setStatus(self::STATUS_OT_FIRST_SIDE, true); // NEW @@ -3161,7 +3162,7 @@ private function startMatch($force_ready = false) $this->say("1st Side OT: LIVE!"); $this->waitForRestart = false; break; - case Map::STATUS_WU_OT_2_SIDE : + case Map::STATUS_WU_OT_2_SIDE: $this->currentMap->setStatus(Map::STATUS_OT_SECOND_SIDE, true); $this->setStatus(self::STATUS_OT_SECOND_SIDE, true); @@ -3511,140 +3512,118 @@ public function adminGoBackRounds($round) { $this->enable = false; $this->pendingContinueMatch = true; - $sql = mysqli_query(Application::getInstance()->db, "SELECT * FROM round_summary WHERE match_id = '" . $this->match_id . "' AND map_id = '" . $this->currentMap->getMapId() . "' AND round_id = $round"); - $req = mysqli_fetch_array($sql); - $backup = $req['backup_file_name']; + $row = mysqli_fetch_array( + mysqli_query( + Application::getInstance()->db, + "SELECT * + FROM round_summary + WHERE match_id = '{$this->match_id}' + AND map_id = '{$this->currentMap->getMapId()}' + AND round_id = {$round}" + ) + ); + + if (!$row) { + $this->addLog("Backup meta‑data for round {$round} not found!", Logger::ERROR); + return false; + } - $this->addLog("Admin backup round: '$round'."); - $this->addLog("Backup file: '$backup'."); + $backupFile = $row['backup_file_name']; + $this->addLog("Admin rollback to round #{$round} (file {$backupFile})."); + + $this->stop = ["ct" => false, "t" => false]; - $this->stop["ct"] = false; - $this->stop["t"] = false; if ($this->isPaused) { - if (\eBot\Config\Config::getInstance()->getPauseMethod() == "nextRound") { - $this->rcon->send("mp_unpause_match"); - } else { - $this->rcon->send("pause"); - } + $this->rcon->send(\eBot\Config\Config::getInstance()->getPauseMethod() == "nextRound" + ? "mp_unpause_match" + : "pause"); $this->isPaused = false; - $this->addLog("Disabling pause."); - \mysqli_query(Application::getInstance()->db, "UPDATE `matchs` SET `is_paused` = '0' WHERE `id` = '" . $this->match_id . "'"); - $this->websocket['match']->sendData(json_encode(['message' => 'status', 'content' => 'is_unpaused', 'id' => $this->match_id])); + $this->addLog("Match unpaused by admin rollback."); + + mysqli_query( + Application::getInstance()->db, + "UPDATE matchs SET is_paused = 0 WHERE id = '{$this->match_id}'" + ); + $this->websocket['match']->sendData( + json_encode(['message' => 'status', 'content' => 'is_unpaused', 'id' => $this->match_id]) + ); } $this->rcon->send("mp_unpause_match"); - $this->score["team_a"] = $req['score_a']; - $this->score["team_b"] = $req['score_b']; - - @mysqli_query(Application::getInstance()->db, "UPDATE `matchs` SET score_a = '" . $this->score["team_a"] . "', score_b ='" . $this->score["team_b"] . "' WHERE id='" . $this->match_id . "'"); + $this->score['team_a'] = (int) $row['score_a']; + $this->score['team_b'] = (int) $row['score_b']; - // To check with overtime - if ($this->score["team_a"] + $this->score["team_b"] < $this->matchData["max_round"]) { - $score = $this->currentMap->getCurrentScore(); - if ($score != null) { - $score->setScore1Side1($this->score['team_a']); - $score->setScore2Side1($this->score['team_b']); - $score->setScore1Side2(0); - $score->setScore2Side2(0); - $score->saveScore(); - } - $this->currentMap->calculScores(); - } else { - $score = $this->currentMap->getCurrentScore(); - if ($score != null) { - $score->setScore1Side2($this->score['team_a'] - $score->getScore1Side1()); - $score->setScore2Side2($this->score['team_b'] - $score->getScore2Side1()); - $score->saveScore(); - } - $this->currentMap->calculScores(); - } - - // Sending roundbackup format file - $this->rcon->send("mp_backup_round_file \"ebot_" . $this->match_id . "\""); + mysqli_query( + Application::getInstance()->db, + "UPDATE matchs + SET score_a = '{$this->score['team_a']}', + score_b = '{$this->score['team_b']}' + WHERE id = '{$this->match_id}'" + ); + $this->currentMap->setScore1($this->score['team_a']); + $this->currentMap->setScore2($this->score['team_b']); - // Prevent the halftime pausetimer + $this->rcon->send("mp_backup_round_file \"ebot_{$this->match_id}\""); $this->rcon->send("mp_halftime_pausetimer 0"); + $this->rcon->send("mp_backup_restore_load_file {$backupFile}"); + $this->rcon->send("mp_backup_round_file_last {$backupFile}"); - // Sending restore - $this->rcon->send("mp_backup_restore_load_file " . $backup); - - // Prevent a bug for double stop - $this->rcon->send("mp_backup_round_file_last " . $backup); - - foreach ($this->players as &$player) { - $player->restoreSnapshot($this->getNbRound() - 1); + foreach ($this->players as &$p) { + $p->restoreSnapshot($this->getNbRound() - 1); } - // Determine status - $status = false; - $total = $this->score["team_a"] + $this->score["team_b"]; - if ($total < $this->matchData["max_round"]) { + $total = $this->score['team_a'] + $this->score['team_b']; + $maxRound = $this->matchData['max_round']; + $status = false; // map‑status we’re switching TO + + if ($total < $maxRound) { $status = self::STATUS_FIRST_SIDE; $statusMap = Map::STATUS_FIRST_SIDE; - } else if ($total < $this->matchData["max_round"] * 2) { + } elseif ($total < $maxRound * 2) { $status = self::STATUS_SECOND_SIDE; $statusMap = Map::STATUS_SECOND_SIDE; - } else { - if ($this->config_ot) { - $total -= $this->matchData["max_round"] * 2; - $total_rest = $total % $this->ot_maxround * 2; - if ($total_rest < $this->ot_maxround) { - $status = self::STATUS_OT_FIRST_SIDE; - $statusMap = Map::STATUS_OT_FIRST_SIDE; - } else { - $status = self::STATUS_OT_SECOND_SIDE; - $statusMap = Map::STATUS_OT_SECOND_SIDE; - } - } - } - - if ($status && $this->getStatus() != $status) { - $this->addLog("Setting match to another status: " . $status . "."); - switch ($this->getStatus()) { - case self::STATUS_SECOND_SIDE: - if ($status == self::STATUS_FIRST_SIDE) { - $this->addLog("Swapping teams!"); - $this->swapSides(); - } - break; - case self::STATUS_OT_FIRST_SIDE: - if ($status == self::STATUS_FIRST_SIDE) { - $this->addLog("Swapping teams!"); - $this->swapSides(); - } - break; - case self::STATUS_OT_SECOND_SIDE: - if ($status == self::STATUS_OT_FIRST_SIDE) { - $this->addLog("Swapping teams!"); - $this->swapSides(); - } - - if ($status == self::STATUS_SECOND_SIDE) { - $this->addLog("Swapping teams!"); - $this->swapSides(); - } - break; + } elseif ($this->config_ot) { + $remain = $total - $maxRound * 2; + $blockSize = $this->ot_maxround * 2; + $status = ($remain % $blockSize) < $this->ot_maxround + ? self::STATUS_OT_FIRST_SIDE + : self::STATUS_OT_SECOND_SIDE; + $statusMap = ($status == self::STATUS_OT_FIRST_SIDE) + ? Map::STATUS_OT_FIRST_SIDE + : Map::STATUS_OT_SECOND_SIDE; + } + + if ($status && $status !== $this->getStatus()) { + if ($status < $this->getStatus()) { + $this->addLog("Swapping teams to match rolled‑back round."); + $this->swapSides(); } $this->setStatus($status, true); $this->currentMap->setStatus($statusMap, true); } - $this->say("Round restored, going LIVE!"); - \mysqli_query(Application::getInstance()->db, "UPDATE `matchs` SET ingame_enable = 1 WHERE id='" . $this->match_id . "'") or $this->addLog("Can't update ingame_enable", Logger::ERROR); - TaskManager::getInstance()->addTask(new Task($this, self::SET_LIVE, microtime(true) + 2)); + $this->say("Round restored — going LIVE!"); + mysqli_query( + Application::getInstance()->db, + "UPDATE matchs SET ingame_enable = 1 WHERE id = '{$this->match_id}'" + ); - $event = new \eBot\Events\Event\RoundScored(); - $event->setMatch($this); - $event->setTeamA($this->teamAName); - $event->setTeamB($this->teamAName); - $event->setScoreA($this->score["team_a"]); - $event->setScoreB($this->score["team_b"]); - $event->setStatus($this->getStatus()); - $event->setStatusText($this->getStatusText()); - $event->setAdmin(true); - \eBot\Events\EventDispatcher::getInstance()->dispatchEvent($event); + TaskManager::getInstance()->addTask( + new Task($this, self::SET_LIVE, microtime(true) + 2) + ); + + $evt = new \eBot\Events\Event\RoundScored(); + $evt->setMatch($this); + $evt->setTeamA($this->teamAName); + $evt->setTeamB($this->teamBName); + $evt->setScoreA($this->score['team_a']); + $evt->setScoreB($this->score['team_b']); + $evt->setStatus($this->getStatus()); + $evt->setStatusText($this->getStatusText()); + $evt->setAdmin(true); + \eBot\Events\EventDispatcher::getInstance()->dispatchEvent($evt); return true; } @@ -3694,15 +3673,18 @@ public function getTeamB() return $this->teamBName; } - public function getServerIp() { + public function getServerIp() + { return $this->server_ip; } - public function getScoreA() { + public function getScoreA() + { return $this->currentMap->getScore1(); } - public function getScoreB() { + public function getScoreB() + { return $this->currentMap->getScore2(); } } From 673c562053993223f89d1dcfd3a279215e18ef46 Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Tue, 29 Jul 2025 01:57:51 +0300 Subject: [PATCH 2/6] fix: undo knife config after force knife end --- src/eBot/Match/Match.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index 9bee3d0..e9e97bb 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -3419,11 +3419,12 @@ public function adminForceKnifeEnd() $this->addLog("Knife round has been skipped by the admin."); $this->addMatchLog("Knife round has been skipped by the admin."); $this->say("Knife round has been skipped by the admin.", "red"); - + $this->ready["ct"] = false; $this->ready["t"] = false; $this->currentMap->setStatus(Map::STATUS_WU_1_SIDE, true); $this->setStatus(self::STATUS_WU_1_SIDE, true); + $this->undoKnifeConfig()->executeMatchConfig()->executeWarmupConfig(); return true; } From 6a76fd4d85f18b4b91a01e10535a8f584235cf5a Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Tue, 29 Jul 2025 02:36:30 +0300 Subject: [PATCH 3/6] fix: update team swapping logic for match rollback --- src/eBot/Match/Match.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index e9e97bb..ebd0328 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -3597,7 +3597,7 @@ public function adminGoBackRounds($round) } if ($status && $status !== $this->getStatus()) { - if ($status < $this->getStatus()) { + if ($status < $this->getStatus() || $status > $this->getStatus()) { $this->addLog("Swapping teams to match rolled‑back round."); $this->swapSides(); } From f5fb2dda1e5af1b27cd9c262f81d506c37dda90b Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Sun, 3 Aug 2025 18:29:51 +0300 Subject: [PATCH 4/6] fix changing sides after backup --- src/eBot/Match/Match.php | 102 +++++++++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index ebd0328..0663cd1 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -25,6 +25,18 @@ class Match implements { + + + + + + + + + + + + const STATUS_NOT_STARTED = 0; const STATUS_STARTING = 1; const STATUS_WU_KNIFE = 2; @@ -1897,22 +1909,60 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) } } - mysqli_query(Application::getInstance()->db, "INSERT INTO round_summary - (`match_id`,`map_id`,`score_a`,`score_b`,`bomb_planted`,`bomb_defused`,`bomb_exploded`,`ct_win`, `t_win`,`round_id`,`win_type`,`team_win`,`best_killer`,`best_killer_fk`,`best_killer_nb`,`best_action_type`,`best_action_param`, `backup_file_name`,`created_at`,`updated_at`) - VALUES - ('" . $this->match_id . "', '" . $this->currentMap->getMapId() . "', '" . $this->score["team_a"] . "', '" . $this->score["team_b"] . "', - '" . (($this->gameBombPlanter != null) ? 1 : 0) . "', - '" . (($message->type == "bombdefused") ? 1 : 0) . "', - '" . (($message->type == "bombeexploded") ? 1 : 0) . "', - '" . (($message->getTeamWin() == "CT") ? 1 : 0) . "', - '" . (($message->getTeamWin() != "CT") ? 1 : 0) . "', - '" . ($this->getNbRound() - 1) . "', - '" . $message->type . "','" . $teamWin . "', - $playerId, " . $playerFirstKill . ", $nb, " . (($bestActionType != null) ? "'$bestActionType'" : "NULL") . ", " . (($bestActionParam != null) ? "'" . addslashes(serialize($bestActionParam)) . "'" : "NULL") . ", - " . $backupFile . ", - NOW(), - NOW() - )") or $this->addLog("Can't insert round summary match " . $this->match_id . " - " . mysqli_error(Application::getInstance()->db), Logger::ERROR); + mysqli_query( + Application::getInstance()->db, + "INSERT INTO round_summary ( + match_id, + map_id, + score_a, + score_b, + side_a, + side_b, + bomb_planted, + bomb_defused, + bomb_exploded, + ct_win, + t_win, + round_id, + win_type, + team_win, + best_killer, + best_killer_fk, + best_killer_nb, + best_action_type, + best_action_param, + backup_file_name, + created_at, + updated_at + ) VALUES ( + '" . $this->match_id . "', + '" . $this->currentMap->getMapId() . "', + '" . $this->score["team_a"] . "', + '" . $this->score["team_b"] . "', + '" . $this->side['team_a'] . "', + '" . $this->side['team_b'] . "', + '" . (($this->gameBombPlanter != null) ? 1 : 0) . "', + '" . (($message->type == "bombdefused") ? 1 : 0) . "', + '" . (($message->type == "bombeexploded") ? 1 : 0) . "', + '" . (($message->getTeamWin() == "CT") ? 1 : 0) . "', + '" . (($message->getTeamWin() != "CT") ? 1 : 0) . "', + '" . ($this->getNbRound() - 1) . "', + '" . $message->type . "', + '" . $teamWin . "', + $playerId, + $playerFirstKill, + $nb, + " . (($bestActionType != null) ? "'$bestActionType'" : "NULL") . ", + " . (($bestActionParam != null) ? "'" . addslashes(serialize($bestActionParam)) . "'" : "NULL") . ", + " . $backupFile . ", + NOW(), + NOW() + )" + ) or $this->addLog( + "Can't insert round summary match " . $this->match_id . " - " . mysqli_error(Application::getInstance()->db), + Logger::ERROR + ); + // END ROUND SUMMARY // Prevent the OverTime bug if ($this->config_ot) { @@ -3419,7 +3469,7 @@ public function adminForceKnifeEnd() $this->addLog("Knife round has been skipped by the admin."); $this->addMatchLog("Knife round has been skipped by the admin."); $this->say("Knife round has been skipped by the admin.", "red"); - + $this->ready["ct"] = false; $this->ready["t"] = false; $this->currentMap->setStatus(Map::STATUS_WU_1_SIDE, true); @@ -3597,14 +3647,24 @@ public function adminGoBackRounds($round) } if ($status && $status !== $this->getStatus()) { - if ($status < $this->getStatus() || $status > $this->getStatus()) { - $this->addLog("Swapping teams to match rolled‑back round."); - $this->swapSides(); - } $this->setStatus($status, true); $this->currentMap->setStatus($statusMap, true); } + $expectedTeamASide = strtolower($row['side_a']); + $expectedTeamBSide = strtolower($row['side_b']); + + $currentTeamASide = $this->side['team_a']; + $currentTeamBSide = $this->side['team_b']; + + if ( + $expectedTeamASide !== $currentTeamASide || + $expectedTeamBSide !== $currentTeamBSide + ) { + $this->addLog("Swapping teams to match rolled-back round sides: team_a ($currentTeamASide → $expectedTeamASide), team_b ($currentTeamBSide → $expectedTeamBSide)"); + $this->swapSides(); + } + $this->say("Round restored — going LIVE!"); mysqli_query( Application::getInstance()->db, From 22fe608300ab37ecb18761f7a5a6e8f9f1e9d62b Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Sun, 3 Aug 2025 19:00:13 +0300 Subject: [PATCH 5/6] fix "Stop to Warmup" --- src/eBot/Match/Match.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index 0663cd1..9ff6df5 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -34,6 +34,15 @@ class Match implements + + + + + + + + + @@ -3010,14 +3019,7 @@ private function stopMatch() mysqli_query(Application::getInstance()->db, "DELETE FROM round WHERE round_id >= 1 AND map_id='" . $this->currentMap->getMapId() . "'"); mysqli_query(Application::getInstance()->db, "DELETE FROM round_summary WHERE round_id >= 1 AND map_id='" . $this->currentMap->getMapId() . "'"); } else { - // Getting file to restore - $data = $this->rcon->send("mp_backup_round_file_last"); - if (preg_match('!"mp_backup_round_file_last" = "(?[a-zA-Z0-9\-_\.]+)"!', $data, $match)) { - $this->backupFile = $match["backup"]; - } else { - $this->addLog("Backup file not found, simulating one."); - $this->backupFile = "ebot_" . $this->match_id . "_round" . sprintf("%02s", $this->getNbRound()) . ".txt"; - } + $this->backupFile = "ebot_" . $this->match_id . "_round" . sprintf("%02s", ($this->getNbRound() - 1)) . ".txt"; $this->addLog("Backup file: '" . $this->backupFile . "'."); @@ -3552,6 +3554,7 @@ public function adminStopBack() $this->stop["t"] = true; $this->stopMatch(); + $this->executeWarmupConfig(); return true; } From 91731f9ea5fa43cb7db79de5299181b02e4a078b Mon Sep 17 00:00:00 2001 From: AHDR3 Date: Sun, 3 Aug 2025 20:33:25 +0300 Subject: [PATCH 6/6] feat: add "Ready on Halftime" option (by default false) --- src/eBot/Match/Match.php | 67 +++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/src/eBot/Match/Match.php b/src/eBot/Match/Match.php index 9ff6df5..9339c1c 100644 --- a/src/eBot/Match/Match.php +++ b/src/eBot/Match/Match.php @@ -43,6 +43,32 @@ class Match implements + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113,6 +139,7 @@ class Match implements private $config_switch_auto = false; private $config_kniferound = false; private $config_streamer = false; + private $config_ready_on_halftime = false; private $streamerReady = false; private $rules; private $maxRound = 15; @@ -285,6 +312,7 @@ public function __construct($match_id, $server_ip, $rcon) $this->ot_maxround = $this->matchData["overtime_max_round"]; } $this->config_streamer = $this->matchData["config_streamer"]; + $this->config_ready_on_halftime = $this->matchData["config_ready_on_halftime"]; $this->maxRound = $this->matchData["max_round"]; $this->oldMaxround = $this->maxRound; @@ -1990,12 +2018,15 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) $this->resetSpecialSituation(); if ($this->getStatus() == self::STATUS_FIRST_SIDE) { if ($this->score["team_a"] + $this->score["team_b"] == $this->maxRound) { - $this->swapSides(); $this->setStatus(self::STATUS_WU_2_SIDE, true); $this->currentMap->setStatus(Map::STATUS_WU_2_SIDE, true); + $this->swapSides(); $this->saveScore(); - $this->rcon->send("mp_halftime_pausetimer 1"); + + if (!$this->config_ready_on_halftime) { + $this->adminForceStart(true); + } } } else if ($this->getStatus() == self::STATUS_SECOND_SIDE) { if (($this->score["team_a"] + $this->score["team_b"] == $this->maxRound * 2) || ($this->score["team_a"] > $this->maxRound && !$this->config_full_score) || ($this->score["team_b"] > $this->maxRound && !$this->config_full_score)) { @@ -2009,9 +2040,10 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) $this->addLog("Going to overtime!"); $this->say("Going to overtime!"); $this->currentMap->setNbMaxRound($this->ot_maxround); - //$this->rcon->send("mp_do_warmup_period 1; mp_warmuptime 30; mp_warmup_pausetimer 1"); - //$this->rcon->send("mp_restartgame 1"); - //$this->sendTeamNames(); + + if (!$this->config_ready_on_halftime) { + $this->adminForceStart(true); + } } else { $this->currentMap->setStatus(Map::STATUS_MAP_ENDED, true); @@ -2028,11 +2060,11 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) $this->currentMap->setStatus(Map::STATUS_WU_OT_2_SIDE, true); $this->saveScore(); $this->swapSides(); - //$this->sendTeamNames(); - // Not needed anymore with last updates - // $this->rcon->send("mp_restartgame 1"); - $this->rcon->send("mp_halftime_pausetimer 1"); + + if (!$this->config_ready_on_halftime) { + $this->adminForceStart(true); + } } } else if ($this->getStatus() == self::STATUS_OT_SECOND_SIDE) { $scoreToReach = $this->oldMaxround * 2 + $this->ot_maxround * 2 + ($this->ot_maxround * 2 * ($this->nbOT - 1)); @@ -2048,8 +2080,11 @@ private function processRoundScored(\eBot\Message\Type\RoundScored $message) $this->currentMap->setNbMaxRound($this->ot_maxround); $this->nbOT++; $this->addLog("Going to overtime!"); - //$this->rcon->send("mp_do_warmup_period 1; mp_warmuptime 30; mp_warmup_pausetimer 1"); - //$this->rcon->send("mp_restartgame 1"); + + + if (!$this->config_ready_on_halftime) { + $this->adminForceStart(true); + } } else { $this->currentMap->setStatus(Map::STATUS_MAP_ENDED, true); @@ -3482,12 +3517,14 @@ public function adminForceKnifeEnd() } } - public function adminForceStart() + public function adminForceStart($no_logs = false) { if ($this->isWarmupRound()) { - $this->addLog("The match start has been forced by the admin."); - $this->addMatchLog("The match start has been forced by the admin."); - $this->say("The match start has been forced by the admin.", "red"); + if (!$no_logs) { + $this->addLog("The match start has been forced by the admin."); + $this->addMatchLog("The match start has been forced by the admin."); + $this->say("The match start has been forced by the admin.", "red"); + } $this->ready["ct"] = true; $this->ready["t"] = true;