diff --git a/GameEngine/Artifacts.php b/GameEngine/Artifacts.php index ecbf6d0c..6e4a8c9d 100644 --- a/GameEngine/Artifacts.php +++ b/GameEngine/Artifacts.php @@ -277,39 +277,58 @@ class Artifacts global $database; //Register the Natars account, the Natars' password is the same as the MH's one + //NOTE: pre-existing anomaly, left untouched - this call passes 8 args + //(..., self::NATARS_DESC, self::NATARS_DESC2) but register() only + //accepts up to $desc (7 params), so NATARS_DESC2 is silently dropped + //by PHP. Not related to the crash fixed below. $password = $database->getUserField(5, 'password', 0); $database->register(TRIBE5, $password, self::NATARS_EMAIL, self::NATARS_TRIBE, null, self::NATARS_UID, self::NATARS_DESC, self::NATARS_DESC2); - - //Convert from coordinates to village IDs - $possibleWids = $database->getVilWrefs(self::NATARS_CAPITAL_COORDINATES); - - //Check if the villages aren't already taken - $wid = $database->getFreeVillage($possibleWids); - //Generate the Natars' capital - // - // The capital starts with the World Wonder and the infrastructure defined - // in NATARS_CAPITAL_BUILDINGS. The data is passed in the same format used - // for World Wonder villages: [0] = column names, [1][] = values. - $capitalBuildings = []; - $capitalBuildings[0] = array_keys(self::NATARS_CAPITAL_BUILDINGS); - $capitalBuildings[1][] = array_values(self::NATARS_CAPITAL_BUILDINGS); + //Idempotency guard: this whole method isn't wrapped in a transaction, + //so a previous run may have already built the Natars' capital (e.g. + //the register() call above went through, then the process died/timed + //out before finishing). Reuse the existing capital instead of trying + //to generate a second one, which would either collide or leave a + //stray village behind. + $existingCapital = $database->getVrefCapital(self::NATARS_UID); + if ($existingCapital) { + $wid = $existingCapital['wref']; + } else { + //Convert from coordinates to village IDs + $possibleWids = $database->getVilWrefs(self::NATARS_CAPITAL_COORDINATES); - // The capital army, using the same format as World Wonder villages: - // [0] = column names, [1][] = values. - $capitalUnits = []; - $capitalUnits[0] = array_keys(self::NATARS_CAPITAL_UNITS); - $capitalUnits[1][] = array_values(self::NATARS_CAPITAL_UNITS); + //Check if the villages aren't already taken + $wid = $database->getFreeVillage($possibleWids); - $wid = $database->generateVillages( - [['wid' => $wid, 'mode' => 2, 'type' => 3, 'kid' => 0, 'capital' => 1, 'pop' => 1163, 'name' => null, 'natar' => 0]], - self::NATARS_UID, - TRIBE5, - $capitalUnits, - $capitalBuildings - ); + //Generate the Natars' capital + // + // The capital starts with the World Wonder and the infrastructure defined + // in NATARS_CAPITAL_BUILDINGS. The data is passed in the same format used + // for World Wonder villages: [0] = column names, [1][] = values. + $capitalBuildings = []; + $capitalBuildings[0] = array_keys(self::NATARS_CAPITAL_BUILDINGS); + $capitalBuildings[1][] = array_values(self::NATARS_CAPITAL_BUILDINGS); + + // The capital army, using the same format as World Wonder villages: + // [0] = column names, [1][] = values. + $capitalUnits = []; + $capitalUnits[0] = array_keys(self::NATARS_CAPITAL_UNITS); + $capitalUnits[1][] = array_values(self::NATARS_CAPITAL_UNITS); + + $wid = $database->generateVillages( + [['wid' => $wid, 'mode' => 2, 'type' => 3, 'kid' => 0, 'capital' => 1, 'pop' => 1163, 'name' => null, 'natar' => 0]], + self::NATARS_UID, + TRIBE5, + $capitalUnits, + $capitalBuildings + ); + } //Scouts all players + //NOTE: if this retry path is taken (capital already existed), players + //get scouted again - harmless duplicate espionage reports, not data + //corruption. Only reachable if a prior run died after building the + //capital but before finishing artifact spawning below. $this->scoutAllPlayers($wid); //Add artifacts diff --git a/GameEngine/Database/DatabaseUserQueries.php b/GameEngine/Database/DatabaseUserQueries.php index 682ec037..2ced213f 100644 --- a/GameEngine/Database/DatabaseUserQueries.php +++ b/GameEngine/Database/DatabaseUserQueries.php @@ -31,6 +31,27 @@ trait DatabaseUserQueries { $time = time(); $access = USER; + // Idempotency guard for pre-assigned IDs (system accounts such as + // Natars, uid=3, called from Artifacts::createNatars()). Regular + // player registration always passes uid=0, so this branch never + // runs for it. It exists because createNatars() is not wrapped in + // a transaction: if it registers the Natars row but then dies + // further down (village/scout/artifact generation), every retry + // used to hit this INSERT again and throw an uncaught + // "Duplicate entry '3' for key PRIMARY" mysqli_sql_exception, + // fatal on every cron tick and page-load fallback. Detect the + // existing row and hand its id back instead of re-inserting. + if ($uid > 0) { + $existing = $this->dblink->prepare("SELECT id FROM `".TB_PREFIX."users` WHERE id = ? LIMIT 1"); + $existing->bind_param("i", $uid); + $existing->execute(); + $existingRow = $existing->get_result()->fetch_assoc(); + $existing->close(); + if ($existingRow) { + return (int)$existingRow['id']; + } + } + // New accounts inherit the configured server language. // English remains the safe fallback if SERVER_LANG is missing or invalid. $serverLang = defined('SERVER_LANG') @@ -902,4 +923,4 @@ trait DatabaseUserQueries { } return empty($errors) ? true : $errors; } -} +} \ No newline at end of file diff --git a/install/data/constant_format.tpl b/install/data/constant_format.tpl index 75430e2d..392bfd95 100644 --- a/install/data/constant_format.tpl +++ b/install/data/constant_format.tpl @@ -120,7 +120,7 @@ define("START_TIME", "%SSTARTTIME%"); // SERVER_LANG is the DEFAULT language of the server (chosen at install / in // the admin "Server Settings"). LANG is the EFFECTIVE display language. // -// Per-user language (issue #166): if the logged-in player picked a language +// Per-user language : if the logged-in player picked a language // in their profile preferences (stored in users.lang and mirrored into // $_SESSION['lang']), LANG becomes that language; otherwise LANG falls back // to SERVER_LANG.