From ea877c9cae5b204263996f45329fea3c9eb759cb Mon Sep 17 00:00:00 2001 From: beu Date: Thu, 27 Mar 2025 15:33:58 +0100 Subject: [PATCH] add eCM plugin --- .../MatchManagerECircuitMania.php | 338 ++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 MatchManagerSuite/MatchManagerECircuitMania.php diff --git a/MatchManagerSuite/MatchManagerECircuitMania.php b/MatchManagerSuite/MatchManagerECircuitMania.php new file mode 100644 index 0000000..2a9de30 --- /dev/null +++ b/MatchManagerSuite/MatchManagerECircuitMania.php @@ -0,0 +1,338 @@ +maniaControl->getChat()->sendErrorToAdmins('MatchManager Core is required to use MatchManagerECircuitMania plugin. Install it and restart Maniacontrol'); + Logger::logError('MatchManager Core is required to use MatchManagerECircuitMania plugin. Install it and restart Maniacontrol'); + return false; +} + +/** + * MatchManagerECircuitMania + * + * @author Beu + * @license http://www.gnu.org/licenses/ GNU General Public License, Version 3 + */ +class MatchManagerECircuitMania implements CallbackListener, ManialinkPageAnswerListener, TimerListener, Plugin { + /* + * Constants + */ + const PLUGIN_ID = 213; + const PLUGIN_VERSION = 1.0; + const PLUGIN_NAME = 'MatchManager eCircuitMania'; + const PLUGIN_AUTHOR = 'Beu'; + + const MATCHMANAGERCORE_PLUGIN = 'MatchManagerSuite\MatchManagerCore'; + const MATCHMANAGERADMINUI_PLUGIN = 'MatchManagerSuite\MatchManagerAdminUI'; + + const ML_ACTION_OPENSETTINGS = 'MatchManagerSuite\MatchManagerECircuitMania.OpenSettings'; + + const SETTING_URL = 'API URL'; + const SETTING_MATCH_API_KEY = 'Match API Key'; + const SETTING_WITHMATCHMANAGER = 'Only send data when a Match Manager match is running'; + + const CB_STARTMAP = 'Maniaplanet.StartMap_Start'; + + /* + * Private properties + */ + private ManiaControl $maniaControl; + private \MatchManagerSuite\MatchManagerCore $MatchManagerCore; + private int $trackNum = 0; + private int $roundNum = 0; + + /** + * @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 "Plugin to send match data to eCM"; + } + + /** + * @see \ManiaControl\Plugins\Plugin::load() + */ + public function load(ManiaControl $maniaControl) { + $this->maniaControl = $maniaControl; + + if ($this->maniaControl->getPluginManager()->getSavedPluginStatus(self::MATCHMANAGERCORE_PLUGIN)) { + $this->maniaControl->getTimerManager()->registerOneTimeListening($this, function () { + $this->MatchManagerCore = $this->maniaControl->getPluginManager()->getPlugin(self::MATCHMANAGERCORE_PLUGIN); + if ($this->MatchManagerCore === null) { + $this->maniaControl->getChat()->sendErrorToAdmins('MatchManager Core is needed to use ' . self::PLUGIN_NAME . ' plugin.'); + $this->maniaControl->getPluginManager()->deactivatePlugin((get_class())); + return; + } + + $this->maniaControl->getCallbackManager()->registerScriptCallbackListener(self::CB_STARTMAP, $this, 'handleStartMap'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::MP_STARTROUNDSTART, $this, 'handleStartRound'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::TM_ONWAYPOINT, $this, 'handleOnWaypoint'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::TM_SCORES, $this, 'handleTrackmaniaScores'); + $this->maniaControl->getCallbackManager()->registerCallbackListener(Callbacks::MP_ENDROUNDEND, $this, 'handleEndRound'); + + $this->updateAdminUIMenuItems(); + }, 1); + } else { + throw new \Exception('MatchManager Core is needed to use ' . self::PLUGIN_NAME); + } + + $this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_MATCH_API_KEY, "", "", 5); + $this->maniaControl->getSettingManager()->initSetting($this, self::SETTING_URL, "https://us-central1-fantasy-trackmania.cloudfunctions.net", "", 10); + + $this->maniaControl->getManialinkManager()->registerManialinkPageAnswerListener(self::ML_ACTION_OPENSETTINGS, $this, 'handleActionOpenSettings'); + } + + /** + * 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_ACTION_OPENSETTINGS); + + $menuItem = new \MatchManagerSuite\MatchManagerAdminUI_MenuItem(); + $menuItem->setActionId(self::ML_ACTION_OPENSETTINGS) + ->setOrder(60) + ->setImageUrl('https://ecircuitmania.com/favicon.png') + ->setSize(4.) + ->setDescription('Open eCM Settings'); + $adminUIPlugin->addMenuItem($menuItem); + } + + /** + * handle Open settings manialink action + * + * @param array $callback + * @param Player $player + */ + public function handleActionOpenSettings(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); + } + + public function handleStartMap(array $structure) { + if (!$this->MatchManagerCore->getMatchIsRunning()) return; + $data = json_decode($structure[1][0]); + + $this->trackNum = $data->valid; + $this->roundNum = 0; + } + + public function handleStartRound(StartEndStructure $structure) { + if (!$this->MatchManagerCore->getMatchIsRunning()) return; + $this->roundNum = $structure->getValidRoundCount(); + } + + public function handleOnWaypoint(OnWayPointEventStructure $structure) { + if (!$this->MatchManagerCore->getMatchIsRunning()) return; + if (!$structure->getIsEndRace()) return; + if ($this->roundNum <= 0) return; // probably during the WU + + $mapuid = ""; + $map = $this->maniaControl->getMapManager()->getCurrentMap(); + if ($map !== null) { + $mapuid = $map->uid; + } + + $payload = json_encode([ + "ubisoftUid" => $structure->getPlayer()->getAccountId(), + "finishTime" => $structure->getRaceTime(), + "mapId" => $mapuid, + "trackNum" => $this->trackNum, + "roundNum" => $this->roundNum + ]); + + $request = $this->getAPIRequest("/match-addRoundTime"); + if ($request !== null) { + $request->setContent($payload)->setCallable(function ($content, $error, $headers) use ($payload) { + if ($content !== "Created" || $error !== null) { + Logger::logWarning("Error on the 'addRoundTime' request. answer: " . $content . " / error: " . $error . " / payload: " . $payload); + } + })->postData(); + } + } + + public function handleTrackmaniaScores(OnScoresStructure $structure) { + if (!$this->MatchManagerCore->getMatchIsRunning()) return; + if ($structure->getSection() !== "PreEndRound") return; + + $scores = []; + foreach ($structure->getPlayerScores() as $playerscore) { + if ($playerscore->getMatchPoints() <= -2000) continue; + $scores[] = $playerscore; + } + + /** @var \ManiaControl\Callbacks\Structures\TrackMania\Models\PlayerScore[] $scores */ + usort($scores, function ($a, $b) { + if ($a->getPrevRaceTime() === -1 && $b->getPrevRaceTime() === -1) { + return $b->getRoundPoints() - $a->getRoundPoints(); + } + + if ($a->getPrevRaceTime() === -1) return 1; + if ($b->getPrevRaceTime() === -1) return -1; + + if ($a->getPrevRaceTime() === $b->getPrevRaceTime()) { + $acheckpoints = $a->getPrevRaceCheckpoints(); + $bcheckpoints = $b->getPrevRaceCheckpoints(); + + while (end($acheckpoints) === end($bcheckpoints)) { + if (count($acheckpoints) === 0 || count($bcheckpoints) === 0) return 0; + array_pop($acheckpoints); + array_pop($bcheckpoints); + } + return end($acheckpoints) - end($bcheckpoints); + } + return $a->getPrevRaceTime() - $b->getPrevRaceTime(); + }); + + $players = []; + $rank = 1; + foreach ($scores as $playerscore) { + /** @var \ManiaControl\Callbacks\Structures\TrackMania\Models\PlayerScore $playerscore */ + if ($playerscore->getPlayer()->isSpectator) continue; + + $players[] = [ + "ubisoftUid" => $playerscore->getPlayer()->getAccountId(), + "finishTime" => $playerscore->getPrevRaceTime(), + "position" => $rank + ]; + $rank++; + } + + $mapuid = ""; + $map = $this->maniaControl->getMapManager()->getCurrentMap(); + if ($map !== null) { + $mapuid = $map->uid; + } + + $payload = json_encode([ + "players" => $players, + "mapId" => $mapuid, + "trackNum" => $this->trackNum, + "roundNum" => $this->roundNum + ]); + + $request = $this->getAPIRequest("/match-addRound"); + if ($request !== null) { + $request->setContent($payload)->setCallable(function ($content, $error, $headers) use ($payload) { + if ($content !== "Created" || $error !== null) { + Logger::logWarning("Error on the 'addRound' request. answer: " . $content . " / error: " . $error . " / payload: " . $payload); + } + })->postData(); + } + } + + public function handleEndRound(StartEndStructure $structure) { + if (!$this->MatchManagerCore->getMatchIsRunning()) return; + $json = $structure->getPlainJsonObject(); + if (!property_exists($json, 'isvalid') || $json->isvalid) return; + + $payload = json_encode([ + "trackNum" => $this->trackNum, + "roundNum" => $this->roundNum + ]); + + $request = $this->getAPIRequest("/match-removeRound"); + if ($request !== null) { + $request->setContent($payload)->setCallable(function ($content, $error, $headers) use ($payload) { + if ($content !== "Created" || $error !== null) { + Logger::logWarning("Error on the 'removeRound' request. answer: " . $content . " / error: " . $error . " / payload: " . $payload); + } + })->postData(); + } + } + + private function getAPIRequest(string $url) : ?AsyncHttpRequest { + $baseurl = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_URL); + $matchapikey = $this->maniaControl->getSettingManager()->getSettingValue($this, self::SETTING_MATCH_API_KEY); + + $array = explode("_", $matchapikey); + if (count($array) !== 2) return null; + + $matchid = $array[0]; + $token = $array[1]; + + if ($baseurl === "") return null; + if ($token === "") return null; + if ($matchid === "") return null; + + $asyncHttpRequest = new AsyncHttpRequest($this->maniaControl, $baseurl . $url . "?matchId=" . $matchid); + $asyncHttpRequest->setContentType("application/json"); + $asyncHttpRequest->setHeaders(["Authorization: " . $token]); + + return $asyncHttpRequest; + } + + /** + * Unload the plugin and its Resources + */ + public function unload() {} +} \ No newline at end of file