From 3804498ad980f737b5f569eca45298698d77289c Mon Sep 17 00:00:00 2001 From: Catalin Novgorodschi <1140613+Shadowss@users.noreply.github.com> Date: Thu, 14 May 2026 15:05:32 +0300 Subject: [PATCH] Incremental Refactor Automation & Database (cache) Incremental Refactor Automation (starvation split in more methods) & Database (cache & checkAllianceEmbassiesStatus refactor), Fix a bug in Alliance.php (now you cannot kick alliance leader) --- GameEngine/Alliance.php | 231 ++++++------- GameEngine/Artifacts.php | 2 +- GameEngine/Automation.php | 402 +++++++++++++--------- GameEngine/Battle.php | 2 +- GameEngine/Building.php | 2 +- GameEngine/Chat.php | 2 +- GameEngine/Database.php | 677 +++++++++++++++----------------------- GameEngine/Form.php | 2 +- GameEngine/Generator.php | 2 +- GameEngine/Logging.php | 2 +- GameEngine/Market.php | 2 +- GameEngine/Profile.php | 2 +- version.php | 8 +- 13 files changed, 626 insertions(+), 710 deletions(-) diff --git a/GameEngine/Alliance.php b/GameEngine/Alliance.php index 2f92457e..6150a784 100755 --- a/GameEngine/Alliance.php +++ b/GameEngine/Alliance.php @@ -7,7 +7,7 @@ ## Version: 30.04.2026 ## ## Filename Alliance.php ## ## Developed by: Dzoki ## -## Refactor by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## URLs: https://travianz.org ## @@ -708,67 +708,50 @@ class Alliance { /***************************************** Function to kick a user from alliance + REFACTORIZAT 14.05.2026 - blochează kick la fondator *****************************************/ - private function kickAlliUser($post) { - global $database, $session, $form; + global $database, $session, $form; $targetUID = (int)($post['a_user'] ?? 0); + $UserData = $database->getUserArray($targetUID, 1); + $allyId = (int)$session->alliance; - // Obținem datele utilizatorului (folosit pentru username și verificări) - $UserData = $database->getUserArray($targetUID, 1); - - // ==================== VERIFICĂRI DE PERMISIUNI ȘI VALIDĂRI ==================== + // ==================== VERIFICĂRI ==================== if ($this->userPermArray['opt2'] == 0) { $form->addError("perm", NO_PERMISSION); - } elseif ($database->getUserField($targetUID, "alliance", 0) != $session->alliance) { - $form->addError("perm", USER_NOT_IN_YOUR_ALLY); - } elseif ($UserData['id'] == $session->uid) { - $form->addError("perm", CANT_EDIT_YOUR_PERMISSIONS); // mesajul original era CANT_EDIT_YOUR_PERMISSIONS - } else { - // ==================== EXECUTARE KICK ==================== - $kickedUsername = $database->getUserField($targetUID, "username", 0); - $database->updateUserField($targetUID, 'alliance', 0, 1); - $database->deleteAlliPermissions($targetUID); - - // !!! Comportament original - șterge alianța la fiecare kick !!! - $database->deleteAlliance($session->alliance); - - // Log notice în alianță - $notice = '' . - addslashes($kickedUsername) . - ' has been expelled from the alliance by ' . - addslashes($session->username) . - '.'; - $database->insertAlliNotice($session->alliance, $notice); - - // ==================== PROMOVARE NOU LIDER (dacă a fost eliminat liderul) ==================== - if ($database->isAllianceOwner($UserData['id']) == $session->alliance) { - $newOwner = $database->getAllMember2($session->alliance); - $newLeader = $newOwner['id']; - - // Dăm permisiuni complete noului lider - $database->updateAlliPermissions( - $newFounderID, - (int)$session->alliance, - 'Alliance Founder', - 1, 1, 1, 1, 1, 1, 1, 1 - ); - - // Actualizăm liderul alianței (SQL mai sigur) - $database->query( - "UPDATE " . TB_PREFIX . "alidata - SET leader = " . (int)$newLeader . " - WHERE id = " . (int)$session->alliance - ); - - Automation::updateMax($newLeader); - } - - // Mesaj de succes (comportament original) - $form->addError("perm", $kickedUsername . ALLY_USER_KICKED); + return; } + if ($database->getUserField($targetUID, "alliance", 0) != $allyId) { + $form->addError("perm", USER_NOT_IN_YOUR_ALLY); + return; + } + if ($UserData['id'] == $session->uid) { + $form->addError("perm", CANT_EDIT_YOUR_PERMISSIONS); + return; + } + // === NOU: nu poți da kick fondatorului === + if ($database->isAllianceOwner($targetUID) == $allyId) { + $form->addError("perm", "You cannot expel the alliance founder."); + return; + } + + // ==================== EXECUTARE KICK ==================== + $kickedUsername = $UserData['username']; + + $database->evictUserFromAlliance($targetUID); + $database->deleteAlliPermissions($targetUID); + + $notice = '' . + addslashes($kickedUsername) . + ' has been expelled from the alliance by ' . addslashes($session->username) . '.'; + $database->insertAlliNotice($allyId, $notice); + + // păstrăm comportamentul original + $database->deleteAlliance($allyId); + + $form->addError("perm", $kickedUsername . ALLY_USER_KICKED); } /***************************************** Function to set forum link @@ -816,94 +799,86 @@ class Alliance { /***************************************** Function to quit from alliance + REFACTORIZAT 14.05.2026 - folosește Database v7 *****************************************/ - private function quitally($post) { - global $database, $session, $form; + global $database, $session, $form; // ==================== VERIFICARE PAROLĂ ==================== if (!isset($post['pw']) || $post['pw'] === '') { $form->addError("pw", PW_EMPTY); - } elseif (!password_verify($post['pw'], $session->userinfo['password'])) { + return; + } + if (!password_verify($post['pw'], $session->userinfo['password'])) { $form->addError("pw", LOGIN_PW_ERROR); - } else { - // Parola este corectă → continuăm cu părăsirea alianței + return; + } - $isFounder = $session->alliance && $database->isAllianceOwner($session->uid) == $session->alliance; - $memberCount = $database->countAllianceMembers($session->alliance); + // Parola corectă → continuăm + $allyId = (int)$session->alliance; + $uid = (int)$session->uid; + $isFounder = $allyId && $database->isAllianceOwner($uid) == $allyId; + $memberCount = $database->countAllianceMembers($allyId); - // ==================== CAZ SPECIAL: LIDERUL PĂRĂSEȘTE ALIANȚA ==================== - if ($isFounder && $memberCount > 1) { - // Trebuie să alegem un nou fondator - if (!isset($post['new_founder'])) { - $form->addError("founder", 'Founder was not selected.'); - return; + // ==================== CAZ SPECIAL: LIDERUL PLEACĂ ==================== + if ($isFounder && $memberCount > 1) { + if (empty($post['new_founder'])) { + $form->addError("founder", 'Founder was not selected.'); + return; + } + $newFounderID = (int)$post['new_founder']; + + // Validăm că noul fondator e în alianță și nu ești tu + $members = $database->getAllMember($allyId); + $valid = false; + foreach ($members as $m) { + if ($m['id'] == $newFounderID && $newFounderID != $uid) { + $valid = true; + $newFounderName = $m['username']; + break; } - $newFounderID = (int)$post['new_founder']; - - // Validăm că noul fondator face parte din alianță - $members = $database->getAllMember($session->alliance); - $validMemberFound = false; - foreach ($members as $member) { - if ($member['id'] == $newFounderID) { - $validMemberFound = true; - break; - } - } - if (!$validMemberFound || $newFounderID == $session->uid) { - $form->addError("founder", 'Invalid founder.'); - return; - } - - // Dăm permisiuni complete noului lider - $database->updateAlliPermissions( - $newFounderID, - (int)$session->alliance, - 'Alliance Founder', - 1, 1, 1, 1, 1, 1, 1, 1 - ); - - // Actualizăm liderul alianței - $_SESSION['alliance_user'] = 0; - $database->query( - "UPDATE " . TB_PREFIX . "alidata - SET leader = " . $newFounderID . " - WHERE id = " . (int)$session->alliance - ); - - Automation::updateMax($newFounderID); - - // Trimitem mesaj în joc noului lider - $messageBody = "Hi!\n\n" . - "This is to inform you that the former leader of your alliance - " . - "" . - $database->escape($session->username) . - ", has decided to quit and elected you as his replacement. " . - "You now gain full access, administration and responsibilities to your alliance.\n\n" . - "Good luck!\n\nYours sincerely,\nServer Robot :)"; - $database->sendMessage( - $newFounderID, - 4, - 'You are now leader of your alliance', - $messageBody, - 0, 0, 0, 0, 0, - true - ); + } + if (!$valid) { + $form->addError("founder", 'Invalid founder.'); + return; } - // ==================== PĂRĂSIRE ALIANȚĂ (pentru toți membrii) ==================== - $database->updateUserField($session->uid, 'alliance', 0, 1); - $database->deleteAlliPermissions($session->uid); + // Mesaj specific pentru quit voluntar + $messageBody = "Hi!\n\n" . + "This is to inform you that the former leader of your alliance - " . + "" . + $database->escape($session->username) . + ", has decided to quit and elected you as his replacement. " . + "You now gain full access, administration and responsibilities to your alliance.\n\n" . + "Good luck!\n\nYours sincerely,\nServer Robot :)"; - // Log notice + ștergere alianță (comportament exact ca în original) - $database->deleteAlliance($session->alliance); - $notice = '' . - addslashes($session->username) . - ' has quit the alliance.'; - $database->insertAlliNotice($session->alliance, $notice); - header("Location: spieler.php?uid=" . $session->uid); - exit; + // Folosim metoda centralizată cu mesaj custom + $database->promoteNewAllianceLeader( + $allyId, + $newFounderID, + $uid, + $newFounderName, + ['username' => $session->username, 'id' => $uid], + $messageBody + ); + + $_SESSION['alliance_user'] = 0; } + + // ==================== PĂRĂSIRE ALIANȚĂ ==================== + $database->updateUserField($uid, 'alliance', 0, 1); + $database->deleteAlliPermissions($uid); + + // șterge alianța dacă e goală (comportament original) + $database->deleteAlliance($allyId); + + $notice = '' . + addslashes($session->username) . + ' has quit the alliance.'; + $database->insertAlliNotice($allyId, $notice); + + header("Location: spieler.php?uid=" . $uid); + exit; } /***************************************** diff --git a/GameEngine/Artifacts.php b/GameEngine/Artifacts.php index 4167334a..19ebdf2b 100644 --- a/GameEngine/Artifacts.php +++ b/GameEngine/Artifacts.php @@ -7,7 +7,7 @@ ## Version: 13.05.2026 ## ## Filename Artifacts.php ## ## Developed by: Dzoki ## -## Refactor by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## URLs: https://travianz.org ## diff --git a/GameEngine/Automation.php b/GameEngine/Automation.php index cfee998d..e2a5e8bc 100644 --- a/GameEngine/Automation.php +++ b/GameEngine/Automation.php @@ -61,6 +61,12 @@ class Automation { private $artifacts; + /** + * Cache pentru utilizatori pentru a reduce query-urile duplicate + * @var array + */ + private $userCache = []; + public function __construct() { //Classes initialization @@ -110,6 +116,19 @@ class Automation { $this->artefactOfTheFool(); } + /** + * Returneaza datele utilizatorului cu cache local + */ + private function getCachedUser($uid, $mode = 1) { + global $database; + $uid = (int)$uid; + if (!isset($this->userCache[$uid])) { + $this->userCache[$uid] = $database->getUserArray($uid, $mode); + } + return $this->userCache[$uid]; + } + + public function procResType($ref, $mode = 0) { //Capital or only 1 village left = cannot be destroyed return addslashes(empty($build = Building::procResType($ref)) && !$mode ? "Village can't be" : $build); @@ -872,6 +891,9 @@ class Automation { } private function sendunitsComplete() { + // PROCESARE ATACURI COMPLETE - functie critica, pastrata 100% compatibila + // Aceasta functie gestioneaza toate atacurile care ajung la destinatie + // Include: batalii, capcane, evaziune erou, distrugere cladiri, cuceriri global $bid19, $bid23, $bid34, $u99, $database, $battle, $technology, $units; $time = time(); @@ -919,16 +941,16 @@ class Automation { $DefenderWref = $data['to']; $NatarCapital = false; - $Attacker['id'] = $database->getUserArray($database->getVillageField($data['from'],"owner"), 1)["id"]; + $Attacker['id'] = $this->getCachedUser($database->getVillageField($data['from'],"owner"),1)['id']; $AttackerID = $Attacker['id']; - $owntribe = $database->getUserArray($database->getVillageField($data['from'],"owner"), 1)["tribe"]; - $ownally = $database->getUserArray($database->getVillageField($data['from'],"owner"), 1)["alliance"]; + $owntribe = $this->getCachedUser($database->getVillageField($data['from'],"owner"),1)['tribe']; + $ownally = $this->getCachedUser($database->getVillageField($data['from'],"owner"),1)['alliance']; $from = $database->getMInfo($data['from']); $fromF = $database->getVillage($data['from']); //It's a village if ($isoasis == 0){ - $DefenderUserData = $database->getUserArray($database->getVillageField($data['to'],"owner"), 1); + $DefenderUserData = $this->getCachedUser($database->getVillageField($data['to'],"owner"),1); $Defender['id'] = $DefenderUserData["id"]; $DefenderID = $Defender['id']; $targettribe = $DefenderUserData["tribe"]; @@ -1094,7 +1116,7 @@ class Automation { }else{ //It's an oasis - $DefenderUserData = $database->getUserArray($database->getOasisField($data['to'], "owner"), 1); + $DefenderUserData = $this->getCachedUser($database->getOasisField($data['to'], "owner"),1); $Defender['id'] = $DefenderUserData["id"]; $DefenderID = $Defender['id']; $targettribe = $DefenderUserData["tribe"]; @@ -3707,175 +3729,253 @@ class Automation { private function starvation() { global $database, $technology; - //Starvation is disabled during Easter/Holidays/Christmas + // Starvation este dezactivat in perioada de pace (sarbatori) if(PEACE) return; $time = time(); - //Update starvation in every village - $starvarray = $database->getProfileVillages(0, 7); - foreach($starvarray as $starv) $database->addStarvationData($starv['wref']); + // 1. Actualizeaza datele de starvation pentru toate satele + $this->starvationUpdateAllVillages(); - //Load villages with minus prod - $starvarray = []; - $starvarray = $database->getStarvation(); + // 2. Incarca satele cu productie negativa + $starvarray = $this->starvationGetVillagesWithDeficit(); + if (empty($starvarray)) return; - $vilIDs = []; - foreach ($starvarray as $starv) $vilIDs[] = $starv['wref']; + $vilIDs = array_column($starvarray, 'wref'); - //Cache + // 3. Pregateste cache-urile pentru a reduce query-urile + $this->starvationPrepareCaches($vilIDs); + + foreach ($starvarray as $starv) { + $this->starvationProcessVillage($starv, $time); + } + } + + /** + * Actualizeaza tabela de starvation pentru toate satele + */ + private function starvationUpdateAllVillages() { + global $database; + $starvarray = $database->getProfileVillages(0, 7); + foreach($starvarray as $starv) { + $database->addStarvationData($starv['wref']); + } + } + + /** + * Returneaza satele cu deficit de crop + */ + private function starvationGetVillagesWithDeficit() { + global $database; + return $database->getStarvation(); + } + + /** + * Pregateste cache-urile pentru trupe + */ + private function starvationPrepareCaches(array $vilIDs) { + global $database; $database->getEnforceVillage($vilIDs, 0); $database->getOasisEnforce($vilIDs, 2); $database->getOasisEnforce($vilIDs, 3); $database->getPrisoners($vilIDs, 1); $database->getMovement(3, $vilIDs, 0); $database->getMovement(4, $vilIDs, 1); + } - foreach ($starvarray as $starv) - { - $unitarrays = $this->getAllUnits($starv['wref']); + /** + * Proceseaza un singur sat pentru starvation + */ + private function starvationProcessVillage($starv, $time) { + global $database, $technology; - $upkeep = $starv['pop'] + $technology->getUpkeep($unitarrays, 0, $starv['wref']); - - $enforceArrays = $prisonerArrays = $unitArrays = $attackArrays = $allTroopsArray = $starvingTroops = $killedUnits = []; - - $enforceArrays = [$database->getOasisEnforce($starv['wref'], 2), - $database->getOasisEnforce($starv['wref'], 3), - $database->getEnforceVillage($starv['wref'], 2), - $database->getEnforceVillage($starv['wref'], 3)]; - - $prisonerArrays = [$database->getPrisoners($starv['wref'], 1)]; - - $unitArrays = ($database->getUnitsNumber($starv['wref'], 0) > 0) ? [[$database->getUnit($starv['wref'])]] : []; + $unitarrays = $this->getAllUnits($starv['wref']); + // Formula originala de upkeep - pastrata exact + $upkeep = $starv['pop'] + $technology->getUpkeep($unitarrays, 0, $starv['wref']); - $attackArrays = [$database->getMovement(3, $starv['wref'], 0), - $database->getMovement(4, $starv['wref'], 1)]; + $troopData = $this->starvationFindFirstTroopGroup($starv); + if (empty($troopData['troops'])) { + // Nu exista trupe, verifica doar resetarea + $this->starvationCheckReset($starv, $upkeep); + return; + } - $allTroopsArray = [$enforceArrays, $prisonerArrays, $unitArrays, $attackArrays]; + $starvingTroops = $troopData['troops']; + $type = $troopData['type']; + $subtype = $troopData['subtype']; - //Find the first not-empty array - foreach($allTroopsArray as $type => $allTroops) - { - if(!empty($allTroops)){ - foreach($allTroops as $subtype => $troops){ - if(!empty($troops)){ - $starvingTroops = reset($troops); - break 2; - } - } - } - } + $timedif = $time - $starv['starvupdate']; + $cropProd = $database->getCropProdstarv($starv['wref']) - $starv['starv']; + + if($cropProd < 0){ + // Calcul deficit - formula originala pastrata + $starvsec = (abs($cropProd) / 3600); + $difcrop = ($timedif * $starvsec); + $oldcrop = $database->getVillageField($starv['wref'], 'crop'); - //If the player has no troops, then skip the next instructions - if(empty($starvingTroops)) continue; - - //Counting - $timedif = $time - $starv['starvupdate']; - $cropProd = $database->getCropProdstarv($starv['wref']) - $starv['starv']; - if($cropProd < 0){ - $starvsec = (abs($cropProd) / 3600); - $difcrop = ($timedif * $starvsec); //crop eat up over time - $newcrop = 0; - $oldcrop = $database->getVillageField($starv['wref'], 'crop'); - - //If the grain is then tries to send all - if ($oldcrop > 100){ - $difcrop = $difcrop - $oldcrop; - if($difcrop < 0){ - $difcrop = 0; - $newcrop = $oldcrop - $difcrop; - $database->setVillageField($starv['wref'], 'crop', $newcrop); - } - } - - if($difcrop > 0 && $oldcrop <= 0){ - $tribe = $database->getUserField(($type == 2) ? $starv['owner'] : $database->getVillageField($starvingTroops['from'], "owner"), "tribe", 0); - $start = ($special = in_array($type, [1, 3])) ? 1 : ($tribe - 1) * 10 + 1; - $end = ($special) ? 10 : $tribe * 10 ; - $utype = ($special) ? 't' : 'u'; - $heroType = ($special) ? 't11' : 'hero'; - - $totalUnits = 0; - $counting = true; - while($difcrop > 0) - { - //S earch the highest troop - $maxcount = $maxtype = 0; - for($i = $start ; $i <= $end ; $i++) - { - $units = (isset($starvingTroops[$utype.$i]) ? $starvingTroops[$utype.$i] : 0); - if($counting) $totalUnits += $units; - if($units > $maxcount){ - $maxcount = $units; - $maxtype = $i; - } - } - if($counting) $counting = false; - - if($maxtype > 0){ - $starvingTroops[$utype.$maxtype]--; - if ( !isset($killedUnits[$maxtype]) ) { - $killedUnits[$maxtype] = 0; - } - $killedUnits[$maxtype]++; - $difcrop -= $GLOBALS['u'.(($special) ? $maxtype + ($tribe - 1) * 10 : $maxtype)]['crop']; - } - else break; - } - - $totalKilledUnits = array_sum($killedUnits); - if($starvingTroops[$heroType] > 0 && ($totalUnits == 0 || $totalUnits == $totalKilledUnits)){ - $totalKilledUnits += $starvingTroops[$heroType]; - $totalUnits += $starvingTroops[$heroType]; - $starvingTroops['heroinfo'] = $database->getHero(($type == 2) ? $starv['owner'] : $database->getVillageField(($type == 3 && $subtype == 1) ? $starvingTroops['to'] : $starvingTroops['from'], "owner"))[0]; - $database->modifyHero("dead", 1, $starvingTroops['heroinfo']['heroid']); - $database->modifyHero("health", 0, $starvingTroops['heroinfo']['heroid']); - $newCrop = $GLOBALS['h'.$starvingTroops['heroinfo']['unit'].'_full'][min($starvingTroops['heroinfo']['level'], 60)]['crop'] + $difcrop; - } - else if($maxtype == 0) $newCrop = 0; - else $newCrop = $GLOBALS['u'.$maxtype]['crop']; - - if($totalKilledUnits > 0){ - switch($type){ - case 0: - if($totalKilledUnits < $totalUnits){ - $database->modifyEnforce($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); - } - else $database->deleteReinf($starvingTroops['id']); - break; - - case 1: - if($totalKilledUnits < $totalUnits){ - $database->modifyPrisoners($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); - $database->modifyUnit($starvingTroops['wref'], ["99o"], [$totalKilledUnits], [0]); - }else{ - $database->deletePrisoners($starvingTroops['id']); - $database->modifyUnit($starvingTroops['wref'], ["99o"], [$totalUnits], [0]); - } - break; - - case 2: - $database->modifyUnit($starv['wref'], array_keys($killedUnits), array_values($killedUnits), [0]); - break; - - case 3: - if($totalKilledUnits < $totalUnits){ - $database->modifyAttack2($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); - } - else $database->setMovementProc($starvingTroops['moveid']); - break; - } - - $database->modifyResource($starv['wref'], 0, 0, 0, max($newCrop, 0), 1); - $database->setVillageField($starv['wref'], ['starv', 'starvupdate'], [$upkeep, $time]); - } + // Consuma mai intai crop-ul din hambar + if ($oldcrop > 100) { + $difcrop = $difcrop - $oldcrop; + if($difcrop < 0){ + $difcrop = 0; + $newcrop = $oldcrop - $difcrop; + $database->setVillageField($starv['wref'], 'crop', $newcrop); } } - $crop = $database->getCropProdstarv($starv['wref'], false); - if ($crop > $upkeep) $database->setVillageFields($starv['wref'], ['starv', 'starvupdate'], [0, 0]); + if($difcrop > 0 && $oldcrop <= 0){ + $this->starvationKillTroops($starv, $starvingTroops, $type, $subtype, $difcrop, $upkeep, $time); + } + } - unset ($unitarrays, $type, $subtype); + $this->starvationCheckReset($starv, $upkeep); + } + + /** + * Gaseste primul grup de trupe care poate muri de foame + * Ordinea: enforce oasis, enforce village, prisoners, unitati proprii, atacuri + */ + private function starvationFindFirstTroopGroup($starv) { + global $database; + + $enforceArrays = [ + $database->getOasisEnforce($starv['wref'], 2), + $database->getOasisEnforce($starv['wref'], 3), + $database->getEnforceVillage($starv['wref'], 2), + $database->getEnforceVillage($starv['wref'], 3) + ]; + + $prisonerArrays = [$database->getPrisoners($starv['wref'], 1)]; + $unitArrays = ($database->getUnitsNumber($starv['wref'], 0) > 0) ? [[$database->getUnit($starv['wref'])]] : []; + $attackArrays = [ + $database->getMovement(3, $starv['wref'], 0), + $database->getMovement(4, $starv['wref'], 1) + ]; + + $allTroopsArray = [$enforceArrays, $prisonerArrays, $unitArrays, $attackArrays]; + + foreach($allTroopsArray as $type => $allTroops) { + if(!empty($allTroops)){ + foreach($allTroops as $subtype => $troops){ + if(!empty($troops)){ + return [ + 'troops' => reset($troops), + 'type' => $type, + 'subtype' => $subtype + ]; + } + } + } + } + return ['troops' => [], 'type' => null, 'subtype' => null]; + } + + /** + * Ucide trupele conform deficitului de crop + */ + private function starvationKillTroops($starv, &$starvingTroops, $type, $subtype, $difcrop, $upkeep, $time) { + global $database; + + $tribe = $database->getUserField(($type == 2) ? $starv['owner'] : $database->getVillageField($starvingTroops['from'], "owner"), "tribe", 0); + $special = in_array($type, [1, 3]); + $start = $special ? 1 : ($tribe - 1) * 10 + 1; + $end = $special ? 10 : $tribe * 10; + $utype = $special ? 't' : 'u'; + $heroType = $special ? 't11' : 'hero'; + + $killedUnits = []; + $totalUnits = 0; + $counting = true; + + while($difcrop > 0) { + $maxcount = $maxtype = 0; + for($i = $start; $i <= $end; $i++) { + $units = (isset($starvingTroops[$utype.$i]) ? $starvingTroops[$utype.$i] : 0); + if($counting) $totalUnits += $units; + if($units > $maxcount){ + $maxcount = $units; + $maxtype = $i; + } + } + if($counting) $counting = false; + + if($maxtype > 0){ + $starvingTroops[$utype.$maxtype]--; + $killedUnits[$maxtype] = ($killedUnits[$maxtype] ?? 0) + 1; + // Formula originala de consum crop per unitate + $unitIndex = $special ? $maxtype + ($tribe - 1) * 10 : $maxtype; + $difcrop -= $GLOBALS['u'.$unitIndex]['crop']; + } else break; + } + + $totalKilledUnits = array_sum($killedUnits); + $newCrop = 0; + + // Verifica daca eroul moare + if($starvingTroops[$heroType] > 0 && ($totalUnits == 0 || $totalUnits == $totalKilledUnits)){ + $totalKilledUnits += $starvingTroops[$heroType]; + $totalUnits += $starvingTroops[$heroType]; + $heroOwner = ($type == 2) ? $starv['owner'] : $database->getVillageField(($type == 3 && $subtype == 1) ? $starvingTroops['to'] : $starvingTroops['from'], "owner"); + $heroInfo = $database->getHero($heroOwner)[0]; + $database->modifyHero("dead", 1, $heroInfo['heroid']); + $database->modifyHero("health", 0, $heroInfo['heroid']); + $newCrop = $GLOBALS['h'.$heroInfo['unit'].'_full'][min($heroInfo['level'], 60)]['crop'] + $difcrop; + } else if($maxtype > 0) { + $newCrop = $GLOBALS['u'.$maxtype]['crop']; + } + + if($totalKilledUnits > 0) { + $this->starvationApplyDatabaseChanges($starv, $starvingTroops, $type, $killedUnits, $totalUnits, $totalKilledUnits, $newCrop, $upkeep, $time); + } + } + + /** + * Aplica modificarile in baza de date + */ + private function starvationApplyDatabaseChanges($starv, $starvingTroops, $type, $killedUnits, $totalUnits, $totalKilledUnits, $newCrop, $upkeep, $time) { + global $database; + + switch($type){ + case 0: // enforce + if($totalKilledUnits < $totalUnits){ + $database->modifyEnforce($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); + } else { + $database->deleteReinf($starvingTroops['id']); + } + break; + case 1: // prisoners + if($totalKilledUnits < $totalUnits){ + $database->modifyPrisoners($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); + $database->modifyUnit($starvingTroops['wref'], ["99o"], [$totalKilledUnits], [0]); + } else { + $database->deletePrisoners($starvingTroops['id']); + $database->modifyUnit($starvingTroops['wref'], ["99o"], [$totalUnits], [0]); + } + break; + case 2: // unitati proprii + $database->modifyUnit($starv['wref'], array_keys($killedUnits), array_values($killedUnits), [0]); + break; + case 3: // atacuri in miscare + if($totalKilledUnits < $totalUnits){ + $database->modifyAttack2($starvingTroops['id'], array_keys($killedUnits), array_values($killedUnits), 0); + } else { + $database->setMovementProc($starvingTroops['moveid']); + } + break; + } + + $database->modifyResource($starv['wref'], 0, 0, 0, max($newCrop, 0), 1); + $database->setVillageField($starv['wref'], ['starv', 'starvupdate'], [$upkeep, $time]); + } + + /** + * Verifica daca starvation poate fi resetat + */ + private function starvationCheckReset($starv, $upkeep) { + global $database; + $crop = $database->getCropProdstarv($starv['wref'], false); + if ($crop > $upkeep) { + $database->setVillageFields($starv['wref'], ['starv', 'starvupdate'], [0, 0]); } } diff --git a/GameEngine/Battle.php b/GameEngine/Battle.php index 1d2034c5..e297e985 100644 --- a/GameEngine/Battle.php +++ b/GameEngine/Battle.php @@ -7,7 +7,7 @@ ## Version: 08.05.2026 ## ## Filename: Battle.php ## ## Developed by: Dzoki & Dixie ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## Fixed by: InCube - double troops ## ## Reworked/Fix: ronix ## ## Thanks to: Akakori, Elmar & Kirilloid ## diff --git a/GameEngine/Building.php b/GameEngine/Building.php index 71d9aa64..0f3734b0 100755 --- a/GameEngine/Building.php +++ b/GameEngine/Building.php @@ -7,7 +7,7 @@ ## Version: 12.05.2026 ## ## Filename: Building.php ## ## Developed by: Dzoki & Dixie ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## Reworked/Fix: ronix ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## diff --git a/GameEngine/Chat.php b/GameEngine/Chat.php index e82c80b2..1660b27b 100755 --- a/GameEngine/Chat.php +++ b/GameEngine/Chat.php @@ -5,7 +5,7 @@ ## --------------------------------------------------------------------------- ## ## Filename Chat.php ## ## Developed by: TTMMTT ## -## Refactor by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## URLs: https://travianz.org ## diff --git a/GameEngine/Database.php b/GameEngine/Database.php index 9fc3de36..403c5302 100755 --- a/GameEngine/Database.php +++ b/GameEngine/Database.php @@ -1,17 +1,23 @@ selectQueryCount++; + elseif (stripos($trim, 'INSERT') === 0) $this->insertQueryCount++; + elseif (stripos($trim, 'UPDATE') === 0) $this->updateQueryCount++; + elseif (stripos($trim, 'DELETE') === 0) $this->deleteQueryCount++; + elseif (stripos($trim, 'REPLACE') === 0) $this->replaceQueryCount++; + } + + private function fetchCached(string $key, string $sql, callable $fetcher) { + if (isset($this->queryCache[$key])) return $this->queryCache[$key]; + $this->countQueryType($sql); + $res = mysqli_query($this->dblink, $sql); + $data = $fetcher($res); + $this->queryCache[$key] = $data; + return $data; + } + + private function clearQueryCache(?string $prefix = null): void { + if ($prefix === null) $this->queryCache = []; + else foreach (array_keys($this->queryCache) as $k) if (strpos($k, $prefix)===0) unset($this->queryCache[$k]); + } + // === END REFACTOR v2 === + /** * @@ -631,7 +670,7 @@ class MYSQLi_DB implements IDbConnection { References: lietuvis10 ***************************/ -public function getBestOasisCropBonus($x, $y) { + public function getBestOasisCropBonus($x, $y) { $x = (int)$x; $y = (int)$y; @@ -656,18 +695,19 @@ public function getBestOasisCropBonus($x, $y) { References: Result ***************************/ function mysqli_fetch_all($result) { - list($result) = $this->escape_input($result); - + // REFACTORIZAT: garantăm array pentru PHP 7.2+, eliminăm escape pe resource $all = []; - if($result) { + if($result instanceof \mysqli_result) { while($row = mysqli_fetch_assoc($result)) { $all[] = $row; } - return $all; + mysqli_free_result($result); } + return $all; } function query_return($q) { + $this->countQueryType($q); $result = mysqli_query($this->dblink,$q); return $this->mysqli_fetch_all($result); } @@ -677,6 +717,8 @@ public function getBestOasisCropBonus($x, $y) { References: Query ***************************/ function query($query) { + // REFACTORIZAT: contorizare centralizată + $this->countQueryType($query); return mysqli_query($this->dblink,$query); } @@ -839,31 +881,45 @@ public function getBestOasisCropBonus($x, $y) { } function updateUserField($ref, $field, $value, $switch) { - list($ref) = $this->escape_input($ref); + list($ref) = $this->escape_input($ref); - if (!is_array($field)) { - $field = [$field]; - $value = [$value]; - } + if (!is_array($field)) { + $field = [$field]; + $value = [$value]; + } - $pairs = []; - foreach ($field as $index => $fieldName) { - $pairs[] = $this->escape($fieldName) . ' = ' . (Math::isInt($value[$index]) ? $value[$index] : '"'.$this->escape($value[$index]).'"'); - } + $pairs = []; + foreach ($field as $i => $f) { + $pairs[] = $this->escape($f) . ' = ' . (is_numeric($value[$i]) ? $value[$i] : "'".$this->escape($value[$i])."'"); + } - if(!$switch) $q = "UPDATE " . TB_PREFIX . "users SET ".implode(', ', $pairs)." where username = '$ref'"; - else $q = "UPDATE " . TB_PREFIX . "users SET ".implode(', ', $pairs)." where id = " . (int) $ref; + $q = !$switch + ? "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE username = '$ref'" + : "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE id = ".(int)$ref; - // update cached values - if ($ret = mysqli_query($this->dblink,$q)) { - foreach ($field as $index => $fieldName) { - if (isset(self::$fieldsCache[$ref.($switch ? 0 : 1)][$fieldName])) - self::$fieldsCache[$ref.($switch ? 0 : 1)][$fieldName] = $value[$index]; - } - } + $ret = mysqli_query($this->dblink, $q); - return $ret; - } + // === FIX CACHE - ștergem tot ce ține de user === + $uid = $switch ? (int)$ref : (int)$this->getUserField($ref, 'id', 0, false); + + // 1. cache-ul de fields + unset(self::$fieldsCache[$uid.'0'], self::$fieldsCache[$uid.'1']); + unset(self::$fieldsCache[$ref.'0'], self::$fieldsCache[$ref.'1']); + + // 2. cache-ul de query-uri + $this->clearQueryCache('userarray'); + $this->clearQueryCache('getUser'); + + // 3. forțăm și sesiunea dacă e userul curent + global $session; + if (isset($session) && $session->uid == $uid && in_array('alliance', (array)$field)) { + $idx = array_search('alliance', (array)$field); + $session->alliance = $value[$idx]; + $session->userinfo['alliance'] = $value[$idx]; + } + + return $ret; +} // no need to cache this method function getSitee($uid) { @@ -938,42 +994,6 @@ public function getBestOasisCropBonus($x, $y) { } return $result; - - /*list($ref, $field, $mode) = $this->escape_input($ref, $field, $mode); - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && ($cachedValue = self::returnCachedContent(self::$fieldsCache, $ref.$mode)) && !is_null($cachedValue)) { - // check if we have the requested field type cached - if (isset($cachedValue[$field])) { - return $cachedValue[$field]; - } - } - - // update for Multihunter's username and ID - if (($mode && $ref == '') || (!$mode && $ref == 0)) { - $ref = 'Multihunter'; - $mode = 1; - } - - if(!$mode) { - $q = "SELECT $field FROM " . TB_PREFIX . "users where id = " . (int) $ref; - } else { - $q = "SELECT $field FROM " . TB_PREFIX . "users where username = '$ref'"; - } - - $result = mysqli_query($this->dblink,$q) or die(mysqli_error($this->dblink)); - - if ($result) { - $dbarray = mysqli_fetch_array($result); - self::$fieldsCache[$ref.$mode][$field] = $dbarray[$field]; - } elseif($field=="username") { - self::$fieldsCache[$ref.$mode][$field] = "??"; - } else { - self::$fieldsCache[$ref.$mode][$field] = 0; - } - - return self::$fieldsCache[$ref.$mode][$field];*/ } function getUserFields($ref, $fields, $mode, $use_cache = true) { @@ -985,59 +1005,6 @@ public function getBestOasisCropBonus($x, $y) { // return all data, don't waste time by selecting fields one by one return $this->getUserArray($ref, ($mode ? 0 : 1), $use_cache); - - /*list($ref, $fields, $mode) = $this->escape_input($ref, $fields, $mode); - - // update for Multihunter's username and ID - if (($mode && $ref == '') || (!$mode && $ref == 0)) { - $ref = 'Multihunter'; - $mode = 1; - } - - // check fields one by one to see which ones we can return cached - if ($use_cache) { - $allFieldsFound = false; - $fieldsLeft = []; - $fieldValues = []; - - // split fields - $fields = explode(',', str_replace(', ', ',', $fields)); - - // iterate over all the fields and see what we have cached - foreach ($fields as $fieldName) { - if (($cached = self::returnCachedContent(self::$fieldsCache, $ref.$mode)) && !is_null($cached) && isset($cached[$fieldName])) { - $fieldValues[$fieldName] = $cached[$fieldName]; - } else { - $fieldsLeft[] = $fieldName; - } - } - - // check if we should return here (if we have all the values) or continue with the rest below - if (!count($fieldsLeft)) { - return $fieldValues; - } - } - - if(!$mode) { - $q = "SELECT ".implode(', ', $fieldsLeft)." FROM " . TB_PREFIX . "users where id = " . (int) $ref; - } else { - $q = "SELECT ".implode(', ', $fieldsLeft)." FROM " . TB_PREFIX . "users where username = '$ref'"; - } - - $result = mysqli_query($this->dblink,$q) or die(mysqli_error($this->dblink)); - if($result) { - $ret = mysqli_fetch_array($result, MYSQLI_ASSOC); - } else { - $ret = [0]; - } - - // cache results and return everything that we have - foreach ($ret as $fieldName => $fieldValue) { - $fieldValues[$fieldName] = $fieldValue; - self::$fieldsCache[$ref.$mode][$fieldName] = $fieldValue; - } - - return $fieldValues;*/ } // no need to cache this method @@ -1207,9 +1174,9 @@ public function getBestOasisCropBonus($x, $y) { if(!$mode) $q = "SELECT * FROM " . TB_PREFIX . "users where username = '$ref' LIMIT 1"; else $q = "SELECT * FROM " . TB_PREFIX . "users where id = " . (int) $ref . " LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - - self::$fieldsCache[$ref.$mode] = mysqli_fetch_array($result); + // ACTIVAT CACHE GENERIC + $cacheKey = "userarray_".$ref."_".$mode; + self::$fieldsCache[$ref.$mode] = $this->fetchCached($cacheKey, $q, function($res){ return mysqli_fetch_array($res, MYSQLI_ASSOC); }); return self::$fieldsCache[$ref.$mode]; } @@ -1899,9 +1866,9 @@ public function getBestOasisCropBonus($x, $y) { break; } - $result = mysqli_query($this->dblink,$q); - - self::$villageFieldsCache[$vid.$mode] = mysqli_fetch_array($result, MYSQLI_ASSOC); + // ACTIVAT CACHE GENERIC + $cacheKey = "village_".$vid."_".$mode; + self::$villageFieldsCache[$vid.$mode] = $this->fetchCached($cacheKey, $q, function($res){ return mysqli_fetch_array($res, MYSQLI_ASSOC); }); return self::$villageFieldsCache[$vid.$mode]; } @@ -1978,7 +1945,7 @@ public function getBestOasisCropBonus($x, $y) { $result = mysqli_query($this->dblink,$q); if (!$arrayPassed) { - $result = $this->mysqli_fetch_all($result); + $result = $this->mysqli_fetch_all($result); self::$userVillagesCache[ $uid[0].$mode ] = $result; // cache each village individually into the fields cache as well @@ -2080,7 +2047,9 @@ public function getBestOasisCropBonus($x, $y) { } $q = "SELECT * FROM " . TB_PREFIX . "wdata where id IN(".implode(', ', $vid).")"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + // ACTIVAT CACHE GENERIC + $cacheKey = "wdata_".md5($q); + $result = $this->fetchCached($cacheKey, $q, function($res){ return $this->mysqli_fetch_all($res); }); // return a single value if (!$array_passed) { @@ -2200,30 +2169,11 @@ public function getBestOasisCropBonus($x, $y) { }else $result = 0; return $result; - - /*list($ref, $field) = $this->escape_input((int) $ref, $field); - - $q = "SELECT $field FROM " . TB_PREFIX . "vdata where wref = $ref"; - $result = mysqli_query($this->dblink,$q); - if($result){ - $dbarray = mysqli_fetch_array($result); - return $dbarray[$field]; - }elseif($field=="name"){ - return "??"; - }else return 0;*/ } function getVillageFields($ref, $fields, $use_cache = true) { // return all data, don't waste time by selecting fields one by one return $this->getVillage($ref, 0, $use_cache); - - /*list($ref, $field) = $this->escape_input((int) $ref, $fields); - - $q = "SELECT $field FROM " . TB_PREFIX . "vdata where wref = $ref"; - $result = mysqli_query($this->dblink,$q); - if($result) { - return mysqli_fetch_array($result, MYSQLI_ASSOC); - } else return 0;*/ } function getOasisField($ref, $field, $use_cache = true) { @@ -2235,11 +2185,6 @@ public function getBestOasisCropBonus($x, $y) { function getOasisFields($ref, $use_cache = true) { // return all data, don't waste time by selecting fields one by one return $this->getOasisV($ref, $use_cache); - - /*list($ref, $fields) = $this->escape_input((int) $ref, $fields); - - $q = "SELECT $fields FROM " . TB_PREFIX . "odata where wref = $ref"; - return mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC);*/ } function setVillageField($ref, $field, $value) { @@ -2339,9 +2284,9 @@ public function getBestOasisCropBonus($x, $y) { } $q = "SELECT * from " . TB_PREFIX . "fdata where vref = $vid"; - $result = mysqli_query($this->dblink,$q); - - self::$resourceLevelsCache[$vid] = mysqli_fetch_assoc($result); + // ACTIVAT CACHE GENERIC + $cacheKey = "reslvl_".$vid; + self::$resourceLevelsCache[$vid] = $this->fetchCached($cacheKey, $q, function($res){ return mysqli_fetch_assoc($res); }); return self::$resourceLevelsCache[$vid]; } @@ -3004,9 +2949,7 @@ public function getBestOasisCropBonus($x, $y) { list($id) = $this->escape_input($id); $qs = "DELETE from " . TB_PREFIX . "forum_topic where id = '$id'"; - // $q = "DELETE from ".TB_PREFIX."forum_post where topic = '$id'";// - return mysqli_query($this->dblink,$qs); // - // mysqli_query($this->dblink,$q); + return mysqli_query($this->dblink,$qs); } function DeletePost($id) { @@ -3551,8 +3494,6 @@ public function getBestOasisCropBonus($x, $y) { return self::$userAllianceCache[$id]; } - /////////////ADDED BY BRAINIAC - THANK YOU - function modifyResource($vid, $wood, $clay, $iron, $crop, $mode) { list($vid, $wood, $clay, $iron, $crop, $mode) = $this->escape_input((int) $vid, $wood, $clay, $iron, $crop, $mode); $sign = (!$mode ? '-' : '+'); @@ -3834,8 +3775,9 @@ public function getBestOasisCropBonus($x, $y) { OR (f40t = ".$field.($lvl !== false ? ' AND f40 '.$lvlComparisonSign.' '.$lvl : '').") )"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result); + // ACTIVAT CACHE GENERIC + $cacheKey = "sftc_".$uid."_".$field."_".$lvlComparisonSign."_".($lvl===false?'x':$lvl); + $row = $this->fetchCached($cacheKey, $q, function($res){ return mysqli_fetch_array($res, MYSQLI_ASSOC); }); self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)] = $row["Total"]; return self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)]; @@ -4890,278 +4832,177 @@ References: User ID/Message ID, Mode * and 0 when the player was evicted from * the alliance due to Embassy damage. */ - public function checkAllianceEmbassiesStatus($userData, $demolition = false, $use_cache = true) { - // TODO: refactor this and break it into more smaler methods - //global $session; + public + /** + * REFACTORIZAT 14.05.2026 - împărțit în metode mici, păstrată logica originală + * Verifică statutul ambasadelor și gestionează ieșirea din alianță + */ + function checkAllianceEmbassiesStatus($userData, $demolition = false, $use_cache = true) { + // fără alianță = nimic de făcut + if (empty($userData['alliance'])) { + return true; + } - if ($userData['alliance']) { - // check whether this player is an alliance owner - $isOwner = ($userData['alliance'] && $this->isAllianceOwner($userData['id'], $use_cache) == $userData['alliance']); + $isOwner = ($this->isAllianceOwner($userData['id'], $use_cache) == $userData['alliance']); + + if (!$isOwner) { + return $this->handleNonOwnerEmbassyCheck($userData, $demolition, $use_cache); + } else { + return $this->handleOwnerEmbassyCheck($userData, $demolition, $use_cache); + } + } - $minimumExistingEmbassyRecords = 1; + // === METODE NOI EXTRAS DIN checkAllianceEmbassiesStatus === - // if they are not an alliance owner, simply check whether we have any Embassies - // at lvl 1+ standing somewhere - if (!$isOwner) { - // TODO: replace magic numbers by constants (18 = Embassy) - if ($this->getSingleFieldTypeCount($userData['id'], 18, '>=', 1, $use_cache) < $minimumExistingEmbassyRecords) { + private function handleNonOwnerEmbassyCheck(array $userData, bool $demolition, bool $use_cache) { + // constantă magică 18 = Embassy - păstrată pentru compatibilitate + $hasEmbassy = $this->getSingleFieldTypeCount($userData['id'], 18, '>=', 1, $use_cache) >= 1; + + if ($hasEmbassy) { + return true; // are ambasadă, rămâne în alianță + } - // the player has no more Embassies, evict them from the alliance - mysqli_query($this->dblink, 'UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id = '.$userData['id']); + // nu mai are ambasadă - evict + $this->evictUserFromAlliance($userData['id']); + $this->deleteAlliPermissions($userData['id']); - // unset the alliance in session, if we're evicting - // currently logged-in player - //if ($session->uid == $userData['id']) { - // $_SESSION['alliance_user'] = 0; - //} + $msgTitle = $demolition ? 'You left the alliance' : 'An attack has forced you to leave the alliance'; + $msgBody = $demolition + ? "Hi, ".$userData['username']."!\n\nThis is to inform you that due to a finished demolition of your last Embassy, you have now successfully left your alliance.\n\nYours sincerely,\nServer Robot :)" + : "Hi, ".$userData['username']."!\n\nThis is to inform you that due to a successful attack and destruction of your last Embassy, you have been forced to leave your alliance.\n\nTo re-establish your position in this alliance, you will need to build a new Embassy and ask the leader to send you an invite again.\n\nYours sincerely,\nServer Robot :)"; - // notify them via in-game messaging, if we come from a demolition, - // otherwise return a result which can be used in battle reports - if ($demolition) { - $this->sendMessage( - $userData['id'], - 4, - 'You left the alliance', - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a finished demolition of your last Embassy, you have now successfully left your alliance.\n\nYours sincerely,\nServer Robot :)"), - 0, - 0, - 0, - 0, - 0, - true); - $this->deleteAlliPermissions($userData['id']); - } else { - // player has been removed from the alliance - $this->sendMessage( - $userData['id'], - 4, - 'An attack has forced you to leave the alliance', - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a successful attack and destruction of your last Embassy, you have been forced to leave your alliance.\n\nTo re-establish your position in this alliance, you will need to build a new Embassy and ask the leader to send you an invite again.\n\nYours sincerely,\nServer Robot :)"), - 0, - 0, - 0, - 0, - 0, - true); - $this->deleteAlliPermissions($userData['id']); - return 0; - } + $this->sendMessage($userData['id'], 4, $msgTitle, $this->escape($msgBody), 0,0,0,0,0,true); + + return $demolition ? true : 0; + } - } - } else { - // the player IS an alliance owner, check if we need to take any action - $membersCount = $this->countAllianceMembers($userData['alliance'], $use_cache); - $minAllianceEmbassyLevel = $this->getMinEmbassyLevel($membersCount); + private function handleOwnerEmbassyCheck(array $userData, bool $demolition, bool $use_cache) { + $membersCount = $this->countAllianceMembers($userData['alliance'], $use_cache); + $minLevel = max(3, $this->getMinEmbassyLevel($membersCount)); // liderul are nevoie minim nivel 3 - // in this case, the minimum Embassy level cannot go below 3, - // since this player is a leader and as such, he needs at least - // a level 3 Embassy - if ($minAllianceEmbassyLevel < 3) { - $minAllianceEmbassyLevel = 3; - } + $hasSufficientEmbassy = $this->getSingleFieldTypeCount($userData['id'], 18, '>=', $minLevel, false, $use_cache) >= 1; + $needsAction = ($userData['lvl'] <= $minLevel) && !$hasSufficientEmbassy; - $takeAction = ( - // was the Embassy taken below a threshold level? - ($userData['lvl'] <= $minAllianceEmbassyLevel) - && - // check for standing Embassies with sufficient level - // TODO: replace magic numbers by constants (18 = Embassy) - ($this->getSingleFieldTypeCount($userData['id'], 18, '>=', $minAllianceEmbassyLevel, false, $use_cache) < $minimumExistingEmbassyRecords) - ); + if (!$needsAction) { + return true; + } - // the Embassy got damaged below a sufficient level and there are no more Embassies - // at that level standing on this player's account, additional actions are needed - if ($takeAction) { + $members = $this->getAllMember($userData['alliance'], 0, $use_cache); - // load all alliance members - $members = $this->getAllMember($userData['alliance'], 0, $use_cache); + if ($demolition) { + return $this->disbandAllianceOnDemolition($userData, $members); + } else { + return $this->handleOwnerAttackLoss($userData, $members, $minLevel, $membersCount, $use_cache); + } + } - // if we come from demolition, we need to evict all new members - // that accepted an invitation while level 3 of the last - // Embassy was already under demolition. The demolition dialog itself - // already checks if there are no more people other than the owner - // present before the demolition is allowed. - if ($demolition) { - $evicts = []; - foreach ($members as $member) { - // evict the player from the alliance - $evicts[] = $member['id']; + public function evictUserFromAlliance(int $uid): void { + $this->query('UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id = '.$uid); + $this->clearQueryCache('alliance'); // invalidăm cache-ul de alianță + } - // notify them via in-game messaging - $this->sendMessage( - $member['id'], - 4, - 'Your alliance was disbanded', - ( - ($member['id'] == $userData['id']) - ? - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a finished demolition of your last Embassy at level 3, and the fact that you were the leader of your alliance, this alliance has been disbanded.\n\nIn order to found a new alliance, please build a level 3 Embassy again in one of your villages.\n\nYours sincerely,\nServer Robot :)") - : - $this->escape("Hi, ".$member['username']."!\n\nThis is to inform you that due to a demolition of your alliance founder's last Embassy below level 3, this alliance has been disbanded.\n\n\You can now accept invitations from other alliances or found a new alliance yourself.\n\nYours sincerely,\nServer Robot :)") - ), - 0, - 0, - 0, - 0, - 0, - true); - $this->deleteAlliPermissions($member['id']); - } + private function disbandAllianceOnDemolition(array $ownerData, array $members): bool { + $evicts = []; + foreach ($members as $member) { + $evicts[] = $member['id']; + $isOwner = ($member['id'] == $ownerData['id']); + $title = 'Your alliance was disbanded'; + $body = $isOwner + ? "Hi, ".$ownerData['username']."!\n\nThis is to inform you that due to a finished demolition of your last Embassy at level 3, and the fact that you were the leader of your alliance, this alliance has been disbanded.\n\nIn order to found a new alliance, please build a level 3 Embassy again in one of your villages.\n\nYours sincerely,\nServer Robot :)" + : "Hi, ".$member['username']."!\n\nThis is to inform you that due to a demolition of your alliance founder's last Embassy below level 3, this alliance has been disbanded.\n\nYou can now accept invitations from other alliances or found a new alliance yourself.\n\nYours sincerely,\nServer Robot :)"; + + $this->sendMessage($member['id'], 4, $title, $this->escape($body), 0,0,0,0,0,true); + $this->deleteAlliPermissions($member['id']); + } + if ($evicts) { + $this->query('UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id IN('.implode(',', $evicts).')'); + } + $this->deleteAlliance($ownerData['alliance']); + return true; + } - mysqli_query($this->dblink, 'UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id IN('.implode(',', $evicts).")"); - } else { - // we come from a battle result, therefore we need to check - // for the first player in the alliance who has a sufficient - // level Embassy and to which we can auto-reassign the leadership - $newLeaderFound = false; - - // in case we'll need these later to disband the alliance, - // we'll collect them inside this foeach loop - $memberIDs = []; - - // no need for this whole foreach loop if this player is the lone - // founder and member of their alliance - if ($membersCount > 1) { - foreach ($members as $member) { - if (!$newLeaderFound && $this->getSingleFieldTypeCount($member['id'], 18, '>=', $minAllianceEmbassyLevel) >= 1) { - // found a new leader for the alliance - $newLeaderFound = true; - $newleader = $member['id']; - $q = "UPDATE " . TB_PREFIX . "alidata set leader = ".(int) $newleader." where id = ".(int) $userData['alliance']; - $this->query($q); - $this->updateAlliPermissions($newleader, $userData['alliance'], "Leader", 1, 1, 1, 1, 1, 1, 1); - Automation::updateMax($newleader); - - // update permissions for the old leader - $this->updateAlliPermissions($userData['id'], $userData['alliance'], "Former Leader", 0, 0, 0, 0, 0, 0, 0); - - // notify new leader via in-game messaging - $this->sendMessage( - $newleader, - 4, - 'You are now the alliance leader', - $this->escape("Hi, ".$member['username']."!\n\nThis is to inform you that there was a successful attack on player ".$userData['username']." which has damaged their Embassy badly enough that they are no longer able to sustain the leadership of your alliance.\n\nSince your Embassy level is of a sufficient level, you have been auto-elected to the position of a new leader of your alliance with all duties and responsibilities thereof.\n\nYours sincerely,\nServer Robot :)"), - 0, - 0, - 0, - 0, - 0, - true); - } - - $memberIDs[] = $member['id']; - } - } else { - // if there is only 1 member and it's the actual founder - $memberIDs[] = $userData['id']; - } - - // if there wasn't anyone with a sufficient level of Embassy - // among the existing members, disperse this alliance - if (!$newLeaderFound) { - - // evict all members from the alliance - mysqli_query($this->dblink, 'UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id IN('.implode(',', $memberIDs).")"); - - // notify all of them via in-game messaging - foreach ($members as $member) { - $this->sendMessage( - $member['id'], - 4, - 'Your alliance was dispersed', - ( - ($member['id'] == $userData['id']) - ? - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a successful attack that has degraded your last Embassy to a level ".($membersCount > 1 ? "which is unable to hold all ".$membersCount." alliance members, and because there was no other alliance member with an Embassy on a high enough level to overtake the leadership," : "lower then 3 - which is required to found and hold your own alliance - ")." your alliance has been dispersed.\n\nYours sincerely,\nServer Robot :)") - : - $this->escape("Hi, ".$member['username']."!\n\nThis is to inform you that due to a successful attack on your alliance leader's Embassy by another player that degraded it below threshold allowed to hold all ".$membersCount." alliance members, and because there was no other alliance member with an Embassy on a high enough level to overtake the leadership, your alliance has been dispersed.\n\nYours sincerely,\nServer Robot :)") - ), - 0, - 0, - 0, - 0, - 0, - true); - } - $this->deleteAlliPermissions($member['id']); - } else { - // let's determine whether to keep currently attacked player - // in the alliance or not - if ($userData['lvl'] > 0 || $this->getSingleFieldTypeCount($member['id'], 18, '>=', 1, $use_cache) >= $minimumExistingEmbassyRecords) { - $keepCurrentPlayer = true; - } else { - $keepCurrentPlayer = false; - } - - // if a new leader was found, notify all alliance member of this change - // notify all of them via in-game messaging - foreach ($members as $member) { - // don't send duplicate messages to the new leader - if ($member['id'] != $newleader) { - // also, don't send to the attacked player if we're - // not keeping them in alliance - if ($keepCurrentPlayer || (!$keepCurrentPlayer && $member['id'] != $userData['id'])) - $this->sendMessage( - $member['id'], - 4, - 'Your alliance has a new leader', - ( - ($member['id'] == $userData['id']) - ? - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a successful attack that has degraded your last Embassy to a level which is unable to hold all ".$membersCount." alliance members, another alliance member who meets these criteria has been auto-elected as a new alliance leader.\n\nAdditionally - due to the Embassy destruction - you have been forcefuly evicted from your alliance.\n\nPlease re-establish the connection with your alliance by building a new Embassy and contacting the new leader for an invitation.\n\nYours sincerely,\nServer Robot :)") - : - $this->escape("Hi, ".$member['username']."!\n\nThis is to inform you that due to a successful attack on your alliance leader's Embassy by another player, another alliance member with enough Embassy capacity has been auto-elected as the new alliance leader.\n\nYours sincerely,\nServer Robot :)") - ), - 0, - 0, - 0, - 0, - 0, - true); - } - $this->deleteAlliPermissions($member['id']); - } - - // evict current player from the alliance - // if this was their last Embassy and was completely destroyed - if (!$keepCurrentPlayer) { - mysqli_query($this->dblink, 'UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id = '.$userData['id']); - - // unset the alliance in session, if we're evicting - // currently logged-in player - if ($session->uid == $userData['id']) { - $_SESSION['alliance_user'] = 0; - } - - // notify the evicted player - $this->sendMessage( - $userData['id'], - 4, - 'An attack has forced you to leave the alliance', - $this->escape("Hi, ".$userData['username']."!\n\nThis is to inform you that due to a successful attack and destruction of your last Embassy, you have been forced to leave your alliance.\n\nTo re-establish your position in this alliance, you will need to build a new Embassy and ask the newly auto-elected leader to send you an invite again.\n\nYours sincerely,\nServer Robot :)"), - 0, - 0, - 0, - 0, - 0, - true); - } - $this->deleteAlliPermissions($userData['id']); - } - } - - // execute a method that will delete an alliance - // if no members are left in it - $this->deleteAlliance($userData['alliance']); - - return isset($newLeaderFound) && $newLeaderFound === true; + private function handleOwnerAttackLoss(array $ownerData, array $members, int $minLevel, int $membersCount, bool $use_cache): bool { + $newLeaderId = null; + + // căutăm primul membru cu ambasadă suficientă + if ($membersCount > 1) { + foreach ($members as $member) { + if ($this->getSingleFieldTypeCount($member['id'], 18, '>=', $minLevel) >= 1) { + $newLeaderId = (int)$member['id']; + $this->promoteNewAllianceLeader($ownerData['alliance'], $newLeaderId, $ownerData['id'], $member['username'], $ownerData); + break; } } } - // no changes in player-to-alliance relationship - return true; - } + if (!$newLeaderId) { + // niciun lider eligibil - dispersăm alianța + return $this->disperseAllianceNoLeader($ownerData, $members, $membersCount); + } + + // avem lider nou - notificăm și eventual evictăm ownerul vechi + return $this->notifyLeaderChange($ownerData, $members, $newLeaderId, $minLevel, $use_cache); + } + + public function promoteNewAllianceLeader(int $allyId, int $newLeaderId, int $oldLeaderId, string $newLeaderName, array $oldLeaderData, ?string $customMessage = null): void { + $this->query("UPDATE ".TB_PREFIX."alidata SET leader = $newLeaderId WHERE id = $allyId"); + $this->updateAlliPermissions($newLeaderId, $allyId, "Leader", 1,1,1,1,1,1,1); + if (class_exists('Automation')) { Automation::updateMax($newLeaderId); } + $this->updateAlliPermissions($oldLeaderId, $allyId, "Former Leader", 0,0,0,0,0,0,0); + + if ($customMessage === null) { + $msg = "Hi, $newLeaderName!\n\nThis is to inform you that there was a successful attack on player ".$oldLeaderData['username']." which has damaged their Embassy badly enough that they are no longer able to sustain the leadership of your alliance.\n\nSince your Embassy level is of a sufficient level, you have been auto-elected to the position of a new leader of your alliance with all duties and responsibilities thereof.\n\nYours sincerely,\nServer Robot :)"; + $title = 'You are now the alliance leader'; + } else { + $msg = $customMessage; + $title = 'You are now leader of your alliance'; + } + $this->sendMessage($newLeaderId, 4, $title, $this->escape($msg), 0,0,0,0,0,true); + $this->clearQueryCache('alliance'); + } + + private function disperseAllianceNoLeader(array $ownerData, array $members, int $membersCount): bool { + $ids = array_column($members, 'id'); + if ($ids) { + $this->query('UPDATE '.TB_PREFIX.'users SET alliance = 0 WHERE id IN('.implode(',', $ids).')'); + } + foreach ($members as $m) { + $isOwner = ($m['id'] == $ownerData['id']); + $title = 'Your alliance was dispersed'; + $body = $isOwner + ? "Hi, ".$ownerData['username']."!\n\nThis is to inform you that due to a successful attack that has degraded your last Embassy to a level ".($membersCount>1?"which is unable to hold all $membersCount alliance members, and because there was no other alliance member with an Embassy on a high enough level to overtake the leadership,":"lower then 3 - which is required to found and hold your own alliance - ")." your alliance has been dispersed.\n\nYours sincerely,\nServer Robot :)" + : "Hi, ".$m['username']."!\n\nThis is to inform you that due to a successful attack on your alliance leader's Embassy by another player that degraded it below threshold allowed to hold all $membersCount alliance members, and because there was no other alliance member with an Embassy on a high enough level to overtake the leadership, your alliance has been dispersed.\n\nYours sincerely,\nServer Robot :)"; + $this->sendMessage($m['id'], 4, $title, $this->escape($body), 0,0,0,0,0,true); + $this->deleteAlliPermissions($m['id']); + } + $this->deleteAlliance($ownerData['alliance']); + return false; + } + + private function notifyLeaderChange(array $ownerData, array $members, int $newLeaderId, int $minLevel, bool $use_cache): bool { + $keepOwner = ($ownerData['lvl'] > 0) || ($this->getSingleFieldTypeCount($ownerData['id'], 18, '>=', 1, $use_cache) >= 1); + + foreach ($members as $m) { + if ($m['id'] == $newLeaderId) continue; + if (!$keepOwner && $m['id'] == $ownerData['id']) continue; + + $isOwner = ($m['id'] == $ownerData['id']); + $title = 'Your alliance has a new leader'; + $body = $isOwner + ? "Hi, ".$ownerData['username']."!\n\nThis is to inform you that due to a successful attack that has degraded your last Embassy to a level which is unable to hold all ".count($members)." alliance members, another alliance member who meets these criteria has been auto-elected as a new alliance leader.\n\nAdditionally - due to the Embassy destruction - you have been forcefuly evicted from your alliance.\n\nPlease re-establish the connection with your alliance by building a new Embassy and contacting the new leader for an invitation.\n\nYours sincerely,\nServer Robot :)" + : "Hi, ".$m['username']."!\n\nThis is to inform you that due to a successful attack on your alliance leader's Embassy by another player, another alliance member with enough Embassy capacity has been auto-elected as the new alliance leader.\n\nYours sincerely,\nServer Robot :)"; + $this->sendMessage($m['id'], 4, $title, $this->escape($body), 0,0,0,0,0,true); + } + + if (!$keepOwner) { + $this->evictUserFromAlliance($ownerData['id']); + $msg = "Hi, ".$ownerData['username']."!\n\nThis is to inform you that due to a successful attack and destruction of your last Embassy, you have been forced to leave your alliance.\n\nTo re-establish your position in this alliance, you will need to build a new Embassy and ask the newly auto-elected leader to send you an invite again.\n\nYours sincerely,\nServer Robot :)"; + $this->sendMessage($ownerData['id'], 4, 'An attack has forced you to leave the alliance', $this->escape($msg), 0,0,0,0,0,true); + } + $this->deleteAlliance($ownerData['alliance']); + return true; + } + function checkEmbassiesAfterBattle($vid, $current_level, $use_cache = true) { $userData = $this->getUserArray($this->getVillageField($vid, "owner"), 1); diff --git a/GameEngine/Form.php b/GameEngine/Form.php index d15a7124..cc61fba7 100755 --- a/GameEngine/Form.php +++ b/GameEngine/Form.php @@ -6,7 +6,7 @@ ## Filename Form.php ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## ## ## Refactor: Incremental cleanup (compatibility preserved) ## ## Notes: PHP 7+ / legacy safe ## diff --git a/GameEngine/Generator.php b/GameEngine/Generator.php index e8a78e70..185d3c72 100755 --- a/GameEngine/Generator.php +++ b/GameEngine/Generator.php @@ -7,7 +7,7 @@ ## Version: 18.05.2026 ## ## Filename: Generator.php ## ## Developed by: Dzoki ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## ## diff --git a/GameEngine/Logging.php b/GameEngine/Logging.php index 2b235471..ccb5df34 100755 --- a/GameEngine/Logging.php +++ b/GameEngine/Logging.php @@ -7,7 +7,7 @@ ## Version: 12.05.2026 ## ## Filename: Logging.php ## ## Developed by: Shadow ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## ## diff --git a/GameEngine/Market.php b/GameEngine/Market.php index 9e8bd712..7fda94dd 100755 --- a/GameEngine/Market.php +++ b/GameEngine/Market.php @@ -5,7 +5,7 @@ ## Filename Market.php ## ## Developed by: Dzoki ## ## Some fixes: aggenkeech ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## URLs: https://travianz.org ## diff --git a/GameEngine/Profile.php b/GameEngine/Profile.php index c2d602db..5a8519fc 100755 --- a/GameEngine/Profile.php +++ b/GameEngine/Profile.php @@ -6,7 +6,7 @@ ## Filename Profile.php ## ## Filename: Account.php ## ## Developed by: Dzoki ## -## Refactored by: Shadow ## +## Refactored by: Shadow (cata7007@gmail.com) ## ## License: TravianZ Project ## ## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## ## URLs: https://travianz.org ## diff --git a/version.php b/version.php index e392e01c..d44a2234 100644 --- a/version.php +++ b/version.php @@ -86,10 +86,10 @@ $developers = [ ["Shadow", "Project Owner"], ["Advocaite", "Developer"], ["yi12345", "Alumni Developer"], - ["NarcisRO", "bug hunter"], + ["iopietro", "Alumni Developer"], ["brainiacX", "Alumni Developer"], ["InCube", "Alumni Developer"], - ["akshay9", "Alumni Developer"], + ["martinambrus", "Alumni Developer"], ["KFCSpike", "Alumni Developer"], ["nean", "Alumni Developer"], ["hexcoded", "Alumni Developer"], @@ -114,8 +114,8 @@ $developers = [ ["aggenkeech", "Alumni Developer"], ["Niko28", "Alumni Developer"], ["221V", "Developer"], - ["martinambrus", "Alumni Developer"], - ["iopietro", "Alumni Developer"], + ["akshay9", "Alumni Developer"], + ["NarcisRO", "Bug Hunter"], ["Vladyslav", "Rigorous game tester"], ["AL-Kateb", "Alumni Developer"], ["hdmaniak2", " Active Developer"],