From 22ce0347bda76984a68e429a20d55c64e70a4b59 Mon Sep 17 00:00:00 2001 From: beu Date: Thu, 13 Aug 2026 15:31:04 +0200 Subject: [PATCH] add Pick And Ban draft plugin --- MatchManagerSuite/MatchManagerPickAndBan.php | 819 +++++++++++++++++++ 1 file changed, 819 insertions(+) create mode 100644 MatchManagerSuite/MatchManagerPickAndBan.php diff --git a/MatchManagerSuite/MatchManagerPickAndBan.php b/MatchManagerSuite/MatchManagerPickAndBan.php new file mode 100644 index 0000000..d523f39 --- /dev/null +++ b/MatchManagerSuite/MatchManagerPickAndBan.php @@ -0,0 +1,819 @@ +maniaControl->getChat()->sendErrorToAdmins('MatchManager Core is required to use MatchManagerPickAndBan plugin. Install it and restart Maniacontrol'); + Logger::logError('MatchManager Core is required to use MatchManagerPickAndBan plugin. Install it and restart Maniacontrol'); + return false; +} + +/** + * MatchManager Pick and Ban + * + * @author Beu + * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 + */ +class MatchManagerPickAndBan implements ManialinkPageAnswerListener, CallbackListener, TimerListener, Plugin { + /* + * Constants + */ + const PLUGIN_ID = 223; + const PLUGIN_VERSION = 0.1; + const PLUGIN_NAME = 'MatchManager Pick & Ban'; + const PLUGIN_AUTHOR = 'Beu'; + + const LOG_PREFIX = '[MatchManagerPickAndBan] '; + + // Settings + const SETTING_ENABLE = 'Enable pick & ban'; + const SETTING_STEPCONFIG = 'Step config'; + const SETTING_STEPDURATION = 'Step duration'; + + // Other MatchManager plugin + const MATCHMANAGERCORE_PLUGIN = 'MatchManagerSuite\MatchManagerCore'; + const MATCHMANAGERADMINUI_PLUGIN = 'MatchManagerSuite\MatchManagerAdminUI'; + + // Steps + const ML_STEP_OPENSETTINGS = 'MatchManagerSuite\MatchManagerPickAndBan.OpenSettings'; + const ML_STEP_MAPCHOOSENREGEX = '/^MatchManagerPickAndBan_MapChoosen:.*/'; + + /* + * Private properties + */ + private ManiaControl $maniaControl; + private MatchManagerCore $MatchManagerCore; + private MatchManagerPickAndBan_State $state; + + /** + * @param \ManiaControl\ManiaControl $maniaControl + * @see \ManiaControl\Plugins\Plugin::prepare() + */ + public static function prepare(ManiaControl $maniaControl) { + } + + /** + * @see \ManiaControl\Plugins\Plugin::getId() + */ + public static function getId() { + return self::PLUGIN_ID; + } + + /** + * @see \ManiaControl\Plugins\Plugin::getName() + */ + public static function getName() { + return self::PLUGIN_NAME; + } + + /** + * @see \ManiaControl\Plugins\Plugin::getVersion() + */ + public static function getVersion() { + return self::PLUGIN_VERSION; + } + + /** + * @see \ManiaControl\Plugins\Plugin::getAuthor() + */ + public static function getAuthor() { + return self::PLUGIN_AUTHOR; + } + + /** + * @see \ManiaControl\Plugins\Plugin::getDescription() + */ + public static function getDescription() { + return 'Pick & Ban plugin for MatchManager'; + } + + /** + * @param \ManiaControl\ManiaControl $maniaControl + * @return bool + * @see \ManiaControl\Plugins\Plugin::load() + */ + public function load(ManiaControl $maniaControl) { + $this->maniaControl = $maniaControl; + + $this->clearManialinks(); + + $this->MatchManagerCore = $this->maniaControl->getPluginManager()->getPlugin(self::MATCHMANAGERCORE_PLUGIN); + + if ($this->MatchManagerCore == Null) { + throw new \Exception('MatchManager Core is needed to use ' . self::PLUGIN_NAME); + } + + $this->state = new MatchManagerPickAndBan_State; + + // Settings + $this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_ENABLE, true, '', 0); + $this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_STEPCONFIG, '', '', 0); + $this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_STEPDURATION, 60, '', 0); + + // Callbacks + $this->maniaControl->getManialinkManager()->registerManialinkPageAnswerListener(self::ML_STEP_OPENSETTINGS, $this, 'handleStepOpenSettings'); + $this->maniaControl->getManialinkManager()->registerManialinkPageAnswerRegexListener(self::ML_STEP_MAPCHOOSENREGEX, $this, 'handleStepMapChoosen'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::AFTERINIT, $this, 'handleAfterInit'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(PluginManager::CB_PLUGIN_LOADED, $this, 'handlePluginLoaded'); + $this->maniaControl->getCallbackManager()->registerCallbackListener($this->MatchManagerCore::CB_MATCHMANAGER_ENDMATCH, $this, 'handleEndMatch'); + $this->maniaControl->getCallbackManager()->registerCallbackListener($this->MatchManagerCore::CB_MATCHMANAGER_STOPMATCH, $this, 'handleEndMatch'); + + $this->MatchManagerCore->addCanStartFunction($this, 'handleCanStartMatch'); + + $this->updateAdminUIMenuItems(); + + $this->sendStateToPlayers(); + } + + /** + * @see \ManiaControl\Plugins\Plugin::unload() + */ + public function unload() { + /** @var \MatchManagerSuite\MatchManagerAdminUI|null */ + $adminUIPlugin = $this->maniaControl->getPluginManager()->getPlugin(self::MATCHMANAGERADMINUI_PLUGIN); + if ($adminUIPlugin !== null) { + $adminUIPlugin->removeMenuItem(self::ML_STEP_OPENSETTINGS); + } + + $this->clearManialinks(); + } + + /** + * Custom log function to add prefix + * + * @param mixed $message + */ + private function log(mixed $message) { + Logger::log(self::LOG_PREFIX . $message); + } + + /** + * Custom logError function to add prefix + * + * @param mixed $message + */ + private function logError(mixed $message) { + Logger::logError(self::LOG_PREFIX . $message); + } + + /** + * handle Plugin Loaded + */ + public function handleAfterInit() { + $this->updateAdminUIMenuItems(); + } + + /** + * handle Plugin Loaded + * + * @param string $pluginClass + */ + public function handlePluginLoaded(string $pluginClass) { + if ($pluginClass === self::MATCHMANAGERADMINUI_PLUGIN) { + $this->updateAdminUIMenuItems(); + } + } + + /** + * Add items in AdminUI plugin + */ + public function updateAdminUIMenuItems() { + /** @var \MatchManagerSuite\MatchManagerAdminUI|null */ + $adminUIPlugin = $this->maniaControl->getPluginManager()->getPlugin(self::MATCHMANAGERADMINUI_PLUGIN); + if ($adminUIPlugin === null) return; + + $adminUIPlugin->removeMenuItem(self::ML_STEP_OPENSETTINGS); + + $menuItem = new \MatchManagerSuite\MatchManagerAdminUI_MenuItem(); + $menuItem->setActionId(self::ML_STEP_OPENSETTINGS) + ->setOrder(40) + ->setStyle('MeshModelerIcons') + ->setSubStyle('LayersFocused') + ->setDescription('Open Pick & Ban Settings'); + $adminUIPlugin->addMenuItem($menuItem); + } + + /** + * manage Step + */ + public function manageStep() { + // Do random pick + while ( + array_key_exists($this->state->stepIndex, $this->state->steps) && + $this->state->steps[$this->state->stepIndex]->login === "" + ) { + $availableMaps = array_filter($this->state->maps, function($map) { + return !$map->picked && !$map->banned; + }); + + $mapUid = array_rand($availableMaps); + + $this->state->maps[$mapUid]->banned = ($this->state->steps[$this->state->stepIndex]->type === MatchManagerPickAndBan_Step::STEP_BAN); + $this->state->maps[$mapUid]->picked = ($this->state->steps[$this->state->stepIndex]->type === MatchManagerPickAndBan_Step::STEP_PICK); + + $this->state->steps[$this->state->stepIndex]->mapUid = $mapUid; + $this->state->stepIndex++; + } + + $this->state->startTimestamp = time(); + $this->state->endTimestamp = time() + $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_STEPDURATION); + + $this->sendStateToPlayers(); + } + + /** + * handle timer + * @return void + */ + public function handleTimer() { + if ($this->state->status !== MatchManagerPickAndBan_State::STATUS_INPROGRESS) { + $this->maniaControl->getTimerManager()->unregisterTimerListening($this, 'handleTimer'); + return; + } + + if ($this->state->endTimestamp < time()) { + if (array_key_exists($this->state->stepIndex, $this->state->steps)) { + $this->state->steps[$this->state->stepIndex]->login = ""; + $this->manageStep(); + } else { + $this->log("Start match"); + $this->state->status = MatchManagerPickAndBan_State::STATUS_COMPLETED; + $this->MatchManagerCore->MatchStart(); + $this->clearManialinks(); + } + } + } + + /** + * handle Open settings manialink step + * + * @param array $callback + * @param Player $player + */ + public function handleStepOpenSettings(array $callback, Player $player) { + if ($player->authLevel <= 0) return; + + $pluginMenu = $this->maniaControl->getPluginManager()->getPluginMenu(); + if (defined("\ManiaControl\ManiaControl::ISTRACKMANIACONTROL")) { + $player->setCache($pluginMenu, PluginMenu::CACHE_SETTING_CLASS, "PluginMenu.Settings." . self::class); + } else { + $player->setCache($pluginMenu, PluginMenu::CACHE_SETTING_CLASS, self::class); + } + $this->maniaControl->getConfigurator()->showMenu($player, $pluginMenu); + } + + /** + * handle Map Choosen + * + * @param array $callback + * @param Player $player + */ + public function handleStepMapChoosen(array $callback, Player $player) { + if ($this->state->status !== MatchManagerPickAndBan_State::STATUS_INPROGRESS) return; + if (!array_key_exists($this->state->stepIndex, $this->state->steps)) return; + + if ($this->state->steps[$this->state->stepIndex]->login !== $player->login) return; + + $step = $callback[1][2]; + $stepArray = explode(':', $step); + + if (count($stepArray) !== 2) return; + + $mapUid = $stepArray[1]; + + if (!array_key_exists($mapUid, $this->state->maps)) return; + + $this->state->maps[$mapUid]->banned = ($this->state->steps[$this->state->stepIndex]->type === MatchManagerPickAndBan_Step::STEP_BAN); + $this->state->maps[$mapUid]->picked = ($this->state->steps[$this->state->stepIndex]->type === MatchManagerPickAndBan_Step::STEP_PICK); + + $this->state->steps[$this->state->stepIndex]->mapUid = $mapUid; + $this->state->stepIndex++; + + $this->manageStep(); + } + + /** + * handle MatchManagerCore Can Start Match callback + */ + public function handleCanStartMatch() { + if (!$this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_ENABLE)) return true; + + if ($this->state->status === MatchManagerPickAndBan_State::STATUS_COMPLETED) { + $maplist = []; + foreach ($this->state->steps as $step) { + if ($step->type !== MatchManagerPickAndBan_Step::STEP_PICK) continue; + if (!array_key_exists($step->mapUid, $this->state->maps)) { + $this->logError("Picked map doesn't exist in map list, should never happen."); + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "Picked map doesn't exist in map list, should never happen."); + + $this->state = new MatchManagerPickAndBan_State; + return false; + } + + + $maplist[] = $this->state->maps[$step->mapUid]->filePath; + } + + $setting = $this->maniaControl->getSettingManager()->getSettingObject($this->MatchManagerCore, $this->MatchManagerCore::SETTING_MODE_MAPS); + $setting->value = implode(',', $maplist); + + $this->log('Starting match with maps "'. $setting->value .'"'); + + return true; + } + + if ($this->state->status === MatchManagerPickAndBan_State::STATUS_INPROGRESS) { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "Pick and Ban in progress"); + return false; + } + + $stepConfig = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_STEPCONFIG); + if ($stepConfig === '') { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "'Step config' setting must be set"); + return false; + } + + if ($this->maniaControl->getSettingManager()->getSettingValue($this->MatchManagerCore, $this->MatchManagerCore::SETTING_MATCH_SETTINGS_MODE) !== 'All from the plugin') { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "Setting mode must be in 'All from the plugin' to use Pick & Ban"); + return false; + } + + $mapFiles = $this->maniaControl->getSettingManager()->getSettingValue($this->MatchManagerCore, $this->MatchManagerCore::SETTING_MODE_MAPS); + if ($mapFiles === '') { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "Maps must be defined to use Pick & Ban"); + return false; + } + + if (count(explode(',', $mapFiles)) < count(explode(',', $stepConfig))) { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . "You must have at least same number of maps than steps"); + return false; + } + + $this->log('Building Pick & Ban config'); + + $this->state = new MatchManagerPickAndBan_State; + $this->state->status = MatchManagerPickAndBan_State::STATUS_INPROGRESS; + $this->state->originalMapList = $mapFiles; + + // build map config + $maps = []; + foreach (explode(',', $mapFiles) as $mapPath) { + try { + $mapInfo = $this->maniaControl->getClient()->getMapInfo($mapPath); + + $mapObject = new MatchManagerPickAndBan_Map(); + $mapObject->mapUid = $mapInfo->uId; + $mapObject->filePath = $mapPath; + $mapObject->name = $mapInfo->name; + $maps[$mapInfo->uId] = $mapObject; + + } catch (Exception $e) { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . 'Error with the Map to play "' . $mapPath . '": ' . $e->getMessage()); + return false; + } + } + + uasort($maps, function ($a, $b) { + return strnatcasecmp($a->name, $b->name); + }); + + $this->state->maps = $maps; + $this->log(count($this->state->maps) . ' maps loaded for Pick & Ban'); + + // build steps + $steps = []; + foreach (explode(',', $stepConfig) as $index => $stepRaw) { + $stepArray = explode(':', $stepRaw); + if (count($stepArray) !== 2) { + $this->maniaControl->getChat()->sendErrorToAdmins($this->MatchManagerCore->getChatPrefix() . 'Invalid step ' . $index . 'config: ' . $stepRaw); + return false; + } + + $step = new MatchManagerPickAndBan_Step; + + if ($stepArray[0] === 'p') { + $step->type = MatchManagerPickAndBan_Step::STEP_PICK; + } else if ($stepArray[0] === 'b') { + $step->type = MatchManagerPickAndBan_Step::STEP_BAN; + } + + if ($stepArray[1] === 'r') { + $step->login = ""; + } else { + $step->login = $stepArray[1]; + } + $steps[] = $step; + } + $this->state->steps = $steps; + $this->log(count($this->state->steps) . ' steps loaded for Pick & Ban'); + + $this->maniaControl->getChat()->sendSuccess('Starting Pick & Ban phase'); + $this->log('Starting Pick & Ban phase'); + $this->sendManialink(); + $this->manageStep(); + + $this->maniaControl->getTimerManager()->registerTimerListening($this, 'handleTimer', 1000); + + return false; + } + + public function handleEndMatch() { + if ($this->state->status === MatchManagerPickAndBan_State::STATUS_COMPLETED) { + $setting = $this->maniaControl->getSettingManager()->getSettingObject($this->MatchManagerCore, $this->MatchManagerCore::SETTING_MODE_MAPS); + $setting->value = $this->state->originalMapList; + $this->state = new MatchManagerPickAndBan_State; + } + } + + private function sendManialink(): void { + $manialink = ' + + +