diff --git a/GameEngine/Automation.php b/GameEngine/Automation.php index ffdaeafc..f54ec828 100644 --- a/GameEngine/Automation.php +++ b/GameEngine/Automation.php @@ -3,18 +3,23 @@ ################################################################################# ## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ## ## --------------------------------------------------------------------------- ## -## Project: TravianZ ## -## Version: 22.06.2015 ## -## Filename Automation.php ## -## Developed by: Mr.php , Advocaite , brainiacX , yi12345 , Shadow , ronix ## -## Fixed by: Shadow - STARVATION , HERO FIXED COMPL. ## -## Fixed by: InCube - double troops ## -## Refactor by: Shadow ## -## License: TravianZ Project ## -## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## -## URLs: https://travianz.org ## -## https://github.com/Shadowss/TravianZ ## -## ## +## Filename : Automation.php ## +## Type : Automation Function for entire TravianZ Game ## +## --------------------------------------------------------------------------- ## +## Developed by : Mr.php , Advocaite , brainiacX , yi12345 , Shadow , ronix ## +## Refactored by : Shadow & Ferywir ## +## Thanks to : InCube, Akakori, Elmar & Kirilloid ## +## Split&Refactor : Shadow ## +## Phase S2 : Methods split into 11 traits (GameEngine/Automation/) ## +## --------------------------------------------------------------------------- ## +## Contact : cata7007@gmail.com ## +## Project : TravianZ ## +## URLs: : https://travianz.org ## +## GitHub : https://github.com/Shadowss/TravianZ ## +## --------------------------------------------------------------------------- ## +## License : TravianZ Project ## +## Copyright : TravianZ (c) 2010-2026. All rights reserved. ## +## --------------------------------------------------------------------------- ## ################################################################################# // make sure we only run the automation script once and wait until it's done, @@ -53,7 +58,35 @@ include_once("Multisort.php"); include_once("Building.php"); include_once("Artifacts.php"); +// === Faza S2: clasa Automation este impartita in trait-uri pe domenii (GameEngine/Automation/) === +// Trait-urile sunt in namespace global, deci sunt incluse explicit (autoloaderul mapeaza doar App\). +include_once __DIR__ . '/Automation/AutomationVillageUpkeep.php'; +include_once __DIR__ . '/Automation/AutomationAccountMaintenance.php'; +include_once __DIR__ . '/Automation/AutomationBuildQueue.php'; +include_once __DIR__ . '/Automation/AutomationMarket.php'; +include_once __DIR__ . '/Automation/AutomationBattleResolution.php'; +include_once __DIR__ . '/Automation/AutomationTroopMovements.php'; +include_once __DIR__ . '/Automation/AutomationTraining.php'; +include_once __DIR__ . '/Automation/AutomationHero.php'; +include_once __DIR__ . '/Automation/AutomationStarvation.php'; +include_once __DIR__ . '/Automation/AutomationNatarsWW.php'; +include_once __DIR__ . '/Automation/AutomationMedals.php'; + class Automation { + // === Faza S2: metodele clasei, grupate pe domenii === + use AutomationVillageUpkeep; + use AutomationAccountMaintenance; + use AutomationBuildQueue; + use AutomationMarket; + use AutomationBattleResolution; + use AutomationTroopMovements; + use AutomationTraining; + use AutomationHero; + use AutomationStarvation; + use AutomationNatarsWW; + use AutomationMedals; + + /** * @var object The artifacts class, used to create Natars, artifacts and obtaining info about them @@ -129,5444 +162,6 @@ class Automation { } 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 ? RC_VILLAGE_CANT_BE : $build); - } - - function recountPop($vid, $use_cache = true){ - global $database; - - $vid = (int) $vid; - $fdata = $database->getResourceLevel($vid, $use_cache); - $popTot = 0; - - for ($i = 1; $i <= 40; $i++) { - $lvl = $fdata["f".$i]; - $building = $fdata["f".$i."t"]; - if($building) $popTot += $this->buildingPOP($building, $lvl); - } - - Building::recountCP($database, $vid); - $q = "UPDATE ".TB_PREFIX."vdata set pop = $popTot where wref = $vid"; - mysqli_query($database->dblink, $q); - $owner = $database->getVillageField($vid, "owner"); - - // Milestone: first player ever to reach 1000 total population, - // summed across all their villages. recountPop() is the single - // funnel every population-changing event (building, demolishing, - // founding/conquering a village) already passes through, so this - // is the one place that's guaranteed to catch the threshold being - // crossed regardless of which village/action caused it. - // Excludes owner 3 (Natars) — see Artifacts::NATARS_UID — same - // convention already used elsewhere in this file (e.g. the - // "fix natar report by ronix" check a few hundred lines below). - if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES && $owner > 0 && $owner != 3) { - $totalPop = (int) $database->getVSumField($owner, 'pop', false); - if ($totalPop >= 1000) { - $database->recordMilestoneIfFirst('population_1000', $owner, $vid); - } - } - - $this->procClimbers($owner); - - return $popTot; - } - - function buildingPOP($f, $lvl){ - $name = "bid".$f; - global $$name; - - $popT = 0; - $dataarray = $$name; - - for ($i = 0; $i <= $lvl; $i++) { - $popT += ((isset($dataarray[$i]) && isset($dataarray[$i]['pop'])) ? $dataarray[$i]['pop'] : 0); - } - return $popT; - } - - private function loyaltyRegeneration() { - global $database; - - $array = []; - $array = $database->getProfileVillages(0, 6); - if(!empty($array)) { - foreach($array as $loyalty) { - if (($t25_level = $this->getTypeLevel(25, $loyalty['wref'])) >= 1) { - $value = $t25_level; - }elseif(($t26_level = $this->getTypeLevel(26, $loyalty['wref'])) >= 1){ - $value = $t26_level; - } - else $value = 0; - - if($value > 0){ - $newloyalty = min(100, $loyalty['loyalty'] + $value * (time() - $loyalty['lastupdate2']) / 3600); - $q = "UPDATE ".TB_PREFIX."vdata SET loyalty = $newloyalty, lastupdate2=".time()." WHERE wref = '".$loyalty['wref']."'"; - $database->query($q); - } - } - } - - $array = []; - $q = "SELECT conqured, loyalty, lastupdated, wref FROM ".TB_PREFIX."odata WHERE loyalty < 100"; - $array = $database->query_return($q); - if(!empty($array)) { - foreach($array as $loyalty) { - $value = $this->getTypeLevel(37, $loyalty['conqured']); - - if($value > 0){ - $newloyalty = min(100, $loyalty['loyalty'] + $value * (time() - $loyalty['lastupdated']) / 3600); - $q = "UPDATE ".TB_PREFIX."odata SET loyalty = $newloyalty, lastupdated=".time()." WHERE wref = '".$loyalty['wref']."'"; - $database->query($q); - } - } - } - } - - public function getTypeLevel($tid, $vid) { - global $database; - - $keyholder = []; - - $resourcearray = $database->getResourceLevel($vid); - foreach(array_keys($resourcearray, $tid) as $key) { - if(strpos($key,'t')) { - $key = preg_replace("/[^0-9]/", '', $key); - array_push($keyholder, $key); - } - } - - $element = count($keyholder); - if($element >= 2) { - if($tid <= 4) { - $temparray = []; - for($i = 0; $i <= $element - 1; $i++) { - array_push($temparray,$resourcearray['f'.$keyholder[$i]]); - } - foreach ($temparray as $key => $val) { - if ($val == max($temparray)) $target = $key; - } - } - else { - $target = 0; - for($i = 1; $i <= $element - 1; $i++) { - if($resourcearray['f'.$keyholder[$i]] > $resourcearray['f'.$keyholder[$target]]) { - $target = $i; - } - } - } - } - else if($element == 1) $target = 0; - else return 0; - - if(!empty($keyholder[$target])) return $resourcearray['f'.$keyholder[$target]]; - else return 0; - } - - private function clearDeleting() { - global $database; - - $needDelete = $database->getNeedDelete(); - if(count($needDelete) > 0) { - - //Remove the time limit, otherwise deleting players with 80 or more villages couldn't be deleted in one run - @set_time_limit(0); - - foreach($needDelete as $need) { - $need['uid'] = (int) $need['uid']; - - //Get the villages which have to be deleted - $needVillages = $database->getVillagesID($need['uid']); - - //Delete all villages - $database->DelVillage($needVillages); - - for($i = 0;$i < 20; $i++){ - $q = "SELECT id FROM ".TB_PREFIX."users where friend".$i." = ".$need['uid']." or friend".$i."wait = ".$need['uid'].""; - $array = $database->query_return($q); - foreach($array as $friend){ - $database->deleteFriend($friend['id'],"friend".$i); - $database->deleteFriend($friend['id'],"friend".$i."wait"); - } - } - - $database->updateUserField($need['uid'], 'alliance', 0, 1); - - if($database->isAllianceOwner($need['uid'])){ - $alliance = $database->getUserAllianceID($need['uid']); - $newowner = $database->getAllMember2($alliance); - $newleader = $newowner['id']; - $q = "UPDATE " . TB_PREFIX . "alidata set leader = ".(int) $newleader." where id = ".(int) $alliance.""; - $database->query($q); - $database->updateAlliPermissions($newleader, $alliance, "Leader", 1, 1, 1, 1, 1, 1, 1); - Automation::updateMax($newleader); - } - - if (isset($alliance)) $database->deleteAlliance($alliance); - - $q = "DELETE FROM ".TB_PREFIX."hero where uid = ".$need['uid']; - $database->query($q); - - $q = "DELETE FROM ".TB_PREFIX."mdata where target = ".$need['uid']." or owner = ".$need['uid']; - $database->query($q); - - $q = "DELETE FROM ".TB_PREFIX."ndata where uid = ".$need['uid']; - $database->query($q); - - $q = "DELETE FROM ".TB_PREFIX."users where id = ".$need['uid']; - $database->query($q); - - $q = "DELETE FROM ".TB_PREFIX."deleting where uid = ".$need['uid']; - $database->query($q); - } - } - } - - private function ClearUser() { - global $database; - - if(AUTO_DEL_INACTIVE) { - $time = time() - UN_ACT_TIME; - - $q = "INSERT INTO ".TB_PREFIX."deleting SELECT id, UNIX_TIMESTAMP() FROM ".TB_PREFIX."users WHERE timestamp < $time AND tribe IN(1, 2, 3)"; - $database->query($q); - } - } - - private function ClearInactive() { - global $database; - - if(TRACK_USR) { - $timeout = time()-USER_TIMEOUT * 60; - $q = "DELETE FROM ".TB_PREFIX."active WHERE timestamp < $timeout"; - $database->query($q); - } - } - - // Clamp resources/storage up to their minimums for the given table (vdata or - // odata): negative resources back to 0 and maxstore/maxcrop back to - // STORAGE_BASE. This floor pass is identical for both tables, so both - // pruneResource() (vdata) and pruneOResource() (odata) share it. The $table - // argument is an internal constant string, never user input. - private function pruneResourceMinimums($table) { - global $database; - - $database->query("UPDATE - ".TB_PREFIX.$table." - SET - wood = IF(wood < 0, 0, wood), - clay = IF(clay < 0, 0, clay), - iron = IF(iron < 0, 0, iron), - crop = IF(crop < 0, 0, crop), - maxstore = IF(maxstore < ".STORAGE_BASE.", ".STORAGE_BASE.", maxstore), - maxcrop = IF(maxcrop < ".STORAGE_BASE.", ".STORAGE_BASE.", maxcrop) - WHERE - maxstore < ".STORAGE_BASE." OR - maxcrop < ".STORAGE_BASE." OR - wood < 0 OR - clay < 0 OR - iron < 0 OR - crop < 0"); - } - - private function pruneOResource() { - if(!ALLOW_BURST) { - $this->pruneResourceMinimums('odata'); - } - } - private function pruneResource() { - global $database; - - if(!ALLOW_BURST) { - $this->pruneResourceMinimums('vdata'); - - $database->query("UPDATE - ".TB_PREFIX."vdata - SET - wood = IF(wood > maxstore, maxstore, wood), - clay = IF(clay > maxstore, maxstore, clay), - iron = IF(iron > maxstore, maxstore, iron), - crop = IF(crop > maxcrop, maxcrop, crop) - WHERE - wood > maxstore OR - clay > maxstore OR - iron > maxstore OR - crop > maxcrop"); - } - } - - private function culturePoints() { - global $database; - - $database->updateVSumField('cp'); - } - - private function buildComplete() { - global $database; - - $time = time(); - // IDs of villages that were affected by this building completion update, - // used to calculate statistical data at the end - $villagesAffected = []; - // holds additional conditions when updating loopcon records in the bdata table - $loopconUpdates = []; - // this will hold IDs of bdata table records to delete - $dbIdsToDelete = []; - - // get all pending builds that should be complete by now - $res = $database->query_return( - "SELECT - id, wid, field, level, type, timestamp - FROM - ".TB_PREFIX."bdata - WHERE - timestamp < $time and master = 0" - ); - - // preload village data - $vilIDs = []; - foreach($res as $indi) { - $vilIDs[$indi['wid']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getProfileVillages($vilIDs, 5); - $database->getEnforceVillage($vilIDs, 0); - - // complete buildings - foreach($res as $indi) { - // store village ID for later for statistical updates - $villageData = $database->getVillageFields($indi['wid'],'owner, maxcrop, maxstore, starv, pop'); - $villageOwner = $villageData['owner']; - $villagesAffected[] = (int) $indi['wid']; - $fieldsToSet = []; - - $q = "UPDATE ".TB_PREFIX."fdata SET f".$indi['field']." = ".$indi['level'].", f".$indi['field']."t = ".$indi['type']." WHERE vref = ".(int) $indi['wid']; - - if($database->query($q)) { - // this will be the level we brought the building to now - $level = $indi['level']; - - // TODO: magic numbers into constants (for building types below) - - // update capacity if we updated a warehouse or a granary - if (in_array($indi['type'], [10, 11, 38, 39])) { - [$fieldDbName, $max] = $this->updateStorageCapacity($indi['type'], $level, $villageData); - $fieldsToSet[$fieldDbName] = $max; - } - - // if we updated Embassy, update maximum members that the alliance can take - if($indi['type'] == 18) Automation::updateMax($villageOwner); - - // World Wonder completion handling (Natar attacks, winner lock, last-upgrade time) - if ($indi['type'] == 40) $this->completeWorldWonder($indi); - - // TODO: find out what exactly these conditions are for - // no special military conditioning for Teutons and Gauls - if ($database->getUserField($villageOwner, "tribe", 0) != 1) $loopconUpdates[$indi['wid']] = ''; - else - { - // special condition for Roman military buildings - if ($indi['field'] > 18) $loopconUpdates[$indi['wid']] = ' AND field > 18'; - else $loopconUpdates[$indi['wid']] = ' AND field < 19'; - } - - $dbIdsToDelete[] = (int) $indi['id']; - } - - //Update starvation data - $database->addStarvationData($indi['wid']); - - // update the requested fields, all at once - $database->setVillageFields($indi['wid'], array_keys($fieldsToSet), array_values($fieldsToSet)); - } - - // update statistical data for affected villages - foreach ($villagesAffected as $affected_id) $this->recountPop($affected_id, false); - - // update data that can be done in one swoop instead of using multiple update queries - // no special checks for Romans - foreach ($loopconUpdates as $villageId => $updateCondition) { - $database->query( - "UPDATE - ".TB_PREFIX."bdata - SET - loopcon = 0 - WHERE - loopcon = 1 AND - master = 0 AND - wid = ".$villageId.$updateCondition); - } - - // delete all processed entries - if (count($dbIdsToDelete)) { - $database->query( "DELETE FROM " . TB_PREFIX . "bdata WHERE id IN(" . implode( ',', $dbIdsToDelete ) . ")" ); - } - } - - /** - * Recompute a warehouse/granary capacity after a build completion. - * Returns [$fieldDbName, $max]: the vdata column to update and its new value. - */ - private function updateStorageCapacity($type, $level, $villageData) { - global $bid10, $bid11, $bid38, $bid39; - - $fieldDbName = (in_array($type, [10, 38]) ? 'maxstore' : 'maxcrop'); - $max = $villageData[$fieldDbName]; - - if($level == 1 && $max == STORAGE_BASE) $max = STORAGE_BASE; - - if ($level != 1) $max -= ${'bid'.$type}[$level - 1]['attri'] * STORAGE_MULTIPLIER; - - $max += ${'bid'.$type}[$level]['attri'] * STORAGE_MULTIPLIER; - - return [$fieldDbName, $max]; - } - - /** - * Handle the side effects of completing a World Wonder (type 40) build: - * launch the Natar attack waves, lock out further winners at level 100, - * and record the last upgrade time. - */ - private function completeWorldWonder($indi) { - global $database; - - if (($indi['level'] % 5 == 0 || $indi['level'] > 95) && $indi['level'] != 100) { - $this->startNatarAttack($indi['level'], $indi['wid'], $indi['timestamp']); - } - - //now can't be more than one winner if ww to level 100 is build by 2 users or more on same time - if ($indi['level'] == 100) { - mysqli_query($database->dblink,"TRUNCATE ".TB_PREFIX."bdata"); - } - - // Update ww last finish upgrade - $qW = "UPDATE ".TB_PREFIX."fdata set ww_lastupdate = ".time()." where vref = ".(int) $indi['wid']; - $database->query($qW); - } - - private function startNatarAttack($level, $vid, $time) { - global $database; - - // bad, but should work :D - // I took the data from my first ww (first .org world) - // TODO: get the algo from the real travian with the 100 biggest offs - - // select the troops^^ - $troops = $this->getNatarTroopTable(); - if (isset($troops[$level])) $units = $troops[$level]; - else return false; - - // get the capital village from the natars - $query = mysqli_query($database->dblink,'SELECT `wref` FROM `' . TB_PREFIX . 'vdata` WHERE `owner` = 3 and `capital` = 1 LIMIT 1') or die(mysqli_error($database->dblink)); - $row = mysqli_fetch_assoc($query); - - // start the attacks - $endtime = $time + round(86400 / INCREASE_SPEED); - - // -.- - $vid = (int) $vid; - mysqli_query($database->dblink,'INSERT INTO `' . TB_PREFIX . 'ww_attacks` (`vid`, `attack_time`) VALUES (' . $vid . ', ' . $endtime . ')'); - mysqli_query($database->dblink,'INSERT INTO `' . TB_PREFIX . 'ww_attacks` (`vid`, `attack_time`) VALUES (' . $vid . ', ' . ($endtime + 1) . ')'); - - // two waves: the second one targets the WW (catapult target 40) one second later - $this->launchNatarWave($row['wref'], $vid, $units[0], 0, $time, $endtime); - $this->launchNatarWave($row['wref'], $vid, $units[1], 40, $time, $endtime + 1); - } - - /** - * Fire a single Natar attack wave at a World Wonder village. - * $unitRow is one troop row [t2, t3, t5, t6, t7, t8]; $ctar1 is the - * catapult target slot (0 for the first wave, 40 for the second). - */ - private function launchNatarWave($source, $vid, $unitRow, $ctar1, $time, $arrival) { - global $database; - - $ref = $database->addAttack($source, 0, $unitRow[0], $unitRow[1], 0, $unitRow[2], $unitRow[3], $unitRow[4], $unitRow[5], 0, 0, 0, 3, $ctar1, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20); - $database->addMovement(3, $source, $vid, $ref, $time, $arrival); - } - - /** - * Hard-coded Natar offensive waves indexed by World Wonder level. - * Each level holds two waves, each a troop row [t2, t3, t5, t6, t7, t8]. - */ - private function getNatarTroopTable() { - return [5 => [[3412, 2814, 4156, 3553, 9, 0], [35, 0, 77, 33, 17, 10]], - 10 => [[4314, 3688, 5265, 4621, 13, 0], [65, 0, 175, 77, 28, 17]], - 15 => [[4645, 4267, 5659, 5272, 15, 0], [99, 0, 305, 134, 40, 25]], - 20 => [[6207, 5881, 7625, 7225, 22, 0], [144, 0, 456, 201, 56, 36]], - 25 => [[6004, 5977, 7400, 7277, 23, 0], [152, 0, 499, 220, 58, 37]], - 30 => [[7073, 7181, 8730, 8713, 27, 0], [183, 0, 607, 268, 69, 45]], - 35 => [[7090, 7320, 8762, 8856, 28, 0], [186, 0, 620, 278, 70, 45]], - 40 => [[7852, 6967, 9606, 8667, 25, 0], [146, 0, 431, 190, 60, 37]], - 45 => [[8480, 8883, 10490, 10719, 35, 0], [223, 0, 750, 331, 83, 54]], - 50 => [[8522, 9038, 10551, 10883, 35, 0], [224, 0, 757, 335, 83, 54]], - 55 => [[8931, 8690, 10992, 10624, 32, 0], [219, 0, 707, 312, 84, 54]], - 60 => [[12138, 13013, 15040, 15642, 51, 0], [318, 0, 1079, 477, 118, 76]], - 65 => [[13397, 14619, 16622, 17521, 58, 0], [345, 0, 1182, 522, 127, 83]], - 70 => [[16323, 17665, 20240, 21201, 70, 0], [424, 0, 1447, 640, 157, 102]], - 75 => [[20739, 22796, 25746, 27288, 91, 0], [529, 0, 1816, 803, 194, 127]], - 80 => [[21857, 24180, 27147, 28914, 97, 0], [551, 0, 1898, 839, 202, 132]], - 85 => [[22476, 25007, 27928, 29876, 100, 0], [560, 0, 1933, 855, 205, 134]], - 90 => [[31345, 35053, 38963, 41843, 141, 0], [771, 0, 2668, 1180, 281, 184]], - 95 => [[31720, 35635, 39443, 42506, 144, 0], [771, 0, 2671, 1181, 281, 184]], - 96 => [[32885, 37007, 40897, 44130, 150, 0], [795, 0, 2757, 1219, 289, 190]], - 97 => [[32940, 37099, 40968, 44235, 150, 0], [794, 0, 2755, 1219, 289, 190]], - 98 => [[33521, 37691, 41686, 44953, 152, 0], [812, 0, 2816, 1246, 296, 194]], - 99 => [[36251, 40861, 45089, 48714, 165, 0], [872, 0, 3025, 1338, 317, 208]]]; - } - - private function checkWWAttacks() { - global $database; - - $query = mysqli_query($database->dblink,'SELECT vid, attack_time FROM `' . TB_PREFIX . 'ww_attacks` WHERE `attack_time` <= ' . time()); - while ($row = mysqli_fetch_assoc($query)) - { - // delete the attack - $query3 = mysqli_query($database->dblink,'DELETE FROM `' . TB_PREFIX . 'ww_attacks` WHERE `vid` = ' . (int) $row['vid'] . ' AND `attack_time` = ' . (int) $row['attack_time']); - } - } - - private function getPop($tid, $level) { - $name = "bid".$tid; - global $$name; - - $dataarray = $$name; - $pop = $dataarray[($level + 1)]['pop']; - $cp = $dataarray[($level + 1)]['cp']; - return [$pop, $cp]; - } - - private function delTradeRoute() { - global $database; - - $database->delTradeRoute(); - } - - private function TradeRoute() { - global $database; - $time = time(); - $q = "SELECT `from`, wood, clay, iron, crop, wid, deliveries, id FROM ".TB_PREFIX."route where timestamp < $time"; - $dataarray = $database->query_return($q); - - $vilIDs = []; - foreach($dataarray as $data) { - $vilIDs[$data['wid']] = true; - $vilIDs[$data['from']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getVillageByWorldID($vilIDs); - - foreach($dataarray as $data) { - $targettribe = $database->getUserField($database->getVillageField($data['from'], "owner"), "tribe", 0); - $this->sendResource2($data['wood'], $data['clay'], $data['iron'], $data['crop'], $data['from'], $data['wid'], $targettribe, $data['deliveries']); - $database->editTradeRoute($data['id'], "timestamp", 86400, 1); - } - } - - private function marketComplete() { - // Two independent phases share a single cutoff timestamp: sort_type = 0 - // new trade deliveries, then sort_type = 2 resending of resources. - $time = microtime(true); - $this->marketCompleteDeliveries($time); - $this->marketCompleteResends($time); - } - - private function marketCompleteDeliveries($time) { - global $database, $units; - - $q = "SELECT s.wood, s.clay, s.iron, s.crop, `to`, `from`, endtime, merchant, send, moveid FROM ".TB_PREFIX."movement m, ".TB_PREFIX."send s WHERE m.ref = s.id AND m.proc = 0 AND sort_type = 0 AND endtime < $time"; - $dataarray = $database->query_return($q); - - foreach($dataarray as $data) { - $userData_from = $database->getUserFields($database->getVillageField($data['from'], "owner"), "alliance, tribe", 0); - $userData_to = $database->getUserFields($database->getVillageField($data['to'], "owner"), "alliance, tribe", 0); - - if($data['wood'] >= $data['clay'] && $data['wood'] >= $data['iron'] && $data['wood'] >= $data['crop']) $sort_type = 10; - elseif($data['clay'] >= $data['wood'] && $data['clay'] >= $data['iron'] && $data['clay'] >= $data['crop']) $sort_type = 11; - elseif($data['iron'] >= $data['wood'] && $data['iron'] >= $data['clay'] && $data['iron'] >= $data['crop']) $sort_type = 12; - elseif($data['crop'] >= $data['wood'] && $data['crop'] >= $data['clay'] && $data['crop'] >= $data['iron']) $sort_type = 13; - - $to = $database->getMInfo($data['to']); - $from = $database->getMInfo($data['from']); - - $ownally = $userData_from['alliance']; - $targetally = $userData_to['alliance']; - - // Report filter preferences (#198): skip merchant-transfer notices - // according to the saved checkboxes of the involved players. - // v4 -> recipient: no report for transfers to own villages - // v6 -> recipient: no report for transfers from foreign villages - // v5 -> sender: no report for transfers to foreign villages - $ownTransfer = ($from['owner'] == $to['owner']); - $skipRecipient = $ownTransfer ? !empty($userData_to['v4']) : !empty($userData_to['v6']); - if(!$skipRecipient) { - $database->addNotice($to['owner'],$to['wref'],$targetally,$sort_type,''.addslashes($from['name']).' send resources to '.addslashes($to['name']).'',''.$from['owner'].','.$from['wref'].','.$data['wood'].','.$data['clay'].','.$data['iron'].','.$data['crop'].'',$data['endtime']); - } - if(!$ownTransfer && empty($userData_from['v5'])) { - $database->addNotice($from['owner'],$to['wref'],$ownally,$sort_type,''.addslashes($from['name']).' send resources to '.addslashes($to['name']).'',''.$from['owner'].','.$from['wref'].','.$data['wood'].','.$data['clay'].','.$data['iron'].','.$data['crop'].'',$data['endtime']); - } - $database->modifyResource($data['to'],$data['wood'],$data['clay'],$data['iron'],$data['crop'],1); - - // Push protection: record completed cross-player deliveries so the - // admin dashboard can compute 7-day resource balance. Best-effort; - // skips own-village transfers internally. - if (!$ownTransfer) { - PushProtection::logTransfer( - (int)$from['wref'], (int)$to['wref'], - (int)$from['owner'], (int)$to['owner'], - (int)$data['wood'], (int)$data['clay'], (int)$data['iron'], (int)$data['crop'], - (int)$data['endtime'] - ); - } - $targettribe = $userData_to["tribe"]; - $endtime = $units->getWalkingTroopsTime($data['from'], $data['to'], 0, 0, [$targettribe], 0) + $data['endtime']; - $database->addMovement(2, $data['to'], $data['from'], $data['merchant'], time(), $endtime, $data['send'], $data['wood'], $data['clay'], $data['iron'], $data['crop']); - $database->setMovementProc($data['moveid']); - } - } - - private function marketCompleteResends($time) { - global $database; - - $q1 = "SELECT send, moveid, `to`, wood, clay, iron, crop, `from` FROM ".TB_PREFIX."movement WHERE proc = 0 and sort_type = 2 and endtime < $time"; - $dataarray1 = $database->query_return($q1); - - $vilIDs = []; - foreach($dataarray1 as $data1) { - $vilIDs[$data1['to']] = true; - $vilIDs[$data1['from']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getVillageByWorldID($vilIDs); - - foreach($dataarray1 as $data1) { - $database->setMovementProc($data1['moveid']); - if($data1['send'] > 1){ - $targettribe1 = $database->getUserFields($database->getVillageField($data1['to'],"owner"),"alliance, tribe",0)['tribe']; - $send = $data1['send']-1; - $this->sendResource2($data1['wood'],$data1['clay'],$data1['iron'],$data1['crop'],$data1['to'],$data1['from'],$targettribe1,$send); - } - } - } - - private function sendResource2($wtrans, $ctrans, $itrans, $crtrans, $from, $to, $tribe, $send) { - global $bid17, $bid28, $database, $units; - - $availableWood = $database->getWoodAvailable($from); - $availableClay = $database->getClayAvailable($from); - $availableIron = $database->getIronAvailable($from); - $availableCrop = $database->getCropAvailable($from); - - if($availableWood + $availableClay + $availableIron + $availableCrop > 0) - { - if($availableWood < $wtrans) $wtrans = $availableWood; - if($availableClay < $ctrans) $ctrans = $availableClay; - if($availableIron < $itrans) $itrans = $availableIron; - if($availableCrop < $crtrans) $crtrans = $availableCrop; - - $merchant2 = ($this->getTypeLevel(17, $from) > 0)? $this->getTypeLevel(17, $from) : 0; - $used2 = $database->totalMerchantUsed($from, false); - $merchantAvail2 = $merchant2 - $used2; - $carrymap = array(1 => 500, 2 => 1000, 3 => 750, 6 => 500, 7 => 750, 8 => 500, 9 => 750); - $maxcarry2 = isset($carrymap[$tribe]) ? $carrymap[$tribe] : 750; - $maxcarry2 *= TRADER_CAPACITY; - - if($this->getTypeLevel(28, $from) != 0) { - $maxcarry2 *= $bid28[$this->getTypeLevel(28, $from)]['attri'] / 100; - } - - $resource = [$wtrans, $ctrans, $itrans, $crtrans]; - $reqMerc = ceil((array_sum($resource) - 0.1) / $maxcarry2); - - if($merchantAvail2 > 0 && $reqMerc <= $merchantAvail2) { - if($database->getVillageState($to)) { - $timetaken = $units->getWalkingTroopsTime($from, $to, 0, 0, [$tribe], 0); - $res = $resource[0] + $resource[1] + $resource[2] + $resource[3]; - if($res > 0){ - $reference = $database->sendResource($resource[0], $resource[1], $resource[2], $resource[3], $reqMerc, 0); - $database->modifyResource($from, $resource[0], $resource[1], $resource[2], $resource[3], 0); - $database->addMovement(0, $from, $to, $reference, microtime(true), microtime(true) + $timetaken, $send); - } - } - } - } - } - - private function resolveCatapultsDestruction(&$bdo, &$battlepart, &$info_cat, &$data, $catapultTarget, $twoRowsCatapultSetup, $isSecondRow, $catp_pic, $can_destroy, $isoasis, &$village_destroyed, $tribe) { - global $battle, $database, $bid34; - - if(isset($catapultTarget)) - { - //Currently targeted building/field level - $tblevel = (int) $bdo['f'.$catapultTarget]; - //Currently targetet building/field GID (ID of the building/field type - woodcutter, cropland, embassy...) - $tbgid = (int) $bdo['f'.$catapultTarget.'t']; - //Currently targeted building/field ID in the database (fdata, the fID field, e.g. f1, f2, f3...) - $tbid = (int) $catapultTarget; - - //If we're targeting the WW - if($catapultTarget == 40){ - $battlepart['catapults']['strongerBuildings'] = 1; - $battlepart['catapults']['moraleBonus'] = 1; - } - - $catapultsDamage = $battle->calculateCatapultsDamage($data['t8'], - $battlepart['catapults']['upgrades'], - $battlepart['catapults']['durability'], - $battlepart['catapults']['attackDefenseRatio'], - $battlepart['catapults']['strongerBuildings'], - $battlepart['catapults']['moraleBonus']); - - $newLevel = $battle->calculateNewBuildingLevel($tblevel, $catapultsDamage / ($twoRowsCatapultSetup ? 2 : 1)); - - //fix: Only modify build queue if actual damage was dealt - if ($newLevel < $tblevel) { - $database->modifyBData($data['to'], $tbid, [$newLevel, $tblevel], $tribe); - } - - // building/field destroyed - if ($newLevel == 0){ - // prepare data to be updated - $fieldsToSet = ["f".$tbid]; - $fieldValuesToSet = [0]; - - // update $bdo, so we don't have to reselect later - $bdo['f'.$catapultTarget] = 0; - - if ($tbid >= 19 && $tbid != 99) { - $fieldsToSet[] = "f".$tbid."t"; - $fieldValuesToSet[] = 0; - $bdo['f'.$catapultTarget."t"] = 0; - } - - // update all that needs updating - $database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet); - - $buildarray = $GLOBALS["bid".$tbgid]; - - if ( isset( $buildarray[$newLevel] ) ) { - // (great) warehouse level was changed - if ($tbgid == 10 || $tbgid == 38) { - $database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']); - } - - // (great) granary level was changed - if ($tbgid == 11 || $tbgid == 39) { - $database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']); - } - } - - // oasis cannot be destroyed - $pop = $this->recountPop($data['to'], false); - if ($isoasis == 0 && $pop == 0 && $can_destroy == 1) $village_destroyed = 1; - - if ($isSecondRow) { - if ($tbid > 0) { - $info_cat .= "".INFORMATION." - \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_DESTROYED')."."; - } - - // embassy level was changed - if ($tbgid == 18){ - $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); - } - - $info_cat .= ""; - } else { - $info_cat = "".$catp_pic.", ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_DESTROYED')."."; - - // embassy level was changed - if ($tbgid == 18){ - $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); - } - } - } - // building/field not damaged - elseif($newLevel == $tblevel){ - if($isSecondRow) { - if ($tbid > 0) { - $info_cat .= "".INFORMATION." - \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED').""; - } - } else { - $info_cat = "".$catp_pic.",".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED'); - } - } - // building/field was damaged, let's calculate the actual damage - else - { - // update $bdo, so we don't have to reselect later - $bdo['f'.$catapultTarget] = $newLevel; - - // building was damaged to a lower level - $info_cata = " ".rc_tok('RC_DAMAGED_FROM_TO', $tblevel, $newLevel); - - $buildarray = $GLOBALS["bid".$tbgid]; - - // (great) warehouse level was changed - if ($tbgid == 10 || $tbgid == 38) { - $database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']); - } - - // (great) granary level was changed - if ($tbgid == 11 || $tbgid == 39) { - $database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']); - } - - $fieldsToSet = ["f".$tbid]; - $fieldValuesToSet = [$newLevel]; - - $database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet); - - // recalculate population and check if the village shouldn't be destroyed at this point - $pop = $this->recountPop($data['to'], false); - if ($isoasis == 0) { - if($pop == 0 && $can_destroy == 1) $village_destroyed = 1; - } - - if ($isSecondRow) { - $info_cat .= "".INFORMATION." - \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy).$info_cata; - - // embassy level was changed - if ($tbgid == 18) { - $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); - } - - $info_cat .= ""; - } else { - $info_cat = "" . $catp_pic . "," . rc_bld($tbgid, $can_destroy).$info_cata; - - // embassy level was changed - if ($tbgid == 18) { - $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); - } - } - } - }else{ - if(!isset($info_cat) || empty($info_cat) || $info_cat == ","){ - $info_cat = "".$catp_pic.", ".rc_tok('RC_NO_BUILDINGS'); - }else if(strpos($info_cat, rc_tok('RC_NO_BUILDINGS')) === false){ - $info_cat .= "".INFORMATION." - \"".rc_tok('RC_CATAPULT')."\" ".rc_tok('RC_NO_BUILDINGS')."."; - } - } - } - - private function applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat) - { - global $database; - - // village_destroyed stays unset in the pop<=0 path of the original; - // downstream treats unset as 0, so initialising to 0 here is equivalent. - $village_destroyed = 0; - - $pop = $this->recountPop($data['to']); - - // village has been destroyed - if ($pop <= 0) { - if ($can_destroy == 1) $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_ALREADY_DESTROYED'); - else $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_CANT_DESTROY'); - } - else - { - // village stands, let's do the damage - /** - * FIRST CATAPULTS ROW - */ - - $basearray = $data['to']; - $bdo = $database->getResourceLevel($basearray, false); - $catapultTarget = $data['ctar1']; - $catapultTarget2 = (isset($data['ctar2']) ? $data['ctar2'] : 0); - - $catapults1TargetRandom = ($catapultTarget == 0); - $catapults2WillNotShoot = ($catapultTarget2 == 0); - $catapults2TargetRandom = ($catapults2WillNotShoot || $catapultTarget2 == 99); - - // we're manually targetting 1st and/or 2nd row of catapults - if (!$catapults1TargetRandom) - { - $_catapultsTarget1Levels = []; - $__catapultsTarget1AltTargets = []; - - // calculate targets for 1st rows of catapults - $j = 0; - for ($i = 1; $i <= 41; $i++) - { - if ($i == 41) $i = 99; - - // 1st row of catapults pre-selected target calculations, if needed - if (!$catapults1TargetRandom && $bdo['f'.$i.'t'] == $catapultTarget && $bdo['f'.$i] > 0 && $i != 40) - { - $j++; - $_catapultsTarget1Levels[$j]=$bdo['f'.$i]; - $__catapultsTarget1AltTargets[$j]=$i; - } - } - - // if we couldn't find a suitable target for 1st row of catapults, - // select a random target instead - if (!$catapults1TargetRandom) { - if ( count( $_catapultsTarget1Levels ) > 0 ) { - if ( max( $_catapultsTarget1Levels ) <= 0 ) { - $catapultTarget = 0; - } else { - $catapultTarget = $__catapultsTarget1AltTargets[rand( 1, $j )]; - } - } else { - $catapultTarget = 0; - $catapults1TargetRandom = true; - } - } - } - - // 1st row of catapults set to target randomly - if ($catapults1TargetRandom) - { - $list = []; - for ($i = 1; $i <= 41; $i++) - { - if ($i == 41) $i = 99; - if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i; - } - $catapultTarget = $list[rand(0, count($list) - 1)]; - } - - /** - * resolve 1st row of catapults - */ - $village_destroyed = 0; - $this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget, !$catapults2WillNotShoot, false, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe); - - /** - * SECOND CATAPULTS ROW - */ - - // we're manually targetting 2nd row of catapults - if (!$catapults2TargetRandom) - { - $_catapultsTarget2Levels = []; - $__catapultsTarget2AltTargets = []; - - // calculate targets for 2nd rows of catapults - $j = 0; - for ($i = 1; $i <= 41; $i++) - { - if ($i == 41) $i = 99; - - // 2nd row of catapults pre-selected target calculations, if needed - if (!$catapults2TargetRandom && !$catapults2WillNotShoot && $bdo['f'.$i.'t'] == $catapultTarget2 && $bdo['f'.$i] > 0 && $i != 40) - { - $j++; - $_catapultsTarget2Levels[$j] = $bdo['f'.$i]; - $__catapultsTarget2AltTargets[$j] = $i; - } - } - - // if we couldn't find a suitable target for 2nd row of catapults, - // select a random target instead - if (!$catapults2TargetRandom) { - if (count($_catapultsTarget2Levels) > 0 ) { - if (max($_catapultsTarget2Levels) <= 0 ) { - $catapultTarget2 = 99; - } - else $catapultTarget2 = $__catapultsTarget2AltTargets[rand( 1, $j )]; - } else { - $catapultTarget2 = 99; - $catapults2TargetRandom = true; - } - } - } - - // 2nd row of catapults set to target randomly - if ($catapults2TargetRandom && !$catapults2WillNotShoot) - { - $list = []; - for ($i = 1; $i <= 41; $i++) - { - if ($i == 41) $i = 99; - if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i; - } - $catapultTarget2 = $list[ rand(0, count($list) - 1) ]; - } - - /** - * resolve 2nd row of catapults - */ - if (!$catapults2WillNotShoot) { - $this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget2, true, true, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe); - } - - // clear resource levels cache, since we might have destroyed buildings/fields by now - call_user_func(get_class($database).'::clearResourseLevelsCache'); - } - - return ['battlepart' => $battlepart, 'info_cat' => $info_cat, 'village_destroyed' => $village_destroyed]; - } - - private function claimMovementRecord($moveid) { - global $database; - - $moveid = (int)$moveid; - if ($moveid <= 0) { - return false; - } - - $q = "UPDATE ".TB_PREFIX."movement SET proc = 1 WHERE moveid = $moveid AND proc = 0"; - mysqli_query($database->dblink, $q); - - return (mysqli_affected_rows($database->dblink) === 1); - } - - /** - * Handle hero evasion: if the defender has evasion active and can afford it, - * send all defender units back to base and charge 2 gold + 1 evasion charge. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current movement row (needs 'to'). - * @param int $DefenderID Defender's user ID. - * @param array $DefenderUnit Unit counts for the defending village. - * @param int $targettribe Defender's tribe (1=Roman, 2=Teuton, 3=Gaul…). - * @param int $evasion Whether evasion is enabled (1) or not (0). - * @param int $maxevasion Remaining evasion charges. - * @param int $gold Defender's current gold. - * @param bool $cannotsend True when troops are already returning and can't be re-sent. - * @param int $attackType Type of incoming attack (must be > 2 to trigger evasion). - */ - private function handleEvasion( - array $data, - int $DefenderID, - array $DefenderUnit, - int $targettribe, - int $evasion, - int $maxevasion, - int $gold, - bool $cannotsend, - int $attackType - ): void { - global $database; - - if (!($evasion == 1 && $maxevasion > 0 && $gold > 1 && !$cannotsend && $attackType > 2)) { - return; - } - - $playerunit = ($targettribe - 1) * 10; - $totaltroops = 0; - $evasionUnitModifications_units = []; - $evasionUnitModifications_amounts = []; - $evasionUnitModifications_modes = []; - - for ($i = 1; $i <= 10; $i++) { - $playerunit += $i; - $data['u' . $i] = $DefenderUnit['u' . $playerunit]; - $evasionUnitModifications_units[] = $playerunit; - $evasionUnitModifications_amounts[] = $DefenderUnit['u' . $playerunit]; - $evasionUnitModifications_modes[] = 0; - $playerunit -= $i; - $totaltroops += $data['u' . $i]; - } - - $data['u11'] = $DefenderUnit['hero']; - $totaltroops += $data['u11']; - - if ($totaltroops > 0) { - $evasionUnitModifications_units[] = 'hero'; - $evasionUnitModifications_amounts[] = $DefenderUnit['hero']; - $evasionUnitModifications_modes[] = 0; - - $attackid = $database->addAttack($data['to'], $data['u1'], $data['u2'], $data['u3'], $data['u4'], $data['u5'], $data['u6'], $data['u7'], $data['u8'], $data['u9'], $data['u10'], $data['u11'], 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); - $database->addMovement(4, 0, $data['to'], $attackid, microtime(true), microtime(true) + (180 / EVASION_SPEED)); - $database->updateUserField($DefenderID, ["gold", "maxevasion"], [$gold - 2, $maxevasion - 1], 1); - } - - $database->modifyUnit($data['to'], $evasionUnitModifications_units, $evasionUnitModifications_amounts, $evasionUnitModifications_modes); - } - - /** - * Process senator/chief attacks: reduce loyalty and, if it hits 0, conquer the village. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current movement row. - * @param int $type Attack type (must be 3 for chiefing). - * @param int $dead9 Chiefs killed in battle. - * @param int $traped9 Chiefs caught in traps. - * @param array $from Attacker's village info row. - * @param array $to Defender's village info row. - * @param array $toF Defender's village field row (has loyalty). - * @param int $owntribe Attacker's tribe. - * @param int $targettribe Defender's tribe. - * @param array $varray All villages of the defender. - * @param array $varray1 All villages of the attacker. - * @param array $battlepart Battle result. - * @param int $isoasis 0 = village, non-zero = oasis. - * @param int $village_destroyed Whether the village has already been destroyed. - * @param int $chief_pic Chief unit sprite ID for report strings. - * @return array{info_chief:string, chiefing_village:int, village_destroyed:int} - */ - private function handleConquest( - array $data, - int $type, - int $dead9, - int $traped9, - int $targettribe, - array $from, - array $to, - array $toF, - int $owntribe, - array $varray, - array $varray1, - array $battlepart, - int $isoasis, - int $village_destroyed, - int $chief_pic - ): array { - global $database, $units; - - $info_chief = ','; - $chiefing_village = 0; - - if (!(($data['t9'] - $dead9 - $traped9) > 0 && $isoasis == 0)) { - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - if ($type != 3) { - $info_chief = $chief_pic . ',' . rc_tok('RC_NO_REDUCE_CP_RAID'); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - $palacelevel = $database->getResourceLevel($from['wref']); - $plevel = 0; - - for ($i = 1; $i <= 40; $i++) { - if ($palacelevel['f' . $i . 't'] == 26) $plevel = $i; - elseif ($palacelevel['f' . $i . 't'] == 25) $plevel = $i; - elseif ($palacelevel['f' . $i . 't'] == 44) $plevel = $i; // Command Center (Huni) - } - - // Command Center: sloturi de expansiune ca la Palat (10 -> 1, 15 -> 2, 20 -> 3) - if ($palacelevel['f' . $plevel . 't'] == 44) { - if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; - elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1; - elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2; - else $canconquer = 3; - } elseif ($palacelevel['f' . $plevel . 't'] == 26) { - if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; - elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1; - elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2; - else $canconquer = 3; - } elseif ($palacelevel['f' . $plevel . 't'] == 25) { - if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; - elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 1; - else $canconquer = 2; - } else { - $canconquer = 0; - } - - $expArray = $database->getVillageFields($from['wref'], 'exp1, exp2, exp3'); - $villexp = ($expArray['exp1'] == 0) ? 0 : (($expArray['exp2'] == 0) ? 1 : (($expArray['exp3'] == 0) ? 2 : 3)); - - $mode = CP; - $cp_mode = $GLOBALS['cp' . $mode]; - $need_cps = $cp_mode[count($varray1) + 1]; - $user_cps = $database->getUserArray($from['owner'], 1)['cp']; - - if ($user_cps < $need_cps) { - $info_chief = $chief_pic . ',' . rc_tok('RC_NOT_ENOUGH_CP'); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - if (count($varray) <= 1 || $to['capital'] == 1 || $villexp >= $canconquer) { - $info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER'); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - if ($to['owner'] == 3 && $to['name'] == 'WW Buildingplan') { - $info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER'); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - if ($database->getFieldLevelInVillage($data['to'], '25, 26, 44')) { - $info_chief = $chief_pic . ',' . rc_tok('RC_RESIDENCE_NOT_DESTROYED'); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - // --- Reduce loyalty --- - $time = time(); - $reducedLoyaltyTotal = 0; - - for ($i = 0; $i < ($data['t9'] - $dead9 - $traped9); $i++) { - $reducedLoyalty = ($owntribe == 1) ? rand(20, 30) : rand(20, 25); - - if ($from['celebration'] > $time && $from['type'] == 2) $reducedLoyalty += 5; - if ($to['celebration'] > $time && $to['type'] == 2) $reducedLoyalty -= 5; - - $reducedLoyalty /= $battlepart['moralBonus']; - - // Bug fix: Brewery (35) is capital-only but empire-wide — its effect - // must be checked on the attacker's CAPITAL, not on $data['from'] (the - // launching village, which may not be the capital at all), and only - // while a Mead-Festival is actually active there, not just because - // the Brewery has been built (it has no permanent effect). - if ($owntribe == 2) { - $attackerCapital = $database->getVillage($from['owner'], 3); - if ($attackerCapital && (int)$attackerCapital['festival'] > $time && $this->getTypeLevel(35, $attackerCapital['wref']) > 0) { - $reducedLoyalty /= 2; - } - } - - $reducedLoyaltyTotal += $reducedLoyalty; - } - - if (($toF['loyalty'] - $reducedLoyaltyTotal) > 0) { - $info_chief = $chief_pic . ',' . rc_tok('RC_LOYALTY_LOWERED', floor($toF['loyalty']), floor($toF['loyalty'] - $reducedLoyaltyTotal)); - $database->setVillageField($data['to'], 'loyalty', ($toF['loyalty'] - $reducedLoyaltyTotal)); - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - if ($village_destroyed) { - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - // --- Village conquered --- - $villname = addslashes($database->getVillageField($data['to'], 'name')); - $artifact = reset($database->getOwnArtefactInfo($data['to'])); - - $info_chief = $chief_pic . ',' . rc_tok('RC_INHABITANTS_JOIN', $villname); - - if ($artifact['vref'] == $data['to']) { - $database->claimArtefact($data['to'], $data['to'], $database->getVillageField($data['from'], 'owner')); - } - - $database->setVillageFields($data['to'], ['loyalty', 'owner'], [0, $database->getVillageField($data['from'], 'owner')]); - - // Milestones: first WW village ever conquered, and — separately — - // first village ever conquered FROM ANOTHER PLAYER (not from - // Natars). $to is this function's own parameter (not re-fetched), - // so $to['natar']/$to['owner'] still reflect the village's state - // from BEFORE this conquest, which is exactly what we need here. - // natar==1 marks one of the 13 pre-built WW conquest targets (see - // Artifacts::createWWVillages()) — Natars' capital and artifact/ - // plan villages are natar=0, so this check cannot misfire on those. - if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { - $newOwner = $database->getVillageField($data['from'], 'owner'); - if ((int)($to['natar'] ?? 0) === 1) { - $database->recordMilestoneIfFirst('first_ww', $newOwner, $data['to']); - } elseif ((int)($to['owner'] ?? 0) !== 3) { - $database->recordMilestoneIfFirst('first_pvp_conquest', $newOwner, $data['to']); - } - } - - $database->query("DELETE FROM " . TB_PREFIX . "abdata WHERE vref = " . (int)$data['to']); - $database->addABTech($data['to']); - - $database->query("DELETE FROM " . TB_PREFIX . "tdata WHERE vref = " . (int)$data['to']); - $database->addTech($data['to']); - - $database->query("DELETE FROM " . TB_PREFIX . "enforcement WHERE `from` = " . (int)$data['to']); - $database->query("DELETE FROM " . TB_PREFIX . "route where wid = " . (int)$data['to'] . " OR `from` = " . (int)$data['to']); - - $units2reset = []; - for ($u = 1; $u <= 90; $u++) $units2reset[] = 'u' . $u . ' = 0'; - // cucerirea goleste spitalul si coada de vindecare - $database->clearHospital($data['to']); - $units2reset[] = 'u99 = 0'; - $units2reset[] = 'u99o = 0'; - $units2reset[] = 'hero = 0'; - $database->query("UPDATE " . TB_PREFIX . "units SET " . implode(',', $units2reset) . " WHERE vref = " . (int)$data['to']); - - $newLevels_fieldNames = []; - $newLevels_fieldValues = []; - - $AttackerID = $database->getVillageField($data['from'], 'owner'); - $DefenderID = $database->getVillageField($data['to'], 'owner'); - $poparray = $database->getVSumField([$AttackerID, $DefenderID], 'pop'); - $pop1 = $poparray[0]['Total']; - $pop2 = $poparray[1]['Total']; - - if ($pop1 > $pop2 && $targettribe != 5) { - $buildlevel = $database->getResourceLevel($data['to']); - for ($i = 1; $i <= 39; $i++) { - if ($buildlevel['f' . $i] != 0) { - if ($buildlevel['f' . $i . 't'] != 35 && $buildlevel['f' . $i . 't'] != 36 && $buildlevel['f' . $i . 't'] != 41) { - // Main Building (gid 15) nu poate cobori sub nivel 1: satul trebuie - // sa ramana locuibil dupa cucerire (altfel pop 0 + nicio cladire). - $minLevel = ($buildlevel['f' . $i . 't'] == 15) ? 1 : 0; - $leveldown = $buildlevel['f' . $i] - 1; - if ($leveldown < $minLevel) $leveldown = $minLevel; - $newLevels_fieldNames[] = 'f' . $i; - $newLevels_fieldValues[] = $leveldown; - // sterge tipul cladirii DOAR daca a ajuns efectiv la 0 - // (bug vechi: "!$leveldown > 0" era mereu adevarat pt leveldown==0, - // deci stergea tipul si cand cladirea cobora la nivel 1) - if ($leveldown <= 0) { - $newLevels_fieldNames[] = 'f' . $i . 't'; - $newLevels_fieldValues[] = 0; - } - } else { - $newLevels_fieldNames[] = 'f' . $i; - $newLevels_fieldValues[] = 0; - $newLevels_fieldNames[] = 'f' . $i . 't'; - $newLevels_fieldValues[] = 0; - } - } - } - if ($buildlevel['f99'] != 0) { - $newLevels_fieldNames[] = 'f99'; - $newLevels_fieldValues[] = $buildlevel['f99'] - 1; - } - } - - // destroy wall - $newLevels_fieldNames[] = 'f40'; - $newLevels_fieldValues[] = 0; - $newLevels_fieldNames[] = 'f40t'; - $newLevels_fieldValues[] = 0; - - $database->clearExpansionSlot($data['to'], 1); - - $expArray2 = $database->getVillageFields($data['from'], 'exp1, exp2, exp3'); - if ($expArray2['exp1'] == 0) { $exp = 'exp1'; } - elseif ($expArray2['exp2'] == 0) { $exp = 'exp2'; } - else { $exp = 'exp3'; } - $database->setVillageField($data['from'], $exp, $data['to']); - - $database->deleteTradeRoutesByVillage($data['to']); - $database->setVillageLevel($data['to'], $newLevels_fieldNames, $newLevels_fieldValues); - - $units->returnTroops($data['to'], 1); - - $chiefing_village = 1; - $database->reassignHero($data['to']); - $this->recountPop($data['to'], false); - - return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; - } - - /** - * Fetch all attacks (sort_type 3, not reinforcement) that have arrived by - * $time, joined with their attack rows, ordered by arrival. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param int $time Current timestamp. - * @return array Rows of pending/completed attacks. - */ - private function fetchCompletedAttacks($time) { - global $database; - - $time = (int) $time; - $q = " - SELECT - `from`, `to`, endtime, ref, ctar1, ctar2, spy, moveid, attack_type, - t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, (SELECT oasistype FROM ".TB_PREFIX."wdata WHERE id = `to`) as oasistype - FROM - ".TB_PREFIX."movement, - ".TB_PREFIX."attacks - WHERE - ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id - AND - ".TB_PREFIX."movement.proc = 0 - AND - ".TB_PREFIX."movement.sort_type = 3 - AND - ".TB_PREFIX."attacks.attack_type != 2 - AND - endtime < $time - ORDER BY endtime ASC"; - return $database->query_return($q); - } - - /** - * Batch-preload the village / unit / tech data used by sendunitsComplete() - * so the per-attack loop hits the in-request cache instead of querying row - * by row. Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $dataarray Completed attacks (each with 'from' and 'to'). - */ - private function preloadBattleData(array $dataarray) { - global $database; - - $vilIDs = []; - foreach ($dataarray as $data) { - $vilIDs[$data['from']] = true; - $vilIDs[$data['to']] = true; - } - $vilIDs = array_keys($vilIDs); - - $database->getProfileVillages($vilIDs, 5); - $database->getUnit($vilIDs); - $database->getEnforceVillage($vilIDs, 0); - $database->getMovement(34, $vilIDs, 1); - $database->getABTech($vilIDs); - $database->getMInfo($vilIDs); - } - - private function buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop) { - global $database; - $info_spy = ""; - if ($data['spy'] == 1){ - $info_spy = "".$spy_pic.",
\"".LUMBER."\"".round($totwood)." | - \"".CLAY."\"".round($totclay)." | - \"".IRON."\"".round($totiron)." | - \"".CROP."\"".round($totcrop)."
-
\"".rc_tok('CARRY')."\"".rc_tok('RC_TOTAL_RESOURCES')." ".round($totwood+$totclay+$totiron+$totcrop)."
- "; - }else if($data['spy'] == 2){ - if ($isoasis == 0){ - $walllevel = $database->getFieldLevelInVillage($data['to'], '31, 32, 33, 42, 43, 47, 50'); - $residencelevel = $database->getFieldLevelInVillage($data['to'], 25); - $palacelevel = $database->getFieldLevelInVillage($data['to'], 26); - $residenceimg = "\"".RESIDENCE."\""; - $palaceimg = "\"".PALACE."\""; - $crannyimg = "\"".CRANNY."\""; - $wallgids = array(1 => 31, 2 => 32, 3 => 33, 6 => 43, 7 => 42, 8 => 47, 9 => 50); - $wallgid = isset($wallgids[$targettribe]) ? $wallgids[$targettribe] : 31; - $wallimg = "\"".rc_tok('RC_WALL')."\""; - $info_spy = "".$spy_pic.","; - if($residencelevel > 0) $info_spy .= $residenceimg." ".rc_tok('RC_RESIDENCE_LEVEL')."".$residencelevel."
"; - elseif($palacelevel > 0) $info_spy .= $palaceimg." ".rc_tok('RC_PALACE_LEVEL')." ".$palacelevel."
"; - - if($walllevel > 0) $info_spy .= $wallimg." ".rc_tok('RC_WALL_LEVEL')." ".$walllevel."
"; - $info_spy .= $crannyimg." ".rc_tok('RC_CRANNY_CAPACITY')." ".$crannySpy.""; - } - else $info_spy = "".$spy_pic.", ".rc_tok('RC_NO_INFO'); - } - return $info_spy; - } - - /** - * Release prisoners trapped in the target village during a conquest attack, - * send them home (with 25% casualties), repair destroyed traps, and return - * the HTML fragment for the battle-report trap line. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row. - * @param array $from Attacker village info (getMInfo). - * @param array $to Defender village info (getMInfo). - * @param int $ownally Attacker's alliance id. - * @param int $type Attack type (3 = normal with rams/catas). - * @param int $totalsend_att Total attacking troops sent. - * @param int $totaldead_att Total attacking troops killed. - * @param int $totaltraped_att Total attacking troops trapped. - * @return string HTML fragment for the trap report line; empty if no prisoners freed. - */ - private function handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) { - global $database, $units, $bid19, $u99; - - if ($type != 3 || $totalsend_att - ($totaldead_att + $totaltraped_att) <= 0) { - return ''; - } - - $prisoners = $database->getPrisoners([$to['wref']], 0, false)[$to['wref'].'0']; - if (count($prisoners) == 0) { - return ''; - } - - $anothertroops = $mytroops = $ownDeads = $anotherDeads = 0; - $prisoners2delete = $movementType = $movementFrom = $movementTo = $movementRef = $movementTime = $movementEndtime = []; - $utime = microtime(true); - - foreach ($prisoners as $prisoner) { - $p_owner = $database->getVillageField($prisoner['from'], "owner"); - - if ($prisoner['from'] == $from['wref']) { - for ($i = 1; $i <= 11; $i++) { - $deadPrisoners = round($prisoner['t'.$i] / 4); - $mytroops += $prisoner['t'.$i]; - $ownDeads += $deadPrisoners; - $prisoner['t'.$i] -= $deadPrisoners; - } - $database->modifyAttack2( - $data['ref'], - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], - [$prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'], - $prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11']] - ); - $prisoners2delete[] = $prisoner['id']; - } else { - $p_alliance = $database->getUserField($p_owner, "alliance", 0); - $friendarray = $database->getAllianceAlly($p_alliance, 1); - $neutralarray = $database->getAllianceAlly($p_alliance, 2); - $friend = ($friendarray[0]['alli1'] > 0 && $friendarray[0]['alli2'] > 0 && $p_alliance > 0) - && ($friendarray[0]['alli1'] == $ownally || $friendarray[0]['alli2'] == $ownally) - && ($ownally != $p_alliance && $ownally && $p_alliance); - $neutral = ($neutralarray[0]['alli1'] > 0 && $neutralarray[0]['alli2'] > 0 && $p_alliance > 0) - && ($neutralarray[0]['alli1'] == $ownally || $neutralarray[0]['alli2'] == $ownally) - && ($ownally != $p_alliance && $ownally && $p_alliance); - - if ($p_alliance == $ownally || $friend || $neutral) { - $p_tribe = $database->getUserField($p_owner, "tribe", 0); - - for ($i = 1; $i <= 11; $i++) { - $deadPrisoners = round($prisoner['t'.$i] / 4); - if ($p_owner == $from['owner']) { - $mytroops += $prisoner['t'.$i]; - $ownDeads += $deadPrisoners; - } else { - $anothertroops += $prisoner['t'.$i]; - $anotherDeads += $deadPrisoners; - } - $prisoner['t'.$i] -= $deadPrisoners; - } - - $troopsTime = $units->getWalkingTroopsTime($prisoner['from'], $prisoner['wref'], $p_owner, $p_tribe, $prisoner, 1, 't'); - $p_time = $database->getArtifactsValueInfluence($p_owner, $prisoner['from'], 2, $troopsTime); - $p_reference = $database->addAttack( - $prisoner['from'], - $prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'], - $prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11'], - 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - ); - $movementType[] = 4; - $movementFrom[] = $prisoner['wref']; - $movementTo[] = $prisoner['from']; - $movementRef[] = $p_reference; - $movementTime[] = $utime; - $movementEndtime[] = $p_time + $utime; - $prisoners2delete[] = $prisoner['id']; - } - } - } - - if (count($movementType)) { - $database->addMovement($movementType, $movementFrom, $movementTo, $movementRef, $movementTime, $movementEndtime); - } - $database->deletePrisoners($prisoners2delete); - - $ownDeadsText = ($ownAlive = $mytroops - $ownDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$ownAlive) : ""; - $anotherDeadsText = ($anotherAlive = $anothertroops - $anotherDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$anotherAlive) : ""; - - $database->addGeneralAttack($ownDeads + $anotherDeads); - - $newtraps = round(($mytroops + $anothertroops) / 3); - $database->modifyUnit($data['to'], ['99', '99o'], [$mytroops + $anothertroops, $mytroops + $anothertroops], [0, 0]); - if ($newtraps > 0) { - $repairDuration = $database->getArtifactsValueInfluence( - $to['owner'], $to['wref'], 5, - round(($bid19[max($this->getTypeLevel(36, $to['wref']), 1)]['attri'] / 100) * $u99['time'] / SPEED) - ); - $database->trainUnit($to['wref'], 99, $newtraps, $u99['pop'], $repairDuration, 0); - } - - $trapper_pic = "\"".rc_tok('RC_TRAP')."\""; - $p_username = $database->getUserField($from['owner'], "username", 0); - - if ($mytroops > 0 && $anothertroops > 0) { - return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText." ".rc_tok('RC_AND_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText."."; - } elseif ($mytroops > 0) { - return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText."."; - } elseif ($anothertroops > 0) { - return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText."."; - } - return ''; - } - - /** - * Assemble the battle-report data string ($data2) and the all-attacker-dead - * fallback ($data_fail) from pre-computed battle results. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param bool $scout True when this is a scouting mission. - * @param array $from Attacker village info (getMInfo). - * @param array $to Defender village info (getMInfo). - * @param int $owntribe Attacker tribe id. - * @param int $targettribe Defender tribe id. - * @param string $unitssend_att CSV of units sent by attacker. - * @param string $unitsdead_att CSV of attacker units killed. - * @param array $steal [wood, clay, iron, crop] looted amounts. - * @param array $battlepart calculateBattle() result. - * @param array $unitssend_def CSV of defender units sent (indexed 0–5). - * @param array $unitsdead_def CSV of defender units dead (indexed 0–5). - * @param array $unitssend_deff Masked defender units for data_fail (indexed 0–5). - * @param array $unitsdead_deff Masked dead units for data_fail (indexed 0–5). - * @param int $rom,$ger,$gal,$nat,$natar Tribe presence flags. - * @param string $DefenderHeroesTot CSV of defender heroes totals by tribe. - * @param string $DefenderHeroesDead CSV of defender heroes dead by tribe. - * @param string $info_ram Ram result fragment. - * @param string $info_cat Catapult result fragment (internally extended if village destroyed). - * @param string $info_chief Chief/senator result fragment. - * @param string $info_spy Scout report fragment (scout missions only). - * @param string $info_trap Prisoner-release fragment (from handlePrisoners). - * @param string $info_hero Hero result fragment. - * @param string $info_troop "None of your soldiers returned" fragment. - * @param array $data Current attack row (for t11). - * @param int $dead11 Hero casualties. - * @param int $herosend_def Defender hero count. - * @param int $deadhero Defender hero casualties. - * @param string $unitstraped_att CSV of attacker units trapped. - * @param int $village_destroyed 1 if the village was destroyed. - * @param int $can_destroy 1 if the village can be destroyed. - * @param int $catp_pic Catapult unit-pic id for the HTML fragment. - * @return array ['data2' => string, 'data_fail' => string] - */ - private function buildCombatReport( - $scout, $from, $to, $owntribe, $targettribe, - $unitssend_att, $unitsdead_att, $steal, $battlepart, - $unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff, - $rom, $ger, $gal, $nat, $natar, - $hun, $egy, $spa, $vik, - $DefenderHeroesTot, $DefenderHeroesDead, - $info_ram, $info_cat, $info_chief, $info_spy, - $info_trap, $info_hero, $info_troop, - $data, $dead11, $herosend_def, $deadhero, $unitstraped_att, - $village_destroyed, $can_destroy, $catp_pic - ) { - if (!empty($scout)) { - $data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att - .',0,0,0,0,0,'.$to['owner'].','.$to['wref'].','.addslashes($to['name']) - .',,,,'.$targettribe - .','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom - .','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger - .','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal - .','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat - .','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar - .','.$unitssend_def[5].','.$unitsdead_def[5] - .','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6] - .','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7] - .','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8] - .','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9] - .','.$DefenderHeroesTot.','.$DefenderHeroesDead - .','.$info_ram.','.$info_cat.','.$info_chief.','.$info_spy - .','.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero - .',,'.$unitstraped_att; - } else { - if (isset($village_destroyed) && $village_destroyed == 1 && $can_destroy == 1) { - if (strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) { - $info_cat .= "".INFORMATION."" - . "\"".rc_tok('RC_CATAPULT')."\"" - . " ".rc_tok('RC_VILLAGE_DESTROYED').""; - } - } - $data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att - .','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty'] - .','.$to['owner'].','.$to['wref'].','.addslashes($to['name']) - .',,,,'.$targettribe - .','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom - .','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger - .','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal - .','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat - .','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar - .','.$unitssend_def[5].','.$unitsdead_def[5] - .','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6] - .','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7] - .','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8] - .','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9] - .','.$DefenderHeroesTot.','.$DefenderHeroesDead - .','.$info_ram.','.$info_cat.','.$info_chief.','.(isset($info_spy) ? $info_spy : '') - .',,'.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero - .','.$unitstraped_att; - - $data2 .= ','.($info_trap !== '' ? addslashes($info_trap) : '').',,'.$info_troop.','.$info_hero; - } - - $data_fail = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att - .','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty'] - .','.$to['owner'].','.$to['wref'].','.addslashes($to['name']) - .',,,,'.$targettribe - .','.$unitssend_deff[0].','.$unitsdead_deff[0].','.$rom - .','.$unitssend_deff[1].','.$unitsdead_deff[1].','.$ger - .','.$unitssend_deff[2].','.$unitsdead_deff[2].','.$gal - .','.$unitssend_deff[3].','.$unitsdead_deff[3].','.$nat - .','.$unitssend_deff[4].','.$unitsdead_deff[4].','.$natar - .','.$unitssend_deff[5].','.$unitsdead_deff[5] - .','.$hun.','.$unitssend_deff[6].','.$unitsdead_deff[6] - .','.$egy.','.$unitssend_deff[7].','.$unitsdead_deff[7] - .','.$spa.','.$unitssend_deff[8].','.$unitsdead_deff[8] - .','.$vik.','.$unitssend_deff[9].','.$unitsdead_deff[9] - .','.$DefenderHeroesTot.','.$DefenderHeroesDead - .',,,'.$data['t11'].','.$dead11.','.$unitstraped_att - .',,'.$info_ram.','.$info_cat.','.$info_chief.','.$info_troop.','.$info_hero; - - return ['data2' => $data2, 'data_fail' => $data_fail]; - } - - /** - * Distribute the battle bounty across the resources actually available in - * the target (after cranny protection) and return how much of each is taken. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $available [wood, clay, iron, crop] lootable amounts. - * @param int $bounty Total carry capacity / bounty from the battle. - * @return array [wood, clay, iron, crop] amounts actually stolen. - */ - private function applyBounty(array $available, $bounty) { - $avtotal = $available; - $steal = [0, 0, 0, 0]; - $btotal = $bounty; - $bmod = 0; - - for ($i = 0; $i < 5; $i++) { - for ($j = 0; $j < 4; $j++) { - if (isset($avtotal[$j])) { - if ($avtotal[$j] < 1) unset($avtotal[$j]); - } - } - - // No resources left to take - if (empty($avtotal) || ($btotal < 1 && $bmod < 1)) break; - - if ($btotal < 1) { - while ($bmod) { - // random select - $rs = array_rand($avtotal); - if (isset($avtotal[$rs])) { - $avtotal[$rs] -= 1; - $steal[$rs] += 1; - $bmod -= 1; - } - } - } - - // handle unbalanced amounts. - $btotal += $bmod; - $bmod = $btotal % count($avtotal); - $btotal -= $bmod; - $bsplit = $btotal / count($avtotal); - - $max_steal = (min($avtotal) < $bsplit) ? min($avtotal) : $bsplit; - - for ($j = 0; $j < 4; $j++) { - if (isset($avtotal[$j])) { - $avtotal[$j] -= $max_steal; - $steal[$j] += $max_steal; - $btotal -= $max_steal; - } - } - } - - return $steal; - } - - /** - * Apply battle casualties to the defender's own (in-village) troops, persist - * the losses, and return the per-unit dead map used later for points/reports. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses 'to'). - * @param int $targettribe Defender tribe (1-5). - * @param array $battlepart Battle result (index 2 = defender kill ratio, - * 'deadherodef' = defender hero losses). - * @return array Map of dead own troops: keys are the unit index (int) plus 'hero'. - */ - private function applyOwnDefenceCasualties($data, $targettribe, $battlepart) { - global $database; - - $owndead = []; - $unitlist = $database->getUnit($data['to'], false); - $start = ($targettribe - 1) * 10 + 1; - $end = ($targettribe * 10); - - $unitModifications_units = []; - $unitModifications_amounts = []; - $unitModifications_modes = []; - for ($i = $start; $i <= $end; $i++) { - if ($unitlist) { - $owndead[$i] = round($battlepart[2] * $unitlist['u'.$i]); - $unitModifications_units[] = $i; - $unitModifications_amounts[] = $owndead[$i]; - $unitModifications_modes[] = 0; - } - } - - $owndead['hero'] = 0; - - if ($unitlist) { - $owndead['hero'] = (isset($battlepart['deadherodef']) ? $battlepart['deadherodef'] : ''); - - $unitModifications_units[] = 'hero'; - $unitModifications_amounts[] = $owndead['hero']; - $unitModifications_modes[] = 0; - } - - // modify units in DB - $database->modifyUnit($data['to'], $unitModifications_units, $unitModifications_amounts, $unitModifications_modes); - - // Spital: o parte din mortii proprii (aparare) devin raniti - doar unitatile de lupta X1-X8 - $woundedMap = []; - for($wi = $start; $wi <= $start + 7; $wi++) { - if(!empty($owndead[$wi])) $woundedMap[$wi] = $owndead[$wi]; - } - $this->applyHospitalWounded($data['to'], $woundedMap); - - return $owndead; - } - - /** - * Apply battle casualties to the reinforcement (foreign) troops stationed in - * the defender's village: compute each reinforcement's dead units/hero, persist - * them (modifyEnforce), notify the reinforcing players, and delete fully-wiped - * reinforcements. Accumulates the dead counts into $alldead and the dead heroes - * into $DefenderHeroesDeadArray (both passed by reference), and reports which - * tribes are present among the reinforcements. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses 'to'). - * @param array $battlepart Battle result (index 2 = defender kill ratio, - * 'deadheroref' = dead hero per enforce id). - * @param array $from Attacker village info (uses 'wref'). - * @param array $to Defender village info (uses 'name','wref'). - * @param int $ownally Attacker's alliance id (for notices). - * @param int $AttackArrivalTime Timestamp stamped on the reinforcement notices. - * @param mixed $scout Scout marker; notices/deletion only fire when empty. - * @param array $alldead [in/out] Per-unit dead reinforcements (+ 'hero'), accumulated. - * @param array $DefenderHeroesDeadArray [in/out] Dead reinforcement heroes per tribe, accumulated. - * @return array Tribe-presence flags among the reinforcements: keys rom,ger,gal,nat,natar (0/1). - */ - private function applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, array &$alldead, array &$DefenderHeroesDeadArray) { - global $database; - - $rom = $ger = $gal = $nat = $natar = $hun = $egy = $spa = $vik = 0; - - //kill other defence in village - // ... once again, units could have changed, so we need to reselect - $enforcementarray3 = $database->getEnforceVillage($data['to'], 0); - foreach ($enforcementarray3 as $enforce) { - $life = ''; $notlife = ''; $wrong = false; - if ($enforce['from'] != 0) { - $tribe = $database->getUserArray($database->getVillageField($enforce['from'], "owner"), 1)["tribe"]; - } else { - $tribe = 4; - } - - $start = ($tribe - 1) * 10 + 1; - $end = ($tribe * 10); - unset($dead); - - switch ($tribe) { - case 1: $rom = 1; break; - case 2: $ger = 1; break; - case 3: $gal = 1; break; - case 4: $nat = 1; break; - case 6: $hun = 1; break; - case 7: $egy = 1; break; - case 8: $spa = 1; break; - case 9: $vik = 1; break; - case 5: - default: $natar = 1; break; - } - - $enforceModificationsById = []; - for ($i = $start; $i <= $end; $i++) { - if ($enforce['u'.$i] > '0') { - if (!isset($enforceModificationsById[$enforce['id']])) { - $enforceModificationsById[$enforce['id']] = [ - 'units' => [], - 'amounts' => [], - 'modes' => [] - ]; - } - $enforceModificationsById[$enforce['id']]['units'][] = $i; - $enforceModificationsById[$enforce['id']]['amounts'][] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); - $enforceModificationsById[$enforce['id']]['modes'][] = 0; - $dead[$i] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); - $checkpoint = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); - - if (!isset($enforce['u'.$i])) $enforce['u'.$i] = 0; - - $alldead[$i] += $dead[$i]; - - $wrong = $checkpoint != $enforce['u'.$i]; - } else { - $dead[$i] = 0; - } - } - - if ($enforce['hero'] > 0) { - $enforceModificationsById[$enforce['id']]['units'][] = 'hero'; - $enforceModificationsById[$enforce['id']]['amounts'][] = $battlepart['deadheroref'][$enforce['id']]; - $enforceModificationsById[$enforce['id']]['modes'][] = 0; - - $dead['hero'] = $battlepart['deadheroref'][$enforce['id']]; - $alldead['hero'] += $dead['hero']; - $wrong = $dead['hero'] != $enforce['hero']; - - //Collecting information for the report - $reinfTribe = ($enforce['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce['from'], "owner"), "tribe", 0); - $DefenderHeroesDeadArray[$reinfTribe] += $dead['hero']; - } - - // modify enforce in DB - foreach ($enforceModificationsById as $enforceId => $enforceArray) { - $database->modifyEnforce($enforceId, $enforceArray['units'], $enforceArray['amounts'], $enforceArray['modes']); - } - - $notlife = ''.$dead[$start].','.$dead[$start+1].','.$dead[$start+2].','.$dead[$start+3].','.$dead[$start+4].','.$dead[$start+5].','.$dead[$start+6].','.$dead[$start+7].','.$dead[$start+8].','.$dead[$start+9].''; - $notlife1 = $dead[$start]+$dead[$start+1]+$dead[$start+2]+$dead[$start+3]+$dead[$start+4]+$dead[$start+5]+$dead[$start+6]+$dead[$start+7]+$dead[$start+8]+$dead[$start+9]; - $life = ''.$enforce['u'.$start.''].','.$enforce['u'.($start+1).''].','.$enforce['u'.($start+2).''].','.$enforce['u'.($start+3).''].','.$enforce['u'.($start+4).''].','.$enforce['u'.($start+5).''].','.$enforce['u'.($start+6).''].','.$enforce['u'.($start+7).''].','.$enforce['u'.($start+8).''].','.$enforce['u'.($start+9).''].''; - $life1 = $enforce['u'.$start.'']+$enforce['u'.($start+1).'']+$enforce['u'.($start+2).'']+$enforce['u'.($start+3).'']+$enforce['u'.($start+4).'']+$enforce['u'.($start+5).'']+$enforce['u'.($start+6).'']+$enforce['u'.($start+7).'']+$enforce['u'.($start+8).'']+$enforce['u'.($start+9).'']; - $lifehero = (isset($enforce['hero']) ? $enforce['hero'] : 0); - $notlifehero = (isset($dead['hero']) ? $dead['hero'] : 0); - $totallife = (isset($enforce['hero']) ? $enforce['hero'] : 0)+$life1; - $totalnotlife = (isset($dead['hero']) ? $dead['hero'] : 0)+$notlife1; - //NEED TO SEND A RAPPORTAGE!!! - $data2 = ''.$database->getVillageField($enforce['from'], "owner").','.$to['wref'].','.addslashes($to['name']).','.$tribe.','.$life.','.$notlife.','.$lifehero.','.$notlifehero.','.$enforce['from'].''; - if (empty($scout)) { - if ($totalnotlife == 0) { - $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 15, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); - } else if ($totallife > $totalnotlife) { - $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 16, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); - } else { - $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 17, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); - } - //delete reinf sting when its killed all. - if (!$wrong) $database->deleteReinf($enforce['id']); - } - } - - return ['rom' => $rom, 'ger' => $ger, 'gal' => $gal, 'nat' => $nat, 'natar' => $natar, 'hun' => $hun, 'egy' => $egy, 'spa' => $spa, 'vik' => $vik]; - } - - /** - * Collect the defender-side report data BEFORE casualties are applied: reset and - * rebuild the reinforcement unit totals ($DefenderEnf) and the per-tribe hero - * totals from a fresh re-select, fold reinforcement heroes into the defender's - * hero count, build the "units sent" report rows (own troops + reinforcements, - * plus their masked variants), and (re)initialise the per-tribe dead-hero - * accumulator to zero. Returns everything the caller needs downstream. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses 'to'). - * @param int $targettribe Defender tribe (1-5). - * @param array $Defender Defender unit array (own troops + 'hero'); passed by value, - * its 'hero' is folded with reinforcement heroes and returned - * via the 'defenderHero' key. - * @return array Report bundle: DefenderEnf, DefenderHeroesTotArray, - * DefenderHeroesDeadArray, unitssend_def, unitssend_deff, - * totalsend_alldef, defenderHero. - */ - private function collectReinforcementReport($data, $targettribe, $Defender) { - global $database; - - $DefenderEnf = []; - $DefenderHeroesTotArray = []; - $DefenderHeroesDeadArray = []; - $unitssend_def = []; - $unitssend_deff = []; - - //Resetting the enforcement arrays - for ($i = 1; $i <= 90; $i++) { - $DefenderEnf['u'.$i] = 0; - if ($i <= 9) { - $DefenderHeroesTotArray[$i] = 0; - $DefenderHeroesDeadArray[$i] = 0; - } - } - - // our reinforcements count could have changed at this point, thus the re-select - $enforcementarray2 = $database->getEnforceVillage($data['to'], 0); - if (count($enforcementarray2) > 0) { - foreach ($enforcementarray2 as $enforce2) { - for ($i = 1; $i <= 90; $i++) { - $DefenderEnf['u'.$i] += $enforce2['u'.$i]; - } - - //Divide heroes by tribe - if ($enforce2['hero'] > 0) { - $reinfTribe = ($enforce2['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce2['from'], "owner"), "tribe", 0); - $DefenderHeroesTotArray[$reinfTribe] += $enforce2['hero']; - $Defender['hero'] += $enforce2['hero']; - } - } - } - - $totalsend_alldef = 0; - - //Own troops - $ownTroops = array_slice($Defender, ($targettribe - 1) * 10 + 1, 10); - $totalsend_alldef = array_sum($ownTroops); - - //Collecting informations for the report - $unitssend_def[0] = implode(",", $ownTroops); - $unitssend_deff[0] = '?,?,?,?,?,?,?,?,?,?,'; - - for ($i = 1; $i <= 9; $i++) { - //Reinforcements - $reinfTroops = array_slice($DefenderEnf, ($i - 1) * 10, 10); - $totalsend_alldef += array_sum($reinfTroops); - - //Collecting informations for the report - $unitssend_def[$i] = implode(",", $reinfTroops); - $unitssend_deff[$i] = $unitssend_deff[0]; - } - $totalsend_alldef += $Defender['hero']; - - return [ - 'DefenderEnf' => $DefenderEnf, - 'DefenderHeroesTotArray' => $DefenderHeroesTotArray, - 'DefenderHeroesDeadArray' => $DefenderHeroesDeadArray, - 'unitssend_def' => $unitssend_def, - 'unitssend_deff' => $unitssend_deff, - 'totalsend_alldef' => $totalsend_alldef, - 'defenderHero' => $Defender['hero'], - ]; - } - - /** - * Compute attacker/defender total populations and fetch their village lists. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $to Defender village info (getMInfo/getOMInfo). - * @param array $from Attacker village info (getMInfo). - * @param int $isoasis Whether the target is an oasis (defender pop counts only for villages). - * @param int $defpop Initial defender population (accumulated onto). - * @param int $attpop Initial attacker population (accumulated onto). - * @return array{varray:array,varray1:array,defpop:int,attpop:int} - */ - private function calculatePopulations($to, $from, $isoasis, $defpop, $attpop) { - global $database; - - $varray = $database->getProfileVillages($to['owner'], 0, false); - - if ($to['owner'] == $from['owner']) $varray1 = $varray; - else $varray1 = $database->getProfileVillages($from['owner'], 0, false); - - // total population of the defender - if($isoasis == 0){ - foreach($varray as $defenderVillage) $defpop += $defenderVillage['pop']; - } - - // total population of the attacker - foreach($varray1 as $attackerVillage) $attpop += $attackerVillage['pop']; - - return [ - 'varray' => $varray, - 'varray1' => $varray1, - 'defpop' => $defpop, - 'attpop' => $attpop, - ]; - } - - /** - * Resolve how many incoming attacker units get caught in the defender's - * traps (Gaul trapper / Natar capital), update the trap counters and the - * prisoners table, and subtract the trapped troops from the attacking army. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row. - * @param array $Defender Defender unit counts (reads u99 / u99o). - * @param array $Attacker Attacker units (u + uhero). - * @param bool $NatarCapital True when the target is a Natar capital (all troops trapped). - * @param int $scout 1 for a scouting attack (no trapping). - * @param int $start First unit index of the attacker tribe. - * @param int $end Last unit index of the attacker tribe. - * @return array { - * traped: int[] trapped count per unit slot (1..11), - * totaltraped_att: int total trapped troops, - * Attacker: array attacker units with trapped troops removed - * } - */ - private function calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end) { - global $database; - - $traped = []; - for($i = 1; $i <= 11; $i++) $traped[$i] = 0; - $totaltraped_att = 0; - - //impossible to attack or scout NATAR Capital Village - if ($NatarCapital){ - for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i]; - } - elseif(empty($scout)) - { - $traps = max($Defender['u99'] - $Defender['u99o'], 0); - - $totalTroops = 0; - for($i = 1; $i <= 11; $i++) $totalTroops += $data['t'.$i]; - - if($traps >= $totalTroops){ - for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i]; - } - else if($totalTroops > 0) - { - $multiplier = $traps / $totalTroops; - - //The hero is excluded, because it can be only trapped if traps > totalTroops - for($i = 1; $i <= 10; $i++){ - $trappedUnits = intval($data['t'.$i] * $multiplier); - $traped[$i] = $trappedUnits; - $traps -= $trappedUnits; - } - - while($traps > 0){ - //There are some traps left, let's distribute them - for($i = 1; $i <= 10 && $traps > 0; $i++){ - if($data['t'.$i] != 0){ - $traped[$i]++; - $traps--; - } - } - } - } - else { - for($i = 1; $i <= 11; $i++) $traped[$i] = 0; - } - } - - if(empty($scout) || $NatarCapital){ - for ($i = 1; $i <= 11; $i++){ - $totaltraped_att += $traped[$i]; - } - - $database->modifyUnit($data['to'], ["99o"], [$totaltraped_att], [1]); - - for($i = $start; $i <= $end; $i++){ - $j = $i-$start+1; - $Attacker['u'.$i] -= $traped[$j]; - } - $Attacker['uhero'] -= $traped[11]; - - if($totaltraped_att > 0){ - $prisoners2 = $database->getPrisoners2($data['to'], $data['from'], false); - if(empty($prisoners2)){ - $database->addPrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]); - }else{ - $database->updatePrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]); - } - } - } - - return [ - 'traped' => $traped, - 'totaltraped_att' => $totaltraped_att, - 'Attacker' => $Attacker, - ]; - } - - /** - * Apply ram damage to the defender's wall during a normal attack (type 3): - * compute the new wall level, update it in the database (recounting the - * village population if the wall is destroyed), build the report fragment, - * and recalculate the battle when the wall level changed. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses t7, to). - * @param int $walllevel Current wall level. - * @param int $wallid Wall building field id (40). - * @param int $ram_pic Ram unit id (for the report fragment). - * @param array $battlepart Battle result (provides the ram damage factors). - * @param array $ctx Battle context for the post-damage recalculation - * (Attacker, Defender, tribes, populations, AB tech, ...). - * @return array { battlepart: array (possibly recalculated), info_ram: string } - */ - private function applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, array $ctx) { - global $database, $battle; - - $info_ram = "".$ram_pic.",".rc_tok('RC_NO_WALL'); - - if($walllevel > 0){ - $ramsDamage = $battle->calculateCatapultsDamage($data['t7'], - $battlepart['rams']['upgrades'], - $battlepart['rams']['durability'], - $battlepart['rams']['attackDefenseRatio'], - $battlepart['rams']['strongerBuildings'], - $battlepart['rams']['moraleBonus']); - $newLevel = $battle->calculateNewBuildingLevel($walllevel, $ramsDamage); - - if ($newLevel == 0){ - $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DESTROYED'); - $database->setVillageLevel($data['to'], ["f".$wallid, "f".$wallid."t"], [0, 0]); - $this->recountPop($data['to']); - }elseif ($newLevel == $walllevel){ - $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_NOT_DAMAGED'); - }else{ - $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DAMAGED_FROM_TO', $walllevel, $newLevel); - $database->setVillageLevel($data['to'],"f".$wallid."",$newLevel); - } - - //If the wall got damaged/destroyed during the attack - //we need to recalculate the whole battle - if($newLevel != $walllevel){ - $battlepart = $battle->calculateBattle($ctx['Attacker'], $ctx['Defender'], $newLevel, $ctx['att_tribe'], $ctx['def_tribe'], $ctx['residence'], $ctx['attpop'], $ctx['defpop'], $ctx['type'], $ctx['def_ab'], $ctx['att_ab1'], $ctx['att_ab2'], $ctx['att_ab3'], $ctx['att_ab4'], $ctx['att_ab5'], $ctx['att_ab6'], $ctx['att_ab7'], $ctx['att_ab8'], $ctx['tblevel'], $ctx['stonemason'], $newLevel, 0, 0, 0, $ctx['AttackerID'], $ctx['DefenderID'], $ctx['AttackerWref'], $ctx['DefenderWref'], $ctx['conqureby'], $ctx['enforcementarray']); - } - } - - return ['battlepart' => $battlepart, 'info_ram' => $info_ram]; - } - - /** - * Build the attacking army for the current attack: per-slot unit counts - * (u + uhero) plus the catapult / ram / chief / scout unit ids - * used in the battle report. Oasis attacks include the Nature siege/chief - * slots (37/38/39). Pure behaviour-preserving extraction (issue #155). - * - * @param array $attackRow The attack row (t1..t11, tribe-relative). - * @param int $owntribe Attacker tribe (1..5). - * @param int $isoasis 0 for a village target, otherwise an oasis. - * @return array { Attacker, start, end, u, catp_pic, ram_pic, chief_pic, spy_pic, hero_pic } - */ - private function buildAttackerUnits(array $attackRow, $owntribe, $isoasis) { - $Attacker = []; - $start = ($owntribe - 1) * 10 + 1; - $end = $owntribe * 10; - $u = ($owntribe - 1) * 10; - - if ($isoasis == 0) { - $catapult = [8, 18, 28, 48, 58, 68, 78, 88]; - $ram = [7, 17, 27, 47, 57, 67, 77, 87]; - $chief = [9, 19, 29, 49, 59, 69, 79, 89]; - } else { - $catapult = [8, 18, 28, 38, 48, 58, 68, 78, 88]; - $ram = [7, 17, 27, 37, 47, 57, 67, 77, 87]; - $chief = [9, 19, 29, 39, 49, 59, 69, 79, 89]; - } - $spys = [4, 14, 23, 44, 52, 64, 74, 82]; - - $catp_pic = $ram_pic = $chief_pic = $spy_pic = null; - - for ($i = $start; $i <= $end; $i++) { - $y = $i - $u; - $Attacker['u'.$i] = $attackRow['t'.$y]; - //there are catas - if (in_array($i, $catapult)) $catp_pic = $i; - if (in_array($i, $ram)) $ram_pic = $i; - if (in_array($i, $chief)) $chief_pic = $i; - if (in_array($i, $spys)) $spy_pic = $i; - } - $Attacker['uhero'] = $attackRow['t11']; - - return [ - 'Attacker' => $Attacker, - 'start' => $start, - 'end' => $end, - 'u' => $u, - 'catp_pic' => $catp_pic, - 'ram_pic' => $ram_pic, - 'chief_pic' => $chief_pic, - 'spy_pic' => $spy_pic, - 'hero_pic' => 'hero', - ]; - } - - /** - * Gather the defender's units for the current attack: the village's own - * troops (normalised to non-negative ints) plus the aggregated reinforcement - * totals, and the raw reinforcement rows. Used for both village and oasis - * targets. Pure behaviour-preserving extraction (issue #155). - * - * @param int $wref Defending village/oasis wref. - * @return array { Defender, enforDefender, enforcementarray } - */ - private function buildDefenderUnits($wref) { - global $database; - - $enforDefender = []; - $Defender = $database->getUnit($wref, false); - $enforcementarray = $database->getEnforceVillage($wref, 0); - - if (count($enforcementarray) > 0) { - foreach ($enforcementarray as $enforce) { - for ($i = 1; $i <= 90; $i++) { - if (!isset($enforDefender['u'.$i])) { - $enforDefender['u'.$i] = 0; - } - $enforDefender['u'.$i] += $enforce['u'.$i]; - } - if (!isset($enforDefender['hero'])) { - $enforDefender['hero'] = 0; - } - $enforDefender['hero'] += $enforce['hero']; - } - } - - for ($i = 1; $i <= 90; $i++) { - if (!isset($Defender['u'.$i]) || empty($Defender['u'.$i]) || $Defender['u'.$i] < 0) { - $Defender['u'.$i] = 0; - } - } - - if (!isset($Defender['hero']) || empty($Defender['hero']) || $Defender['hero'] < 0) { - $Defender['hero'] = 0; - } - - return [ - 'Defender' => $Defender, - 'enforDefender' => $enforDefender, - 'enforcementarray' => $enforcementarray, - ]; - } - - /** - * Resolve the per-attack context that does not depend on the target type: - * the attacker village/owner data (tribe, alliance), the war references and - * a few base flags. Read-only — no DB writes. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row. - * @return array {isoasis, AttackArrivalTime, AttackerWref, DefenderWref, - * NatarCapital, AttackerID, owntribe, ownally, from, fromF} - */ - private function resolveAttackContext($data) { - global $database; - - $owner = $this->getCachedUser($database->getVillageField($data['from'], "owner"), 1); - - return [ - 'isoasis' => $data['oasistype'], - 'AttackArrivalTime' => $data['endtime'], - 'AttackerWref' => $data['from'], - 'DefenderWref' => $data['to'], - 'NatarCapital' => false, - 'AttackerID' => $owner['id'], - 'owntribe' => $owner['tribe'], - 'ownally' => $owner['alliance'], - 'from' => $database->getMInfo($data['from']), - 'fromF' => $database->getVillage($data['from']), - ]; - } - - /** - * Resolve the defender target context for a VILLAGE attack: target owner - * (tribe/alliance), map info, conquest flag, evasion inputs and the battle - * environment (wall, armory/blacksmith tech, residence, siege masonry). - * Read-only — no DB writes. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row. - * @param array $dataarray Full batch of completed attacks. - * @param int $data_num Index of $data within $dataarray. - * @param int $owntribe Attacker tribe. - * @return array Target + battle-environment context. - */ - private function resolveVillageTarget($data, $dataarray, $data_num, $owntribe) { - global $database, $units; - - $DefenderUserData = $this->getCachedUser($database->getVillageField($data['to'],"owner"),1); - $DefenderID = $DefenderUserData["id"]; - $targettribe = $DefenderUserData["tribe"]; - $targetally = $DefenderUserData["alliance"]; - $to = $database->getMInfo($data['to']); - $toF = $database->getVillage($data['to']); - $conqureby = 0; - $NatarCapital = ($toF['owner'] == 3 && $toF['capital'] == 1); - if(!isset($to['name']) || empty($to['name'])) $to['name'] = "[?]"; - - $DefenderUnit = $database->getUnit($data['to']); - $evasion = $toF["evasion"]; - $maxevasion = $DefenderUserData["maxevasion"]; - $gold = $DefenderUserData["gold"]; - $cannotsend = false; - - $movements = $database->getMovement(34, $data['to'], 1); - for($y = 0; $y < count($movements); $y++){ - if(property_exists($units, $y)){ - $returntime = $units->$y['endtime'] - time(); - if($units->$y['sort_type'] == 4 && $units->$y['from'] != 0 && $returntime <= 10){ - $cannotsend = true; - } - } - } - - //need to set these variables. - $def_wall = $database->getFieldLevel($data['to'], 40, false); - $att_tribe = $owntribe; - $def_tribe = $targettribe; - $attpop = $defpop = $residence = 0; - $def_ab = []; - - //get level of palace or residence - $residence = $database->getFieldLevelInVillage($data['to'], '25, 26', false); - - //type of attack - $type = $dataarray[$data_num]['attack_type']; - $scout = ($type == 1) ? 1 : 0; - - $ud = ($def_tribe - 1) * 10; - $att_ab = $database->getABTech($data['from']); // Blacksmith level - $att_ab1 = $att_ab['b1']; - $att_ab2 = $att_ab['b2']; - $att_ab3 = $att_ab['b3']; - $att_ab4 = $att_ab['b4']; - $att_ab5 = $att_ab['b5']; - $att_ab6 = $att_ab['b6']; - $att_ab7 = $att_ab['b7']; - $att_ab8 = $att_ab['b8']; - $armory = $database->getABTech($data['to']); // Armory level - $def_ab[$ud + 1] = $armory['a1']; - $def_ab[$ud + 2] = $armory['a2']; - $def_ab[$ud + 3] = $armory['a3']; - $def_ab[$ud + 4] = $armory['a4']; - $def_ab[$ud + 5] = $armory['a5']; - $def_ab[$ud + 6] = $armory['a6']; - $def_ab[$ud + 7] = $armory['a7']; - $def_ab[$ud + 8] = $armory['a8']; - - //rams attack - $walllevel = 0; - $wallid = 0; - if (($data['t7']) > 0 && $type == 3) { - $basearraywall = $to; - if (($walllevel = $database->getFieldLevel($basearraywall['wref'], 40, false)) > 0){ - $wallid = 40; - } - } - - $tblevel = 1; - $stonemason = $database->getFieldLevelInVillage($data['to'], 34); - - return [ - 'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally, - 'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby, 'NatarCapital' => $NatarCapital, - 'DefenderUnit' => $DefenderUnit, 'evasion' => $evasion, 'maxevasion' => $maxevasion, - 'gold' => $gold, 'cannotsend' => $cannotsend, - 'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, - 'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab, - 'type' => $type, 'scout' => $scout, - 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, - 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, - 'walllevel' => $walllevel, 'wallid' => $wallid, - 'tblevel' => $tblevel, 'stonemason' => $stonemason, - ]; - } - - /** - * Resolve the defender target context for an OASIS attack: target owner - * (tribe/alliance), oasis map info, conquest flag and the (mostly fixed) - * battle environment. Read-only — no DB writes. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row. - * @param array $dataarray Full batch of completed attacks. - * @param int $data_num Index of $data within $dataarray. - * @param int $owntribe Attacker tribe. - * @return array Target + battle-environment context. - */ - private function resolveOasisTarget($data, $dataarray, $data_num, $owntribe) { - global $database; - - $DefenderUserData = $this->getCachedUser($database->getOasisField($data['to'], "owner"),1); - $DefenderID = $DefenderUserData["id"]; - $targettribe = $DefenderUserData["tribe"]; - $targetally = $DefenderUserData["alliance"]; - $to = $database->getOMInfo($data['to']); - $toF = $database->getOasisV($data['to']); - $conqureby = $toF['conqured']; - - //need to set these variables. - $def_wall = $residence = $attpop = 0; - $att_tribe = $owntribe; - $def_tribe = $targettribe; - $defpop = 500; - - //type of attack - $type = $dataarray[$data_num]['attack_type']; - $scout = ($type == 1) ? 1 : 0; - - $att_ab1 = $att_ab2 = $att_ab3 = $att_ab4 = $att_ab5 = $att_ab6 = $att_ab7 = $att_ab8 = 0; - $def_ab = []; - $def_ab[31] = $def_ab[32] = $def_ab[33] = $def_ab[34] = $def_ab[35] = $def_ab[36] = $def_ab[37] = $def_ab[38] = 0; - - $walllevel = $tblevel = $stonemason = 0; - - return [ - 'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally, - 'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby, - 'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, - 'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab, - 'type' => $type, 'scout' => $scout, - 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, - 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, - 'walllevel' => $walllevel, 'tblevel' => $tblevel, 'stonemason' => $stonemason, - ]; - } - - /** - * Compute attacker/defender hero XP and victory points from the battle - * casualties, persist them (hero XP, player points, alliance points) and - * return the total defender losses. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $alldead Reinforcement casualties by global unit id (+ 'hero'). - * @param array $owndead Defender own casualties by global unit id (+ 'hero'). - * @param array $attackerDead Attacker casualties, 0-indexed t1..t11 (index 10 = hero). - * @param int $targettribe Defender tribe id. - * @param int $att_tribe Attacker tribe id. - * @param array $Attacker Attacker troop row (uses 'uhero'). - * @param mixed $AttackerHeroID Attacker hero id (for the XP write). - * @param array $Defender Defender troop row (uses 'hero'). - * @param array $DefendersHeroID Defender hero ids (for the XP writes). - * @param array $toF Defender village (uses 'owner'). - * @param array $from Attacker village info (uses 'owner'). - * @param int $targetally Defender alliance id. - * @param int $ownally Attacker alliance id. - * @param mixed $heroxp OUT (by ref): attacker hero XP, set only if the attacker has a hero. - * @param mixed $defheroxp OUT (by ref): per-defender hero XP, set only if the defender has a hero. - * @return int Total defender losses (reinforcements + own troops). - */ - private function calculateHeroXpAndPoints( - array $alldead, array $owndead, array $attackerDead, - $targettribe, $att_tribe, - $Attacker, $AttackerHeroID, $Defender, $DefendersHeroID, - $toF, $from, $targetally, $ownally, - &$heroxp, &$defheroxp - ) { - global $database; - - $totaldead_def = 0; - $totalpoint_att = 0; - - for($i = 1 ;$i <= 90; $i++) { - $unitarray = $GLOBALS["u".$i]; - - //Reinforcements dead troops - $totaldead_def += $alldead[$i]; - $totalpoint_att += ($alldead[$i] * $unitarray['pop']); - - //Own dead troops - if($i >= ($targettribe - 1) * 10 + 1 && $i <= $targettribe * 10){ - $totaldead_def += $owndead[$i]; - $totalpoint_att += ($owndead[$i] * $unitarray['pop']); - } - } - $totalpoint_att += ((isset($alldead['hero']) ? $alldead['hero'] : 0) * 6); - $totalpoint_att += ((isset($owndead['hero']) ? $owndead['hero'] : 0) * 6); - - if ($Attacker['uhero'] > 0){ - $heroxp = $totalpoint_att; - $database->modifyHeroXp("experience", $heroxp, $AttackerHeroID); - } - - for($i = 1; $i <= 10; $i++){ - $unitarray = $GLOBALS["u".(($att_tribe - 1) * 10 + $i)]; - if ( !isset($totalpoint_def) ) { - $totalpoint_def = 0; - } - $totalpoint_def += ($attackerDead[$i - 1] * $unitarray['pop']); - } - - $totalpoint_def += $attackerDead[10] * 6; - - if($Defender['hero'] > 0){ - //counting heroxp - $defheroxp = intval($totalpoint_def / count($DefendersHeroID)); - foreach($DefendersHeroID as $HeroID){ - $database->modifyHeroXp("experience",$defheroxp,$HeroID); - } - } - - // we don't need these two variables anymore - unset($AttackerHeroID, $DefendersHeroID); - - $database->modifyPoints( - $toF['owner'], - ['dpall', 'dp'], - [$totalpoint_def, $totalpoint_def] - ); - - $database->modifyPoints( - $from['owner'], - ['apall', 'ap'], - [$totalpoint_att, $totalpoint_att] - ); - - $database->modifyPointsAlly( - $targetally, - ['Adp', 'dp'], - [$totalpoint_def, $totalpoint_def] - ); - - $database->modifyPointsAlly( - $ownally, - ['Aap', 'ap'], - [$totalpoint_att, $totalpoint_att] - ); - - return $totaldead_def; - } - - /** - * Resolve the resources lootable from the battle target after cranny - * protection: compute the cranny efficiency, pull the current (capped) - * stocks of the target village/oasis and derive the lootable amounts. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses 'to'). - * @param int $isoasis Oasis flag (0 = village). - * @param int $conqureby Owner village of a conquered oasis (0 = none). - * @param int $owntribe Attacker tribe id (Teuton cranny bonus). - * @param int $targettribe Defender tribe id (Gaul cranny bonus). - * @param mixed $crannySpy OUT (by ref): cranny value seen by a scout; set only for village targets (isoasis == 0). - * @return array ['totclay','totiron','totwood','totcrop','avclay','aviron','avwood','avcrop']. - */ - private function resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, &$crannySpy) { - global $database, $bid23; - - if ($isoasis == 0){ - // get total cranny value: - $buildarray = $database->getResourceLevel($data['to']); - $cranny = 0; - for($i = 19; $i < 39;$i++){ - if($buildarray['f'.$i.'t'] == 23){ - $cranny += $bid23[$buildarray['f'.$i]]['attri'] * CRANNY_CAPACITY; - } - } - - //cranny efficiency - $atk_bonus = ($owntribe == 2) ? (4 / 5) : 1; - $def_bonus = ($targettribe == 3) ? 2 : 1; - $to_owner = $database->getVillageField($data['to'], "owner"); - - $crannySpy = $database->getArtifactsValueInfluence($to_owner, $data['to'], 7, $cranny * $def_bonus); - $cranny_eff = $crannySpy * $atk_bonus; - - // work out available resources. - $this->updateRes($data['to']); - $this->pruneResource(); - - $villageData = $database->getVillageFields($data['to'], 'clay, iron, wood, crop', false); - $totclay = $villageData['clay']; - $totiron = $villageData['iron']; - $totwood = $villageData['wood']; - $totcrop = $villageData['crop']; - }else{ - $cranny_eff = 0; - - if ($conqureby > 0) { //10% from owner proc village owner - fix by ronix - exploit fixed by iopietro - $this->updateRes($conqureby); - $this->pruneResource(); - - $villageData = $database->getVillageFields($conqureby, 'clay, iron, wood, crop', false); - $totclay = intval($villageData['clay'] / 10); - $totiron = intval($villageData['iron'] / 10); - $totwood = intval($villageData['wood'] / 10); - $totcrop = intval($villageData['crop'] / 10); - }else{ - // work out available resources. - $this->updateORes($data['to']); - $this->pruneOResource(); - - $oasisData = $database->getOasisFields($data['to'], false); - $totclay = $oasisData['clay']; - $totiron = $oasisData['iron']; - $totwood = $oasisData['wood']; - $totcrop = $oasisData['crop']; - } - } - - $avclay = floor($totclay - $cranny_eff); - $aviron = floor($totiron - $cranny_eff); - $avwood = floor($totwood - $cranny_eff); - $avcrop = floor($totcrop - $cranny_eff); - - $avclay = ($avclay < 0) ? 0 : $avclay; - $aviron = ($aviron < 0) ? 0 : $aviron; - $avwood = ($avwood < 0) ? 0 : $avwood; - $avcrop = ($avcrop < 0) ? 0 : $avcrop; - - return [ - 'totclay' => $totclay, 'totiron' => $totiron, 'totwood' => $totwood, 'totcrop' => $totcrop, - 'avclay' => $avclay, 'aviron' => $aviron, 'avwood' => $avwood, 'avcrop' => $avcrop, - ]; - } - - /** - * Resolve the attacker hero's outcome after the battle: build the hero - * report line ($info_hero), and on a surviving hero handle oasis conquest / - * loyalty reduction (oasis targets) or artifact claiming with possible - * village destruction (village targets). - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param array $data Current attack row (uses 't11', 'from', 'to'). - * @param array $from Attacker village info (uses 'owner'). - * @param array $to Defender village/oasis info (uses 'owner', 'loyalty'). - * @param int $dead11 Attacker hero casualties. - * @param int $traped11 Attacker hero trapped count. - * @param mixed $heroxp Attacker hero XP gained this battle. - * @param string $hero_pic Hero unit picture id (report markup). - * @param int $isoasis Oasis flag (0 = village). - * @param int $type Attack type (3 = normal with rams/catas). - * @param int $targettribe Defender tribe id. - * @param string $info_cat Catapult report fragment (read for the destruction notice). - * @param string $info_hero IN/OUT (by ref): hero report line. - * @param int $can_destroy IN/OUT (by ref): village-destruction allowed flag. - * @param int $village_destroyed IN/OUT (by ref): village-destroyed flag. - * @return void - */ - private function handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, &$info_hero, &$can_destroy, &$village_destroyed) { - global $database; - - if(($data['t11'] - $dead11 - $traped11) > 0){ //hero - if ($heroxp == 0) { - $xp = ""; - $info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_KILL'); - } else { - $xp = " ".rc_tok('RC_HERO_AND_GAINED_XP_BATTLE', $heroxp); - $info_hero = $hero_pic.",".rc_tok('RC_HERO_GAINED_XP', $heroxp); - } - - if ($isoasis != 0) { //oasis fix by ronix - if ($to['owner'] != $from['owner']) { - $troopcount = $database->countOasisTroops($data['to'], false); - $canqured = $database->canConquerOasis($data['from'], $data['to'], false); - if ($canqured == 1 && $troopcount == 0) { - $database->conquerOasis($data['from'], $data['to']); - $info_hero = $hero_pic.",".rc_tok('RC_HERO_CONQUERED_OASIS').$xp; - }else{ - if ($canqured == 3 && $troopcount == 0) { - if ($type == 3) { - $Oloyaltybefore = intval($to['loyalty']); - //$database->modifyOasisLoyalty($data['to']); - //$OasisInfo = $database->getOasisInfo($data['to']); - $Oloyaltynow = intval($database->modifyOasisLoyalty($data['to']));//intval($OasisInfo['loyalty']); - $info_hero = $hero_pic.",".rc_tok('RC_HERO_REDUCED_OASIS_LOYALTY', $Oloyaltynow, $Oloyaltybefore).$xp; - } - else $info_hero = $hero_pic.",".rc_tok('RC_NO_REDUCE_LOYALTY_RAID').$xp; - } - } - } - } else { - if ($heroxp == 0) $xp=" ".rc_tok('RC_HERO_NO_XP_BATTLE'); - else $xp=" ".rc_tok('RC_HERO_GAINED_XP_BATTLE', $heroxp); - - $artifact = reset($database->getOwnArtefactInfo($data['to'])); - if (!empty($artifact)) { - if ($type == 3) { - if (empty($artifactError = $database->canClaimArtifact($data['from'], $artifact['vref'], $artifact['size'], $artifact['type']))) { - $database->claimArtefact($data['from'], $data['to'], $database->getVillageField($data['from'], "owner")); - $info_hero = $hero_pic.",".rc_tok('RC_HERO_CARRYING_ARTIFACT', $artifact['name']).$xp; - - // if the defender pop is 0 with no artefact, then destroy the village - if($database->getVillageField($data['to'], "pop") == 0 || $targettribe == 5){ - $can_destroy = $village_destroyed = 1; - if(strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) $info_hero .= " ".rc_tok('RC_VILLAGE_DESTROYED'); - } - } - else $info_hero = $hero_pic.",".$artifactError.$xp; - } - else $info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_ARTIFACT_RAID').$xp; - } - } - }elseif($data['t11'] > 0) { - if ($heroxp == 0) $xp = ""; - else $xp = " ".rc_tok('RC_HERO_BUT_GAINED_XP_BATTLE', $heroxp); - - if ($traped11 > 0) $info_hero = $hero_pic.",".rc_tok('RC_HERO_TRAPPED').$xp; - else $info_hero = $hero_pic.",".rc_tok('RC_HERO_DIED').$xp; - } - } - - /** - * Send the battle/scout report notification to the defender. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param mixed $scout Truthy when the attack is a scouting run. - * @param array $battlepart Battle result (uses 'casualties_attacker'). - * @param array $from Attacker village info (uses 'owner', 'name'). - * @param array $to Defender village/oasis info (uses 'owner', 'wref', 'name'). - * @param int $targetally Defender alliance id. - * @param string $data2 Combat-report payload. - * @param int $AttackArrivalTime Battle resolution timestamp. - * @param string $unitsdead_att Attacker dead units string. - * @param string $unitssend_att Attacker sent units string. - * @param bool $defspy Whether the defence has scouts. - * @param string $info_troop Troop-return report fragment. - * @param int $totalsend_alldef Total defending troops (incl. reinforcements). - * @param int $totalsend_att Total attacking troops sent. - * @param int $totaldead_att Total attacking troops killed. - * @param int $totaltraped_att Total attacking troops trapped. - * @param int $totaldead_alldef Total defending troops killed. - * @return void - */ - private function sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef) { - global $database; - - //Undetected and detected in here. - if(!empty($scout)){ - for($i = 1; $i <= 10; $i++){ - if($battlepart['casualties_attacker'][$i]){ - if($from['owner'] == 3){ - $database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - break; - }else if($unitsdead_att == $unitssend_att && $defspy){ //fix by ronix - $database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2.',,'.$info_troop,$AttackArrivalTime); - break; - }else if($defspy){ //fix by ronix - $database->addNotice($to['owner'],$to['wref'],$targetally,21,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - break; - } - } - } - }else{ - if($totalsend_alldef == 0 && $totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0){ - $database->addNotice($to['owner'],$to['wref'],$targetally,7,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - }else if($totaldead_alldef == 0){ - $database->addNotice($to['owner'],$to['wref'],$targetally,4,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - }else if($totalsend_alldef > $totaldead_alldef){ - $database->addNotice($to['owner'],$to['wref'],$targetally,5,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - }else if($totalsend_alldef == $totaldead_alldef){ - $database->addNotice($to['owner'],$to['wref'],$targetally,6,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); - } - } - } - - /** - * Finalize the fate of the attacking troops: if any survived they return - * home (attacker report + return movement + loot transfer / RR points / - * leftover-chief enforcement), otherwise they die without returning (only - * the all-dead report is sent). Pure behaviour-preserving extraction - * (refactor for issue #155). - * - * @param array $data Current attack row (uses 't1..t11', 'moveid', 'ref'). - * @param array $from Attacker village info (uses 'wref', 'owner', 'name'). - * @param array $to Defender village/oasis info (uses 'wref', 'owner', 'name'). - * @param int $owntribe Attacker tribe id. - * @param int $type Attack type (1 = scout). - * @param int $isoasis Oasis flag (0 = village). - * @param int $conqureby Owner village of a conquered oasis (0 = none). - * @param int $AttackArrivalTime Battle resolution timestamp. - * @param int $targetally Defender alliance id. - * @param int $ownally Attacker alliance id. - * @param string $data2 Combat-report payload (survivors). - * @param string $data_fail Combat-report payload (all dead). - * @param int $chiefing_village Whether this attack chiefed the village. - * @param int $DefenderWref Defender world ref. - * @param int $AttackerWref Attacker world ref. - * @param array $steal [wood, clay, iron, crop] looted. - * @param array $dead Attacker casualties, 1-indexed t1..t11. - * @param array $traped Attacker trapped, 1-indexed t1..t11. - * @param array $troopsdead Attacker dead counts for enforcement, 1-indexed t1..t11. - * @param int $totalsend_att Total attacking troops sent. - * @param int $totaldead_att Total attacking troops killed. - * @param int $totaltraped_att Total attacking troops trapped. - * @param int $totalstolentaken IN/OUT (by ref): running RR loss accumulator for the defender. - * @return void - */ - private function finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $dead, $traped, $troopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, &$totalstolentaken) { - global $database, $units; - - // Spital: o parte din trupele proprii moarte IN ATAC devin ranite acasa. - // $dead e indexat 1..11 pe sloturile tribului; doar unitatile de lupta (1-8). - if($owntribe >= 1 && $owntribe <= 9) { - $woundedAtt = []; - for($wj = 1; $wj <= 8; $wj++) { - if(!empty($dead[$wj])) $woundedAtt[($owntribe - 1) * 10 + $wj] = $dead[$wj]; - } - if(!empty($woundedAtt)) $this->applyHospitalWounded($from['wref'], $woundedAtt); - } - - // If the dead units not equal the ammount sent they will return and report - if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0) - { - $returningTroops = []; - for($i = 1; $i <= 11; $i++) $returningTroops['t'.$i] = $data['t'.$i] - $traped[$i] - $dead[$i]; - // T4 hero port (Phase 6): bandages revive part of the - // losses when the hero comes home alive; revived units - // are written back to the attacks row (the source - // returnunitsComplete reads on arrival). No-op when off. - $returningTroops = HeroBattleBonus::applyBandages($from['owner'], $returningTroops, $dead, (int) $data['ref']); - $troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $returningTroops, 1, 't'); - $endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime); - $endtime += $AttackArrivalTime; - // T4 hero port (Phase 5): the map shortens the return - // leg when the SURVIVING hero travels home. No-op when off. - $endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $returningTroops['t11'], $AttackArrivalTime, $endtime); - if($type == 1){ - if($from['owner'] == 3){ // fix natar report by ronix - $database->addNotice($to['owner'], $to['wref'], $targetally, 20, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); - }elseif($totaldead_att == 0 && $totaltraped_att == 0){ - $database->addNotice($from['owner'], $to['wref'], $ownally, 18, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); - }else{ - $database->addNotice($from['owner'], $to['wref'], $ownally, 21, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); - } - }else{ - if((empty($totaldead_att) || $totaldead_att == 0) && (empty($totaltraped_att) || $totaltraped_att == 0)){ - $database->addNotice($from['owner'], $to['wref'], $ownally, 1, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); - }else{ - $database->addNotice($from['owner'], $to['wref'], $ownally, 2, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); - } - } - - $database->setMovementProc($data['moveid']); - - if (!isset($chiefing_village)) $chiefing_village = 0; - - if($chiefing_village != 1){ - $database->addMovement(4, $DefenderWref, $AttackerWref, $data['ref'], $AttackArrivalTime, $endtime, 1, $steal[0], $steal[1], $steal[2], $steal[3]); - if($type !== 1){ - if ($isoasis == 0) $database->modifyResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0); - else - { - //if it's an oasis but it's conquered by someone, resources must be modified in the owner's village - if($conqureby > 0) $database->modifyResource($conqureby, $steal[0], $steal[1], $steal[2], $steal[3], 0); - else $database->modifyOasisResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0); - } - $totalstolengain = $steal[0] + $steal[1] + $steal[2] + $steal[3]; - $totalstolentaken = ((isset($totalstolentaken) ? $totalstolentaken : 0) - ($steal[0] + $steal[1] + $steal[2] + $steal[3])); - $database->modifyPoints($from['owner'], 'RR', $totalstolengain); - $database->modifyPoints($to['owner'], 'RR', $totalstolentaken); - $database->modifyPointsAlly($targetally, 'RR', $totalstolentaken); - $database->modifyPointsAlly($ownally, 'RR', $totalstolengain); - } - }else{ //fix by ronix if only 1 chief left to conqured - don't add with zero enforces - if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 1){ - $database->addEnforce2($data, $owntribe, $troopsdead[1], $troopsdead[2], $troopsdead[3], $troopsdead[4], $troopsdead[5], $troopsdead[6], $troopsdead[7], $troopsdead[8], $troopsdead[9], $troopsdead[10], $troopsdead[11]); - } - } - } - else //else they die and don't return or report. - { - $database->setMovementProc($data['moveid']); - if($type == 1){ - $database->addNotice($from['owner'], $to['wref'], $ownally, 19, addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime); - }else{ - $database->addNotice($from['owner'], $to['wref'], $ownally, 3, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime); - } - } - } - - /** - * Destroy the target village when the battle flagged it for destruction: - * reassign the capital to the player's most populated remaining village if - * needed, delete the village and reassign its hero. - * Pure behaviour-preserving extraction (refactor for issue #155). - * - * @param int $village_destroyed Village-destroyed flag. - * @param int $can_destroy Village-destruction allowed flag. - * @param array $data Current attack row (uses 'to'). - * @param array $to Defender village info (uses 'capital'). - * @param array $varray The defender owner's villages. - * @return void - */ - private function handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray) { - global $database; - - if (!($village_destroyed == 1 && $can_destroy == 1)) { - return; - } - - if($to['capital'] == 1){ - $mostPopulatedVillage = []; - //Search for the most populated village - foreach($varray as $village){ - if($village['wref'] != $data['to'] && (empty($mostPopulatedVillage) || $mostPopulatedVillage['pop'] < $village['pop'])){ - $mostPopulatedVillage = $village; - } - } - //Set the new capital - $database->changeCapital($mostPopulatedVillage['wref']); - } - - //Delete the village - $database->DelVillage($data['to']); - - //Reassign the hero, if dead and assigned to the deleted village - $database->reassignHero($data['to']); - } - - 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 $bid23, $database, $battle, $technology, $units; - - $time = time(); - $dataarray = $this->fetchCompletedAttacks($time); - $totalattackdead = $data_num = 0; - - if ($dataarray && count($dataarray)) { - // preload village data (batched) so the per-attack loop hits the - // cache instead of querying row by row - $this->preloadBattleData($dataarray); - - // tiles whose village was razed earlier in this same batch (issue #298) - $razedTargets = []; - - // calculate battles - foreach($dataarray as $data) { - //set base things - $totaltraped_att = 0; - for($i = 1; $i <= 11; $i++) ${'traped'.$i} = 0; - // per-attack context (attacker village/owner) — extracted to resolveAttackContext() [#155] - $ctx = $this->resolveAttackContext($data); - $isoasis = $ctx['isoasis']; - $AttackArrivalTime = $ctx['AttackArrivalTime']; - $AttackerWref = $ctx['AttackerWref']; - $DefenderWref = $ctx['DefenderWref']; - $NatarCapital = $ctx['NatarCapital']; - $AttackerID = $ctx['AttackerID']; - $Attacker['id'] = $ctx['AttackerID']; - $owntribe = $ctx['owntribe']; - $ownally = $ctx['ownally']; - $from = $ctx['from']; - $fromF = $ctx['fromF']; - - //It's a village - if ($isoasis == 0){ - // target + battle environment — extracted to resolveVillageTarget() [#155] - $vt = $this->resolveVillageTarget($data, $dataarray, $data_num, $owntribe); - $DefenderID = $vt['DefenderID']; - $targettribe = $vt['targettribe']; - $targetally = $vt['targetally']; - $to = $vt['to']; - $toF = $vt['toF']; - $conqureby = $vt['conqureby']; - $NatarCapital = $vt['NatarCapital']; - $DefenderUnit = $vt['DefenderUnit']; - $def_wall = $vt['def_wall']; - $att_tribe = $vt['att_tribe']; - $def_tribe = $vt['def_tribe']; - $attpop = $vt['attpop']; - $defpop = $vt['defpop']; - $residence = $vt['residence']; - $def_ab = $vt['def_ab']; - $type = $vt['type']; - $scout = $vt['scout']; - $att_ab1 = $vt['att_ab1']; - $att_ab2 = $vt['att_ab2']; - $att_ab3 = $vt['att_ab3']; - $att_ab4 = $vt['att_ab4']; - $att_ab5 = $vt['att_ab5']; - $att_ab6 = $vt['att_ab6']; - $att_ab7 = $vt['att_ab7']; - $att_ab8 = $vt['att_ab8']; - $walllevel = $vt['walllevel']; - $wallid = $vt['wallid']; - $tblevel = $vt['tblevel']; - $stonemason = $vt['stonemason']; - - // Issue #298: the target village no longer exists — it was razed - // either earlier in this same batch ($razedTargets), or in an - // earlier tick whose still-in-flight follow-up waves DelVillage() - // failed to bounce. getMInfo() then returns NULL vdata columns - // ($to['wref'] is NULL), so resolving a battle here would fight a - // phantom village and compute the return trip from NULL coordinates - // — a bogus arrival time that strands the troops in an endless loop - // (report against "[?]"). Bounce the whole army straight home - // instead, exactly like DelVillage() does for in-flight attacks, - // and mark the movement processed so it stops being re-fetched. - if (isset($razedTargets[$data['to']]) || empty($to['wref'])) { - // only own the bounce if DelVillage() hasn't already handled it - // (setMovementProc() returns true only when it flips proc 0->1), - // so we never create a duplicate return movement - if ($this->setMovementProc($data['moveid'])) { - $bounceTime = $units->getWalkingTroopsTime($from['wref'], $data['to'], $from['owner'], $owntribe, $data, 1, 't'); - $bounceEnd = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $bounceTime) + $AttackArrivalTime; - $database->addMovement(4, $data['to'], $from['wref'], $data['ref'], $AttackArrivalTime, $bounceEnd); - } - $data_num++; - continue; - } - - $this->handleEvasion($data, $DefenderID, $DefenderUnit, $targettribe, $vt['evasion'], $vt['maxevasion'], $vt['gold'], $vt['cannotsend'], $dataarray[$data_num]['attack_type']); - - // defence units gathered — extracted to buildDefenderUnits() [#155] - $defUnits = $this->buildDefenderUnits($data['to']); - $Defender = $defUnits['Defender']; - $enforDefender = $defUnits['enforDefender']; - $enforcementarray = $defUnits['enforcementarray']; - - // attacker army built — extracted to buildAttackerUnits() [#155] - $atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis); - $Attacker = $atkUnits['Attacker']; - $start = $atkUnits['start']; - $end = $atkUnits['end']; - $catp_pic = $atkUnits['catp_pic']; - $ram_pic = $atkUnits['ram_pic']; - $chief_pic = $atkUnits['chief_pic']; - $spy_pic = $atkUnits['spy_pic']; - $hero_pic = $atkUnits['hero_pic']; - - }else{ //It's an oasis - // target + battle environment — extracted to resolveOasisTarget() [#155] - $ot = $this->resolveOasisTarget($data, $dataarray, $data_num, $owntribe); - $DefenderID = $ot['DefenderID']; - $targettribe = $ot['targettribe']; - $targetally = $ot['targetally']; - $to = $ot['to']; - $toF = $ot['toF']; - $conqureby = $ot['conqureby']; - $def_wall = $ot['def_wall']; - $att_tribe = $ot['att_tribe']; - $def_tribe = $ot['def_tribe']; - $attpop = $ot['attpop']; - $defpop = $ot['defpop']; - $residence = $ot['residence']; - $def_ab = $ot['def_ab']; - $type = $ot['type']; - $scout = $ot['scout']; - $att_ab1 = $ot['att_ab1']; - $att_ab2 = $ot['att_ab2']; - $att_ab3 = $ot['att_ab3']; - $att_ab4 = $ot['att_ab4']; - $att_ab5 = $ot['att_ab5']; - $att_ab6 = $ot['att_ab6']; - $att_ab7 = $ot['att_ab7']; - $att_ab8 = $ot['att_ab8']; - $walllevel = $ot['walllevel']; - $tblevel = $ot['tblevel']; - $stonemason = $ot['stonemason']; - - // defence units gathered — extracted to buildDefenderUnits() [#155] - $defUnits = $this->buildDefenderUnits($data['to']); - $Defender = $defUnits['Defender']; - $enforDefender = $defUnits['enforDefender']; - $enforcementarray = $defUnits['enforcementarray']; - - // T4 hero port (Phase 8): cages capture animals on an - // UNOCCUPIED oasis before the fight - captured animals do - // not defend and are stationed at home as reinforcements. - // No-op when the flag is off, no hero rides along, the - // oasis is owned, or the attacker has no cages. - if ($isoasis != 0 && (int) $conqureby === 0 && (int) $data['t11'] > 0) { - $Defender = HeroBattleBonus::applyCages($from['owner'], (int) $data['from'], (int) $data['to'], $Defender); - } - - // attacker army built — extracted to buildAttackerUnits() [#155] - $atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis); - $Attacker = $atkUnits['Attacker']; - $start = $atkUnits['start']; - $end = $atkUnits['end']; - $catp_pic = $atkUnits['catp_pic']; - $ram_pic = $atkUnits['ram_pic']; - $chief_pic = $atkUnits['chief_pic']; - $spy_pic = $atkUnits['spy_pic']; - $hero_pic = $atkUnits['hero_pic']; - } - - // attacker/defender populations + village lists — extracted to calculatePopulations() [#155] - $popData = $this->calculatePopulations($to, $from, $isoasis, $defpop, $attpop); - $varray = $popData['varray']; - $varray1 = $popData['varray1']; - $defpop = $popData['defpop']; - $attpop = $popData['attpop']; - - //fix by ronix - for ($i = 1; $i <= 90; $i++) { - if (!isset($enforDefender['u'.$i])) { - $enforDefender['u'.$i] = 0; - } - $enforDefender['u'.$i] += (isset($Defender['u'.$i]) ? $Defender['u'.$i] : 0); - } - - $defspy = $enforDefender['u4'] > 0 || $enforDefender['u14'] > 0 || $enforDefender['u23'] > 0 || $enforDefender['u44'] > 0; - - if(PEACE == 0 || $targettribe == 4 || $targettribe == 5 || $scout){ - // trapper resolution + prisoners — extracted to calculateTrappedUnits() [#155] - $trapResult = $this->calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end); - for($i = 1; $i <= 11; $i++) ${'traped'.$i} = $trapResult['traped'][$i]; - $totaltraped_att = $trapResult['totaltraped_att']; - $Attacker = $trapResult['Attacker']; - - // we need to save the attacker heroid before the battle - $AttackerHeroID = 0; - if(isset($Attacker['uhero']) && $Attacker['uhero'] > 0){ - $AttackerHeroID = $database->getHeroField($from['owner'], "heroid"); - } - - // and the defender(s) heroid - $DefendersHeroID = []; - $herosend_def = 0; - - // check if our hero is defending the village, if so, add it to the list - if (isset($Defender['hero']) && $Defender['hero'] > 0) { - $DefendersHeroID[] = $database->getHeroField($DefenderID, "heroid"); - - // collecting information for the battle report - $herosend_def += $Defender['hero']; - } - - // check if there are other heroes defending the village - if(count($enforcementarray) > 0){ - foreach($enforcementarray as $enforcement){ - if($enforcement['hero'] > 0){ - $heroOwner = $database->getVillageField($enforcement['from'], "owner"); - $DefendersHeroID[] = $database->getHeroField($heroOwner, "heroid"); - } - } - } - - //fix by ronix - if (!isset($walllevel)) $walllevel = 0; - - $battlepart = $battle->calculateBattle($Attacker, $Defender, $def_wall, $att_tribe, $def_tribe, $residence, $attpop, $defpop, $type, $def_ab, $att_ab1, $att_ab2, $att_ab3, $att_ab4, $att_ab5, $att_ab6, $att_ab7, $att_ab8, $tblevel, $stonemason, $walllevel, 0, 0, 0, $AttackerID, $DefenderID, $AttackerWref, $DefenderWref, $conqureby, $enforcementarray); - - //Data for when troops return. - //catapults look :D - $info_cat = $info_chief = $info_ram = $info_hero = ","; - - //check to see if can destroy village - if (count($varray) > 1 && !$database->villageHasArtefact($DefenderWref) && empty($to['natar'])) { - $can_destroy = 1; - } - else $can_destroy = 0; - - //Catapults and rams management - //TODO: Move this in Battle.php - if($isoasis == 0){ - if ($type == 3){ - if (($data['t7'] - $traped7) > 0){ - // ram damage on the wall (+ battle recalc) — extracted to applyRamDamage() [#155] - $ramResult = $this->applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, [ - 'Attacker' => $Attacker, 'Defender' => $Defender, - 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, - 'residence' => $residence, 'attpop' => $attpop, 'defpop' => $defpop, - 'type' => $type, 'def_ab' => $def_ab, - 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, - 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, - 'tblevel' => $tblevel, 'stonemason' => $stonemason, - 'AttackerID' => $AttackerID, 'DefenderID' => $DefenderID, - 'AttackerWref' => $AttackerWref, 'DefenderWref' => $DefenderWref, - 'conqureby' => $conqureby, 'enforcementarray' => $enforcementarray, - ]); - $battlepart = $ramResult['battlepart']; - $info_ram = $ramResult['info_ram']; - } - - if (($data['t8'] - $traped8) > 0) - { - $catResult = $this->applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat); - $battlepart = $catResult['battlepart']; - $info_cat = $catResult['info_cat']; - $village_destroyed = $catResult['village_destroyed']; - } - } elseif (($data['t7'] - $traped7) > 0) { - $info_ram = "".$ram_pic.",Hint: The ram does not work during a raid."; - } - } - else $can_destroy = 0; - - //units attack string for battleraport - $unitssend_att = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].''; - $herosend_att = $data['t11']; - - //reinforcement report (pre-casualty defender data) — extracted to collectReinforcementReport() [#155] - $reinfReport = $this->collectReinforcementReport($data, $targettribe, $Defender); - $DefenderHeroesTotArray = $reinfReport['DefenderHeroesTotArray']; - $DefenderHeroesDeadArray = $reinfReport['DefenderHeroesDeadArray']; - $unitssend_def = $reinfReport['unitssend_def']; - $unitssend_deff = $reinfReport['unitssend_deff']; - $totalsend_alldef = $reinfReport['totalsend_alldef']; - $Defender['hero'] = $reinfReport['defenderHero']; - - for($i = 1; $i <= 11; $i++){ - //MUST TO BE FIX : This is only for defender and still not properly coded - if (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] <= 0) { - ${'dead'.$i} = 0; - } else if (isset($data['t'.$i]) && isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] > $data['t'.$i]) { - ${'dead'.$i} = $data['t'.$i]; - } else { - ${'dead'.$i} = (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) ? $battlepart['casualties_attacker'][$i] : 0); - } - } - - $dead = []; - $owndead = []; - $alldead = []; - - for($i = 1; $i <= 90; $i++) $alldead[$i] = 0; - - //kill own defence — extracted to applyOwnDefenceCasualties() [#155] - $owndead = $this->applyOwnDefenceCasualties($data, $targettribe, $battlepart); - - //kill other defence in village (reinforcements) — extracted to applyReinforcementCasualties() [#155] - $tribesPresent = $this->applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, $alldead, $DefenderHeroesDeadArray); - $rom = $tribesPresent['rom']; - $ger = $tribesPresent['ger']; - $gal = $tribesPresent['gal']; - $nat = $tribesPresent['nat']; - $natar = $tribesPresent['natar']; - $hun = $tribesPresent['hun']; - $egy = $tribesPresent['egy']; - $spa = $tribesPresent['spa']; - $vik = $tribesPresent['vik']; - $totalsend_att = $data['t1']+$data['t2']+$data['t3']+$data['t4']+$data['t5']+$data['t6']+$data['t7']+$data['t8']+$data['t9']+$data['t10']+$data['t11']; - - $DefenderHeroesTot = implode(",", $DefenderHeroesTotArray); - $DefenderHeroesDead = implode(",", $DefenderHeroesDeadArray); - - if (empty($alldead['hero'])) $alldead['hero'] = 0; - if (empty($owndead['hero'])) $owndead['hero'] = 0; - // sursa autoritativa: rezultatul luptei (owndead['hero'] se poate pierde - // pe unele cai de executie, iar raportul afisa eroul aparator ca viu) - $deadhero = (int)(isset($battlepart['deadherodef']) && $battlepart['deadherodef'] > 0 - ? $battlepart['deadherodef'] - : $owndead['hero']); - - //Counting own total dead troops - $ownDeadTroops = array_slice($owndead, 0, 10); - $totaldead_alldef = array_sum($ownDeadTroops); - - //Collecting informations for the report - $unitsdead_def[0] = implode(",", $ownDeadTroops); - $unitsdead_deff[0] = '?,?,?,?,?,?,?,?,?,?,'; - for($i = 1; $i <= 9; $i++){ - //Counting reinforcements total dead troops - $deadTroops = array_slice($alldead, ($i - 1) * 10, 10); - $totaldead_alldef += array_sum($deadTroops); - //Collecting informations for the report - $unitsdead_def[$i] = implode(",", $deadTroops); - $unitsdead_deff[$i] = $unitsdead_deff[0]; - } - $totaldead_alldef += ($deadhero + $alldead['hero']); - - if (!isset($totalattackdead)) $totalattackdead = 0; - $totalattackdead += $totaldead_alldef; - - // Set units returning from attack - - $p_units = []; - for ($i = 1; $i <= 11; $i++) { - if (!isset(${'dead'.$i})) ${'dead'.$i} = 0; - if (!isset(${'traped'.$i})) ${'traped'.$i} = 0; - $p_units[] = "t".$i." = t".$i." - ".(${'dead'.$i} + ${'traped'.$i}); - } - - $database->modifyAttack3($data['ref'],implode(', ', $p_units)); - - $unitsdead_att = $dead1.','.$dead2.','.$dead3.','.$dead4.','.$dead5.','.$dead6.','.$dead7.','.$dead8.','.$dead9.','.$dead10; - $unitstraped_att = $traped1.','.$traped2.','.$traped3.','.$traped4.','.$traped5.','.$traped6.','.$traped7.','.$traped8.','.$traped9.','.$traped10.','.$traped11; - - //top 10 attack and defence update - $totaldead_att = $dead1 + $dead2 + $dead3 + $dead4 + $dead5 + $dead6 + $dead7 + $dead8 + $dead9 + $dead10 + $dead11; - $totalattackdead += $totaldead_att; - $troopsdead1 = $dead1; - $troopsdead2 = $dead2; - $troopsdead3 = $dead3; - $troopsdead4 = $dead4; - $troopsdead5 = $dead5; - $troopsdead6 = $dead6; - $troopsdead7 = $dead7; - $troopsdead8 = $dead8; - $troopsdead9 = $dead9 + 1; - $troopsdead10 = $dead10; - $troopsdead11 = $dead11; - - // hero XP, player points and alliance points — extracted to calculateHeroXpAndPoints() [#155] - $totaldead_def = $this->calculateHeroXpAndPoints( - $alldead, $owndead, - [$dead1, $dead2, $dead3, $dead4, $dead5, $dead6, $dead7, $dead8, $dead9, $dead10, $dead11], - $targettribe, $att_tribe, - $Attacker, $AttackerHeroID, $Defender, $DefendersHeroID, - $toF, $from, $targetally, $ownally, - $heroxp, $defheroxp - ); - - // resources lootable after cranny protection — extracted to resolveResourcesAfterBattle() [#155] - $resAfter = $this->resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, $crannySpy); - $totclay = $resAfter['totclay']; - $totiron = $resAfter['totiron']; - $totwood = $resAfter['totwood']; - $totcrop = $resAfter['totcrop']; - $avclay = $resAfter['avclay']; - $aviron = $resAfter['aviron']; - $avwood = $resAfter['avwood']; - $avcrop = $resAfter['avcrop']; - - // bounty distributed across the resources available after cranny protection (extracted to applyBounty() [#155]) - // T4 hero port (Phase 5): thief sacks raise the carry - // bounty when the attacker's living hero joined the strike. - $heroBounty = (int) round($battlepart['bounty'] * HeroBattleBonus::raidMultiplier($from['owner'], $data['t11'])); - $steal = $this->applyBounty([$avwood, $avclay, $aviron, $avcrop], $heroBounty); - - //chiefing village — extracted to handleConquest() [#155] - if (!isset($village_destroyed)) $village_destroyed = 0; - $chiefResult = $this->handleConquest($data, $type, $dead9, $traped9, $targettribe, $from, $to, $toF, $owntribe, $varray, $varray1, $battlepart, $isoasis, $village_destroyed, $chief_pic); - $info_chief = $chiefResult['info_chief']; - $chiefing_village = $chiefResult['chiefing_village']; - $village_destroyed = $chiefResult['village_destroyed']; - - // attacker hero outcome (oasis conquest/loyalty, artifact claim, report line) — extracted to handleHeroPostBattle() [#155] - $this->handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, $info_hero, $can_destroy, $village_destroyed); - - if ($DefenderID == 0) $natar = 0; - - if (!empty($scout)) { - $info_spy = $this->buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop); - } - - // prisoners freed during conquest attacks — extracted to handlePrisoners() [#155] - $info_trap = empty($scout) ? $this->handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) : ''; - $info_troop = ($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) <= 0) ? rc_tok('RC_NONE_RETURNED') : ""; - - // assemble data2 + data_fail — extracted to buildCombatReport() [#155] - $report = $this->buildCombatReport( - $scout, $from, $to, $owntribe, $targettribe, - $unitssend_att, $unitsdead_att, $steal, $battlepart, - $unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff, - $rom, $ger, $gal, $nat, $natar, - $hun, $egy, $spa, $vik, - $DefenderHeroesTot, $DefenderHeroesDead, - $info_ram, $info_cat, $info_chief, isset($info_spy) ? $info_spy : '', - $info_trap, $info_hero, $info_troop, - $data, $dead11, $herosend_def, $deadhero, $unitstraped_att, - $village_destroyed, $can_destroy, $catp_pic - ); - $data2 = $report['data2']; - $data_fail = $report['data_fail']; - - // notify the defender of the incoming battle/scout — extracted to sendBattleNotifications() [#155] - $this->sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef); - // surviving attacker troops return (report + movement + loot/RR/enforce) or die — extracted to finalizeReturnOrDeath() [#155] - $retDead = $retTraped = $retTroopsdead = []; - for ($i = 1; $i <= 11; $i++) { $retDead[$i] = ${'dead'.$i}; $retTraped[$i] = ${'traped'.$i}; $retTroopsdead[$i] = ${'troopsdead'.$i}; } - $this->finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $retDead, $retTraped, $retTroopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, $totalstolentaken); - if($type == 3 || $type == 4) $database->addGeneralAttack($totalattackdead); - - if (!isset($village_destroyed)) $village_destroyed = 0; - - // delete the target village if it was destroyed — extracted to handleVillageDestruction() [#155] - $this->handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray); - - // remember the razed tile so any follow-up wave still queued in - // this same batch bounces home instead of fighting a phantom - // (now-deleted) village. Needed on top of the $to['wref'] check - // because getMInfo() is cached: a same-batch wave would still see - // the stale "alive" village row — see the guard right after - // resolveVillageTarget() (issue #298). - if ($village_destroyed == 1 && $can_destroy == 1) { - $razedTargets[$data['to']] = true; - } - }else{ - //units attack string for battleraport - $unitssend_att1 = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].''; - $herosend_att = $data['t11']; - $unitssend_att= $unitssend_att1.','.$herosend_att; - - $troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $data, 1, 't'); - $endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime); - $endtime += $AttackArrivalTime; - // T4 hero port (Phase 5): map return-speed bonus, same as - // the combat return leg above. No-op when the flag is off. - $endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $data['t11'], $AttackArrivalTime, $endtime); - - $database->setMovementProc($data['moveid']); - $database->addMovement(4, $to['wref'], $from['wref'], $data['ref'], $AttackArrivalTime, $endtime); - $peace = PEACE; - $data2 = $from['owner'].','.$from['wref'].','.$to['owner'].','.$owntribe.','.$unitssend_att.','.$peace; - $time = time(); - $database->addNotice($from['owner'], $to['wref'], $ownally, 22,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time); - $database->addNotice($to['owner'], $to['wref'], $targetally, 23,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time); - } - - //Update starvation data - $database->addStarvationData($to['wref']); - - $data_num++; - - unset( - $Attacker - ,$Defender - ,$DefenderHeroesTotArray - ,$DefenderHeroesDeadArray - ,$DefenderHeroesTot - ,$DefenderHeroesDead - ,$unitssend_att - ,$unitssend_def - ,$battlepart - ,$unitsdead_def - ,$dead - ,$steal - ,$from - ,$data - ,$data2 - ,$to - ,$data_fail - ,$owntribe - ,$unitsdead_att - ,$herosend_def - ,$deadhero - ,$heroxp - ,$AttackerID - ,$DefenderID - ,$totalsend_att - ,$totalsend_alldef - ,$totaldead_att - ,$totaltraped_att - ,$totaldead_def - ,$totalattackdead - ,$defheroxp - ,$AttackerWref - ,$DefenderWref - ,$troopsdead1 - ,$troopsdead2 - ,$troopsdead3 - ,$troopsdead4 - ,$troopsdead5 - ,$troopsdead6 - ,$troopsdead7 - ,$troopsdead8 - ,$troopsdead9 - ,$troopsdead10 - ,$troopsdead11 - ,$DefenderUnit - ,$info_trap - ,$report); - } - } - } - - private function sendreinfunitsComplete() { - global $bid23, $database, $technology, $battle; - - $time = time(); - $q = " - SELECT - `to`, `from`, moveid, - t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11 - FROM - ".TB_PREFIX."movement, - ".TB_PREFIX."attacks - WHERE - ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id - AND - ".TB_PREFIX."movement.proc = 0 - AND - ".TB_PREFIX."movement.sort_type = 3 - AND - ".TB_PREFIX."attacks.attack_type = 2 - AND - endtime < $time"; - $dataarray = $database->query_return($q); - - if ($dataarray && count($dataarray)) { - // preload village data - $vilIDs = []; - $tos = []; - $froms = []; - foreach($dataarray as $data) { - $vilIDs[$data['from']] = true; - $vilIDs[$data['to']] = true; - $tos[$data['to']] = true; - $froms[$data['from']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getProfileVillages($vilIDs, 5); - $database->getUnit($vilIDs); - $database->getEnforce(array_keys($tos), array_keys($froms)); - $database->getVillageByWorldID($vilIDs); - - // calculate reinforcements data - foreach($dataarray as $data) { - if (!$this->claimMovementRecord($data['moveid'])) { - continue; - } - - $isoasis = $database->isVillageOases($data['to']); - if($isoasis == 0){ - $to = $database->getMInfo($data['to']); - $toF = $database->getVillage($data['to']); - $DefenderID = $to['owner']; - $targettribe = $database->getUserField($DefenderID, "tribe", 0); - $conqureby = 0; - }else{ - $to = $database->getOMInfo($data['to']); - $toF = $database->getOasisV($data['to']); - $DefenderID = $to['owner']; - $targettribe = $database->getUserField($DefenderID, "tribe", 0); - $conqureby = $toF['conqured']; - } - - if($data['from'] == 0){ - // flow 1: a "village of the elders" reinforcement returning home - $this->completeReinforcementFromElders($data, $to); - }else{ - // flow 2: a standard reinforcement delivery (hero transfer + enforce merge + report) - $this->completeReinforcementDelivery($data, $to, $isoasis, $DefenderID); - } - - //Update starvation data - $database->addStarvationData($data['to']); - - //check empty reinforcement in rally point - $e_units = ''; - for ($i = 1; $i <= 90; $i++) $e_units.= 'u'.$i.'= 0 AND '; - - $e_units.= 'hero = 0'; - $q = "DELETE FROM ".TB_PREFIX."enforcement WHERE ".$e_units." AND (vref=".(int) $data['to']." OR `from`=".(int) $data['to'].")"; - $database->query($q); - } - } - } - - // Flow 1 of sendreinfunitsComplete(): a "village of the elders" reinforcement - // (from = 0) returning home. $targetally / $AttackArrivalTime are intentionally - // left undefined here, exactly as in the original inline code. - private function completeReinforcementFromElders($data, $to) { - global $database; - - $DefenderID = $database->getVillageField($data['to'], "owner"); - $database->addEnforce(['from' => $data['from'], 'to' => $data['to'], 't1' => 0, 't2' => 0, 't3' => 0, 't4' => 0, 't5' => 0, 't6' => 0, 't7' => 0, 't8' => 0, 't9' => 0, 't10' => 0, 't11' => 0]); - $reinf = $database->getEnforce($data['to'], $data['from']); - $database->modifyEnforce($reinf['id'], 31, 1, 1); - $data_fail = '0,0,4,1,0,0,0,0,0,0,0,0,0,0'; - $database->addNotice($to['owner'], $to['wref'], (isset($targetally) ? $targetally : 0), 8, 'village of the elders reinforcement ' . addslashes($to['name']), $data_fail, isset($AttackArrivalTime) ? $AttackArrivalTime : time()); - } - - // Flow 2 of sendreinfunitsComplete(): a standard reinforcement delivery. Handles - // the lone-hero transfer between own villages, the enforcement merge-or-create, - // and the reinforcement notices. $ownally / $targetally / $AttackArrivalTime are - // intentionally left undefined (guarded by isset()), exactly as in the original. - private function completeReinforcementDelivery($data, $to, $isoasis, $DefenderID) { - global $database; - - //set base things - $from = $database->getMInfo($data['from']); - $fromF = $database->getVillage($data['from']); - $AttackerID = $from['owner']; - $owntribe = $database->getUserField($AttackerID,"tribe",0); - - $HeroTransfer = $troopsPresent = 0; - for($i = 1;$i <= 10; $i++) { - if($data['t'.$i] > 0) { - $troopsPresent = 1; - break; - } - } - - //check if the hero is present and we're not sending him to an occupied oasis - //only add hero if we're sending him alone - if($data['t11'] > 0 && !$isoasis && !$troopsPresent) { - //check if we're sending a hero between own villages - if($AttackerID == $DefenderID) { - //check if there's a Mansion at target village - if($this->getTypeLevel(37, $data['to']) > 0){ - //don't reinforce, addunit instead - $database->modifyUnit($data['to'], ["hero"], [1], [1]); - $heroid = $database->getHeroField($DefenderID, 'heroid'); - $database->modifyHero("wref", $data['to'], $heroid); - $HeroTransfer = 1; - } - } - } - - if($data['t11'] > 0 || $troopsPresent) { - $this->mergeOrCreateEnforcement($data, $owntribe, $HeroTransfer); - } - - //send rapport - $unitssend_att = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].','.$data['t11'].''; - $data_fail = ''.$from['wref'].','.$from['owner'].','.$owntribe.','.$unitssend_att.''; - - - if($isoasis == 0) $to_name = $to['name']; - else $to_name = "Oasis ".$database->getVillageField($to['conqured'],"name"); - - $database->addNotice($from['owner'],$from['wref'],(isset($ownally) ? $ownally : 0),8,''.addslashes($from['name']).' reinforcement '.addslashes($to_name).'',$data_fail,(isset($AttackArrivalTime) ? $AttackArrivalTime : time())); - if($from['owner'] != $to['owner']) { - $database->addNotice($to['owner'],$to['wref'],(isset($targetally) ? $targetally : 0),8,''.addslashes($from['name']).' reinforcement '.addslashes($to_name).'',$data_fail,(isset($AttackArrivalTime) ? $AttackArrivalTime : time())); - } - } - - // Flow 3 of sendreinfunitsComplete(): merge the incoming troops into an existing - // enforcement record at the target, or create a new one. When the hero was already - // transferred as a unit ($HeroTransfer), his t11 is temporarily zeroed for the - // merge and restored afterwards, exactly as in the original. - private function mergeOrCreateEnforcement($data, $owntribe, $HeroTransfer) { - global $database; - - $temphero = $data['t11']; - if ($HeroTransfer) $data['t11'] = 0; - //check if there is defence from town in to town - $check = $database->getEnforce($data['to'], $data['from']); - if (!isset($check['id'])) $database->addEnforce($data); - else - { - //yes - $start = ($owntribe - 1) * 10 + 1; - $end = ($owntribe * 10); - - //add unit. - $t_units = ''; - for($i = $start, $j = 1; $i <= $end; $i++, $j++) - { - $t_units .= "u".$i." = u".$i." + ".$data['t'.$j].(($j > 9) ? '' : ', '); - } - - $q = "UPDATE ".TB_PREFIX."enforcement set $t_units where id =".(int) $check['id']; - $database->query($q); - $database->modifyEnforce($check['id'], 'hero', $data['t11'], 1); - } - $data['t11'] = $temphero; - } - - private function returnunitsComplete() { - global $database, $technology; - - $time = time(); - $q = " - SELECT - `to`, `from`, moveid, starttime, endtime, wood, clay, iron, crop, - t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11 - FROM - ".TB_PREFIX."movement, - ".TB_PREFIX."attacks - WHERE - ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id - AND - ".TB_PREFIX."movement.proc = 0 - AND - ".TB_PREFIX."movement.sort_type = 4 - AND - endtime < $time"; - $dataarray = $database->query_return($q); - - if ($dataarray && count($dataarray)) { - // preload village data - $vilIDs = []; - foreach($dataarray as $data) { - $vilIDs[$data['from']] = true; - $vilIDs[$data['to']] = true; - } - $database->getProfileVillages(array_keys($vilIDs), 5); - $database->getOasisEnforce($vilIDs, 0); - $database->getOasisEnforce($vilIDs, 1); - - foreach($dataarray as $data) { - if (!$this->claimMovementRecord($data['moveid'])) { - continue; - } - - $tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0); - $u = $tribe == 1 ? "" : $tribe - 1; - $database->modifyUnit( - $data['to'], - [$u."1", $u."2", $u."3", $u."4", $u."5", $u."6", $u."7", $u."8", $u."9", $tribe."0", "hero"], - [$data['t1'], $data['t2'], $data['t3'], $data['t4'], $data['t5'], $data['t6'], $data['t7'], $data['t8'], $data['t9'], $data['t10'], $data['t11']], - [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - ); - - //If there's at least 1 resource, add it to the village - if($data['wood'] + $data['clay'] + $data['iron'] + $data['crop'] > 0){ - $database->modifyResource($data['to'], $data['wood'], $data['clay'], $data['iron'], $data['crop'], 1); - } - - //Update starvation data - $database->addStarvationData($data['to']); - } - - $this->pruneResource(); - } - - // Settlers - $q = "SELECT `to`, moveid FROM ".TB_PREFIX."movement where ref = 0 and proc = '0' and sort_type = '4' and endtime < $time"; - $dataarray = $database->query_return($q); - if ($dataarray && count($dataarray)) { - foreach($dataarray as $data) { - if (!$this->claimMovementRecord($data['moveid'])) { - continue; - } - - $tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0); - $database->modifyUnit($data['to'], [$tribe."0"], [3], [1]); - - //If a settling is canceled, add 750 for each resource type - $database->modifyResource($data['to'], 750, 750, 750, 750, 1); - } - } - } - - private function sendSettlersComplete() { - global $database; - - $time = microtime(true); - $q = "SELECT `to`, `from`, moveid, starttime, ref FROM ".TB_PREFIX."movement where proc = 0 and sort_type = 5 and endtime < $time"; - - $dataarray = $database->query_return($q); - $fieldIDs = []; - $addUnitsWrefs = []; - $addTechWrefs = []; - $addABTechWrefs = []; - $time = microtime(true); - $types = []; - $froms = []; - $tos = []; - $refs = []; - $times = []; - $endtimes = []; - - // preload village data - $vilIDs = []; - foreach($dataarray as $data) { - $vilIDs[$data['from']] = true; - $vilIDs[$data['to']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getProfileVillages($vilIDs, 5); - $database->getVillageByWorldID($vilIDs); - - foreach($dataarray as $data) { - if (!$this->claimMovementRecord($data['moveid'])) { - continue; - } - - $ownerID = $database->getUserField($database->getVillageField($data['from'], "owner"), "id", 0); - $to = $database->getMInfo($data['from']); - $user = addslashes($database->getUserField($to['owner'], 'username', 0)); - $taken = $database->getVillageState($data['to']); - if($taken != 1){ - $fieldIDs[] = $data['to']; - $database->addVillage($data['to'], $to['owner'], $user, '0'); - $database->addResourceFields($data['to'], $database->getVillageType($data['to'])); - $addUnitsWrefs[] = $data['to']; - $addTechWrefs[] = $data['to']; - $addABTechWrefs[] = $data['to']; - - $exp1 = $database->getVillageField($data['from'], 'exp1'); - $exp2 = $database->getVillageField($data['from'], 'exp2'); - $exp3 = $database->getVillageField($data['from'], 'exp3'); - - if($exp1 == 0){ - $exp = 'exp1'; - $value = $data['to']; - }elseif($exp2 == 0){ - $exp = 'exp2'; - $value = $data['to']; - }else{ - $exp = 'exp3'; - $value = $data['to']; - } - - $database->setVillageField($data['from'], $exp, $value); - - // Report: new village founded (issue #178) - $ncoor = $database->getCoor($data['to']); - $database->addNotice($to['owner'], $data['to'], 0, 24, 'New village founded', ($ncoor['x'] ?? 0) . ',' . ($ncoor['y'] ?? 0), time()); - - // Milestone: first player ever to settle their 2nd village. - // Checked right after the new village row exists, so the - // COUNT below already includes it. - if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { - $villageCount = $database->countVillages($to['owner']); - if ($villageCount == 2) { - $database->recordMilestoneIfFirst('second_village', $to['owner'], $data['to']); - } - if ($villageCount == 5) { - $database->recordMilestoneIfFirst('five_villages', $to['owner'], $data['to']); - } - } - }else{ - // here must come movement from returning settlers - $types[] = 4; - $froms[] = $data['to']; - $tos[] = $data['from']; - $refs[] = $data['ref']; - $times[] = $time; - $endtimes[] = $time + ($time - $data['starttime']); - - // Report: valley already occupied, settlers returning (issue #178) - $fcoor = $database->getCoor($data['to']); - $database->addNotice($to['owner'], $data['to'], 0, 25, 'Settlers returned - valley occupied', ($fcoor['x'] ?? 0) . ',' . ($fcoor['y'] ?? 0), time()); - } - } - - $database->addMovement($types, $froms, $tos, $refs, $times, $endtimes); - $database->setFieldTaken($fieldIDs); - $database->addUnits($addUnitsWrefs); - $database->addTech($addTechWrefs); - $database->addABTech($addABTechWrefs); - - } - - /** - * Create the Natars account and spawn artifacts - * - */ - - private function spawnNatars(){ - global $database; - - //Check if Natars account is already created and if the time - //is come and we have to create Natars and spawn their artifacts - if($database->areArtifactsSpawned() || strtotime(START_DATE) + (NATARS_SPAWN_TIME * 86400) > time()) return; - - //Create the Natars account and his capital - $this->artifacts->createNatars(); - - //Write the system message - $database->displaySystemMessage(ARTEFACT); - } - - /** - * Spawn WW Villages - * - */ - - private function spawnWWVillages(){ - global $database; - - //Check if Natars account has already been created, if WW villages have already been spawned - //and if it's the time to spawn them or not - if(!$database->areArtifactsSpawned() || $database->areWWVillagesSpawned() || strtotime(START_DATE) + (NATARS_WW_SPAWN_TIME * 86400) > time()) return; - - //Create WW villages - $this->artifacts->createWWVillages(); - - //Write the system message - $database->displaySystemMessage(WWVILLAGEMSG); - } - - /** - * Spawn WW Building plans - * - */ - - private function spawnWWBuildingPlans(){ - global $database; - - //Check if Natars account is already spawned, if WW building plans have already been spawned - //and if it's the time to spawn them or not - if(!$database->areArtifactsSpawned() || $database->areArtifactsSpawned(true) || strtotime(START_DATE) + (NATARS_WW_BUILDING_PLAN_SPAWN_TIME * 86400) > time()) return; - - //Create WW building plans - $this->artifacts->createWWBuildingPlans(); - - //Set the system message to contain the infos of the WW building plans - $database->displaySystemMessage(PLAN_INFO); - } - - /** - * Automatically activate all artifacts that need to be activated - * - */ - - private function activateArtifacts() { - global $database; - - //Check if there's at least one artifact, if not, return - if(!$database->areArtifactsSpawned()) return; - - //Activate the artifacts that need to be activated - $this->artifacts->activateArtifacts(); - } - - private function researchComplete() { - global $database; - - $time = time(); - $deleteIDs = []; - $tdata = []; - $abdata = []; - - $q = "SELECT tech, vref, id FROM ".TB_PREFIX."research where timestamp < $time"; - $dataarray = $database->query_return($q); - - foreach($dataarray as $data) { - $sort_type = substr($data['tech'],0,1); - switch($sort_type) { - case "t": - if (!isset($tdata[$data['vref']])) $tdata[$data['vref']] = []; - $tdata[$data['vref']][] = $data['tech'].' = 1'; - break; - case "a": - case "b": - if (!isset($abdata[$data['vref']])) $abdata[$data['vref']] = []; - $abdata[$data['vref']][] = $data['tech']." = ".$data['tech']." + 1"; - break; - } - $deleteIDs[] = (int) $data['id']; - } - - // execute queries with consolidated research data - if (count($tdata)) { - foreach ( $tdata as $vid => $preparedData ) { - $q = "UPDATE ".TB_PREFIX."tdata SET ".implode(', ', $preparedData)." WHERE vref = ".$vid; - $database->query($q); - } - } - - if (count($abdata)) { - foreach ( $abdata as $vid => $preparedData ) { - $q = "UPDATE ".TB_PREFIX."abdata SET ".implode(', ', $preparedData)." WHERE vref = ".$vid; - $database->query($q); - } - } - - if (count($deleteIDs)) { - $q = "DELETE FROM " . TB_PREFIX . "research where id IN(" . implode( ', ', $deleteIDs ) . ")"; - $database->query( $q ); - } - } - - private function updateORes($bountywid) { - global $database; - - $oasisInfoArray = $database->getOasisV($bountywid); - $timepast = time() - $oasisInfoArray['lastupdated']; - $nwood = (OASIS_WOOD_PRODUCTION / 3600) * $timepast; - $nclay = (OASIS_CLAY_PRODUCTION / 3600) * $timepast; - $niron = (OASIS_IRON_PRODUCTION / 3600) * $timepast; - $ncrop = (OASIS_CROP_PRODUCTION / 3600) * $timepast; - $database->modifyOasisResource($bountywid, $nwood, $nclay, $niron, $ncrop, 1); - $database->updateOasis($bountywid); - } - - private function updateRes($bountywid) { - global $database, $technology; - - //Get village infos - $villageInfoArray = $database->getVillage($bountywid); - - //Get building and resource fields array - $resArray = $database->getResourceLevel($bountywid, false); - - //Get oasis array - $oasisArray = $database->getOasis($bountywid); - - //Get an array with the numbers of the oasis - $numberOfOasis = $this->bountysortOasis($oasisArray); - - //Set the village population (if WW Villages, it's halved) - $villagePopulation = !$villageInfoArray['natar'] ? $villageInfoArray['pop'] : round($villageInfoArray['pop'] / 2); - - //Get the upkeep of the village - $upkeep = $technology->getUpkeep($this->getAllUnits($bountywid), 0, $bountywid); - - //Calculate the produced resources - $timepast = time() - $villageInfoArray['lastupdate']; - $nwood = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 1) / 3600) * $timepast; - $nclay = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 2) / 3600) * $timepast; - $niron = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 3) / 3600) * $timepast; - $ncrop = (($this->bountyGetResourceProd($resArray, $numberOfOasis, 4) - $villagePopulation - $upkeep) / 3600) * $timepast; - $database->modifyResource($bountywid, $nwood, $nclay, $niron, $ncrop, 1); - $database->updateVillage($bountywid); - } - - private function bountysortOasis($oasisArray) { - $crop = $clay = $wood = $iron = 0; - foreach ($oasisArray as $oasis) { - switch($oasis['type']) { - case 1: - case 2: - $wood++; - break; - case 3: - $wood++; - $crop++; - break; - case 4: - case 5: - $clay++; - break; - case 6: - $clay++; - $crop++; - break; - case 7: - case 8: - $iron++; - break; - case 9: - $iron++; - $crop++; - break; - case 10: - case 11: - $crop++; - break; - case 12: - $crop += 2; - break; - } - } - return [$wood, $clay, $iron, $crop]; - } - - function getAllUnits($base, $use_cache = true) { - global $database; - - $ownunit = $database->getUnit($base, $use_cache); - $enforcementarray = $database->getEnforceVillage($base, 0); - - if(count($enforcementarray) > 0){ - foreach($enforcementarray as $enforce){ - for($i = 1; $i <= 90; $i++){ - $ownunit['u'.$i] += $enforce['u'.$i]; - } - } - } - - $enforceoasis = $database->getOasisEnforce($base, 0, $use_cache); - if(count($enforceoasis) > 0){ - foreach($enforceoasis as $enforce){ - for($i = 1; $i <= 90; $i++){ - $ownunit['u'.$i] += $enforce['u'.$i]; - } - } - } - - $enforceoasis1 = $database->getOasisEnforce($base, 1, $use_cache); - if(count($enforceoasis1) > 0){ - foreach($enforceoasis1 as $enforce){ - for($i = 1; $i <= 90; $i++){ - $ownunit['u'.$i] += $enforce['u'.$i]; - } - } - } - - $movement = $database->getVillageMovement($base); - if(!empty($movement)){ - for($i = 1; $i <= 90; $i++){ - if(!isset($ownunit['u' . $i])){ - $ownunit['u'.$i] = 0; - } - - $ownunit['u'.$i] += (isset($movement['u'.$i]) ? $movement['u'.$i] : 0); - } - } - - $prisoners = $database->getPrisoners($base, 1); - if(!empty($prisoners)){ - foreach($prisoners as $prisoner){ - $owner = $database->getVillageField($base, "owner"); - $ownertribe = $database->getUserField($owner, "tribe", 0); - $start = ($ownertribe - 1) * 10 + 1; - $end = ($ownertribe * 10); - for($i = $start; $i <= $end; $i++){ - $j = $i - $start + 1; - $ownunit['u'.$i] += $prisoner['t'.$j]; - } - $ownunit['hero'] += $prisoner['t11']; - } - } - return $ownunit; - } - - // Production of a single resource (1=wood, 2=clay, 3=iron, 4=crop) for the - // given village field layout, used by the bounty/production catch-up. The - // four resources only differ by: the production-building data global, the - // booster building field-type(s) whose 'attri' adds a % bonus, and the oasis - // bonus slot (resourceType - 1). Crop additionally has two boosters - // (grain mill type 8, then bakery type 9 applied on the running total) and - // the gold-club +25% crop bonus (b4) keyed on the village owner. - private function bountyGetResourceProd($resArray, $oasisNumber, $resourceType) { - global $bid1, $bid2, $bid3, $bid4, $bid5, $bid6, $bid7, $bid8, $bid9, $database; - - $prodBid = [1 => $bid1, 2 => $bid2, 3 => $bid3, 4 => $bid4][$resourceType]; - $boosterBid = [5 => $bid5, 6 => $bid6, 7 => $bid7, 8 => $bid8, 9 => $bid9]; - // Booster field-types per resource, in application order (crop: mill then bakery). - $boosterTypes = [1 => [5], 2 => [6], 3 => [7], 4 => [8, 9]][$resourceType]; - - $prod = 0; - $holders = []; - $boosterLevels = array_fill_keys($boosterTypes, 0); - for($i = 1; $i <= 38; $i++) { - if($resArray['f'.$i.'t'] == $resourceType) $holders[] = 'f'.$i; - if(isset($boosterLevels[$resArray['f'.$i.'t']])) $boosterLevels[$resArray['f'.$i.'t']] = $resArray['f'.$i]; - } - - foreach($holders as $holder) $prod += $prodBid[$resArray[$holder]]['prod']; - - foreach($boosterTypes as $bt) { - $level = $boosterLevels[$bt]; - if($level >= 1) $prod += $prod / 100 * (isset($boosterBid[$bt][$level]['attri']) ? $boosterBid[$bt][$level]['attri'] : 0); - } - - $oasisIndex = $resourceType - 1; - if($oasisNumber[$oasisIndex] > 0) $prod += $prod * 0.25 * $oasisNumber[$oasisIndex]; - - if($resourceType == 4 && !empty($resArray['vref']) && is_numeric($resArray['vref'])) { - $who = $database->getVillageField($resArray['vref'], "owner"); - $croptrue = $database->getUserField($who, "b4", 0); - if($croptrue > time()) $prod *= 1.25; - } - - return round($prod * SPEED); - } - - /** - * Spital (gid 46) / Spital Mare (gid 48): o parte din trupele proprii moarte - * in apararea satului devin ranite, in limita capacitatii spitalului. - * Cota: 40% (Spital) / 50% (Spital Mare). Capacitate: nivel x 30 / nivel x 60. - * Doar unitatile de lupta (X1-X8); chief, settler si eroul nu pot fi raniti. - */ - private function applyHospitalWounded($vref, array $deadByUnit) { - global $database; - - $hospital = $database->getFieldLevelInVillage($vref, '46'); - $bighospital = $database->getFieldLevelInVillage($vref, '48'); - if($hospital <= 0 && $bighospital <= 0) return; - - $share = $bighospital > 0 ? 0.50 : 0.40; - $capacity = $bighospital > 0 ? $bighospital * 60 : $hospital * 30; - - $stored = 0; - $current = $database->getWounded($vref); - if(!empty($current)) { - for($i = 1; $i <= 90; $i++) $stored += isset($current['u'.$i]) ? (int)$current['u'.$i] : 0; - } - $free = $capacity - $stored; - if($free <= 0) return; - - $units = []; $amounts = []; - foreach($deadByUnit as $i => $deadAmt) { - if(empty($deadAmt)) continue; - $wounded = (int)floor($deadAmt * $share); - if($wounded <= 0) continue; - if($wounded > $free) $wounded = $free; - $units[] = $i; - $amounts[] = $wounded; - $free -= $wounded; - if($free <= 0) break; - } - if(!empty($units)) $database->addWounded($vref, $units, $amounts); - } - - /** - * Proceseaza coada de vindecare: unitatile vindecate se intorc in sat - * incremental (una la fiecare 'eachtime' secunde), ca la antrenare. - */ - private function healingComplete() { - global $database; - - $time = time(); - $healinglist = $database->getHealingDue($time); - foreach($healinglist as $heal) { - $healed = 1; - if($heal['eachtime'] > 0) { - $healed += (int)floor(($time - $heal['timestamp2']) / $heal['eachtime']); - } - if($healed > $heal['amt']) $healed = (int)$heal['amt']; - if($healed <= 0) continue; - - $database->modifyUnit($heal['vref'], [$heal['unit']], [$healed], [1]); - - $remaining = (int)$heal['amt'] - $healed; - if($remaining <= 0) { - $database->deleteHealing($heal['id']); - } else { - $database->updateHealing($heal['id'], $remaining, (int)$heal['timestamp2'] + $healed * (int)$heal['eachtime']); - } - } - } - - private function trainingComplete() { - global $database, $technology; - - $time = time(); - $trainlist = $database->getTrainingList(); - if(count($trainlist) > 0){ - // preload village data - $vilIDs = []; - foreach($trainlist as $train){ - $vilIDs[$train['vref']] = true; - } - $vilIDs = array_keys($vilIDs); - $database->getProfileVillages($vilIDs, 5); - $database->cacheResourceLevels($vilIDs); - $database->getUnit($vilIDs); - $database->getEnforceVillage($vilIDs, 0 ); - $database->getMovement(3, $vilIDs, 0); - $database->getMovement(4, $vilIDs, 1); - $database->getMovement(5, $vilIDs, 0); - $database->getOasisEnforce($vilIDs, 0); - $database->getOasisEnforce($vilIDs, 1); - $database->getPrisoners($vilIDs, 1); - - // calculate training updates - foreach($trainlist as $train){ - $timepast = $train['timestamp2'] - $time; - $pop = $train['pop']; - $valuesUpdated = false; - if($timepast <= 0 && $train['amt'] > 0) { - $valuesUpdated = true; - if($train['eachtime'] > 0){ - $timepast2 = $time - $train['timestamp2']; - $trained = 1; - while($timepast2 >= $train['eachtime']){ - $timepast2 -= $train['eachtime']; - $trained += 1; - } - - if($trained > $train['amt']) $trained = $train['amt']; - } - else $trained = $train['amt']; - - if($train['unit'] > 1000 && $train['unit'] != 99){ - $database->modifyUnit($train['vref'], [$train['unit'] - 1000], [$trained], [1]); - } - else $database->modifyUnit($train['vref'], [$train['unit']], [$trained], [1]); - - $database->updateTraining($train['id'], $trained, $trained * $train['eachtime']); - - if($train['amt'] - $trained <= 0) $database->trainUnit($train['id'], 0, 0, 0, 0, 1); - } - - if ($valuesUpdated) call_user_func(get_class($database).'::clearUnitsCache'); - - //Update starvation data - $database->addStarvationData($train['vref']); - } - } - } - - private function getsort_typeLevel($tid, $resarray) { - $keyholder = []; - - foreach(array_keys($resarray, $tid) as $key) { - if(strpos($key, 't')) { - $key = preg_replace("/[^0-9]/", '', $key); - array_push($keyholder, $key); - } - } - - $element = count($keyholder); - if($element >= 2) { - if($tid <= 4) { - $temparray = []; - - for($i = 0; $i <= $element - 1; $i++) { - array_push($temparray, $resarray['f'.$keyholder[$i]]); - } - - foreach ($temparray as $key => $val) { - if ($val == max($temparray)) $target = $key; - } - } - } - else if($element == 1) $target = 0; - else return 0; - - if(!empty($keyholder[$target])) return $resarray['f'.$keyholder[$target]]; - else return 0; - } - - private function celebrationComplete() { - global $database; - - $varray = $database->getCel(); - foreach($varray as $vil){ - $id = $vil['wref']; - $type = $vil['type']; - $user = $vil['owner']; - $cp = ($type == 1) ? 500 : 2000; - $database->clearCel($id); - $database->setCelCp($user, $cp); - } - } - - /** - * Expires Mead-Festivals (Brewery, building 35). Unlike celebrationComplete() - * this grants no reward — the festival only gated the temporary combat - * bonus / chief penalty / catapult randomization while it was active. - */ - private function festivalComplete() { - global $database; - - $varray = $database->getFestivals(); - foreach($varray as $vil){ - $database->clearFestival($vil['wref']); - } - } - - private function demolitionComplete() { - global $database; - - $varray = $database->getDemolition(); - foreach($varray as $vil) { - if ($vil['lvl'] < 0) { - $database->delDemolition($vil['vref'], true); - continue; - } - if ($vil['timetofinish'] <= time()) { - $type = $database->getFieldType($vil['vref'],$vil['buildnumber']); - $level = $database->getFieldLevel($vil['vref'],$vil['buildnumber']); - - $newLevel = max(0, $level - 1); - - $buildarray = $GLOBALS["bid".$type]; - - if ($type == 10 || $type == 38) { - $database->query(" - UPDATE ".TB_PREFIX."vdata - SET - `maxstore` = IF(`maxstore` - ".$buildarray[$level]['attri']." <= ".STORAGE_BASE.", ".STORAGE_BASE.", `maxstore` - ".$buildarray[$level]['attri'].") - WHERE - wref=".(int) $vil['vref']); - } - - if ($type == 11 || $type == 39) { - $database->query(" - UPDATE ".TB_PREFIX."vdata - SET - `maxcrop` = IF(`maxcrop` - ".$buildarray[$level]['attri']." <= ".STORAGE_BASE.", ".STORAGE_BASE.", `maxcrop` - ".$buildarray[$level]['attri'].") - WHERE - wref=".(int) $vil['vref']); - } - - if ($level == 1) $clear = ",f".$vil['buildnumber']."t=0"; - else $clear = ""; - - if ($database->getVillageField($vil['vref'], 'natar') == 1 && $type == 40) $clear = ""; //fix by ronix - fixed by iopietro - $q = "UPDATE ".TB_PREFIX."fdata SET f".$vil['buildnumber']."=".$newLevel." ".$clear." WHERE vref=".(int)$vil['vref']; - $database->query($q); - - $pop = $this->getPop($type, $newLevel); - $database->modifyPop($vil['vref'], $pop[0], 1); - $this->procClimbers($database->getVillageField($vil['vref'], 'owner')); - $database->delDemolition($vil['vref'], true); - - if ($type == 18) Automation::updateMax($database->getVillageField($vil['vref'], 'owner')); - } - } - - } - - /** - * T4 Hero port (Phase 3+4): process adventure arrivals (sort_type 20, - * rewards + ntype 26 report + return movement), returns (sort_type 21, - * hero re-enters units.hero, loot credited), top up offer lists, then - * finalize ended auctions and restock the NPC merchant. - * Fully feature-flagged - a no-op unless NEW_FUNCTIONS_HERO_T4 is enabled. - */ - private function heroAdventureComplete() { - if (!defined('NEW_FUNCTIONS_HERO_T4') || !NEW_FUNCTIONS_HERO_T4) { - return; - } - - $adventures = new HeroAdventure(); - $adventures->processArrivals(); - $adventures->processReturns(); - $adventures->generateOffersBatch(); - - $auctions = new HeroAuction(); - $auctions->processFinished(); - $auctions->seedNpcAuctions(); - } - - private function updateHero() { - global $database, $hero_levels; - - $harray = $database->getHero(); - if(!empty($harray)){ - // first of all, prepare all unit data at once for these heroes - $heroVillageIDs = []; - foreach($harray as $hdata) { - $heroVillageIDs[] = $hdata['wref']; - } - - // load data for those prepared IDs - $unitData = $database->getUnit($heroVillageIDs); - - // now do the math - $lastUpdateIDs = []; - $timeNow = time(); - foreach($harray as $hdata){ - $columns = []; - $columnValues = []; - $modes = []; - $lastUpdateTime = $timeNow; - - // 1. passive HP regeneration - $newHealth = $this->calculateHealthRegen($hdata); - - // 2. level-up + score points - $this->mergeHeroColumns($columns, $columnValues, $modes, $this->calculateLevelUp($hdata)); - - // 3. revive completion (forces health to 100 and stamps the update time) - $revive = $this->handleReviveCompletion($hdata); - $this->mergeHeroColumns($columns, $columnValues, $modes, $revive); - if ($revive['health'] !== null) $newHealth = $revive['health']; - if ($revive['lastUpdate'] !== null) $lastUpdateTime = $revive['lastUpdate']; - - // 4. training completion (does not touch health) - $training = $this->handleTrainingCompletion($hdata); - $this->mergeHeroColumns($columns, $columnValues, $modes, $training); - if ($training['lastUpdate'] !== null) $lastUpdateTime = $training['lastUpdate']; - - // update health, if needed - if ($newHealth > -1) { - $columns[] = 'health'; - $columnValues[] = $newHealth; - $modes[] = null; - } - - if ($lastUpdateTime != $timeNow) { - // last update timestamp - $columns[] = 'lastupdate'; - $columnValues[] = $lastUpdateTime; - $modes[] = null; - } else { - // leave same last update values for multiple heroes to the end - $lastUpdateIDs[] = $hdata['heroid']; - } - - if (count($columns)) $database->modifyHero($columns, $columnValues, $hdata['heroid'], $modes); - } - - if (count($lastUpdateIDs)) { - mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "hero SET lastupdate = $timeNow WHERE heroid IN(".implode(', ', $lastUpdateIDs).")"); - } - } - } - - // Append a hero column fragment (['columns'=>[], 'values'=>[], 'modes'=>[]]) - // onto the running modifyHero() arrays, preserving order. - private function mergeHeroColumns(&$columns, &$columnValues, &$modes, $fragment) { - foreach ($fragment['columns'] as $k => $col) { - $columns[] = $col; - $columnValues[] = $fragment['values'][$k]; - $modes[] = $fragment['modes'][$k]; - } - } - - // Passive HP regeneration: returns the new health value, or -1 if unchanged. - private function calculateHealthRegen($hdata) { - if((time()-$hdata['lastupdate']) >= 1){ - if($hdata['health'] < 100 and $hdata['health'] > 0){ - if(SPEED <= 10) $speed = SPEED; - else if(SPEED <= 100) $speed = ceil(SPEED / 10); - else $speed = ceil(SPEED / 100); - - $reg = $hdata['health'] + $hdata['regeneration'] * 5 * $speed / 86400 * (time() - $hdata['lastupdate']); - - return ($reg <= 100) ? $reg : 100; - } - } - return -1; - } - - // Level-up + score points. Returns a column fragment. - private function calculateLevelUp($hdata) { - global $hero_levels; - - $columns = []; - $values = []; - $modes = []; - - $herolevel = $hdata['level']; - $newLevel = - 1; - $scorePoints = false; - for ($i = $herolevel + 1; $i < 100; $i++){ - if($hdata['experience'] >= $hero_levels[$i]){ - $newLevel = $i; - if ($i < 99) $scorePoints = true; - } - } - - // upgrade hero to a new level, if needed - if ($newLevel > -1) { - $columns[] = 'level'; - $values[] = $newLevel; - $modes[] = null; - } - - // add as many points as needed, if we're below level 100 - if ($scorePoints) { - $columns[] = 'points'; - $values[] = (5 * ($newLevel - $herolevel)); - $modes[] = 1; - } - - return ['columns' => $columns, 'values' => $values, 'modes' => $modes, 'health' => null, 'lastUpdate' => null]; - } - - // Revive completion: marks the hero unit alive and clears the revive flag. - // Returns a column fragment plus the health (100) and lastUpdate overrides. - private function handleReviveCompletion($hdata) { - global $database; - - if($hdata['trainingtime'] < time() && $hdata['inrevive'] == 1){ - mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "units SET hero = 1 WHERE vref = ".(int) $hdata['wref'].""); - - return [ - 'columns' => ['dead', 'inrevive', 'inrevive'], - 'values' => [0, 0, 0], - 'modes' => [null, null, null], - 'health' => 100, - 'lastUpdate' => (int) $hdata['trainingtime'], - ]; - } - - return ['columns' => [], 'values' => [], 'modes' => [], 'health' => null, 'lastUpdate' => null]; - } - - // Training completion: marks the hero unit alive and clears the training flag. - // Returns a column fragment plus the lastUpdate override (health untouched). - private function handleTrainingCompletion($hdata) { - global $database; - - if($hdata['trainingtime'] < time() && $hdata['intraining'] == 1){ - mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "units SET hero = 1 WHERE vref = ".(int) $hdata['wref']); - - return [ - 'columns' => ['dead', 'intraining'], - 'values' => [0, 0], - 'modes' => [null, null], - 'health' => null, - 'lastUpdate' => (int) $hdata['trainingtime'], - ]; - } - - return ['columns' => [], 'values' => [], 'modes' => [], 'health' => null, 'lastUpdate' => null]; - } - - private function updateStore() { - global $database, $bid10, $bid38, $bid11, $bid39; - - $result = mysqli_query($database->dblink, 'SELECT * FROM `' . TB_PREFIX . 'fdata`'); - - mysqli_begin_transaction($database->dblink); - while ($row = mysqli_fetch_assoc($result)) - { - $ress = $crop = 0; - for ($i = 19; $i < 40; ++$i) - { - //Warehouse - if ($row['f' . $i . 't'] == 10) - { - $ress += ((isset($bid10[$row['f' . $i]]) && isset($bid10[$row['f' . $i]]['attri'])) ? $bid10[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); - } - - //Great warehouse - if ($row['f' . $i . 't'] == 38) - { - $ress += ((isset($bid38[$row['f' . $i]]) && isset($bid38[$row['f' . $i]]['attri'])) ? $bid38[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); - } - - //Granary - if ($row['f' . $i . 't'] == 11) - { - $crop += ((isset($bid11[$row['f' . $i]]) && isset($bid11[$row['f' . $i]]['attri'])) ? $bid11[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); - } - - //Great granary - if ($row['f' . $i . 't'] == 39) - { - $crop += ((isset($bid39[$row['f' . $i]]) && isset($bid39[$row['f' . $i]]['attri'])) ? $bid39[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); - } - } - - // no need for update, since we didn't find any warehouses or granaries - // and maximums would have been set to correct values inside prune* functions already - if ($ress == 0 && $crop == 0) continue; - - // maxstore nor maxcrop can go below the minimum threshold - if ($ress < STORAGE_BASE) $ress = STORAGE_BASE; - if ($crop < STORAGE_BASE) $crop = STORAGE_BASE; - - mysqli_query($database->dblink,'UPDATE `' . TB_PREFIX . 'vdata` SET `maxstore` = ' . (int) $ress . ', `maxcrop` = ' . (int) $crop . ' WHERE `wref` = ' . (int) $row['vref']); - } - mysqli_commit($database->dblink); - } - - private function checkInvitedPlayes() { - global $database; - - $q = "SELECT id, invited FROM ".TB_PREFIX."users WHERE invited > 0"; - $array = $database->query_return($q); - - // preload villages data - $userIDs = []; - foreach($array as $user) { - $userIDs[] = $user['id']; - } - $database->getProfileVillages($userIDs); - - // continue... - foreach($array as $user) { - $numusers = mysqli_fetch_array(mysqli_query($database->dblink,"SELECT Count(*) as Total FROM ".TB_PREFIX."users WHERE id = ".(int) $user['invited']), MYSQLI_ASSOC); - if($numusers['Total'] > 0){ - $varray = count($database->getProfileVillages($user['id'])); - if($varray > 1){ - $usergold = $database->getUserField($user['invited'],"gold",0); - $gold = $usergold+50; - $database->updateUserField($user['invited'],"gold",$gold,1); - $database->updateUserField($user['id'],"invited",0,1); - } - } - } - } - - private function updateGeneralAttack() { - global $database; - - mysqli_query($database->dblink, " - UPDATE ".TB_PREFIX."general - SET - shown = 0 - WHERE - shown = 1 AND - `time` < (UNIX_TIMESTAMP() - (86400 * 8))"); - } - - private function MasterBuilder() { - global $database; - - $q = "SELECT id, wid, type, level, field, timestamp FROM ".TB_PREFIX."bdata WHERE master = 1"; - $array = $database->query_return($q); - - foreach($array as $master) { - $owner = $database->getVillageField($master['wid'], 'owner'); - $tribe = $database->getUserField($owner, 'tribe', 0); - $villwood = $database->getVillageField($master['wid'], 'wood'); - $villclay = $database->getVillageField($master['wid'], 'clay'); - $villiron = $database->getVillageField($master['wid'], 'iron'); - $villcrop = $database->getVillageField($master['wid'], 'crop'); - $type = $master['type']; - $level = $master['level']; - $buildarray = $GLOBALS["bid".$type]; - $buildwood = $buildarray[$level]['wood']; - $buildclay = $buildarray[$level]['clay']; - $buildiron = $buildarray[$level]['iron']; - $buildcrop = $buildarray[$level]['crop']; - $ww = count($database->getBuildingByType($master['wid'], 40)); - - if($tribe == 1){ - if($master['field'] < 19){ - $bdata = $database->getDorf1Building($master['wid']); - $bdataTotal = count($bdata); - $bbdata = count($database->getDorf2Building($master['wid'])); - }else{ - $bdata = $database->getDorf2Building($master['wid']); - $bdataTotal = count($bdata); - $bbdata = count($database->getDorf1Building($master['wid'])); - } - }else{ - $bdata = array_merge($database->getDorf1Building($master['wid']), $database->getDorf2Building($master['wid'])); - $bdataTotal = $bbdata = count($bdata); - } - - if($database->getUserField($owner, 'plus', 0) > time() || $ww > 0){ - if($bbdata < 2) $inbuild = 2; - else $inbuild = 1; - } - else $inbuild = 1; - - $usergold = $database->getUserField($owner, 'gold', 0); - - if($bdataTotal < $inbuild && $buildwood <= $villwood && $buildclay <= $villclay && $buildiron <= $villiron && $buildcrop <= $villcrop && $usergold > 0){ - $time = $master['timestamp'] + time(); - - if(!empty($bdata)){ - foreach($bdata as $masterLoop) $time += ($masterLoop['timestamp'] - time()); - } - - if($bdataTotal == 0) $database->updateBuildingWithMaster($master['id'], $time, 0); - else $database->updateBuildingWithMaster($master['id'], $time, 1); - - $database->updateUserField($owner, 'gold', --$usergold, 1); - $database->modifyResource($master['wid'], $buildwood, $buildclay, $buildiron, $buildcrop, 0); - } - } - } - - /** - * Function for starvation - by brainiacX and Shadow - * Rework by ronix - * Refactored by iopietro - */ - - private function starvation() { - global $database, $technology; - - // Starvation is disabled during the peace period (holidays). - if(PEACE) return; - - $time = time(); - - // 1. Update starvation data for all villages. - $this->starvationUpdateAllVillages(); - - // 2. Load villages with negative production. - $starvarray = $this->starvationGetVillagesWithDeficit(); - if (empty($starvarray)) return; - - $vilIDs = array_column($starvarray, 'wref'); - - // 3. Prepare caches to reduce queries. - $this->starvationPrepareCaches($vilIDs); - - foreach ($starvarray as $starv) { - $this->starvationProcessVillage($starv, $time); - } - } - - /** - * Update the starvation table for all villages. - **/ - - private function starvationUpdateAllVillages() { - global $database; - $starvarray = $database->getProfileVillages(0, 7); - foreach($starvarray as $starv) { - $database->addStarvationData($starv['wref']); - } - } - - /** - * Return villages with crop deficit. - **/ - - private function starvationGetVillagesWithDeficit() { - global $database; - return $database->getStarvation(); - } - - /** - * Prepare troop caches. - **/ - - 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); - } - - /** - * Process a single village for starvation. - **/ - - private function starvationProcessVillage($starv, $time) { - global $database, $technology; - - $unitarrays = $this->getAllUnits($starv['wref']); - // Original upkeep formula - $upkeep = $starv['pop'] + $technology->getUpkeep($unitarrays, 0, $starv['wref']); - - $troopData = $this->starvationFindFirstTroopGroup($starv); - if (empty($troopData['troops'])) { - // No troops exist, only check reset. - $this->starvationCheckReset($starv, $upkeep); - return; - } - - $starvingTroops = $troopData['troops']; - $type = $troopData['type']; - $subtype = $troopData['subtype']; - - $timedif = $time - $starv['starvupdate']; - $cropProd = $database->getCropProdstarv($starv['wref']) - $starv['starv']; - - if($cropProd < 0){ - // Deficit calculation - $starvsec = (abs($cropProd) / 3600); - $difcrop = ($timedif * $starvsec); - $oldcrop = $database->getVillageField($starv['wref'], 'crop'); - - // Use granary crop first for consumption. - 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){ - $this->starvationKillTroops($starv, $starvingTroops, $type, $subtype, $difcrop, $upkeep, $time); - } - } - - $this->starvationCheckReset($starv, $upkeep); - } - - /** - * Locate the first troop group eligible for starvation. - * Processing order: oasis enforcement → village enforcement → prisoners → own units → attacks. - **/ - - 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]; - } - - /** - * Kill troops according to crop deficit. - **/ - - 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; - // Original per-unit crop consumption formula unchanged. - $unitIndex = $special ? $maxtype + ($tribe - 1) * 10 : $maxtype; - $difcrop -= $GLOBALS['u'.$unitIndex]['crop']; - } else break; - } - - $totalKilledUnits = array_sum($killedUnits); - $newCrop = 0; - - // Determine whether the hero dies. - 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); - } - } - - /** - * Apply starvation changes to the database. - **/ - - 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: // own units - $database->modifyUnit($starv['wref'], array_keys($killedUnits), array_values($killedUnits), [0]); - break; - case 3: // moving attacks. - 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]); - } - - /** - * Verify whether starvation reset is allowed. - **/ - - private function starvationCheckReset($starv, $upkeep) { - global $database; - $crop = $database->getCropProdstarv($starv['wref'], false); - if ($crop > $upkeep) { - $database->setVillageFields($starv['wref'], ['starv', 'starvupdate'], [0, 0]); - } - } - - private function procNewClimbers() { - global $database, $ranking; - - $ranking->procRankArray(); - $climbers = $ranking->getRank(); - if(count($climbers) > 0){ - $q = "SELECT week FROM ".TB_PREFIX."medal order by week DESC LIMIT 0, 1"; - $result = mysqli_query($database->dblink,$q); - if(mysqli_num_rows($result)) { - $row = mysqli_fetch_assoc($result); - $week = $row['week'] + 1; - } - else $week = 1; - - $q = "SELECT id FROM ".TB_PREFIX."users where oldrank = 0 and id > 5"; - $array = $database->query_return($q); - foreach($array as $user){ - $newrank = $ranking->getUserRank($user['id']); - if($week > 1){ - for($i = $newrank + 1; $i < count($climbers); $i++) { - if(isset($climbers[$i]['userid'])){ - $oldrank = $ranking->getUserRank($climbers[$i]['userid']); - $totalpoints = $oldrank - $climbers[$i]['oldrank']; - $database->removeclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - } - } - $database->updateoldrank($user['id'], $newrank); - }else{ - $totalpoints = count($climbers) - $newrank; - $database->setclimberrankpop($user['id'], $totalpoints); - $database->updateoldrank($user['id'], $newrank); - for($i = 1; $i < $newrank; $i++){ - if(isset($climbers[$i]['userid'])){ - $oldrank = $ranking->getUserRank($climbers[$i]['userid']); - $totalpoints = count($climbers) - $oldrank; - $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - } - } - for($i = $newrank + 1; $i < count($climbers); $i++){ - if(isset($climbers[$i]['userid'])){ - $oldrank = $ranking->getUserRank($climbers[$i]['userid']); - $totalpoints = count($climbers) - $oldrank; - $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - } - } - } - } - } - } - - private function procClimbers($uid) { - global $database, $ranking; - - $ranking->procRankArray(); - $climbers = $ranking->getRank(); - if(count($ranking->getRank()) > 0){ - $q = "SELECT week FROM ".TB_PREFIX."medal order by week DESC LIMIT 0, 1"; - $result = mysqli_query($database->dblink,$q); - if(mysqli_num_rows($result)) { - $row = mysqli_fetch_assoc($result); - $week = $row['week'] + 1; - } - else $week = 1; - - $myrank = $ranking->getUserRank($uid); - if(isset($climbers[$myrank]['oldrank']) && $climbers[$myrank]['oldrank'] > $myrank){ - for($i = $myrank + 1; $i <= $climbers[$myrank]['oldrank']; $i++) { - if(isset($climbers[$i]['oldrank'])){ - $oldrank = $ranking->getUserRank($climbers[$i]['userid']); - if($week > 1){ - $totalpoints = $oldrank - $climbers[$i]['oldrank']; - $database->removeclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - }else{ - $totalpoints = count($ranking->getRank()) - $oldrank; - $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - } - } - } - if(isset($climbers[$myrank]['oldrank'])){ - if($week > 1){ - $totalpoints = $climbers[$myrank]['oldrank'] - $myrank; - $database->addclimberrankpop($climbers[$myrank]['userid'], $totalpoints); - $database->updateoldrank($climbers[$myrank]['userid'], $myrank); - }else{ - $totalpoints = count($ranking->getRank()) - $myrank; - $database->setclimberrankpop($climbers[$myrank]['userid'], $totalpoints); - $database->updateoldrank($climbers[$myrank]['userid'], $myrank); - } - } - }else if(isset($climbers[$myrank]['oldrank']) && $climbers[$myrank]['oldrank'] < $myrank){ - for($i = $climbers[$myrank]['oldrank']; $i < $myrank; $i++) { - if(isset($climbers[$i]['oldrank'])){ - $oldrank = $ranking->getUserRank($climbers[$i]['userid']); - if($week > 1){ - $totalpoints = $climbers[$i]['oldrank'] - $oldrank; - $database->addclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - }else{ - $totalpoints = count($ranking->getRank()) - $oldrank; - $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); - $database->updateoldrank($climbers[$i]['userid'], $oldrank); - } - } - } - if(isset($climbers[$myrank-1]['oldrank'])){ - if($week > 1){ - $totalpoints = $myrank - $climbers[$myrank-1]['oldrank']; - $database->removeclimberrankpop($climbers[$myrank-1]['userid'], $totalpoints); - $database->updateoldrank($climbers[$myrank-1]['userid'], $myrank); - }else{ - $totalpoints = count($ranking->getRank()) - $myrank; - $database->setclimberrankpop($climbers[$myrank-1]['userid'], $totalpoints); - $database->updateoldrank($climbers[$myrank-1]['userid'], $myrank); - } - } - } - } - $ranking->procARankArray(); - $aid = $database->getUserField($uid,"alliance",0); - if(count($ranking->getRank()) > 0 && $aid != 0){ - $ally = $database->getAlliance($aid); - $memberlist = $database->getAllMember($ally['id']); - $oldrank = 0; - - $memberIDs = []; - foreach($memberlist as $member) { - $memberIDs[] = $member['id']; - } - $data = $database->getVSumField($memberIDs,"pop"); - - if (count($data)) { - foreach ($data as $row) { - $oldrank += $row['Total']; - } - } - - if($ally['oldrank'] != $oldrank){ - if($ally['oldrank'] < $oldrank) { - $totalpoints = $oldrank - $ally['oldrank']; - $database->addclimberrankpopAlly($ally['id'], $totalpoints); - $database->updateoldrankAlly($ally['id'], $oldrank); - } else - if($ally['oldrank'] > $oldrank) { - $totalpoints = $ally['oldrank'] - $oldrank; - $database->removeclimberrankpopAlly($ally['id'], $totalpoints); - $database->updateoldrankAlly($ally['id'], $oldrank); - } - } - } - } - - private function checkBan() { - global $database; - - mysqli_query($database->dblink, " - UPDATE ".TB_PREFIX."banlist as b - JOIN ".TB_PREFIX."users as u ON b.uid = u.id - SET - b.active = 0, - u.access = 2 - WHERE - b.active = 1 AND - b.`end` < UNIX_TIMESTAMP() AND - b.`end` > 0"); - } - - private function regenerateOasisTroops() { - global $database; - - $timeFinal = time() - NATURE_REGTIME; - $q = "SELECT wref FROM " . TB_PREFIX . "odata where conqured = 0 and lastupdated2 < $timeFinal"; - $array = $database->query_return($q); - if (count($array)) { - $ids = []; - foreach($array as $oasis) $ids[] = $oasis['wref']; - $database->regenerateOasisUnits($ids, true); - } - } - - public static function updateMax($leader) { - global $bid18, $database; - - $q = mysqli_fetch_array(mysqli_query($database->dblink,"SELECT Count(*) as Total FROM " . TB_PREFIX . "alidata where leader = ". (int) $leader), MYSQLI_ASSOC); - if ($q['Total'] > 0) { - $villages = $database->getVillagesID2($leader); - $max = 0; - - // cache resource levels - $vilIDs = []; - foreach($villages as $village){ - $vilIDs[$village['wref']] = true; - } - $database->cacheResourceLevels(array_keys($vilIDs)); - - foreach($villages as $village){ - $field = $database->getResourceLevel($village['wref'], false); - for($i = 19; $i <= 40; $i++){ - if($field['f'.$i.'t'] == 18){ - $level = $field['f'.$i]; - $attri = $bid18[$level]['attri']; - } - } - if($attri > $max){ - $max = $attri; - } - } - $q = "UPDATE ".TB_PREFIX."alidata set max = ".(int) $max." where leader = ".(int) $leader; - $database->query($q); - } - } - - /** - * Function for automate medals - by yi12345 and Shadow - * - */ - - function medals() { - global $database; - - // Check the timing window; $time is the next "last awarded" stamp to write. - $time = $this->shouldAwardMedalsNow(); - if ($time === null) { - return; - } - - // Exclude BANNED (0), MH (8), ADMIN (9) - $userFilter = "id > 5 AND access NOT IN (0,8,9)"; - - $week = $this->getNextMedalWeek('medal'); - $allyweek = $this->getNextMedalWeek('allimedal'); - - $this->awardPlayerMedals($week, $userFilter); - $this->resetWeeklyStats('users', $userFilter, "ap=0, dp=0, Rc=0, clp=0, RR=0"); - $this->awardAllianceMedals($allyweek); - $this->resetWeeklyStats('alidata', '', "ap=0, dp=0, RR=0, clp=0"); - - // Update last awarded time - $database->query("UPDATE ".TB_PREFIX."config SET lastgavemedal=".(int)$time); - } - - // Returns the next "lastgavemedal" stamp to write if medals are due now, or - // null to skip this run. On the very first run after server start it only - // schedules the next run (and returns null). - private function shouldAwardMedalsNow() { - global $database; - - $giveMedal = false; - $time = null; - $q = "SELECT lastgavemedal FROM ".TB_PREFIX."config"; - $result = mysqli_query($database->dblink, $q); - if ($result) { - $row = mysqli_fetch_assoc($result); - $stime = strtotime(START_DATE) - strtotime(date('d.m.Y')) + strtotime(START_TIME); - - if ($row['lastgavemedal'] == 0 && $stime < time()) { - // First run after server start - schedule next run - $setDays = round(MEDALINTERVAL / 86400); - $newtime = $setDays < 7? strtotime(($setDays + 1).' day midnight') : strtotime('next monday'); - $database->query("UPDATE ".TB_PREFIX."config SET lastgavemedal = ".(int)$newtime); - } elseif ($row['lastgavemedal']!= 0) { - $time = $row['lastgavemedal'] + MEDALINTERVAL; - $giveMedal = $row['lastgavemedal'] < time(); - } - } - - if (!($giveMedal && MEDALINTERVAL > 0)) { - return null; - } - return $time; - } - - // Next week number for a medal table (medal / allimedal). - private function getNextMedalWeek($table) { - global $database; - - $q = "SELECT week FROM ".TB_PREFIX."$table ORDER BY week DESC LIMIT 1"; - $res = mysqli_query($database->dblink, $q); - return $res && mysqli_num_rows($res)? (mysqli_fetch_assoc($res)['week'] + 1) : 1; - } - - // Insert a user medal row. - private function insertUserMedal($userid, $category, $place, $week, $points, $img) { - global $database; - - $q = "INSERT INTO ".TB_PREFIX."medal (userid, categorie, plaats, week, points, img) VALUES (". - (int)$userid.", ".(int)$category.", ".(int)$place.", ".(int)$week.", '".mysqli_real_escape_string($database->dblink, $points)."', '".mysqli_real_escape_string($database->dblink, $img)."')"; - mysqli_query($database->dblink, $q); - } - - // Insert an alliance medal row. - private function insertAllianceMedal($allyid, $cat, $place, $week, $points, $img) { - global $database; - - $q = "INSERT INTO ".TB_PREFIX."allimedal (allyid, categorie, plaats, week, points, img) VALUES (".(int)$allyid.", '".(int)$cat."', ".(int)$place.", '".(int)$week."', '".mysqli_real_escape_string($database->dblink, $points)."', '".mysqli_real_escape_string($database->dblink, $img)."')"; - mysqli_query($database->dblink, $q); - } - - // Award the top 10 users for a stat field (one category). - private function awardTopMedals($field, $category, $imgPrefix, $week, $userFilter) { - global $database; - - $q = "SELECT id, $field FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY $field DESC, id DESC LIMIT 10"; - $res = mysqli_query($database->dblink, $q); - $i = 0; - while ($row = mysqli_fetch_array($res)) { - $i++; - $this->insertUserMedal($row['id'], $category, $i, $week, $row[$field], $imgPrefix.$i); - } - } - - // Award milestone ribbons for 3/5/10 appearances in a top3 or top10 category. - private function awardMilestoneMedals($field, $sourceCat, $topLimit, $targetCat, $imgBase, $week, $userFilter) { - global $database; - - $res = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY $field DESC, id DESC LIMIT 10"); - while ($u = mysqli_fetch_array($res)) { - $cnt = mysqli_fetch_row(mysqli_query($database->dblink, - "SELECT COUNT(*) FROM ".TB_PREFIX."medal WHERE userid=".(int)$u['id']." AND categorie=".(int)$sourceCat." AND plaats<=".(int)$topLimit))[0]; - - $map = ['3' => ['Three', $imgBase.'0_1'], '5' => ['Five', $imgBase.'1_1'], '10' => ['Ten', $imgBase.'2_1']]; - if (isset($map[$cnt])) { - $this->insertUserMedal($u['id'], $targetCat, 0, $week, $map[$cnt][0], $map[$cnt][1]); - } - } - } - - // Player medals: top 10 of each category, the attack+defense bonus, then milestones. - private function awardPlayerMedals($week, $userFilter) { - global $database; - - // Top 10 for each category - $this->awardTopMedals('ap', 1, 't2_', $week, $userFilter); // Attackers of the week (cat 1) - $this->awardTopMedals('dp', 2, 't3_', $week, $userFilter); // Defenders of the week (cat 2) - $this->awardTopMedals('Rc', 3, 't1_', $week, $userFilter); // Climbers of the week (cat 3) - $this->awardTopMedals('clp', 10, 't6_', $week, $userFilter); // Rank climbers of the week (cat 10) - $this->awardTopMedals('RR', 4, 't4_', $week, $userFilter); // Robbers of the week (cat 4) - - // --- Bonus: player in both top10 attack AND defense (cat 5) --- - $topAttackers = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY ap DESC, id DESC LIMIT 10"); - while ($a = mysqli_fetch_array($topAttackers)) { - $topDefenders = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY dp DESC, id DESC LIMIT 10"); - while ($d = mysqli_fetch_array($topDefenders)) { - if ($a['id'] == $d['id']) { - $cnt = mysqli_fetch_row(mysqli_query($database->dblink, "SELECT COUNT(*) FROM ".TB_PREFIX."medal WHERE userid=".(int)$a['id']." AND categorie=5"))[0]; - if ($cnt <= 2) { - $texts = [0 => '', 1 => 'twice ', 2 => 'three times ']; - $this->insertUserMedal($a['id'], 5, 0, $week, $texts[$cnt], 't22'.$cnt.'_1'); - } - } - } - } - - // --- Milestone ribbons for 3/5/10 times in top3 or top10 --- - // Attackers milestones - $this->awardMilestoneMedals('ap', 1, 3, 6, 't12', $week, $userFilter); // top3 attackers - $this->awardMilestoneMedals('ap', 1, 10, 12, 't13', $week, $userFilter); // top10 attackers - // Defenders milestones - $this->awardMilestoneMedals('dp', 2, 3, 7, 't14', $week, $userFilter); - $this->awardMilestoneMedals('dp', 2, 10, 13, 't15', $week, $userFilter); - // Climbers milestones - $this->awardMilestoneMedals('Rc', 3, 3, 8, 't10', $week, $userFilter); - $this->awardMilestoneMedals('Rc', 3, 10, 14, 't11', $week, $userFilter); - // Rank climbers milestones - $this->awardMilestoneMedals('clp', 10, 3, 11, 't20', $week, $userFilter); - $this->awardMilestoneMedals('clp', 10, 10, 16, 't21', $week, $userFilter); - // Robbers milestones - $this->awardMilestoneMedals('RR', 4, 3, 9, 't16', $week, $userFilter); - $this->awardMilestoneMedals('RR', 4, 10, 15, 't17', $week, $userFilter); - } - - // Alliance medals: top 10 of each category, then the attack+defense bonus. - private function awardAllianceMedals($allyweek) { - global $database; - - $allyCats = [ - ['ap', 1, 'a2_'], - ['dp', 2, 'a3_'], - ['RR', 4, 'a4_'], - ['clp', 3, 'a1_'] - ]; - foreach ($allyCats as [$field, $cat, $img]) { - $res = mysqli_query($database->dblink, "SELECT id, $field FROM ".TB_PREFIX."alidata ORDER BY $field DESC, id DESC LIMIT 10"); - $i = 0; - while ($r = mysqli_fetch_array($res)) { $i++; $this->insertAllianceMedal($r['id'], $cat, $i, $allyweek, $r[$field], $img.$i); } - } - - // Alliance bonus for attack+defense - $resA = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."alidata ORDER BY ap DESC, id DESC LIMIT 10"); - while ($a = mysqli_fetch_array($resA)) { - $resD = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."alidata ORDER BY dp DESC, id DESC LIMIT 10"); - while ($d = mysqli_fetch_array($resD)) { - if ($a['id'] == $d['id']) { - $cnt = mysqli_fetch_row(mysqli_query($database->dblink, "SELECT COUNT(*) FROM ".TB_PREFIX."allimedal WHERE allyid=".(int)$a['id']." AND categorie=5"))[0]; - if ($cnt <= 2) { - $texts = [0 => '', 1 => 'twice ', 2 => 'three times ']; - $this->insertAllianceMedal($a['id'], 5, 0, $allyweek, $texts[$cnt], 't22'.$cnt.'_1'); - } - } - } - } - } - - // Reset the weekly stat columns for every id in a table (optionally filtered). - private function resetWeeklyStats($table, $where, $setClause) { - global $database; - - $ids = []; - $sql = "SELECT id FROM ".TB_PREFIX.$table; - if ($where !== '') $sql .= " WHERE ".$where; - $res = mysqli_query($database->dblink, $sql); - while ($r = mysqli_fetch_row($res)) { $ids[] = (int)$r[0]; } - if ($ids) { - mysqli_query($database->dblink, "UPDATE ".TB_PREFIX.$table." SET ".$setClause." WHERE id IN(".implode(',', $ids).")"); - } - } - - private function artefactOfTheFool() { - global $database; - - $time = time(); - $q = "SELECT id, size FROM " . TB_PREFIX . "artefacts where type = 8 AND active = 1 AND del = 0 AND lastupdate <= ".($time - (86400 / (SPEED == 2 ? 1.5 : (SPEED == 3 ? 2 : SPEED)))); - $array = $database->query_return($q); - if ($array) { - foreach($array as $artefact) { - $kind = rand(1, 7); - - while($kind == 6) $kind = rand(1, 7); - - if($artefact['size'] != 3) $bad_effect = rand(0, 1); - else $bad_effect = 0; - - switch($kind) { - case 1: - $effect = rand(1, 5); - break; - case 2: - $effect = rand(1, 3); - break; - case 3: - $effect = rand(3, 10); - break; - case 4: - case 5: - $effect = rand(2, 4); - break; - case 7: - $effect = rand(1, 6); - break; - } - mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."artefacts SET kind = ". (int) $kind. ", bad_effect = $bad_effect, effect2 = $effect, lastupdate = $time WHERE id = ".(int) $artefact['id']); - } - } - } } $automation = new Automation; diff --git a/GameEngine/Automation/AutomationAccountMaintenance.php b/GameEngine/Automation/AutomationAccountMaintenance.php new file mode 100644 index 00000000..36d53685 --- /dev/null +++ b/GameEngine/Automation/AutomationAccountMaintenance.php @@ -0,0 +1,347 @@ +getNeedDelete(); + if(count($needDelete) > 0) { + + //Remove the time limit, otherwise deleting players with 80 or more villages couldn't be deleted in one run + @set_time_limit(0); + + foreach($needDelete as $need) { + $need['uid'] = (int) $need['uid']; + + //Get the villages which have to be deleted + $needVillages = $database->getVillagesID($need['uid']); + + //Delete all villages + $database->DelVillage($needVillages); + + for($i = 0;$i < 20; $i++){ + $q = "SELECT id FROM ".TB_PREFIX."users where friend".$i." = ".$need['uid']." or friend".$i."wait = ".$need['uid'].""; + $array = $database->query_return($q); + foreach($array as $friend){ + $database->deleteFriend($friend['id'],"friend".$i); + $database->deleteFriend($friend['id'],"friend".$i."wait"); + } + } + + $database->updateUserField($need['uid'], 'alliance', 0, 1); + + if($database->isAllianceOwner($need['uid'])){ + $alliance = $database->getUserAllianceID($need['uid']); + $newowner = $database->getAllMember2($alliance); + $newleader = $newowner['id']; + $q = "UPDATE " . TB_PREFIX . "alidata set leader = ".(int) $newleader." where id = ".(int) $alliance.""; + $database->query($q); + $database->updateAlliPermissions($newleader, $alliance, "Leader", 1, 1, 1, 1, 1, 1, 1); + Automation::updateMax($newleader); + } + + if (isset($alliance)) $database->deleteAlliance($alliance); + + $q = "DELETE FROM ".TB_PREFIX."hero where uid = ".$need['uid']; + $database->query($q); + + $q = "DELETE FROM ".TB_PREFIX."mdata where target = ".$need['uid']." or owner = ".$need['uid']; + $database->query($q); + + $q = "DELETE FROM ".TB_PREFIX."ndata where uid = ".$need['uid']; + $database->query($q); + + $q = "DELETE FROM ".TB_PREFIX."users where id = ".$need['uid']; + $database->query($q); + + $q = "DELETE FROM ".TB_PREFIX."deleting where uid = ".$need['uid']; + $database->query($q); + } + } + } + + private function ClearUser() { + global $database; + + if(AUTO_DEL_INACTIVE) { + $time = time() - UN_ACT_TIME; + + $q = "INSERT INTO ".TB_PREFIX."deleting SELECT id, UNIX_TIMESTAMP() FROM ".TB_PREFIX."users WHERE timestamp < $time AND tribe IN(1, 2, 3)"; + $database->query($q); + } + } + + private function ClearInactive() { + global $database; + + if(TRACK_USR) { + $timeout = time()-USER_TIMEOUT * 60; + $q = "DELETE FROM ".TB_PREFIX."active WHERE timestamp < $timeout"; + $database->query($q); + } + } + + private function checkInvitedPlayes() { + global $database; + + $q = "SELECT id, invited FROM ".TB_PREFIX."users WHERE invited > 0"; + $array = $database->query_return($q); + + // preload villages data + $userIDs = []; + foreach($array as $user) { + $userIDs[] = $user['id']; + } + $database->getProfileVillages($userIDs); + + // continue... + foreach($array as $user) { + $numusers = mysqli_fetch_array(mysqli_query($database->dblink,"SELECT Count(*) as Total FROM ".TB_PREFIX."users WHERE id = ".(int) $user['invited']), MYSQLI_ASSOC); + if($numusers['Total'] > 0){ + $varray = count($database->getProfileVillages($user['id'])); + if($varray > 1){ + $usergold = $database->getUserField($user['invited'],"gold",0); + $gold = $usergold+50; + $database->updateUserField($user['invited'],"gold",$gold,1); + $database->updateUserField($user['id'],"invited",0,1); + } + } + } + } + + private function updateGeneralAttack() { + global $database; + + mysqli_query($database->dblink, " + UPDATE ".TB_PREFIX."general + SET + shown = 0 + WHERE + shown = 1 AND + `time` < (UNIX_TIMESTAMP() - (86400 * 8))"); + } + + private function procNewClimbers() { + global $database, $ranking; + + $ranking->procRankArray(); + $climbers = $ranking->getRank(); + if(count($climbers) > 0){ + $q = "SELECT week FROM ".TB_PREFIX."medal order by week DESC LIMIT 0, 1"; + $result = mysqli_query($database->dblink,$q); + if(mysqli_num_rows($result)) { + $row = mysqli_fetch_assoc($result); + $week = $row['week'] + 1; + } + else $week = 1; + + $q = "SELECT id FROM ".TB_PREFIX."users where oldrank = 0 and id > 5"; + $array = $database->query_return($q); + foreach($array as $user){ + $newrank = $ranking->getUserRank($user['id']); + if($week > 1){ + for($i = $newrank + 1; $i < count($climbers); $i++) { + if(isset($climbers[$i]['userid'])){ + $oldrank = $ranking->getUserRank($climbers[$i]['userid']); + $totalpoints = $oldrank - $climbers[$i]['oldrank']; + $database->removeclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + } + } + $database->updateoldrank($user['id'], $newrank); + }else{ + $totalpoints = count($climbers) - $newrank; + $database->setclimberrankpop($user['id'], $totalpoints); + $database->updateoldrank($user['id'], $newrank); + for($i = 1; $i < $newrank; $i++){ + if(isset($climbers[$i]['userid'])){ + $oldrank = $ranking->getUserRank($climbers[$i]['userid']); + $totalpoints = count($climbers) - $oldrank; + $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + } + } + for($i = $newrank + 1; $i < count($climbers); $i++){ + if(isset($climbers[$i]['userid'])){ + $oldrank = $ranking->getUserRank($climbers[$i]['userid']); + $totalpoints = count($climbers) - $oldrank; + $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + } + } + } + } + } + } + + private function procClimbers($uid) { + global $database, $ranking; + + $ranking->procRankArray(); + $climbers = $ranking->getRank(); + if(count($ranking->getRank()) > 0){ + $q = "SELECT week FROM ".TB_PREFIX."medal order by week DESC LIMIT 0, 1"; + $result = mysqli_query($database->dblink,$q); + if(mysqli_num_rows($result)) { + $row = mysqli_fetch_assoc($result); + $week = $row['week'] + 1; + } + else $week = 1; + + $myrank = $ranking->getUserRank($uid); + if(isset($climbers[$myrank]['oldrank']) && $climbers[$myrank]['oldrank'] > $myrank){ + for($i = $myrank + 1; $i <= $climbers[$myrank]['oldrank']; $i++) { + if(isset($climbers[$i]['oldrank'])){ + $oldrank = $ranking->getUserRank($climbers[$i]['userid']); + if($week > 1){ + $totalpoints = $oldrank - $climbers[$i]['oldrank']; + $database->removeclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + }else{ + $totalpoints = count($ranking->getRank()) - $oldrank; + $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + } + } + } + if(isset($climbers[$myrank]['oldrank'])){ + if($week > 1){ + $totalpoints = $climbers[$myrank]['oldrank'] - $myrank; + $database->addclimberrankpop($climbers[$myrank]['userid'], $totalpoints); + $database->updateoldrank($climbers[$myrank]['userid'], $myrank); + }else{ + $totalpoints = count($ranking->getRank()) - $myrank; + $database->setclimberrankpop($climbers[$myrank]['userid'], $totalpoints); + $database->updateoldrank($climbers[$myrank]['userid'], $myrank); + } + } + }else if(isset($climbers[$myrank]['oldrank']) && $climbers[$myrank]['oldrank'] < $myrank){ + for($i = $climbers[$myrank]['oldrank']; $i < $myrank; $i++) { + if(isset($climbers[$i]['oldrank'])){ + $oldrank = $ranking->getUserRank($climbers[$i]['userid']); + if($week > 1){ + $totalpoints = $climbers[$i]['oldrank'] - $oldrank; + $database->addclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + }else{ + $totalpoints = count($ranking->getRank()) - $oldrank; + $database->setclimberrankpop($climbers[$i]['userid'], $totalpoints); + $database->updateoldrank($climbers[$i]['userid'], $oldrank); + } + } + } + if(isset($climbers[$myrank-1]['oldrank'])){ + if($week > 1){ + $totalpoints = $myrank - $climbers[$myrank-1]['oldrank']; + $database->removeclimberrankpop($climbers[$myrank-1]['userid'], $totalpoints); + $database->updateoldrank($climbers[$myrank-1]['userid'], $myrank); + }else{ + $totalpoints = count($ranking->getRank()) - $myrank; + $database->setclimberrankpop($climbers[$myrank-1]['userid'], $totalpoints); + $database->updateoldrank($climbers[$myrank-1]['userid'], $myrank); + } + } + } + } + $ranking->procARankArray(); + $aid = $database->getUserField($uid,"alliance",0); + if(count($ranking->getRank()) > 0 && $aid != 0){ + $ally = $database->getAlliance($aid); + $memberlist = $database->getAllMember($ally['id']); + $oldrank = 0; + + $memberIDs = []; + foreach($memberlist as $member) { + $memberIDs[] = $member['id']; + } + $data = $database->getVSumField($memberIDs,"pop"); + + if (count($data)) { + foreach ($data as $row) { + $oldrank += $row['Total']; + } + } + + if($ally['oldrank'] != $oldrank){ + if($ally['oldrank'] < $oldrank) { + $totalpoints = $oldrank - $ally['oldrank']; + $database->addclimberrankpopAlly($ally['id'], $totalpoints); + $database->updateoldrankAlly($ally['id'], $oldrank); + } else + if($ally['oldrank'] > $oldrank) { + $totalpoints = $ally['oldrank'] - $oldrank; + $database->removeclimberrankpopAlly($ally['id'], $totalpoints); + $database->updateoldrankAlly($ally['id'], $oldrank); + } + } + } + } + + private function checkBan() { + global $database; + + mysqli_query($database->dblink, " + UPDATE ".TB_PREFIX."banlist as b + JOIN ".TB_PREFIX."users as u ON b.uid = u.id + SET + b.active = 0, + u.access = 2 + WHERE + b.active = 1 AND + b.`end` < UNIX_TIMESTAMP() AND + b.`end` > 0"); + } + + public static function updateMax($leader) { + global $bid18, $database; + + $q = mysqli_fetch_array(mysqli_query($database->dblink,"SELECT Count(*) as Total FROM " . TB_PREFIX . "alidata where leader = ". (int) $leader), MYSQLI_ASSOC); + if ($q['Total'] > 0) { + $villages = $database->getVillagesID2($leader); + $max = 0; + + // cache resource levels + $vilIDs = []; + foreach($villages as $village){ + $vilIDs[$village['wref']] = true; + } + $database->cacheResourceLevels(array_keys($vilIDs)); + + foreach($villages as $village){ + $field = $database->getResourceLevel($village['wref'], false); + for($i = 19; $i <= 40; $i++){ + if($field['f'.$i.'t'] == 18){ + $level = $field['f'.$i]; + $attri = $bid18[$level]['attri']; + } + } + if($attri > $max){ + $max = $attri; + } + } + $q = "UPDATE ".TB_PREFIX."alidata set max = ".(int) $max." where leader = ".(int) $leader; + $database->query($q); + } + } +} diff --git a/GameEngine/Automation/AutomationBattleResolution.php b/GameEngine/Automation/AutomationBattleResolution.php new file mode 100644 index 00000000..23ee761b --- /dev/null +++ b/GameEngine/Automation/AutomationBattleResolution.php @@ -0,0 +1,2967 @@ +calculateCatapultsDamage($data['t8'], + $battlepart['catapults']['upgrades'], + $battlepart['catapults']['durability'], + $battlepart['catapults']['attackDefenseRatio'], + $battlepart['catapults']['strongerBuildings'], + $battlepart['catapults']['moraleBonus']); + + $newLevel = $battle->calculateNewBuildingLevel($tblevel, $catapultsDamage / ($twoRowsCatapultSetup ? 2 : 1)); + + //fix: Only modify build queue if actual damage was dealt + if ($newLevel < $tblevel) { + $database->modifyBData($data['to'], $tbid, [$newLevel, $tblevel], $tribe); + } + + // building/field destroyed + if ($newLevel == 0){ + // prepare data to be updated + $fieldsToSet = ["f".$tbid]; + $fieldValuesToSet = [0]; + + // update $bdo, so we don't have to reselect later + $bdo['f'.$catapultTarget] = 0; + + if ($tbid >= 19 && $tbid != 99) { + $fieldsToSet[] = "f".$tbid."t"; + $fieldValuesToSet[] = 0; + $bdo['f'.$catapultTarget."t"] = 0; + } + + // update all that needs updating + $database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet); + + $buildarray = $GLOBALS["bid".$tbgid]; + + if ( isset( $buildarray[$newLevel] ) ) { + // (great) warehouse level was changed + if ($tbgid == 10 || $tbgid == 38) { + $database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']); + } + + // (great) granary level was changed + if ($tbgid == 11 || $tbgid == 39) { + $database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']); + } + } + + // oasis cannot be destroyed + $pop = $this->recountPop($data['to'], false); + if ($isoasis == 0 && $pop == 0 && $can_destroy == 1) $village_destroyed = 1; + + if ($isSecondRow) { + if ($tbid > 0) { + $info_cat .= "".INFORMATION." + \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_DESTROYED')."."; + } + + // embassy level was changed + if ($tbgid == 18){ + $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); + } + + $info_cat .= ""; + } else { + $info_cat = "".$catp_pic.", ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_DESTROYED')."."; + + // embassy level was changed + if ($tbgid == 18){ + $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); + } + } + } + // building/field not damaged + elseif($newLevel == $tblevel){ + if($isSecondRow) { + if ($tbid > 0) { + $info_cat .= "".INFORMATION." + \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED').""; + } + } else { + $info_cat = "".$catp_pic.",".rc_bld($tbgid, $can_destroy)." ".rc_tok('RC_NOT_DAMAGED'); + } + } + // building/field was damaged, let's calculate the actual damage + else + { + // update $bdo, so we don't have to reselect later + $bdo['f'.$catapultTarget] = $newLevel; + + // building was damaged to a lower level + $info_cata = " ".rc_tok('RC_DAMAGED_FROM_TO', $tblevel, $newLevel); + + $buildarray = $GLOBALS["bid".$tbgid]; + + // (great) warehouse level was changed + if ($tbgid == 10 || $tbgid == 38) { + $database->setMaxStoreForVillage($data['to'], $buildarray[$newLevel]['attri']); + } + + // (great) granary level was changed + if ($tbgid == 11 || $tbgid == 39) { + $database->setMaxCropForVillage($data['to'], $buildarray[$newLevel]['attri']); + } + + $fieldsToSet = ["f".$tbid]; + $fieldValuesToSet = [$newLevel]; + + $database->setVillageLevel($data['to'], $fieldsToSet, $fieldValuesToSet); + + // recalculate population and check if the village shouldn't be destroyed at this point + $pop = $this->recountPop($data['to'], false); + if ($isoasis == 0) { + if($pop == 0 && $can_destroy == 1) $village_destroyed = 1; + } + + if ($isSecondRow) { + $info_cat .= "".INFORMATION." + \"".rc_tok('RC_CATAPULT')."\" ".rc_bld($tbgid, $can_destroy).$info_cata; + + // embassy level was changed + if ($tbgid == 18) { + $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); + } + + $info_cat .= ""; + } else { + $info_cat = "" . $catp_pic . "," . rc_bld($tbgid, $can_destroy).$info_cata; + + // embassy level was changed + if ($tbgid == 18) { + $info_cat .= $database->checkEmbassiesAfterBattle($data['to'], $bdo['f'.$catapultTarget], false); + } + } + } + }else{ + if(!isset($info_cat) || empty($info_cat) || $info_cat == ","){ + $info_cat = "".$catp_pic.", ".rc_tok('RC_NO_BUILDINGS'); + }else if(strpos($info_cat, rc_tok('RC_NO_BUILDINGS')) === false){ + $info_cat .= "".INFORMATION." + \"".rc_tok('RC_CATAPULT')."\" ".rc_tok('RC_NO_BUILDINGS')."."; + } + } + } + + private function applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat) + { + global $database; + + // village_destroyed stays unset in the pop<=0 path of the original; + // downstream treats unset as 0, so initialising to 0 here is equivalent. + $village_destroyed = 0; + + $pop = $this->recountPop($data['to']); + + // village has been destroyed + if ($pop <= 0) { + if ($can_destroy == 1) $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_ALREADY_DESTROYED'); + else $info_cat = "".$catp_pic.", ".rc_tok('RC_VILLAGE_CANT_DESTROY'); + } + else + { + // village stands, let's do the damage + /** + * FIRST CATAPULTS ROW + */ + + $basearray = $data['to']; + $bdo = $database->getResourceLevel($basearray, false); + $catapultTarget = $data['ctar1']; + $catapultTarget2 = (isset($data['ctar2']) ? $data['ctar2'] : 0); + + $catapults1TargetRandom = ($catapultTarget == 0); + $catapults2WillNotShoot = ($catapultTarget2 == 0); + $catapults2TargetRandom = ($catapults2WillNotShoot || $catapultTarget2 == 99); + + // we're manually targetting 1st and/or 2nd row of catapults + if (!$catapults1TargetRandom) + { + $_catapultsTarget1Levels = []; + $__catapultsTarget1AltTargets = []; + + // calculate targets for 1st rows of catapults + $j = 0; + for ($i = 1; $i <= 41; $i++) + { + if ($i == 41) $i = 99; + + // 1st row of catapults pre-selected target calculations, if needed + if (!$catapults1TargetRandom && $bdo['f'.$i.'t'] == $catapultTarget && $bdo['f'.$i] > 0 && $i != 40) + { + $j++; + $_catapultsTarget1Levels[$j]=$bdo['f'.$i]; + $__catapultsTarget1AltTargets[$j]=$i; + } + } + + // if we couldn't find a suitable target for 1st row of catapults, + // select a random target instead + if (!$catapults1TargetRandom) { + if ( count( $_catapultsTarget1Levels ) > 0 ) { + if ( max( $_catapultsTarget1Levels ) <= 0 ) { + $catapultTarget = 0; + } else { + $catapultTarget = $__catapultsTarget1AltTargets[rand( 1, $j )]; + } + } else { + $catapultTarget = 0; + $catapults1TargetRandom = true; + } + } + } + + // 1st row of catapults set to target randomly + if ($catapults1TargetRandom) + { + $list = []; + for ($i = 1; $i <= 41; $i++) + { + if ($i == 41) $i = 99; + if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i; + } + $catapultTarget = $list[rand(0, count($list) - 1)]; + } + + /** + * resolve 1st row of catapults + */ + $village_destroyed = 0; + $this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget, !$catapults2WillNotShoot, false, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe); + + /** + * SECOND CATAPULTS ROW + */ + + // we're manually targetting 2nd row of catapults + if (!$catapults2TargetRandom) + { + $_catapultsTarget2Levels = []; + $__catapultsTarget2AltTargets = []; + + // calculate targets for 2nd rows of catapults + $j = 0; + for ($i = 1; $i <= 41; $i++) + { + if ($i == 41) $i = 99; + + // 2nd row of catapults pre-selected target calculations, if needed + if (!$catapults2TargetRandom && !$catapults2WillNotShoot && $bdo['f'.$i.'t'] == $catapultTarget2 && $bdo['f'.$i] > 0 && $i != 40) + { + $j++; + $_catapultsTarget2Levels[$j] = $bdo['f'.$i]; + $__catapultsTarget2AltTargets[$j] = $i; + } + } + + // if we couldn't find a suitable target for 2nd row of catapults, + // select a random target instead + if (!$catapults2TargetRandom) { + if (count($_catapultsTarget2Levels) > 0 ) { + if (max($_catapultsTarget2Levels) <= 0 ) { + $catapultTarget2 = 99; + } + else $catapultTarget2 = $__catapultsTarget2AltTargets[rand( 1, $j )]; + } else { + $catapultTarget2 = 99; + $catapults2TargetRandom = true; + } + } + } + + // 2nd row of catapults set to target randomly + if ($catapults2TargetRandom && !$catapults2WillNotShoot) + { + $list = []; + for ($i = 1; $i <= 41; $i++) + { + if ($i == 41) $i = 99; + if ($bdo['f'.$i] > 0 && $i != 40) $list[] = $i; + } + $catapultTarget2 = $list[ rand(0, count($list) - 1) ]; + } + + /** + * resolve 2nd row of catapults + */ + if (!$catapults2WillNotShoot) { + $this->resolveCatapultsDestruction($bdo, $battlepart, $info_cat, $data, $catapultTarget2, true, true, $catp_pic, $can_destroy, $isoasis, $village_destroyed, $targettribe); + } + + // clear resource levels cache, since we might have destroyed buildings/fields by now + call_user_func(get_class($database).'::clearResourseLevelsCache'); + } + + return ['battlepart' => $battlepart, 'info_cat' => $info_cat, 'village_destroyed' => $village_destroyed]; + } + + private function claimMovementRecord($moveid) { + global $database; + + $moveid = (int)$moveid; + if ($moveid <= 0) { + return false; + } + + $q = "UPDATE ".TB_PREFIX."movement SET proc = 1 WHERE moveid = $moveid AND proc = 0"; + mysqli_query($database->dblink, $q); + + return (mysqli_affected_rows($database->dblink) === 1); + } + + /** + * Handle hero evasion: if the defender has evasion active and can afford it, + * send all defender units back to base and charge 2 gold + 1 evasion charge. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current movement row (needs 'to'). + * @param int $DefenderID Defender's user ID. + * @param array $DefenderUnit Unit counts for the defending village. + * @param int $targettribe Defender's tribe (1=Roman, 2=Teuton, 3=Gaul…). + * @param int $evasion Whether evasion is enabled (1) or not (0). + * @param int $maxevasion Remaining evasion charges. + * @param int $gold Defender's current gold. + * @param bool $cannotsend True when troops are already returning and can't be re-sent. + * @param int $attackType Type of incoming attack (must be > 2 to trigger evasion). + */ + private function handleEvasion( + array $data, + int $DefenderID, + array $DefenderUnit, + int $targettribe, + int $evasion, + int $maxevasion, + int $gold, + bool $cannotsend, + int $attackType + ): void { + global $database; + + if (!($evasion == 1 && $maxevasion > 0 && $gold > 1 && !$cannotsend && $attackType > 2)) { + return; + } + + $playerunit = ($targettribe - 1) * 10; + $totaltroops = 0; + $evasionUnitModifications_units = []; + $evasionUnitModifications_amounts = []; + $evasionUnitModifications_modes = []; + + for ($i = 1; $i <= 10; $i++) { + $playerunit += $i; + $data['u' . $i] = $DefenderUnit['u' . $playerunit]; + $evasionUnitModifications_units[] = $playerunit; + $evasionUnitModifications_amounts[] = $DefenderUnit['u' . $playerunit]; + $evasionUnitModifications_modes[] = 0; + $playerunit -= $i; + $totaltroops += $data['u' . $i]; + } + + $data['u11'] = $DefenderUnit['hero']; + $totaltroops += $data['u11']; + + if ($totaltroops > 0) { + $evasionUnitModifications_units[] = 'hero'; + $evasionUnitModifications_amounts[] = $DefenderUnit['hero']; + $evasionUnitModifications_modes[] = 0; + + $attackid = $database->addAttack($data['to'], $data['u1'], $data['u2'], $data['u3'], $data['u4'], $data['u5'], $data['u6'], $data['u7'], $data['u8'], $data['u9'], $data['u10'], $data['u11'], 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + $database->addMovement(4, 0, $data['to'], $attackid, microtime(true), microtime(true) + (180 / EVASION_SPEED)); + $database->updateUserField($DefenderID, ["gold", "maxevasion"], [$gold - 2, $maxevasion - 1], 1); + } + + $database->modifyUnit($data['to'], $evasionUnitModifications_units, $evasionUnitModifications_amounts, $evasionUnitModifications_modes); + } + + /** + * Process senator/chief attacks: reduce loyalty and, if it hits 0, conquer the village. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current movement row. + * @param int $type Attack type (must be 3 for chiefing). + * @param int $dead9 Chiefs killed in battle. + * @param int $traped9 Chiefs caught in traps. + * @param array $from Attacker's village info row. + * @param array $to Defender's village info row. + * @param array $toF Defender's village field row (has loyalty). + * @param int $owntribe Attacker's tribe. + * @param int $targettribe Defender's tribe. + * @param array $varray All villages of the defender. + * @param array $varray1 All villages of the attacker. + * @param array $battlepart Battle result. + * @param int $isoasis 0 = village, non-zero = oasis. + * @param int $village_destroyed Whether the village has already been destroyed. + * @param int $chief_pic Chief unit sprite ID for report strings. + * @return array{info_chief:string, chiefing_village:int, village_destroyed:int} + */ + private function handleConquest( + array $data, + int $type, + int $dead9, + int $traped9, + int $targettribe, + array $from, + array $to, + array $toF, + int $owntribe, + array $varray, + array $varray1, + array $battlepart, + int $isoasis, + int $village_destroyed, + int $chief_pic + ): array { + global $database, $units; + + $info_chief = ','; + $chiefing_village = 0; + + if (!(($data['t9'] - $dead9 - $traped9) > 0 && $isoasis == 0)) { + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + if ($type != 3) { + $info_chief = $chief_pic . ',' . rc_tok('RC_NO_REDUCE_CP_RAID'); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + $palacelevel = $database->getResourceLevel($from['wref']); + $plevel = 0; + + for ($i = 1; $i <= 40; $i++) { + if ($palacelevel['f' . $i . 't'] == 26) $plevel = $i; + elseif ($palacelevel['f' . $i . 't'] == 25) $plevel = $i; + elseif ($palacelevel['f' . $i . 't'] == 44) $plevel = $i; // Command Center (Huni) + } + + // Command Center: sloturi de expansiune ca la Palat (10 -> 1, 15 -> 2, 20 -> 3) + if ($palacelevel['f' . $plevel . 't'] == 44) { + if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; + elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1; + elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2; + else $canconquer = 3; + } elseif ($palacelevel['f' . $plevel . 't'] == 26) { + if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; + elseif ($palacelevel['f' . $plevel] < 15) $canconquer = 1; + elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 2; + else $canconquer = 3; + } elseif ($palacelevel['f' . $plevel . 't'] == 25) { + if ($palacelevel['f' . $plevel] < 10) $canconquer = 0; + elseif ($palacelevel['f' . $plevel] < 20) $canconquer = 1; + else $canconquer = 2; + } else { + $canconquer = 0; + } + + $expArray = $database->getVillageFields($from['wref'], 'exp1, exp2, exp3'); + $villexp = ($expArray['exp1'] == 0) ? 0 : (($expArray['exp2'] == 0) ? 1 : (($expArray['exp3'] == 0) ? 2 : 3)); + + $mode = CP; + $cp_mode = $GLOBALS['cp' . $mode]; + $need_cps = $cp_mode[count($varray1) + 1]; + $user_cps = $database->getUserArray($from['owner'], 1)['cp']; + + if ($user_cps < $need_cps) { + $info_chief = $chief_pic . ',' . rc_tok('RC_NOT_ENOUGH_CP'); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + if (count($varray) <= 1 || $to['capital'] == 1 || $villexp >= $canconquer) { + $info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER'); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + if ($to['owner'] == 3 && $to['name'] == 'WW Buildingplan') { + $info_chief = $chief_pic . ',' . rc_tok('RC_CANT_TAKEOVER'); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + if ($database->getFieldLevelInVillage($data['to'], '25, 26, 44')) { + $info_chief = $chief_pic . ',' . rc_tok('RC_RESIDENCE_NOT_DESTROYED'); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + // --- Reduce loyalty --- + $time = time(); + $reducedLoyaltyTotal = 0; + + for ($i = 0; $i < ($data['t9'] - $dead9 - $traped9); $i++) { + $reducedLoyalty = ($owntribe == 1) ? rand(20, 30) : rand(20, 25); + + if ($from['celebration'] > $time && $from['type'] == 2) $reducedLoyalty += 5; + if ($to['celebration'] > $time && $to['type'] == 2) $reducedLoyalty -= 5; + + $reducedLoyalty /= $battlepart['moralBonus']; + + // Bug fix: Brewery (35) is capital-only but empire-wide — its effect + // must be checked on the attacker's CAPITAL, not on $data['from'] (the + // launching village, which may not be the capital at all), and only + // while a Mead-Festival is actually active there, not just because + // the Brewery has been built (it has no permanent effect). + if ($owntribe == 2) { + $attackerCapital = $database->getVillage($from['owner'], 3); + if ($attackerCapital && (int)$attackerCapital['festival'] > $time && $this->getTypeLevel(35, $attackerCapital['wref']) > 0) { + $reducedLoyalty /= 2; + } + } + + $reducedLoyaltyTotal += $reducedLoyalty; + } + + if (($toF['loyalty'] - $reducedLoyaltyTotal) > 0) { + $info_chief = $chief_pic . ',' . rc_tok('RC_LOYALTY_LOWERED', floor($toF['loyalty']), floor($toF['loyalty'] - $reducedLoyaltyTotal)); + $database->setVillageField($data['to'], 'loyalty', ($toF['loyalty'] - $reducedLoyaltyTotal)); + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + if ($village_destroyed) { + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + // --- Village conquered --- + $villname = addslashes($database->getVillageField($data['to'], 'name')); + $artifact = reset($database->getOwnArtefactInfo($data['to'])); + + $info_chief = $chief_pic . ',' . rc_tok('RC_INHABITANTS_JOIN', $villname); + + if ($artifact['vref'] == $data['to']) { + $database->claimArtefact($data['to'], $data['to'], $database->getVillageField($data['from'], 'owner')); + } + + $database->setVillageFields($data['to'], ['loyalty', 'owner'], [0, $database->getVillageField($data['from'], 'owner')]); + + // Milestones: first WW village ever conquered, and — separately — + // first village ever conquered FROM ANOTHER PLAYER (not from + // Natars). $to is this function's own parameter (not re-fetched), + // so $to['natar']/$to['owner'] still reflect the village's state + // from BEFORE this conquest, which is exactly what we need here. + // natar==1 marks one of the 13 pre-built WW conquest targets (see + // Artifacts::createWWVillages()) — Natars' capital and artifact/ + // plan villages are natar=0, so this check cannot misfire on those. + if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { + $newOwner = $database->getVillageField($data['from'], 'owner'); + if ((int)($to['natar'] ?? 0) === 1) { + $database->recordMilestoneIfFirst('first_ww', $newOwner, $data['to']); + } elseif ((int)($to['owner'] ?? 0) !== 3) { + $database->recordMilestoneIfFirst('first_pvp_conquest', $newOwner, $data['to']); + } + } + + $database->query("DELETE FROM " . TB_PREFIX . "abdata WHERE vref = " . (int)$data['to']); + $database->addABTech($data['to']); + + $database->query("DELETE FROM " . TB_PREFIX . "tdata WHERE vref = " . (int)$data['to']); + $database->addTech($data['to']); + + $database->query("DELETE FROM " . TB_PREFIX . "enforcement WHERE `from` = " . (int)$data['to']); + $database->query("DELETE FROM " . TB_PREFIX . "route where wid = " . (int)$data['to'] . " OR `from` = " . (int)$data['to']); + + $units2reset = []; + for ($u = 1; $u <= 90; $u++) $units2reset[] = 'u' . $u . ' = 0'; + // cucerirea goleste spitalul si coada de vindecare + $database->clearHospital($data['to']); + $units2reset[] = 'u99 = 0'; + $units2reset[] = 'u99o = 0'; + $units2reset[] = 'hero = 0'; + $database->query("UPDATE " . TB_PREFIX . "units SET " . implode(',', $units2reset) . " WHERE vref = " . (int)$data['to']); + + $newLevels_fieldNames = []; + $newLevels_fieldValues = []; + + $AttackerID = $database->getVillageField($data['from'], 'owner'); + $DefenderID = $database->getVillageField($data['to'], 'owner'); + $poparray = $database->getVSumField([$AttackerID, $DefenderID], 'pop'); + $pop1 = $poparray[0]['Total']; + $pop2 = $poparray[1]['Total']; + + if ($pop1 > $pop2 && $targettribe != 5) { + $buildlevel = $database->getResourceLevel($data['to']); + for ($i = 1; $i <= 39; $i++) { + if ($buildlevel['f' . $i] != 0) { + if ($buildlevel['f' . $i . 't'] != 35 && $buildlevel['f' . $i . 't'] != 36 && $buildlevel['f' . $i . 't'] != 41) { + // Main Building (gid 15) nu poate cobori sub nivel 1: satul trebuie + // sa ramana locuibil dupa cucerire (altfel pop 0 + nicio cladire). + $minLevel = ($buildlevel['f' . $i . 't'] == 15) ? 1 : 0; + $leveldown = $buildlevel['f' . $i] - 1; + if ($leveldown < $minLevel) $leveldown = $minLevel; + $newLevels_fieldNames[] = 'f' . $i; + $newLevels_fieldValues[] = $leveldown; + // sterge tipul cladirii DOAR daca a ajuns efectiv la 0 + // (bug vechi: "!$leveldown > 0" era mereu adevarat pt leveldown==0, + // deci stergea tipul si cand cladirea cobora la nivel 1) + if ($leveldown <= 0) { + $newLevels_fieldNames[] = 'f' . $i . 't'; + $newLevels_fieldValues[] = 0; + } + } else { + $newLevels_fieldNames[] = 'f' . $i; + $newLevels_fieldValues[] = 0; + $newLevels_fieldNames[] = 'f' . $i . 't'; + $newLevels_fieldValues[] = 0; + } + } + } + if ($buildlevel['f99'] != 0) { + $newLevels_fieldNames[] = 'f99'; + $newLevels_fieldValues[] = $buildlevel['f99'] - 1; + } + } + + // destroy wall + $newLevels_fieldNames[] = 'f40'; + $newLevels_fieldValues[] = 0; + $newLevels_fieldNames[] = 'f40t'; + $newLevels_fieldValues[] = 0; + + $database->clearExpansionSlot($data['to'], 1); + + $expArray2 = $database->getVillageFields($data['from'], 'exp1, exp2, exp3'); + if ($expArray2['exp1'] == 0) { $exp = 'exp1'; } + elseif ($expArray2['exp2'] == 0) { $exp = 'exp2'; } + else { $exp = 'exp3'; } + $database->setVillageField($data['from'], $exp, $data['to']); + + $database->deleteTradeRoutesByVillage($data['to']); + $database->setVillageLevel($data['to'], $newLevels_fieldNames, $newLevels_fieldValues); + + $units->returnTroops($data['to'], 1); + + $chiefing_village = 1; + $database->reassignHero($data['to']); + $this->recountPop($data['to'], false); + + return ['info_chief' => $info_chief, 'chiefing_village' => $chiefing_village, 'village_destroyed' => $village_destroyed]; + } + + /** + * Fetch all attacks (sort_type 3, not reinforcement) that have arrived by + * $time, joined with their attack rows, ordered by arrival. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param int $time Current timestamp. + * @return array Rows of pending/completed attacks. + */ + private function fetchCompletedAttacks($time) { + global $database; + + $time = (int) $time; + $q = " + SELECT + `from`, `to`, endtime, ref, ctar1, ctar2, spy, moveid, attack_type, + t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, (SELECT oasistype FROM ".TB_PREFIX."wdata WHERE id = `to`) as oasistype + FROM + ".TB_PREFIX."movement, + ".TB_PREFIX."attacks + WHERE + ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id + AND + ".TB_PREFIX."movement.proc = 0 + AND + ".TB_PREFIX."movement.sort_type = 3 + AND + ".TB_PREFIX."attacks.attack_type != 2 + AND + endtime < $time + ORDER BY endtime ASC"; + return $database->query_return($q); + } + + /** + * Batch-preload the village / unit / tech data used by sendunitsComplete() + * so the per-attack loop hits the in-request cache instead of querying row + * by row. Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $dataarray Completed attacks (each with 'from' and 'to'). + */ + private function preloadBattleData(array $dataarray) { + global $database; + + $vilIDs = []; + foreach ($dataarray as $data) { + $vilIDs[$data['from']] = true; + $vilIDs[$data['to']] = true; + } + $vilIDs = array_keys($vilIDs); + + $database->getProfileVillages($vilIDs, 5); + $database->getUnit($vilIDs); + $database->getEnforceVillage($vilIDs, 0); + $database->getMovement(34, $vilIDs, 1); + $database->getABTech($vilIDs); + $database->getMInfo($vilIDs); + } + + private function buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop) { + global $database; + $info_spy = ""; + if ($data['spy'] == 1){ + $info_spy = "".$spy_pic.",
\"".LUMBER."\"".round($totwood)." | + \"".CLAY."\"".round($totclay)." | + \"".IRON."\"".round($totiron)." | + \"".CROP."\"".round($totcrop)."
+
\"".rc_tok('CARRY')."\"".rc_tok('RC_TOTAL_RESOURCES')." ".round($totwood+$totclay+$totiron+$totcrop)."
+ "; + }else if($data['spy'] == 2){ + if ($isoasis == 0){ + $walllevel = $database->getFieldLevelInVillage($data['to'], '31, 32, 33, 42, 43, 47, 50'); + $residencelevel = $database->getFieldLevelInVillage($data['to'], 25); + $palacelevel = $database->getFieldLevelInVillage($data['to'], 26); + $residenceimg = "\"".RESIDENCE."\""; + $palaceimg = "\"".PALACE."\""; + $crannyimg = "\"".CRANNY."\""; + $wallgids = array(1 => 31, 2 => 32, 3 => 33, 6 => 43, 7 => 42, 8 => 47, 9 => 50); + $wallgid = isset($wallgids[$targettribe]) ? $wallgids[$targettribe] : 31; + $wallimg = "\"".rc_tok('RC_WALL')."\""; + $info_spy = "".$spy_pic.","; + if($residencelevel > 0) $info_spy .= $residenceimg." ".rc_tok('RC_RESIDENCE_LEVEL')."".$residencelevel."
"; + elseif($palacelevel > 0) $info_spy .= $palaceimg." ".rc_tok('RC_PALACE_LEVEL')." ".$palacelevel."
"; + + if($walllevel > 0) $info_spy .= $wallimg." ".rc_tok('RC_WALL_LEVEL')." ".$walllevel."
"; + $info_spy .= $crannyimg." ".rc_tok('RC_CRANNY_CAPACITY')." ".$crannySpy.""; + } + else $info_spy = "".$spy_pic.", ".rc_tok('RC_NO_INFO'); + } + return $info_spy; + } + + /** + * Release prisoners trapped in the target village during a conquest attack, + * send them home (with 25% casualties), repair destroyed traps, and return + * the HTML fragment for the battle-report trap line. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row. + * @param array $from Attacker village info (getMInfo). + * @param array $to Defender village info (getMInfo). + * @param int $ownally Attacker's alliance id. + * @param int $type Attack type (3 = normal with rams/catas). + * @param int $totalsend_att Total attacking troops sent. + * @param int $totaldead_att Total attacking troops killed. + * @param int $totaltraped_att Total attacking troops trapped. + * @return string HTML fragment for the trap report line; empty if no prisoners freed. + */ + private function handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) { + global $database, $units, $bid19, $u99; + + if ($type != 3 || $totalsend_att - ($totaldead_att + $totaltraped_att) <= 0) { + return ''; + } + + $prisoners = $database->getPrisoners([$to['wref']], 0, false)[$to['wref'].'0']; + if (count($prisoners) == 0) { + return ''; + } + + $anothertroops = $mytroops = $ownDeads = $anotherDeads = 0; + $prisoners2delete = $movementType = $movementFrom = $movementTo = $movementRef = $movementTime = $movementEndtime = []; + $utime = microtime(true); + + foreach ($prisoners as $prisoner) { + $p_owner = $database->getVillageField($prisoner['from'], "owner"); + + if ($prisoner['from'] == $from['wref']) { + for ($i = 1; $i <= 11; $i++) { + $deadPrisoners = round($prisoner['t'.$i] / 4); + $mytroops += $prisoner['t'.$i]; + $ownDeads += $deadPrisoners; + $prisoner['t'.$i] -= $deadPrisoners; + } + $database->modifyAttack2( + $data['ref'], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + [$prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'], + $prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11']] + ); + $prisoners2delete[] = $prisoner['id']; + } else { + $p_alliance = $database->getUserField($p_owner, "alliance", 0); + $friendarray = $database->getAllianceAlly($p_alliance, 1); + $neutralarray = $database->getAllianceAlly($p_alliance, 2); + $friend = ($friendarray[0]['alli1'] > 0 && $friendarray[0]['alli2'] > 0 && $p_alliance > 0) + && ($friendarray[0]['alli1'] == $ownally || $friendarray[0]['alli2'] == $ownally) + && ($ownally != $p_alliance && $ownally && $p_alliance); + $neutral = ($neutralarray[0]['alli1'] > 0 && $neutralarray[0]['alli2'] > 0 && $p_alliance > 0) + && ($neutralarray[0]['alli1'] == $ownally || $neutralarray[0]['alli2'] == $ownally) + && ($ownally != $p_alliance && $ownally && $p_alliance); + + if ($p_alliance == $ownally || $friend || $neutral) { + $p_tribe = $database->getUserField($p_owner, "tribe", 0); + + for ($i = 1; $i <= 11; $i++) { + $deadPrisoners = round($prisoner['t'.$i] / 4); + if ($p_owner == $from['owner']) { + $mytroops += $prisoner['t'.$i]; + $ownDeads += $deadPrisoners; + } else { + $anothertroops += $prisoner['t'.$i]; + $anotherDeads += $deadPrisoners; + } + $prisoner['t'.$i] -= $deadPrisoners; + } + + $troopsTime = $units->getWalkingTroopsTime($prisoner['from'], $prisoner['wref'], $p_owner, $p_tribe, $prisoner, 1, 't'); + $p_time = $database->getArtifactsValueInfluence($p_owner, $prisoner['from'], 2, $troopsTime); + $p_reference = $database->addAttack( + $prisoner['from'], + $prisoner['t1'], $prisoner['t2'], $prisoner['t3'], $prisoner['t4'], $prisoner['t5'], + $prisoner['t6'], $prisoner['t7'], $prisoner['t8'], $prisoner['t9'], $prisoner['t10'], $prisoner['t11'], + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ); + $movementType[] = 4; + $movementFrom[] = $prisoner['wref']; + $movementTo[] = $prisoner['from']; + $movementRef[] = $p_reference; + $movementTime[] = $utime; + $movementEndtime[] = $p_time + $utime; + $prisoners2delete[] = $prisoner['id']; + } + } + } + + if (count($movementType)) { + $database->addMovement($movementType, $movementFrom, $movementTo, $movementRef, $movementTime, $movementEndtime); + } + $database->deletePrisoners($prisoners2delete); + + $ownDeadsText = ($ownAlive = $mytroops - $ownDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$ownAlive) : ""; + $anotherDeadsText = ($anotherAlive = $anothertroops - $anotherDeads) > 0 ? " ".rc_tok('RC_OF_WHICH_SAVED',$anotherAlive) : ""; + + $database->addGeneralAttack($ownDeads + $anotherDeads); + + $newtraps = round(($mytroops + $anothertroops) / 3); + $database->modifyUnit($data['to'], ['99', '99o'], [$mytroops + $anothertroops, $mytroops + $anothertroops], [0, 0]); + if ($newtraps > 0) { + $repairDuration = $database->getArtifactsValueInfluence( + $to['owner'], $to['wref'], 5, + round(($bid19[max($this->getTypeLevel(36, $to['wref']), 1)]['attri'] / 100) * $u99['time'] / SPEED) + ); + $database->trainUnit($to['wref'], 99, $newtraps, $u99['pop'], $repairDuration, 0); + } + + $trapper_pic = "\"".rc_tok('RC_TRAP')."\""; + $p_username = $database->getUserField($from['owner'], "username", 0); + + if ($mytroops > 0 && $anothertroops > 0) { + return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText." ".rc_tok('RC_AND_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText."."; + } elseif ($mytroops > 0) { + return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FROM_HIS_TROOPS', $mytroops).$ownDeadsText."."; + } elseif ($anothertroops > 0) { + return $trapper_pic." ".$p_username." ".rc_tok('RC_FREED_FRIENDLY_TROOPS', $anothertroops).$anotherDeadsText."."; + } + return ''; + } + + /** + * Assemble the battle-report data string ($data2) and the all-attacker-dead + * fallback ($data_fail) from pre-computed battle results. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param bool $scout True when this is a scouting mission. + * @param array $from Attacker village info (getMInfo). + * @param array $to Defender village info (getMInfo). + * @param int $owntribe Attacker tribe id. + * @param int $targettribe Defender tribe id. + * @param string $unitssend_att CSV of units sent by attacker. + * @param string $unitsdead_att CSV of attacker units killed. + * @param array $steal [wood, clay, iron, crop] looted amounts. + * @param array $battlepart calculateBattle() result. + * @param array $unitssend_def CSV of defender units sent (indexed 0–5). + * @param array $unitsdead_def CSV of defender units dead (indexed 0–5). + * @param array $unitssend_deff Masked defender units for data_fail (indexed 0–5). + * @param array $unitsdead_deff Masked dead units for data_fail (indexed 0–5). + * @param int $rom,$ger,$gal,$nat,$natar Tribe presence flags. + * @param string $DefenderHeroesTot CSV of defender heroes totals by tribe. + * @param string $DefenderHeroesDead CSV of defender heroes dead by tribe. + * @param string $info_ram Ram result fragment. + * @param string $info_cat Catapult result fragment (internally extended if village destroyed). + * @param string $info_chief Chief/senator result fragment. + * @param string $info_spy Scout report fragment (scout missions only). + * @param string $info_trap Prisoner-release fragment (from handlePrisoners). + * @param string $info_hero Hero result fragment. + * @param string $info_troop "None of your soldiers returned" fragment. + * @param array $data Current attack row (for t11). + * @param int $dead11 Hero casualties. + * @param int $herosend_def Defender hero count. + * @param int $deadhero Defender hero casualties. + * @param string $unitstraped_att CSV of attacker units trapped. + * @param int $village_destroyed 1 if the village was destroyed. + * @param int $can_destroy 1 if the village can be destroyed. + * @param int $catp_pic Catapult unit-pic id for the HTML fragment. + * @return array ['data2' => string, 'data_fail' => string] + */ + private function buildCombatReport( + $scout, $from, $to, $owntribe, $targettribe, + $unitssend_att, $unitsdead_att, $steal, $battlepart, + $unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff, + $rom, $ger, $gal, $nat, $natar, + $hun, $egy, $spa, $vik, + $DefenderHeroesTot, $DefenderHeroesDead, + $info_ram, $info_cat, $info_chief, $info_spy, + $info_trap, $info_hero, $info_troop, + $data, $dead11, $herosend_def, $deadhero, $unitstraped_att, + $village_destroyed, $can_destroy, $catp_pic + ) { + if (!empty($scout)) { + $data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att + .',0,0,0,0,0,'.$to['owner'].','.$to['wref'].','.addslashes($to['name']) + .',,,,'.$targettribe + .','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom + .','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger + .','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal + .','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat + .','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar + .','.$unitssend_def[5].','.$unitsdead_def[5] + .','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6] + .','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7] + .','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8] + .','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9] + .','.$DefenderHeroesTot.','.$DefenderHeroesDead + .','.$info_ram.','.$info_cat.','.$info_chief.','.$info_spy + .','.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero + .',,'.$unitstraped_att; + } else { + if (isset($village_destroyed) && $village_destroyed == 1 && $can_destroy == 1) { + if (strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) { + $info_cat .= "".INFORMATION."" + . "\"".rc_tok('RC_CATAPULT')."\"" + . " ".rc_tok('RC_VILLAGE_DESTROYED').""; + } + } + $data2 = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att + .','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty'] + .','.$to['owner'].','.$to['wref'].','.addslashes($to['name']) + .',,,,'.$targettribe + .','.$unitssend_def[0].','.$unitsdead_def[0].','.$rom + .','.$unitssend_def[1].','.$unitsdead_def[1].','.$ger + .','.$unitssend_def[2].','.$unitsdead_def[2].','.$gal + .','.$unitssend_def[3].','.$unitsdead_def[3].','.$nat + .','.$unitssend_def[4].','.$unitsdead_def[4].','.$natar + .','.$unitssend_def[5].','.$unitsdead_def[5] + .','.$hun.','.$unitssend_def[6].','.$unitsdead_def[6] + .','.$egy.','.$unitssend_def[7].','.$unitsdead_def[7] + .','.$spa.','.$unitssend_def[8].','.$unitsdead_def[8] + .','.$vik.','.$unitssend_def[9].','.$unitsdead_def[9] + .','.$DefenderHeroesTot.','.$DefenderHeroesDead + .','.$info_ram.','.$info_cat.','.$info_chief.','.(isset($info_spy) ? $info_spy : '') + .',,'.$data['t11'].','.$dead11.','.$herosend_def.','.$deadhero + .','.$unitstraped_att; + + $data2 .= ','.($info_trap !== '' ? addslashes($info_trap) : '').',,'.$info_troop.','.$info_hero; + } + + $data_fail = ''.$from['owner'].','.$from['wref'].','.$owntribe.','.$unitssend_att.','.$unitsdead_att + .','.$steal[0].','.$steal[1].','.$steal[2].','.$steal[3].','.$battlepart['bounty'] + .','.$to['owner'].','.$to['wref'].','.addslashes($to['name']) + .',,,,'.$targettribe + .','.$unitssend_deff[0].','.$unitsdead_deff[0].','.$rom + .','.$unitssend_deff[1].','.$unitsdead_deff[1].','.$ger + .','.$unitssend_deff[2].','.$unitsdead_deff[2].','.$gal + .','.$unitssend_deff[3].','.$unitsdead_deff[3].','.$nat + .','.$unitssend_deff[4].','.$unitsdead_deff[4].','.$natar + .','.$unitssend_deff[5].','.$unitsdead_deff[5] + .','.$hun.','.$unitssend_deff[6].','.$unitsdead_deff[6] + .','.$egy.','.$unitssend_deff[7].','.$unitsdead_deff[7] + .','.$spa.','.$unitssend_deff[8].','.$unitsdead_deff[8] + .','.$vik.','.$unitssend_deff[9].','.$unitsdead_deff[9] + .','.$DefenderHeroesTot.','.$DefenderHeroesDead + .',,,'.$data['t11'].','.$dead11.','.$unitstraped_att + .',,'.$info_ram.','.$info_cat.','.$info_chief.','.$info_troop.','.$info_hero; + + return ['data2' => $data2, 'data_fail' => $data_fail]; + } + + /** + * Distribute the battle bounty across the resources actually available in + * the target (after cranny protection) and return how much of each is taken. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $available [wood, clay, iron, crop] lootable amounts. + * @param int $bounty Total carry capacity / bounty from the battle. + * @return array [wood, clay, iron, crop] amounts actually stolen. + */ + private function applyBounty(array $available, $bounty) { + $avtotal = $available; + $steal = [0, 0, 0, 0]; + $btotal = $bounty; + $bmod = 0; + + for ($i = 0; $i < 5; $i++) { + for ($j = 0; $j < 4; $j++) { + if (isset($avtotal[$j])) { + if ($avtotal[$j] < 1) unset($avtotal[$j]); + } + } + + // No resources left to take + if (empty($avtotal) || ($btotal < 1 && $bmod < 1)) break; + + if ($btotal < 1) { + while ($bmod) { + // random select + $rs = array_rand($avtotal); + if (isset($avtotal[$rs])) { + $avtotal[$rs] -= 1; + $steal[$rs] += 1; + $bmod -= 1; + } + } + } + + // handle unbalanced amounts. + $btotal += $bmod; + $bmod = $btotal % count($avtotal); + $btotal -= $bmod; + $bsplit = $btotal / count($avtotal); + + $max_steal = (min($avtotal) < $bsplit) ? min($avtotal) : $bsplit; + + for ($j = 0; $j < 4; $j++) { + if (isset($avtotal[$j])) { + $avtotal[$j] -= $max_steal; + $steal[$j] += $max_steal; + $btotal -= $max_steal; + } + } + } + + return $steal; + } + + /** + * Apply battle casualties to the defender's own (in-village) troops, persist + * the losses, and return the per-unit dead map used later for points/reports. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses 'to'). + * @param int $targettribe Defender tribe (1-5). + * @param array $battlepart Battle result (index 2 = defender kill ratio, + * 'deadherodef' = defender hero losses). + * @return array Map of dead own troops: keys are the unit index (int) plus 'hero'. + */ + private function applyOwnDefenceCasualties($data, $targettribe, $battlepart) { + global $database; + + $owndead = []; + $unitlist = $database->getUnit($data['to'], false); + $start = ($targettribe - 1) * 10 + 1; + $end = ($targettribe * 10); + + $unitModifications_units = []; + $unitModifications_amounts = []; + $unitModifications_modes = []; + for ($i = $start; $i <= $end; $i++) { + if ($unitlist) { + $owndead[$i] = round($battlepart[2] * $unitlist['u'.$i]); + $unitModifications_units[] = $i; + $unitModifications_amounts[] = $owndead[$i]; + $unitModifications_modes[] = 0; + } + } + + $owndead['hero'] = 0; + + if ($unitlist) { + $owndead['hero'] = (isset($battlepart['deadherodef']) ? $battlepart['deadherodef'] : ''); + + $unitModifications_units[] = 'hero'; + $unitModifications_amounts[] = $owndead['hero']; + $unitModifications_modes[] = 0; + } + + // modify units in DB + $database->modifyUnit($data['to'], $unitModifications_units, $unitModifications_amounts, $unitModifications_modes); + + // Spital: o parte din mortii proprii (aparare) devin raniti - doar unitatile de lupta X1-X8 + $woundedMap = []; + for($wi = $start; $wi <= $start + 7; $wi++) { + if(!empty($owndead[$wi])) $woundedMap[$wi] = $owndead[$wi]; + } + $this->applyHospitalWounded($data['to'], $woundedMap); + + return $owndead; + } + + /** + * Apply battle casualties to the reinforcement (foreign) troops stationed in + * the defender's village: compute each reinforcement's dead units/hero, persist + * them (modifyEnforce), notify the reinforcing players, and delete fully-wiped + * reinforcements. Accumulates the dead counts into $alldead and the dead heroes + * into $DefenderHeroesDeadArray (both passed by reference), and reports which + * tribes are present among the reinforcements. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses 'to'). + * @param array $battlepart Battle result (index 2 = defender kill ratio, + * 'deadheroref' = dead hero per enforce id). + * @param array $from Attacker village info (uses 'wref'). + * @param array $to Defender village info (uses 'name','wref'). + * @param int $ownally Attacker's alliance id (for notices). + * @param int $AttackArrivalTime Timestamp stamped on the reinforcement notices. + * @param mixed $scout Scout marker; notices/deletion only fire when empty. + * @param array $alldead [in/out] Per-unit dead reinforcements (+ 'hero'), accumulated. + * @param array $DefenderHeroesDeadArray [in/out] Dead reinforcement heroes per tribe, accumulated. + * @return array Tribe-presence flags among the reinforcements: keys rom,ger,gal,nat,natar (0/1). + */ + private function applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, array &$alldead, array &$DefenderHeroesDeadArray) { + global $database; + + $rom = $ger = $gal = $nat = $natar = $hun = $egy = $spa = $vik = 0; + + //kill other defence in village + // ... once again, units could have changed, so we need to reselect + $enforcementarray3 = $database->getEnforceVillage($data['to'], 0); + foreach ($enforcementarray3 as $enforce) { + $life = ''; $notlife = ''; $wrong = false; + if ($enforce['from'] != 0) { + $tribe = $database->getUserArray($database->getVillageField($enforce['from'], "owner"), 1)["tribe"]; + } else { + $tribe = 4; + } + + $start = ($tribe - 1) * 10 + 1; + $end = ($tribe * 10); + unset($dead); + + switch ($tribe) { + case 1: $rom = 1; break; + case 2: $ger = 1; break; + case 3: $gal = 1; break; + case 4: $nat = 1; break; + case 6: $hun = 1; break; + case 7: $egy = 1; break; + case 8: $spa = 1; break; + case 9: $vik = 1; break; + case 5: + default: $natar = 1; break; + } + + $enforceModificationsById = []; + for ($i = $start; $i <= $end; $i++) { + if ($enforce['u'.$i] > '0') { + if (!isset($enforceModificationsById[$enforce['id']])) { + $enforceModificationsById[$enforce['id']] = [ + 'units' => [], + 'amounts' => [], + 'modes' => [] + ]; + } + $enforceModificationsById[$enforce['id']]['units'][] = $i; + $enforceModificationsById[$enforce['id']]['amounts'][] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); + $enforceModificationsById[$enforce['id']]['modes'][] = 0; + $dead[$i] = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); + $checkpoint = (isset($battlepart[2]) ? round($battlepart[2]*$enforce['u'.$i]) : 0); + + if (!isset($enforce['u'.$i])) $enforce['u'.$i] = 0; + + $alldead[$i] += $dead[$i]; + + $wrong = $checkpoint != $enforce['u'.$i]; + } else { + $dead[$i] = 0; + } + } + + if ($enforce['hero'] > 0) { + $enforceModificationsById[$enforce['id']]['units'][] = 'hero'; + $enforceModificationsById[$enforce['id']]['amounts'][] = $battlepart['deadheroref'][$enforce['id']]; + $enforceModificationsById[$enforce['id']]['modes'][] = 0; + + $dead['hero'] = $battlepart['deadheroref'][$enforce['id']]; + $alldead['hero'] += $dead['hero']; + $wrong = $dead['hero'] != $enforce['hero']; + + //Collecting information for the report + $reinfTribe = ($enforce['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce['from'], "owner"), "tribe", 0); + $DefenderHeroesDeadArray[$reinfTribe] += $dead['hero']; + } + + // modify enforce in DB + foreach ($enforceModificationsById as $enforceId => $enforceArray) { + $database->modifyEnforce($enforceId, $enforceArray['units'], $enforceArray['amounts'], $enforceArray['modes']); + } + + $notlife = ''.$dead[$start].','.$dead[$start+1].','.$dead[$start+2].','.$dead[$start+3].','.$dead[$start+4].','.$dead[$start+5].','.$dead[$start+6].','.$dead[$start+7].','.$dead[$start+8].','.$dead[$start+9].''; + $notlife1 = $dead[$start]+$dead[$start+1]+$dead[$start+2]+$dead[$start+3]+$dead[$start+4]+$dead[$start+5]+$dead[$start+6]+$dead[$start+7]+$dead[$start+8]+$dead[$start+9]; + $life = ''.$enforce['u'.$start.''].','.$enforce['u'.($start+1).''].','.$enforce['u'.($start+2).''].','.$enforce['u'.($start+3).''].','.$enforce['u'.($start+4).''].','.$enforce['u'.($start+5).''].','.$enforce['u'.($start+6).''].','.$enforce['u'.($start+7).''].','.$enforce['u'.($start+8).''].','.$enforce['u'.($start+9).''].''; + $life1 = $enforce['u'.$start.'']+$enforce['u'.($start+1).'']+$enforce['u'.($start+2).'']+$enforce['u'.($start+3).'']+$enforce['u'.($start+4).'']+$enforce['u'.($start+5).'']+$enforce['u'.($start+6).'']+$enforce['u'.($start+7).'']+$enforce['u'.($start+8).'']+$enforce['u'.($start+9).'']; + $lifehero = (isset($enforce['hero']) ? $enforce['hero'] : 0); + $notlifehero = (isset($dead['hero']) ? $dead['hero'] : 0); + $totallife = (isset($enforce['hero']) ? $enforce['hero'] : 0)+$life1; + $totalnotlife = (isset($dead['hero']) ? $dead['hero'] : 0)+$notlife1; + //NEED TO SEND A RAPPORTAGE!!! + $data2 = ''.$database->getVillageField($enforce['from'], "owner").','.$to['wref'].','.addslashes($to['name']).','.$tribe.','.$life.','.$notlife.','.$lifehero.','.$notlifehero.','.$enforce['from'].''; + if (empty($scout)) { + if ($totalnotlife == 0) { + $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 15, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); + } else if ($totallife > $totalnotlife) { + $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 16, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); + } else { + $database->addNotice($database->getVillageField($enforce['from'], "owner"), $from['wref'], $ownally, 17, 'Reinforcement in '.addslashes($to['name']).' was attacked', $data2, $AttackArrivalTime); + } + //delete reinf sting when its killed all. + if (!$wrong) $database->deleteReinf($enforce['id']); + } + } + + return ['rom' => $rom, 'ger' => $ger, 'gal' => $gal, 'nat' => $nat, 'natar' => $natar, 'hun' => $hun, 'egy' => $egy, 'spa' => $spa, 'vik' => $vik]; + } + + /** + * Collect the defender-side report data BEFORE casualties are applied: reset and + * rebuild the reinforcement unit totals ($DefenderEnf) and the per-tribe hero + * totals from a fresh re-select, fold reinforcement heroes into the defender's + * hero count, build the "units sent" report rows (own troops + reinforcements, + * plus their masked variants), and (re)initialise the per-tribe dead-hero + * accumulator to zero. Returns everything the caller needs downstream. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses 'to'). + * @param int $targettribe Defender tribe (1-5). + * @param array $Defender Defender unit array (own troops + 'hero'); passed by value, + * its 'hero' is folded with reinforcement heroes and returned + * via the 'defenderHero' key. + * @return array Report bundle: DefenderEnf, DefenderHeroesTotArray, + * DefenderHeroesDeadArray, unitssend_def, unitssend_deff, + * totalsend_alldef, defenderHero. + */ + private function collectReinforcementReport($data, $targettribe, $Defender) { + global $database; + + $DefenderEnf = []; + $DefenderHeroesTotArray = []; + $DefenderHeroesDeadArray = []; + $unitssend_def = []; + $unitssend_deff = []; + + //Resetting the enforcement arrays + for ($i = 1; $i <= 90; $i++) { + $DefenderEnf['u'.$i] = 0; + if ($i <= 9) { + $DefenderHeroesTotArray[$i] = 0; + $DefenderHeroesDeadArray[$i] = 0; + } + } + + // our reinforcements count could have changed at this point, thus the re-select + $enforcementarray2 = $database->getEnforceVillage($data['to'], 0); + if (count($enforcementarray2) > 0) { + foreach ($enforcementarray2 as $enforce2) { + for ($i = 1; $i <= 90; $i++) { + $DefenderEnf['u'.$i] += $enforce2['u'.$i]; + } + + //Divide heroes by tribe + if ($enforce2['hero'] > 0) { + $reinfTribe = ($enforce2['from'] == 0) ? 4 : $database->getUserField($database->getVillageField($enforce2['from'], "owner"), "tribe", 0); + $DefenderHeroesTotArray[$reinfTribe] += $enforce2['hero']; + $Defender['hero'] += $enforce2['hero']; + } + } + } + + $totalsend_alldef = 0; + + //Own troops + $ownTroops = array_slice($Defender, ($targettribe - 1) * 10 + 1, 10); + $totalsend_alldef = array_sum($ownTroops); + + //Collecting informations for the report + $unitssend_def[0] = implode(",", $ownTroops); + $unitssend_deff[0] = '?,?,?,?,?,?,?,?,?,?,'; + + for ($i = 1; $i <= 9; $i++) { + //Reinforcements + $reinfTroops = array_slice($DefenderEnf, ($i - 1) * 10, 10); + $totalsend_alldef += array_sum($reinfTroops); + + //Collecting informations for the report + $unitssend_def[$i] = implode(",", $reinfTroops); + $unitssend_deff[$i] = $unitssend_deff[0]; + } + $totalsend_alldef += $Defender['hero']; + + return [ + 'DefenderEnf' => $DefenderEnf, + 'DefenderHeroesTotArray' => $DefenderHeroesTotArray, + 'DefenderHeroesDeadArray' => $DefenderHeroesDeadArray, + 'unitssend_def' => $unitssend_def, + 'unitssend_deff' => $unitssend_deff, + 'totalsend_alldef' => $totalsend_alldef, + 'defenderHero' => $Defender['hero'], + ]; + } + + /** + * Compute attacker/defender total populations and fetch their village lists. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $to Defender village info (getMInfo/getOMInfo). + * @param array $from Attacker village info (getMInfo). + * @param int $isoasis Whether the target is an oasis (defender pop counts only for villages). + * @param int $defpop Initial defender population (accumulated onto). + * @param int $attpop Initial attacker population (accumulated onto). + * @return array{varray:array,varray1:array,defpop:int,attpop:int} + */ + private function calculatePopulations($to, $from, $isoasis, $defpop, $attpop) { + global $database; + + $varray = $database->getProfileVillages($to['owner'], 0, false); + + if ($to['owner'] == $from['owner']) $varray1 = $varray; + else $varray1 = $database->getProfileVillages($from['owner'], 0, false); + + // total population of the defender + if($isoasis == 0){ + foreach($varray as $defenderVillage) $defpop += $defenderVillage['pop']; + } + + // total population of the attacker + foreach($varray1 as $attackerVillage) $attpop += $attackerVillage['pop']; + + return [ + 'varray' => $varray, + 'varray1' => $varray1, + 'defpop' => $defpop, + 'attpop' => $attpop, + ]; + } + + /** + * Resolve how many incoming attacker units get caught in the defender's + * traps (Gaul trapper / Natar capital), update the trap counters and the + * prisoners table, and subtract the trapped troops from the attacking army. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row. + * @param array $Defender Defender unit counts (reads u99 / u99o). + * @param array $Attacker Attacker units (u + uhero). + * @param bool $NatarCapital True when the target is a Natar capital (all troops trapped). + * @param int $scout 1 for a scouting attack (no trapping). + * @param int $start First unit index of the attacker tribe. + * @param int $end Last unit index of the attacker tribe. + * @return array { + * traped: int[] trapped count per unit slot (1..11), + * totaltraped_att: int total trapped troops, + * Attacker: array attacker units with trapped troops removed + * } + */ + private function calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end) { + global $database; + + $traped = []; + for($i = 1; $i <= 11; $i++) $traped[$i] = 0; + $totaltraped_att = 0; + + //impossible to attack or scout NATAR Capital Village + if ($NatarCapital){ + for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i]; + } + elseif(empty($scout)) + { + $traps = max($Defender['u99'] - $Defender['u99o'], 0); + + $totalTroops = 0; + for($i = 1; $i <= 11; $i++) $totalTroops += $data['t'.$i]; + + if($traps >= $totalTroops){ + for($i = 1; $i <= 11; $i++) $traped[$i] = $data['t'.$i]; + } + else if($totalTroops > 0) + { + $multiplier = $traps / $totalTroops; + + //The hero is excluded, because it can be only trapped if traps > totalTroops + for($i = 1; $i <= 10; $i++){ + $trappedUnits = intval($data['t'.$i] * $multiplier); + $traped[$i] = $trappedUnits; + $traps -= $trappedUnits; + } + + while($traps > 0){ + //There are some traps left, let's distribute them + for($i = 1; $i <= 10 && $traps > 0; $i++){ + if($data['t'.$i] != 0){ + $traped[$i]++; + $traps--; + } + } + } + } + else { + for($i = 1; $i <= 11; $i++) $traped[$i] = 0; + } + } + + if(empty($scout) || $NatarCapital){ + for ($i = 1; $i <= 11; $i++){ + $totaltraped_att += $traped[$i]; + } + + $database->modifyUnit($data['to'], ["99o"], [$totaltraped_att], [1]); + + for($i = $start; $i <= $end; $i++){ + $j = $i-$start+1; + $Attacker['u'.$i] -= $traped[$j]; + } + $Attacker['uhero'] -= $traped[11]; + + if($totaltraped_att > 0){ + $prisoners2 = $database->getPrisoners2($data['to'], $data['from'], false); + if(empty($prisoners2)){ + $database->addPrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]); + }else{ + $database->updatePrisoners($data['to'],$data['from'],$traped[1],$traped[2],$traped[3],$traped[4],$traped[5],$traped[6],$traped[7],$traped[8],$traped[9],$traped[10],$traped[11]); + } + } + } + + return [ + 'traped' => $traped, + 'totaltraped_att' => $totaltraped_att, + 'Attacker' => $Attacker, + ]; + } + + /** + * Apply ram damage to the defender's wall during a normal attack (type 3): + * compute the new wall level, update it in the database (recounting the + * village population if the wall is destroyed), build the report fragment, + * and recalculate the battle when the wall level changed. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses t7, to). + * @param int $walllevel Current wall level. + * @param int $wallid Wall building field id (40). + * @param int $ram_pic Ram unit id (for the report fragment). + * @param array $battlepart Battle result (provides the ram damage factors). + * @param array $ctx Battle context for the post-damage recalculation + * (Attacker, Defender, tribes, populations, AB tech, ...). + * @return array { battlepart: array (possibly recalculated), info_ram: string } + */ + private function applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, array $ctx) { + global $database, $battle; + + $info_ram = "".$ram_pic.",".rc_tok('RC_NO_WALL'); + + if($walllevel > 0){ + $ramsDamage = $battle->calculateCatapultsDamage($data['t7'], + $battlepart['rams']['upgrades'], + $battlepart['rams']['durability'], + $battlepart['rams']['attackDefenseRatio'], + $battlepart['rams']['strongerBuildings'], + $battlepart['rams']['moraleBonus']); + $newLevel = $battle->calculateNewBuildingLevel($walllevel, $ramsDamage); + + if ($newLevel == 0){ + $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DESTROYED'); + $database->setVillageLevel($data['to'], ["f".$wallid, "f".$wallid."t"], [0, 0]); + $this->recountPop($data['to']); + }elseif ($newLevel == $walllevel){ + $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_NOT_DAMAGED'); + }else{ + $info_ram = "".$ram_pic.",".rc_tok('RC_WALL_DAMAGED_FROM_TO', $walllevel, $newLevel); + $database->setVillageLevel($data['to'],"f".$wallid."",$newLevel); + } + + //If the wall got damaged/destroyed during the attack + //we need to recalculate the whole battle + if($newLevel != $walllevel){ + $battlepart = $battle->calculateBattle($ctx['Attacker'], $ctx['Defender'], $newLevel, $ctx['att_tribe'], $ctx['def_tribe'], $ctx['residence'], $ctx['attpop'], $ctx['defpop'], $ctx['type'], $ctx['def_ab'], $ctx['att_ab1'], $ctx['att_ab2'], $ctx['att_ab3'], $ctx['att_ab4'], $ctx['att_ab5'], $ctx['att_ab6'], $ctx['att_ab7'], $ctx['att_ab8'], $ctx['tblevel'], $ctx['stonemason'], $newLevel, 0, 0, 0, $ctx['AttackerID'], $ctx['DefenderID'], $ctx['AttackerWref'], $ctx['DefenderWref'], $ctx['conqureby'], $ctx['enforcementarray']); + } + } + + return ['battlepart' => $battlepart, 'info_ram' => $info_ram]; + } + + /** + * Build the attacking army for the current attack: per-slot unit counts + * (u + uhero) plus the catapult / ram / chief / scout unit ids + * used in the battle report. Oasis attacks include the Nature siege/chief + * slots (37/38/39). Pure behaviour-preserving extraction (issue #155). + * + * @param array $attackRow The attack row (t1..t11, tribe-relative). + * @param int $owntribe Attacker tribe (1..5). + * @param int $isoasis 0 for a village target, otherwise an oasis. + * @return array { Attacker, start, end, u, catp_pic, ram_pic, chief_pic, spy_pic, hero_pic } + */ + private function buildAttackerUnits(array $attackRow, $owntribe, $isoasis) { + $Attacker = []; + $start = ($owntribe - 1) * 10 + 1; + $end = $owntribe * 10; + $u = ($owntribe - 1) * 10; + + if ($isoasis == 0) { + $catapult = [8, 18, 28, 48, 58, 68, 78, 88]; + $ram = [7, 17, 27, 47, 57, 67, 77, 87]; + $chief = [9, 19, 29, 49, 59, 69, 79, 89]; + } else { + $catapult = [8, 18, 28, 38, 48, 58, 68, 78, 88]; + $ram = [7, 17, 27, 37, 47, 57, 67, 77, 87]; + $chief = [9, 19, 29, 39, 49, 59, 69, 79, 89]; + } + $spys = [4, 14, 23, 44, 52, 64, 74, 82]; + + $catp_pic = $ram_pic = $chief_pic = $spy_pic = null; + + for ($i = $start; $i <= $end; $i++) { + $y = $i - $u; + $Attacker['u'.$i] = $attackRow['t'.$y]; + //there are catas + if (in_array($i, $catapult)) $catp_pic = $i; + if (in_array($i, $ram)) $ram_pic = $i; + if (in_array($i, $chief)) $chief_pic = $i; + if (in_array($i, $spys)) $spy_pic = $i; + } + $Attacker['uhero'] = $attackRow['t11']; + + return [ + 'Attacker' => $Attacker, + 'start' => $start, + 'end' => $end, + 'u' => $u, + 'catp_pic' => $catp_pic, + 'ram_pic' => $ram_pic, + 'chief_pic' => $chief_pic, + 'spy_pic' => $spy_pic, + 'hero_pic' => 'hero', + ]; + } + + /** + * Gather the defender's units for the current attack: the village's own + * troops (normalised to non-negative ints) plus the aggregated reinforcement + * totals, and the raw reinforcement rows. Used for both village and oasis + * targets. Pure behaviour-preserving extraction (issue #155). + * + * @param int $wref Defending village/oasis wref. + * @return array { Defender, enforDefender, enforcementarray } + */ + private function buildDefenderUnits($wref) { + global $database; + + $enforDefender = []; + $Defender = $database->getUnit($wref, false); + $enforcementarray = $database->getEnforceVillage($wref, 0); + + if (count($enforcementarray) > 0) { + foreach ($enforcementarray as $enforce) { + for ($i = 1; $i <= 90; $i++) { + if (!isset($enforDefender['u'.$i])) { + $enforDefender['u'.$i] = 0; + } + $enforDefender['u'.$i] += $enforce['u'.$i]; + } + if (!isset($enforDefender['hero'])) { + $enforDefender['hero'] = 0; + } + $enforDefender['hero'] += $enforce['hero']; + } + } + + for ($i = 1; $i <= 90; $i++) { + if (!isset($Defender['u'.$i]) || empty($Defender['u'.$i]) || $Defender['u'.$i] < 0) { + $Defender['u'.$i] = 0; + } + } + + if (!isset($Defender['hero']) || empty($Defender['hero']) || $Defender['hero'] < 0) { + $Defender['hero'] = 0; + } + + return [ + 'Defender' => $Defender, + 'enforDefender' => $enforDefender, + 'enforcementarray' => $enforcementarray, + ]; + } + + /** + * Resolve the per-attack context that does not depend on the target type: + * the attacker village/owner data (tribe, alliance), the war references and + * a few base flags. Read-only — no DB writes. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row. + * @return array {isoasis, AttackArrivalTime, AttackerWref, DefenderWref, + * NatarCapital, AttackerID, owntribe, ownally, from, fromF} + */ + private function resolveAttackContext($data) { + global $database; + + $owner = $this->getCachedUser($database->getVillageField($data['from'], "owner"), 1); + + return [ + 'isoasis' => $data['oasistype'], + 'AttackArrivalTime' => $data['endtime'], + 'AttackerWref' => $data['from'], + 'DefenderWref' => $data['to'], + 'NatarCapital' => false, + 'AttackerID' => $owner['id'], + 'owntribe' => $owner['tribe'], + 'ownally' => $owner['alliance'], + 'from' => $database->getMInfo($data['from']), + 'fromF' => $database->getVillage($data['from']), + ]; + } + + /** + * Resolve the defender target context for a VILLAGE attack: target owner + * (tribe/alliance), map info, conquest flag, evasion inputs and the battle + * environment (wall, armory/blacksmith tech, residence, siege masonry). + * Read-only — no DB writes. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row. + * @param array $dataarray Full batch of completed attacks. + * @param int $data_num Index of $data within $dataarray. + * @param int $owntribe Attacker tribe. + * @return array Target + battle-environment context. + */ + private function resolveVillageTarget($data, $dataarray, $data_num, $owntribe) { + global $database, $units; + + $DefenderUserData = $this->getCachedUser($database->getVillageField($data['to'],"owner"),1); + $DefenderID = $DefenderUserData["id"]; + $targettribe = $DefenderUserData["tribe"]; + $targetally = $DefenderUserData["alliance"]; + $to = $database->getMInfo($data['to']); + $toF = $database->getVillage($data['to']); + $conqureby = 0; + $NatarCapital = ($toF['owner'] == 3 && $toF['capital'] == 1); + if(!isset($to['name']) || empty($to['name'])) $to['name'] = "[?]"; + + $DefenderUnit = $database->getUnit($data['to']); + $evasion = $toF["evasion"]; + $maxevasion = $DefenderUserData["maxevasion"]; + $gold = $DefenderUserData["gold"]; + $cannotsend = false; + + $movements = $database->getMovement(34, $data['to'], 1); + for($y = 0; $y < count($movements); $y++){ + if(property_exists($units, $y)){ + $returntime = $units->$y['endtime'] - time(); + if($units->$y['sort_type'] == 4 && $units->$y['from'] != 0 && $returntime <= 10){ + $cannotsend = true; + } + } + } + + //need to set these variables. + $def_wall = $database->getFieldLevel($data['to'], 40, false); + $att_tribe = $owntribe; + $def_tribe = $targettribe; + $attpop = $defpop = $residence = 0; + $def_ab = []; + + //get level of palace or residence + $residence = $database->getFieldLevelInVillage($data['to'], '25, 26', false); + + //type of attack + $type = $dataarray[$data_num]['attack_type']; + $scout = ($type == 1) ? 1 : 0; + + $ud = ($def_tribe - 1) * 10; + $att_ab = $database->getABTech($data['from']); // Blacksmith level + $att_ab1 = $att_ab['b1']; + $att_ab2 = $att_ab['b2']; + $att_ab3 = $att_ab['b3']; + $att_ab4 = $att_ab['b4']; + $att_ab5 = $att_ab['b5']; + $att_ab6 = $att_ab['b6']; + $att_ab7 = $att_ab['b7']; + $att_ab8 = $att_ab['b8']; + $armory = $database->getABTech($data['to']); // Armory level + $def_ab[$ud + 1] = $armory['a1']; + $def_ab[$ud + 2] = $armory['a2']; + $def_ab[$ud + 3] = $armory['a3']; + $def_ab[$ud + 4] = $armory['a4']; + $def_ab[$ud + 5] = $armory['a5']; + $def_ab[$ud + 6] = $armory['a6']; + $def_ab[$ud + 7] = $armory['a7']; + $def_ab[$ud + 8] = $armory['a8']; + + //rams attack + $walllevel = 0; + $wallid = 0; + if (($data['t7']) > 0 && $type == 3) { + $basearraywall = $to; + if (($walllevel = $database->getFieldLevel($basearraywall['wref'], 40, false)) > 0){ + $wallid = 40; + } + } + + $tblevel = 1; + $stonemason = $database->getFieldLevelInVillage($data['to'], 34); + + return [ + 'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally, + 'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby, 'NatarCapital' => $NatarCapital, + 'DefenderUnit' => $DefenderUnit, 'evasion' => $evasion, 'maxevasion' => $maxevasion, + 'gold' => $gold, 'cannotsend' => $cannotsend, + 'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, + 'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab, + 'type' => $type, 'scout' => $scout, + 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, + 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, + 'walllevel' => $walllevel, 'wallid' => $wallid, + 'tblevel' => $tblevel, 'stonemason' => $stonemason, + ]; + } + + /** + * Resolve the defender target context for an OASIS attack: target owner + * (tribe/alliance), oasis map info, conquest flag and the (mostly fixed) + * battle environment. Read-only — no DB writes. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row. + * @param array $dataarray Full batch of completed attacks. + * @param int $data_num Index of $data within $dataarray. + * @param int $owntribe Attacker tribe. + * @return array Target + battle-environment context. + */ + private function resolveOasisTarget($data, $dataarray, $data_num, $owntribe) { + global $database; + + $DefenderUserData = $this->getCachedUser($database->getOasisField($data['to'], "owner"),1); + $DefenderID = $DefenderUserData["id"]; + $targettribe = $DefenderUserData["tribe"]; + $targetally = $DefenderUserData["alliance"]; + $to = $database->getOMInfo($data['to']); + $toF = $database->getOasisV($data['to']); + $conqureby = $toF['conqured']; + + //need to set these variables. + $def_wall = $residence = $attpop = 0; + $att_tribe = $owntribe; + $def_tribe = $targettribe; + $defpop = 500; + + //type of attack + $type = $dataarray[$data_num]['attack_type']; + $scout = ($type == 1) ? 1 : 0; + + $att_ab1 = $att_ab2 = $att_ab3 = $att_ab4 = $att_ab5 = $att_ab6 = $att_ab7 = $att_ab8 = 0; + $def_ab = []; + $def_ab[31] = $def_ab[32] = $def_ab[33] = $def_ab[34] = $def_ab[35] = $def_ab[36] = $def_ab[37] = $def_ab[38] = 0; + + $walllevel = $tblevel = $stonemason = 0; + + return [ + 'DefenderID' => $DefenderID, 'targettribe' => $targettribe, 'targetally' => $targetally, + 'to' => $to, 'toF' => $toF, 'conqureby' => $conqureby, + 'def_wall' => $def_wall, 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, + 'attpop' => $attpop, 'defpop' => $defpop, 'residence' => $residence, 'def_ab' => $def_ab, + 'type' => $type, 'scout' => $scout, + 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, + 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, + 'walllevel' => $walllevel, 'tblevel' => $tblevel, 'stonemason' => $stonemason, + ]; + } + + /** + * Compute attacker/defender hero XP and victory points from the battle + * casualties, persist them (hero XP, player points, alliance points) and + * return the total defender losses. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $alldead Reinforcement casualties by global unit id (+ 'hero'). + * @param array $owndead Defender own casualties by global unit id (+ 'hero'). + * @param array $attackerDead Attacker casualties, 0-indexed t1..t11 (index 10 = hero). + * @param int $targettribe Defender tribe id. + * @param int $att_tribe Attacker tribe id. + * @param array $Attacker Attacker troop row (uses 'uhero'). + * @param mixed $AttackerHeroID Attacker hero id (for the XP write). + * @param array $Defender Defender troop row (uses 'hero'). + * @param array $DefendersHeroID Defender hero ids (for the XP writes). + * @param array $toF Defender village (uses 'owner'). + * @param array $from Attacker village info (uses 'owner'). + * @param int $targetally Defender alliance id. + * @param int $ownally Attacker alliance id. + * @param mixed $heroxp OUT (by ref): attacker hero XP, set only if the attacker has a hero. + * @param mixed $defheroxp OUT (by ref): per-defender hero XP, set only if the defender has a hero. + * @return int Total defender losses (reinforcements + own troops). + */ + private function calculateHeroXpAndPoints( + array $alldead, array $owndead, array $attackerDead, + $targettribe, $att_tribe, + $Attacker, $AttackerHeroID, $Defender, $DefendersHeroID, + $toF, $from, $targetally, $ownally, + &$heroxp, &$defheroxp + ) { + global $database; + + $totaldead_def = 0; + $totalpoint_att = 0; + + for($i = 1 ;$i <= 90; $i++) { + $unitarray = $GLOBALS["u".$i]; + + //Reinforcements dead troops + $totaldead_def += $alldead[$i]; + $totalpoint_att += ($alldead[$i] * $unitarray['pop']); + + //Own dead troops + if($i >= ($targettribe - 1) * 10 + 1 && $i <= $targettribe * 10){ + $totaldead_def += $owndead[$i]; + $totalpoint_att += ($owndead[$i] * $unitarray['pop']); + } + } + $totalpoint_att += ((isset($alldead['hero']) ? $alldead['hero'] : 0) * 6); + $totalpoint_att += ((isset($owndead['hero']) ? $owndead['hero'] : 0) * 6); + + if ($Attacker['uhero'] > 0){ + $heroxp = $totalpoint_att; + $database->modifyHeroXp("experience", $heroxp, $AttackerHeroID); + } + + for($i = 1; $i <= 10; $i++){ + $unitarray = $GLOBALS["u".(($att_tribe - 1) * 10 + $i)]; + if ( !isset($totalpoint_def) ) { + $totalpoint_def = 0; + } + $totalpoint_def += ($attackerDead[$i - 1] * $unitarray['pop']); + } + + $totalpoint_def += $attackerDead[10] * 6; + + if($Defender['hero'] > 0){ + //counting heroxp + $defheroxp = intval($totalpoint_def / count($DefendersHeroID)); + foreach($DefendersHeroID as $HeroID){ + $database->modifyHeroXp("experience",$defheroxp,$HeroID); + } + } + + // we don't need these two variables anymore + unset($AttackerHeroID, $DefendersHeroID); + + $database->modifyPoints( + $toF['owner'], + ['dpall', 'dp'], + [$totalpoint_def, $totalpoint_def] + ); + + $database->modifyPoints( + $from['owner'], + ['apall', 'ap'], + [$totalpoint_att, $totalpoint_att] + ); + + $database->modifyPointsAlly( + $targetally, + ['Adp', 'dp'], + [$totalpoint_def, $totalpoint_def] + ); + + $database->modifyPointsAlly( + $ownally, + ['Aap', 'ap'], + [$totalpoint_att, $totalpoint_att] + ); + + return $totaldead_def; + } + + /** + * Resolve the resources lootable from the battle target after cranny + * protection: compute the cranny efficiency, pull the current (capped) + * stocks of the target village/oasis and derive the lootable amounts. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses 'to'). + * @param int $isoasis Oasis flag (0 = village). + * @param int $conqureby Owner village of a conquered oasis (0 = none). + * @param int $owntribe Attacker tribe id (Teuton cranny bonus). + * @param int $targettribe Defender tribe id (Gaul cranny bonus). + * @param mixed $crannySpy OUT (by ref): cranny value seen by a scout; set only for village targets (isoasis == 0). + * @return array ['totclay','totiron','totwood','totcrop','avclay','aviron','avwood','avcrop']. + */ + private function resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, &$crannySpy) { + global $database, $bid23; + + if ($isoasis == 0){ + // get total cranny value: + $buildarray = $database->getResourceLevel($data['to']); + $cranny = 0; + for($i = 19; $i < 39;$i++){ + if($buildarray['f'.$i.'t'] == 23){ + $cranny += $bid23[$buildarray['f'.$i]]['attri'] * CRANNY_CAPACITY; + } + } + + //cranny efficiency + $atk_bonus = ($owntribe == 2) ? (4 / 5) : 1; + $def_bonus = ($targettribe == 3) ? 2 : 1; + $to_owner = $database->getVillageField($data['to'], "owner"); + + $crannySpy = $database->getArtifactsValueInfluence($to_owner, $data['to'], 7, $cranny * $def_bonus); + $cranny_eff = $crannySpy * $atk_bonus; + + // work out available resources. + $this->updateRes($data['to']); + $this->pruneResource(); + + $villageData = $database->getVillageFields($data['to'], 'clay, iron, wood, crop', false); + $totclay = $villageData['clay']; + $totiron = $villageData['iron']; + $totwood = $villageData['wood']; + $totcrop = $villageData['crop']; + }else{ + $cranny_eff = 0; + + if ($conqureby > 0) { //10% from owner proc village owner - fix by ronix - exploit fixed by iopietro + $this->updateRes($conqureby); + $this->pruneResource(); + + $villageData = $database->getVillageFields($conqureby, 'clay, iron, wood, crop', false); + $totclay = intval($villageData['clay'] / 10); + $totiron = intval($villageData['iron'] / 10); + $totwood = intval($villageData['wood'] / 10); + $totcrop = intval($villageData['crop'] / 10); + }else{ + // work out available resources. + $this->updateORes($data['to']); + $this->pruneOResource(); + + $oasisData = $database->getOasisFields($data['to'], false); + $totclay = $oasisData['clay']; + $totiron = $oasisData['iron']; + $totwood = $oasisData['wood']; + $totcrop = $oasisData['crop']; + } + } + + $avclay = floor($totclay - $cranny_eff); + $aviron = floor($totiron - $cranny_eff); + $avwood = floor($totwood - $cranny_eff); + $avcrop = floor($totcrop - $cranny_eff); + + $avclay = ($avclay < 0) ? 0 : $avclay; + $aviron = ($aviron < 0) ? 0 : $aviron; + $avwood = ($avwood < 0) ? 0 : $avwood; + $avcrop = ($avcrop < 0) ? 0 : $avcrop; + + return [ + 'totclay' => $totclay, 'totiron' => $totiron, 'totwood' => $totwood, 'totcrop' => $totcrop, + 'avclay' => $avclay, 'aviron' => $aviron, 'avwood' => $avwood, 'avcrop' => $avcrop, + ]; + } + + /** + * Resolve the attacker hero's outcome after the battle: build the hero + * report line ($info_hero), and on a surviving hero handle oasis conquest / + * loyalty reduction (oasis targets) or artifact claiming with possible + * village destruction (village targets). + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param array $data Current attack row (uses 't11', 'from', 'to'). + * @param array $from Attacker village info (uses 'owner'). + * @param array $to Defender village/oasis info (uses 'owner', 'loyalty'). + * @param int $dead11 Attacker hero casualties. + * @param int $traped11 Attacker hero trapped count. + * @param mixed $heroxp Attacker hero XP gained this battle. + * @param string $hero_pic Hero unit picture id (report markup). + * @param int $isoasis Oasis flag (0 = village). + * @param int $type Attack type (3 = normal with rams/catas). + * @param int $targettribe Defender tribe id. + * @param string $info_cat Catapult report fragment (read for the destruction notice). + * @param string $info_hero IN/OUT (by ref): hero report line. + * @param int $can_destroy IN/OUT (by ref): village-destruction allowed flag. + * @param int $village_destroyed IN/OUT (by ref): village-destroyed flag. + * @return void + */ + private function handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, &$info_hero, &$can_destroy, &$village_destroyed) { + global $database; + + if(($data['t11'] - $dead11 - $traped11) > 0){ //hero + if ($heroxp == 0) { + $xp = ""; + $info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_KILL'); + } else { + $xp = " ".rc_tok('RC_HERO_AND_GAINED_XP_BATTLE', $heroxp); + $info_hero = $hero_pic.",".rc_tok('RC_HERO_GAINED_XP', $heroxp); + } + + if ($isoasis != 0) { //oasis fix by ronix + if ($to['owner'] != $from['owner']) { + $troopcount = $database->countOasisTroops($data['to'], false); + $canqured = $database->canConquerOasis($data['from'], $data['to'], false); + if ($canqured == 1 && $troopcount == 0) { + $database->conquerOasis($data['from'], $data['to']); + $info_hero = $hero_pic.",".rc_tok('RC_HERO_CONQUERED_OASIS').$xp; + }else{ + if ($canqured == 3 && $troopcount == 0) { + if ($type == 3) { + $Oloyaltybefore = intval($to['loyalty']); + //$database->modifyOasisLoyalty($data['to']); + //$OasisInfo = $database->getOasisInfo($data['to']); + $Oloyaltynow = intval($database->modifyOasisLoyalty($data['to']));//intval($OasisInfo['loyalty']); + $info_hero = $hero_pic.",".rc_tok('RC_HERO_REDUCED_OASIS_LOYALTY', $Oloyaltynow, $Oloyaltybefore).$xp; + } + else $info_hero = $hero_pic.",".rc_tok('RC_NO_REDUCE_LOYALTY_RAID').$xp; + } + } + } + } else { + if ($heroxp == 0) $xp=" ".rc_tok('RC_HERO_NO_XP_BATTLE'); + else $xp=" ".rc_tok('RC_HERO_GAINED_XP_BATTLE', $heroxp); + + $artifact = reset($database->getOwnArtefactInfo($data['to'])); + if (!empty($artifact)) { + if ($type == 3) { + if (empty($artifactError = $database->canClaimArtifact($data['from'], $artifact['vref'], $artifact['size'], $artifact['type']))) { + $database->claimArtefact($data['from'], $data['to'], $database->getVillageField($data['from'], "owner")); + $info_hero = $hero_pic.",".rc_tok('RC_HERO_CARRYING_ARTIFACT', $artifact['name']).$xp; + + // if the defender pop is 0 with no artefact, then destroy the village + if($database->getVillageField($data['to'], "pop") == 0 || $targettribe == 5){ + $can_destroy = $village_destroyed = 1; + if(strpos($info_cat, rc_tok('RC_VILLAGE_DESTROYED')) === false) $info_hero .= " ".rc_tok('RC_VILLAGE_DESTROYED'); + } + } + else $info_hero = $hero_pic.",".$artifactError.$xp; + } + else $info_hero = $hero_pic.",".rc_tok('RC_HERO_NO_ARTIFACT_RAID').$xp; + } + } + }elseif($data['t11'] > 0) { + if ($heroxp == 0) $xp = ""; + else $xp = " ".rc_tok('RC_HERO_BUT_GAINED_XP_BATTLE', $heroxp); + + if ($traped11 > 0) $info_hero = $hero_pic.",".rc_tok('RC_HERO_TRAPPED').$xp; + else $info_hero = $hero_pic.",".rc_tok('RC_HERO_DIED').$xp; + } + } + + /** + * Send the battle/scout report notification to the defender. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param mixed $scout Truthy when the attack is a scouting run. + * @param array $battlepart Battle result (uses 'casualties_attacker'). + * @param array $from Attacker village info (uses 'owner', 'name'). + * @param array $to Defender village/oasis info (uses 'owner', 'wref', 'name'). + * @param int $targetally Defender alliance id. + * @param string $data2 Combat-report payload. + * @param int $AttackArrivalTime Battle resolution timestamp. + * @param string $unitsdead_att Attacker dead units string. + * @param string $unitssend_att Attacker sent units string. + * @param bool $defspy Whether the defence has scouts. + * @param string $info_troop Troop-return report fragment. + * @param int $totalsend_alldef Total defending troops (incl. reinforcements). + * @param int $totalsend_att Total attacking troops sent. + * @param int $totaldead_att Total attacking troops killed. + * @param int $totaltraped_att Total attacking troops trapped. + * @param int $totaldead_alldef Total defending troops killed. + * @return void + */ + private function sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef) { + global $database; + + //Undetected and detected in here. + if(!empty($scout)){ + for($i = 1; $i <= 10; $i++){ + if($battlepart['casualties_attacker'][$i]){ + if($from['owner'] == 3){ + $database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + break; + }else if($unitsdead_att == $unitssend_att && $defspy){ //fix by ronix + $database->addNotice($to['owner'],$to['wref'],$targetally,20,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2.',,'.$info_troop,$AttackArrivalTime); + break; + }else if($defspy){ //fix by ronix + $database->addNotice($to['owner'],$to['wref'],$targetally,21,''.addslashes($from['name']).' scouts '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + break; + } + } + } + }else{ + if($totalsend_alldef == 0 && $totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0){ + $database->addNotice($to['owner'],$to['wref'],$targetally,7,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + }else if($totaldead_alldef == 0){ + $database->addNotice($to['owner'],$to['wref'],$targetally,4,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + }else if($totalsend_alldef > $totaldead_alldef){ + $database->addNotice($to['owner'],$to['wref'],$targetally,5,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + }else if($totalsend_alldef == $totaldead_alldef){ + $database->addNotice($to['owner'],$to['wref'],$targetally,6,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'',$data2,$AttackArrivalTime); + } + } + } + + /** + * Finalize the fate of the attacking troops: if any survived they return + * home (attacker report + return movement + loot transfer / RR points / + * leftover-chief enforcement), otherwise they die without returning (only + * the all-dead report is sent). Pure behaviour-preserving extraction + * (refactor for issue #155). + * + * @param array $data Current attack row (uses 't1..t11', 'moveid', 'ref'). + * @param array $from Attacker village info (uses 'wref', 'owner', 'name'). + * @param array $to Defender village/oasis info (uses 'wref', 'owner', 'name'). + * @param int $owntribe Attacker tribe id. + * @param int $type Attack type (1 = scout). + * @param int $isoasis Oasis flag (0 = village). + * @param int $conqureby Owner village of a conquered oasis (0 = none). + * @param int $AttackArrivalTime Battle resolution timestamp. + * @param int $targetally Defender alliance id. + * @param int $ownally Attacker alliance id. + * @param string $data2 Combat-report payload (survivors). + * @param string $data_fail Combat-report payload (all dead). + * @param int $chiefing_village Whether this attack chiefed the village. + * @param int $DefenderWref Defender world ref. + * @param int $AttackerWref Attacker world ref. + * @param array $steal [wood, clay, iron, crop] looted. + * @param array $dead Attacker casualties, 1-indexed t1..t11. + * @param array $traped Attacker trapped, 1-indexed t1..t11. + * @param array $troopsdead Attacker dead counts for enforcement, 1-indexed t1..t11. + * @param int $totalsend_att Total attacking troops sent. + * @param int $totaldead_att Total attacking troops killed. + * @param int $totaltraped_att Total attacking troops trapped. + * @param int $totalstolentaken IN/OUT (by ref): running RR loss accumulator for the defender. + * @return void + */ + private function finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $dead, $traped, $troopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, &$totalstolentaken) { + global $database, $units; + + // Spital: o parte din trupele proprii moarte IN ATAC devin ranite acasa. + // $dead e indexat 1..11 pe sloturile tribului; doar unitatile de lupta (1-8). + if($owntribe >= 1 && $owntribe <= 9) { + $woundedAtt = []; + for($wj = 1; $wj <= 8; $wj++) { + if(!empty($dead[$wj])) $woundedAtt[($owntribe - 1) * 10 + $wj] = $dead[$wj]; + } + if(!empty($woundedAtt)) $this->applyHospitalWounded($from['wref'], $woundedAtt); + } + + // If the dead units not equal the ammount sent they will return and report + if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 0) + { + $returningTroops = []; + for($i = 1; $i <= 11; $i++) $returningTroops['t'.$i] = $data['t'.$i] - $traped[$i] - $dead[$i]; + // T4 hero port (Phase 6): bandages revive part of the + // losses when the hero comes home alive; revived units + // are written back to the attacks row (the source + // returnunitsComplete reads on arrival). No-op when off. + $returningTroops = HeroBattleBonus::applyBandages($from['owner'], $returningTroops, $dead, (int) $data['ref']); + $troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $returningTroops, 1, 't'); + $endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime); + $endtime += $AttackArrivalTime; + // T4 hero port (Phase 5): the map shortens the return + // leg when the SURVIVING hero travels home. No-op when off. + $endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $returningTroops['t11'], $AttackArrivalTime, $endtime); + if($type == 1){ + if($from['owner'] == 3){ // fix natar report by ronix + $database->addNotice($to['owner'], $to['wref'], $targetally, 20, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); + }elseif($totaldead_att == 0 && $totaltraped_att == 0){ + $database->addNotice($from['owner'], $to['wref'], $ownally, 18, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); + }else{ + $database->addNotice($from['owner'], $to['wref'], $ownally, 21, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); + } + }else{ + if((empty($totaldead_att) || $totaldead_att == 0) && (empty($totaltraped_att) || $totaltraped_att == 0)){ + $database->addNotice($from['owner'], $to['wref'], $ownally, 1, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); + }else{ + $database->addNotice($from['owner'], $to['wref'], $ownally, 2, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime); + } + } + + $database->setMovementProc($data['moveid']); + + if (!isset($chiefing_village)) $chiefing_village = 0; + + if($chiefing_village != 1){ + $database->addMovement(4, $DefenderWref, $AttackerWref, $data['ref'], $AttackArrivalTime, $endtime, 1, $steal[0], $steal[1], $steal[2], $steal[3]); + if($type !== 1){ + if ($isoasis == 0) $database->modifyResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0); + else + { + //if it's an oasis but it's conquered by someone, resources must be modified in the owner's village + if($conqureby > 0) $database->modifyResource($conqureby, $steal[0], $steal[1], $steal[2], $steal[3], 0); + else $database->modifyOasisResource($DefenderWref, $steal[0], $steal[1], $steal[2], $steal[3], 0); + } + $totalstolengain = $steal[0] + $steal[1] + $steal[2] + $steal[3]; + $totalstolentaken = ((isset($totalstolentaken) ? $totalstolentaken : 0) - ($steal[0] + $steal[1] + $steal[2] + $steal[3])); + $database->modifyPoints($from['owner'], 'RR', $totalstolengain); + $database->modifyPoints($to['owner'], 'RR', $totalstolentaken); + $database->modifyPointsAlly($targetally, 'RR', $totalstolentaken); + $database->modifyPointsAlly($ownally, 'RR', $totalstolengain); + } + }else{ //fix by ronix if only 1 chief left to conqured - don't add with zero enforces + if($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) > 1){ + $database->addEnforce2($data, $owntribe, $troopsdead[1], $troopsdead[2], $troopsdead[3], $troopsdead[4], $troopsdead[5], $troopsdead[6], $troopsdead[7], $troopsdead[8], $troopsdead[9], $troopsdead[10], $troopsdead[11]); + } + } + } + else //else they die and don't return or report. + { + $database->setMovementProc($data['moveid']); + if($type == 1){ + $database->addNotice($from['owner'], $to['wref'], $ownally, 19, addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime); + }else{ + $database->addNotice($from['owner'], $to['wref'], $ownally, 3, '' . addslashes($from['name']) . ' attacks ' . addslashes($to['name']) . '', $data_fail, $AttackArrivalTime); + } + } + } + + /** + * Destroy the target village when the battle flagged it for destruction: + * reassign the capital to the player's most populated remaining village if + * needed, delete the village and reassign its hero. + * Pure behaviour-preserving extraction (refactor for issue #155). + * + * @param int $village_destroyed Village-destroyed flag. + * @param int $can_destroy Village-destruction allowed flag. + * @param array $data Current attack row (uses 'to'). + * @param array $to Defender village info (uses 'capital'). + * @param array $varray The defender owner's villages. + * @return void + */ + private function handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray) { + global $database; + + if (!($village_destroyed == 1 && $can_destroy == 1)) { + return; + } + + if($to['capital'] == 1){ + $mostPopulatedVillage = []; + //Search for the most populated village + foreach($varray as $village){ + if($village['wref'] != $data['to'] && (empty($mostPopulatedVillage) || $mostPopulatedVillage['pop'] < $village['pop'])){ + $mostPopulatedVillage = $village; + } + } + //Set the new capital + $database->changeCapital($mostPopulatedVillage['wref']); + } + + //Delete the village + $database->DelVillage($data['to']); + + //Reassign the hero, if dead and assigned to the deleted village + $database->reassignHero($data['to']); + } + + private function sendunitsComplete() { + // COMPLETE ATTACK PROCESSING - critical function, kept 100% compatible + // This function handles all attacks that reach their destination. + // Includes: battles, traps, hero evasion, building destruction, and village conquest. + global $bid23, $database, $battle, $technology, $units; + + $time = time(); + $dataarray = $this->fetchCompletedAttacks($time); + $totalattackdead = $data_num = 0; + + if ($dataarray && count($dataarray)) { + // preload village data (batched) so the per-attack loop hits the + // cache instead of querying row by row + $this->preloadBattleData($dataarray); + + // tiles whose village was razed earlier in this same batch (issue #298) + $razedTargets = []; + + // calculate battles + foreach($dataarray as $data) { + //set base things + $totaltraped_att = 0; + for($i = 1; $i <= 11; $i++) ${'traped'.$i} = 0; + // per-attack context (attacker village/owner) — extracted to resolveAttackContext() [#155] + $ctx = $this->resolveAttackContext($data); + $isoasis = $ctx['isoasis']; + $AttackArrivalTime = $ctx['AttackArrivalTime']; + $AttackerWref = $ctx['AttackerWref']; + $DefenderWref = $ctx['DefenderWref']; + $NatarCapital = $ctx['NatarCapital']; + $AttackerID = $ctx['AttackerID']; + $Attacker['id'] = $ctx['AttackerID']; + $owntribe = $ctx['owntribe']; + $ownally = $ctx['ownally']; + $from = $ctx['from']; + $fromF = $ctx['fromF']; + + //It's a village + if ($isoasis == 0){ + // target + battle environment — extracted to resolveVillageTarget() [#155] + $vt = $this->resolveVillageTarget($data, $dataarray, $data_num, $owntribe); + $DefenderID = $vt['DefenderID']; + $targettribe = $vt['targettribe']; + $targetally = $vt['targetally']; + $to = $vt['to']; + $toF = $vt['toF']; + $conqureby = $vt['conqureby']; + $NatarCapital = $vt['NatarCapital']; + $DefenderUnit = $vt['DefenderUnit']; + $def_wall = $vt['def_wall']; + $att_tribe = $vt['att_tribe']; + $def_tribe = $vt['def_tribe']; + $attpop = $vt['attpop']; + $defpop = $vt['defpop']; + $residence = $vt['residence']; + $def_ab = $vt['def_ab']; + $type = $vt['type']; + $scout = $vt['scout']; + $att_ab1 = $vt['att_ab1']; + $att_ab2 = $vt['att_ab2']; + $att_ab3 = $vt['att_ab3']; + $att_ab4 = $vt['att_ab4']; + $att_ab5 = $vt['att_ab5']; + $att_ab6 = $vt['att_ab6']; + $att_ab7 = $vt['att_ab7']; + $att_ab8 = $vt['att_ab8']; + $walllevel = $vt['walllevel']; + $wallid = $vt['wallid']; + $tblevel = $vt['tblevel']; + $stonemason = $vt['stonemason']; + + // Issue #298: the target village no longer exists — it was razed + // either earlier in this same batch ($razedTargets), or in an + // earlier tick whose still-in-flight follow-up waves DelVillage() + // failed to bounce. getMInfo() then returns NULL vdata columns + // ($to['wref'] is NULL), so resolving a battle here would fight a + // phantom village and compute the return trip from NULL coordinates + // — a bogus arrival time that strands the troops in an endless loop + // (report against "[?]"). Bounce the whole army straight home + // instead, exactly like DelVillage() does for in-flight attacks, + // and mark the movement processed so it stops being re-fetched. + if (isset($razedTargets[$data['to']]) || empty($to['wref'])) { + // only own the bounce if DelVillage() hasn't already handled it + // (setMovementProc() returns true only when it flips proc 0->1), + // so we never create a duplicate return movement + if ($this->setMovementProc($data['moveid'])) { + $bounceTime = $units->getWalkingTroopsTime($from['wref'], $data['to'], $from['owner'], $owntribe, $data, 1, 't'); + $bounceEnd = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $bounceTime) + $AttackArrivalTime; + $database->addMovement(4, $data['to'], $from['wref'], $data['ref'], $AttackArrivalTime, $bounceEnd); + } + $data_num++; + continue; + } + + $this->handleEvasion($data, $DefenderID, $DefenderUnit, $targettribe, $vt['evasion'], $vt['maxevasion'], $vt['gold'], $vt['cannotsend'], $dataarray[$data_num]['attack_type']); + + // defence units gathered — extracted to buildDefenderUnits() [#155] + $defUnits = $this->buildDefenderUnits($data['to']); + $Defender = $defUnits['Defender']; + $enforDefender = $defUnits['enforDefender']; + $enforcementarray = $defUnits['enforcementarray']; + + // attacker army built — extracted to buildAttackerUnits() [#155] + $atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis); + $Attacker = $atkUnits['Attacker']; + $start = $atkUnits['start']; + $end = $atkUnits['end']; + $catp_pic = $atkUnits['catp_pic']; + $ram_pic = $atkUnits['ram_pic']; + $chief_pic = $atkUnits['chief_pic']; + $spy_pic = $atkUnits['spy_pic']; + $hero_pic = $atkUnits['hero_pic']; + + }else{ //It's an oasis + // target + battle environment — extracted to resolveOasisTarget() [#155] + $ot = $this->resolveOasisTarget($data, $dataarray, $data_num, $owntribe); + $DefenderID = $ot['DefenderID']; + $targettribe = $ot['targettribe']; + $targetally = $ot['targetally']; + $to = $ot['to']; + $toF = $ot['toF']; + $conqureby = $ot['conqureby']; + $def_wall = $ot['def_wall']; + $att_tribe = $ot['att_tribe']; + $def_tribe = $ot['def_tribe']; + $attpop = $ot['attpop']; + $defpop = $ot['defpop']; + $residence = $ot['residence']; + $def_ab = $ot['def_ab']; + $type = $ot['type']; + $scout = $ot['scout']; + $att_ab1 = $ot['att_ab1']; + $att_ab2 = $ot['att_ab2']; + $att_ab3 = $ot['att_ab3']; + $att_ab4 = $ot['att_ab4']; + $att_ab5 = $ot['att_ab5']; + $att_ab6 = $ot['att_ab6']; + $att_ab7 = $ot['att_ab7']; + $att_ab8 = $ot['att_ab8']; + $walllevel = $ot['walllevel']; + $tblevel = $ot['tblevel']; + $stonemason = $ot['stonemason']; + + // defence units gathered — extracted to buildDefenderUnits() [#155] + $defUnits = $this->buildDefenderUnits($data['to']); + $Defender = $defUnits['Defender']; + $enforDefender = $defUnits['enforDefender']; + $enforcementarray = $defUnits['enforcementarray']; + + // T4 hero port (Phase 8): cages capture animals on an + // UNOCCUPIED oasis before the fight - captured animals do + // not defend and are stationed at home as reinforcements. + // No-op when the flag is off, no hero rides along, the + // oasis is owned, or the attacker has no cages. + if ($isoasis != 0 && (int) $conqureby === 0 && (int) $data['t11'] > 0) { + $Defender = HeroBattleBonus::applyCages($from['owner'], (int) $data['from'], (int) $data['to'], $Defender); + } + + // attacker army built — extracted to buildAttackerUnits() [#155] + $atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis); + $Attacker = $atkUnits['Attacker']; + $start = $atkUnits['start']; + $end = $atkUnits['end']; + $catp_pic = $atkUnits['catp_pic']; + $ram_pic = $atkUnits['ram_pic']; + $chief_pic = $atkUnits['chief_pic']; + $spy_pic = $atkUnits['spy_pic']; + $hero_pic = $atkUnits['hero_pic']; + } + + // attacker/defender populations + village lists — extracted to calculatePopulations() [#155] + $popData = $this->calculatePopulations($to, $from, $isoasis, $defpop, $attpop); + $varray = $popData['varray']; + $varray1 = $popData['varray1']; + $defpop = $popData['defpop']; + $attpop = $popData['attpop']; + + //fix by ronix + for ($i = 1; $i <= 90; $i++) { + if (!isset($enforDefender['u'.$i])) { + $enforDefender['u'.$i] = 0; + } + $enforDefender['u'.$i] += (isset($Defender['u'.$i]) ? $Defender['u'.$i] : 0); + } + + $defspy = $enforDefender['u4'] > 0 || $enforDefender['u14'] > 0 || $enforDefender['u23'] > 0 || $enforDefender['u44'] > 0; + + if(PEACE == 0 || $targettribe == 4 || $targettribe == 5 || $scout){ + // trapper resolution + prisoners — extracted to calculateTrappedUnits() [#155] + $trapResult = $this->calculateTrappedUnits($data, $Defender, $Attacker, $NatarCapital, $scout, $start, $end); + for($i = 1; $i <= 11; $i++) ${'traped'.$i} = $trapResult['traped'][$i]; + $totaltraped_att = $trapResult['totaltraped_att']; + $Attacker = $trapResult['Attacker']; + + // we need to save the attacker heroid before the battle + $AttackerHeroID = 0; + if(isset($Attacker['uhero']) && $Attacker['uhero'] > 0){ + $AttackerHeroID = $database->getHeroField($from['owner'], "heroid"); + } + + // and the defender(s) heroid + $DefendersHeroID = []; + $herosend_def = 0; + + // check if our hero is defending the village, if so, add it to the list + if (isset($Defender['hero']) && $Defender['hero'] > 0) { + $DefendersHeroID[] = $database->getHeroField($DefenderID, "heroid"); + + // collecting information for the battle report + $herosend_def += $Defender['hero']; + } + + // check if there are other heroes defending the village + if(count($enforcementarray) > 0){ + foreach($enforcementarray as $enforcement){ + if($enforcement['hero'] > 0){ + $heroOwner = $database->getVillageField($enforcement['from'], "owner"); + $DefendersHeroID[] = $database->getHeroField($heroOwner, "heroid"); + } + } + } + + //fix by ronix + if (!isset($walllevel)) $walllevel = 0; + + $battlepart = $battle->calculateBattle($Attacker, $Defender, $def_wall, $att_tribe, $def_tribe, $residence, $attpop, $defpop, $type, $def_ab, $att_ab1, $att_ab2, $att_ab3, $att_ab4, $att_ab5, $att_ab6, $att_ab7, $att_ab8, $tblevel, $stonemason, $walllevel, 0, 0, 0, $AttackerID, $DefenderID, $AttackerWref, $DefenderWref, $conqureby, $enforcementarray); + + //Data for when troops return. + //catapults look :D + $info_cat = $info_chief = $info_ram = $info_hero = ","; + + //check to see if can destroy village + if (count($varray) > 1 && !$database->villageHasArtefact($DefenderWref) && empty($to['natar'])) { + $can_destroy = 1; + } + else $can_destroy = 0; + + //Catapults and rams management + //TODO: Move this in Battle.php + if($isoasis == 0){ + if ($type == 3){ + if (($data['t7'] - $traped7) > 0){ + // ram damage on the wall (+ battle recalc) — extracted to applyRamDamage() [#155] + $ramResult = $this->applyRamDamage($data, $walllevel, $wallid, $ram_pic, $battlepart, [ + 'Attacker' => $Attacker, 'Defender' => $Defender, + 'att_tribe' => $att_tribe, 'def_tribe' => $def_tribe, + 'residence' => $residence, 'attpop' => $attpop, 'defpop' => $defpop, + 'type' => $type, 'def_ab' => $def_ab, + 'att_ab1' => $att_ab1, 'att_ab2' => $att_ab2, 'att_ab3' => $att_ab3, 'att_ab4' => $att_ab4, + 'att_ab5' => $att_ab5, 'att_ab6' => $att_ab6, 'att_ab7' => $att_ab7, 'att_ab8' => $att_ab8, + 'tblevel' => $tblevel, 'stonemason' => $stonemason, + 'AttackerID' => $AttackerID, 'DefenderID' => $DefenderID, + 'AttackerWref' => $AttackerWref, 'DefenderWref' => $DefenderWref, + 'conqureby' => $conqureby, 'enforcementarray' => $enforcementarray, + ]); + $battlepart = $ramResult['battlepart']; + $info_ram = $ramResult['info_ram']; + } + + if (($data['t8'] - $traped8) > 0) + { + $catResult = $this->applyCatapults($data, $battlepart, $catp_pic, $can_destroy, $isoasis, $targettribe, $info_cat); + $battlepart = $catResult['battlepart']; + $info_cat = $catResult['info_cat']; + $village_destroyed = $catResult['village_destroyed']; + } + } elseif (($data['t7'] - $traped7) > 0) { + $info_ram = "".$ram_pic.",Hint: The ram does not work during a raid."; + } + } + else $can_destroy = 0; + + //units attack string for battleraport + $unitssend_att = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].''; + $herosend_att = $data['t11']; + + //reinforcement report (pre-casualty defender data) — extracted to collectReinforcementReport() [#155] + $reinfReport = $this->collectReinforcementReport($data, $targettribe, $Defender); + $DefenderHeroesTotArray = $reinfReport['DefenderHeroesTotArray']; + $DefenderHeroesDeadArray = $reinfReport['DefenderHeroesDeadArray']; + $unitssend_def = $reinfReport['unitssend_def']; + $unitssend_deff = $reinfReport['unitssend_deff']; + $totalsend_alldef = $reinfReport['totalsend_alldef']; + $Defender['hero'] = $reinfReport['defenderHero']; + + for($i = 1; $i <= 11; $i++){ + //MUST TO BE FIX : This is only for defender and still not properly coded + if (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] <= 0) { + ${'dead'.$i} = 0; + } else if (isset($data['t'.$i]) && isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) && $battlepart['casualties_attacker'][$i] > $data['t'.$i]) { + ${'dead'.$i} = $data['t'.$i]; + } else { + ${'dead'.$i} = (isset($battlepart['casualties_attacker']) && isset($battlepart['casualties_attacker'][$i]) ? $battlepart['casualties_attacker'][$i] : 0); + } + } + + $dead = []; + $owndead = []; + $alldead = []; + + for($i = 1; $i <= 90; $i++) $alldead[$i] = 0; + + //kill own defence — extracted to applyOwnDefenceCasualties() [#155] + $owndead = $this->applyOwnDefenceCasualties($data, $targettribe, $battlepart); + + //kill other defence in village (reinforcements) — extracted to applyReinforcementCasualties() [#155] + $tribesPresent = $this->applyReinforcementCasualties($data, $battlepart, $from, $to, $ownally, $AttackArrivalTime, $scout, $alldead, $DefenderHeroesDeadArray); + $rom = $tribesPresent['rom']; + $ger = $tribesPresent['ger']; + $gal = $tribesPresent['gal']; + $nat = $tribesPresent['nat']; + $natar = $tribesPresent['natar']; + $hun = $tribesPresent['hun']; + $egy = $tribesPresent['egy']; + $spa = $tribesPresent['spa']; + $vik = $tribesPresent['vik']; + $totalsend_att = $data['t1']+$data['t2']+$data['t3']+$data['t4']+$data['t5']+$data['t6']+$data['t7']+$data['t8']+$data['t9']+$data['t10']+$data['t11']; + + $DefenderHeroesTot = implode(",", $DefenderHeroesTotArray); + $DefenderHeroesDead = implode(",", $DefenderHeroesDeadArray); + + if (empty($alldead['hero'])) $alldead['hero'] = 0; + if (empty($owndead['hero'])) $owndead['hero'] = 0; + // sursa autoritativa: rezultatul luptei (owndead['hero'] se poate pierde + // pe unele cai de executie, iar raportul afisa eroul aparator ca viu) + $deadhero = (int)(isset($battlepart['deadherodef']) && $battlepart['deadherodef'] > 0 + ? $battlepart['deadherodef'] + : $owndead['hero']); + + //Counting own total dead troops + $ownDeadTroops = array_slice($owndead, 0, 10); + $totaldead_alldef = array_sum($ownDeadTroops); + + //Collecting informations for the report + $unitsdead_def[0] = implode(",", $ownDeadTroops); + $unitsdead_deff[0] = '?,?,?,?,?,?,?,?,?,?,'; + for($i = 1; $i <= 9; $i++){ + //Counting reinforcements total dead troops + $deadTroops = array_slice($alldead, ($i - 1) * 10, 10); + $totaldead_alldef += array_sum($deadTroops); + //Collecting informations for the report + $unitsdead_def[$i] = implode(",", $deadTroops); + $unitsdead_deff[$i] = $unitsdead_deff[0]; + } + $totaldead_alldef += ($deadhero + $alldead['hero']); + + if (!isset($totalattackdead)) $totalattackdead = 0; + $totalattackdead += $totaldead_alldef; + + // Set units returning from attack + + $p_units = []; + for ($i = 1; $i <= 11; $i++) { + if (!isset(${'dead'.$i})) ${'dead'.$i} = 0; + if (!isset(${'traped'.$i})) ${'traped'.$i} = 0; + $p_units[] = "t".$i." = t".$i." - ".(${'dead'.$i} + ${'traped'.$i}); + } + + $database->modifyAttack3($data['ref'],implode(', ', $p_units)); + + $unitsdead_att = $dead1.','.$dead2.','.$dead3.','.$dead4.','.$dead5.','.$dead6.','.$dead7.','.$dead8.','.$dead9.','.$dead10; + $unitstraped_att = $traped1.','.$traped2.','.$traped3.','.$traped4.','.$traped5.','.$traped6.','.$traped7.','.$traped8.','.$traped9.','.$traped10.','.$traped11; + + //top 10 attack and defence update + $totaldead_att = $dead1 + $dead2 + $dead3 + $dead4 + $dead5 + $dead6 + $dead7 + $dead8 + $dead9 + $dead10 + $dead11; + $totalattackdead += $totaldead_att; + $troopsdead1 = $dead1; + $troopsdead2 = $dead2; + $troopsdead3 = $dead3; + $troopsdead4 = $dead4; + $troopsdead5 = $dead5; + $troopsdead6 = $dead6; + $troopsdead7 = $dead7; + $troopsdead8 = $dead8; + $troopsdead9 = $dead9 + 1; + $troopsdead10 = $dead10; + $troopsdead11 = $dead11; + + // hero XP, player points and alliance points — extracted to calculateHeroXpAndPoints() [#155] + $totaldead_def = $this->calculateHeroXpAndPoints( + $alldead, $owndead, + [$dead1, $dead2, $dead3, $dead4, $dead5, $dead6, $dead7, $dead8, $dead9, $dead10, $dead11], + $targettribe, $att_tribe, + $Attacker, $AttackerHeroID, $Defender, $DefendersHeroID, + $toF, $from, $targetally, $ownally, + $heroxp, $defheroxp + ); + + // resources lootable after cranny protection — extracted to resolveResourcesAfterBattle() [#155] + $resAfter = $this->resolveResourcesAfterBattle($data, $isoasis, $conqureby, $owntribe, $targettribe, $crannySpy); + $totclay = $resAfter['totclay']; + $totiron = $resAfter['totiron']; + $totwood = $resAfter['totwood']; + $totcrop = $resAfter['totcrop']; + $avclay = $resAfter['avclay']; + $aviron = $resAfter['aviron']; + $avwood = $resAfter['avwood']; + $avcrop = $resAfter['avcrop']; + + // bounty distributed across the resources available after cranny protection (extracted to applyBounty() [#155]) + // T4 hero port (Phase 5): thief sacks raise the carry + // bounty when the attacker's living hero joined the strike. + $heroBounty = (int) round($battlepart['bounty'] * HeroBattleBonus::raidMultiplier($from['owner'], $data['t11'])); + $steal = $this->applyBounty([$avwood, $avclay, $aviron, $avcrop], $heroBounty); + + //chiefing village — extracted to handleConquest() [#155] + if (!isset($village_destroyed)) $village_destroyed = 0; + $chiefResult = $this->handleConquest($data, $type, $dead9, $traped9, $targettribe, $from, $to, $toF, $owntribe, $varray, $varray1, $battlepart, $isoasis, $village_destroyed, $chief_pic); + $info_chief = $chiefResult['info_chief']; + $chiefing_village = $chiefResult['chiefing_village']; + $village_destroyed = $chiefResult['village_destroyed']; + + // attacker hero outcome (oasis conquest/loyalty, artifact claim, report line) — extracted to handleHeroPostBattle() [#155] + $this->handleHeroPostBattle($data, $from, $to, $dead11, $traped11, $heroxp, $hero_pic, $isoasis, $type, $targettribe, $info_cat, $info_hero, $can_destroy, $village_destroyed); + + if ($DefenderID == 0) $natar = 0; + + if (!empty($scout)) { + $info_spy = $this->buildScoutReport($data, $spy_pic, $isoasis, $targettribe, $crannySpy, $totwood, $totclay, $totiron, $totcrop); + } + + // prisoners freed during conquest attacks — extracted to handlePrisoners() [#155] + $info_trap = empty($scout) ? $this->handlePrisoners($data, $from, $to, $ownally, $type, $totalsend_att, $totaldead_att, $totaltraped_att) : ''; + $info_troop = ($totalsend_att - ($totaldead_att + (isset($totaltraped_att) ? $totaltraped_att : 0)) <= 0) ? rc_tok('RC_NONE_RETURNED') : ""; + + // assemble data2 + data_fail — extracted to buildCombatReport() [#155] + $report = $this->buildCombatReport( + $scout, $from, $to, $owntribe, $targettribe, + $unitssend_att, $unitsdead_att, $steal, $battlepart, + $unitssend_def, $unitsdead_def, $unitssend_deff, $unitsdead_deff, + $rom, $ger, $gal, $nat, $natar, + $hun, $egy, $spa, $vik, + $DefenderHeroesTot, $DefenderHeroesDead, + $info_ram, $info_cat, $info_chief, isset($info_spy) ? $info_spy : '', + $info_trap, $info_hero, $info_troop, + $data, $dead11, $herosend_def, $deadhero, $unitstraped_att, + $village_destroyed, $can_destroy, $catp_pic + ); + $data2 = $report['data2']; + $data_fail = $report['data_fail']; + + // notify the defender of the incoming battle/scout — extracted to sendBattleNotifications() [#155] + $this->sendBattleNotifications($scout, $battlepart, $from, $to, $targetally, $data2, $AttackArrivalTime, $unitsdead_att, $unitssend_att, $defspy, $info_troop, $totalsend_alldef, $totalsend_att, $totaldead_att, $totaltraped_att, $totaldead_alldef); + // surviving attacker troops return (report + movement + loot/RR/enforce) or die — extracted to finalizeReturnOrDeath() [#155] + $retDead = $retTraped = $retTroopsdead = []; + for ($i = 1; $i <= 11; $i++) { $retDead[$i] = ${'dead'.$i}; $retTraped[$i] = ${'traped'.$i}; $retTroopsdead[$i] = ${'troopsdead'.$i}; } + $this->finalizeReturnOrDeath($data, $from, $to, $owntribe, $type, $isoasis, $conqureby, $AttackArrivalTime, $targetally, $ownally, $data2, $data_fail, $chiefing_village, $DefenderWref, $AttackerWref, $steal, $retDead, $retTraped, $retTroopsdead, $totalsend_att, $totaldead_att, $totaltraped_att, $totalstolentaken); + if($type == 3 || $type == 4) $database->addGeneralAttack($totalattackdead); + + if (!isset($village_destroyed)) $village_destroyed = 0; + + // delete the target village if it was destroyed — extracted to handleVillageDestruction() [#155] + $this->handleVillageDestruction($village_destroyed, $can_destroy, $data, $to, $varray); + + // remember the razed tile so any follow-up wave still queued in + // this same batch bounces home instead of fighting a phantom + // (now-deleted) village. Needed on top of the $to['wref'] check + // because getMInfo() is cached: a same-batch wave would still see + // the stale "alive" village row — see the guard right after + // resolveVillageTarget() (issue #298). + if ($village_destroyed == 1 && $can_destroy == 1) { + $razedTargets[$data['to']] = true; + } + }else{ + //units attack string for battleraport + $unitssend_att1 = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].''; + $herosend_att = $data['t11']; + $unitssend_att= $unitssend_att1.','.$herosend_att; + + $troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $data, 1, 't'); + $endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime); + $endtime += $AttackArrivalTime; + // T4 hero port (Phase 5): map return-speed bonus, same as + // the combat return leg above. No-op when the flag is off. + $endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $data['t11'], $AttackArrivalTime, $endtime); + + $database->setMovementProc($data['moveid']); + $database->addMovement(4, $to['wref'], $from['wref'], $data['ref'], $AttackArrivalTime, $endtime); + $peace = PEACE; + $data2 = $from['owner'].','.$from['wref'].','.$to['owner'].','.$owntribe.','.$unitssend_att.','.$peace; + $time = time(); + $database->addNotice($from['owner'], $to['wref'], $ownally, 22,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time); + $database->addNotice($to['owner'], $to['wref'], $targetally, 23,''.addslashes($from['name']).' attacks '.addslashes($to['name']).'', $data2, $time); + } + + //Update starvation data + $database->addStarvationData($to['wref']); + + $data_num++; + + unset( + $Attacker + ,$Defender + ,$DefenderHeroesTotArray + ,$DefenderHeroesDeadArray + ,$DefenderHeroesTot + ,$DefenderHeroesDead + ,$unitssend_att + ,$unitssend_def + ,$battlepart + ,$unitsdead_def + ,$dead + ,$steal + ,$from + ,$data + ,$data2 + ,$to + ,$data_fail + ,$owntribe + ,$unitsdead_att + ,$herosend_def + ,$deadhero + ,$heroxp + ,$AttackerID + ,$DefenderID + ,$totalsend_att + ,$totalsend_alldef + ,$totaldead_att + ,$totaltraped_att + ,$totaldead_def + ,$totalattackdead + ,$defheroxp + ,$AttackerWref + ,$DefenderWref + ,$troopsdead1 + ,$troopsdead2 + ,$troopsdead3 + ,$troopsdead4 + ,$troopsdead5 + ,$troopsdead6 + ,$troopsdead7 + ,$troopsdead8 + ,$troopsdead9 + ,$troopsdead10 + ,$troopsdead11 + ,$DefenderUnit + ,$info_trap + ,$report); + } + } + } + + private function bountysortOasis($oasisArray) { + $crop = $clay = $wood = $iron = 0; + foreach ($oasisArray as $oasis) { + switch($oasis['type']) { + case 1: + case 2: + $wood++; + break; + case 3: + $wood++; + $crop++; + break; + case 4: + case 5: + $clay++; + break; + case 6: + $clay++; + $crop++; + break; + case 7: + case 8: + $iron++; + break; + case 9: + $iron++; + $crop++; + break; + case 10: + case 11: + $crop++; + break; + case 12: + $crop += 2; + break; + } + } + return [$wood, $clay, $iron, $crop]; + } + + function getAllUnits($base, $use_cache = true) { + global $database; + + $ownunit = $database->getUnit($base, $use_cache); + $enforcementarray = $database->getEnforceVillage($base, 0); + + if(count($enforcementarray) > 0){ + foreach($enforcementarray as $enforce){ + for($i = 1; $i <= 90; $i++){ + $ownunit['u'.$i] += $enforce['u'.$i]; + } + } + } + + $enforceoasis = $database->getOasisEnforce($base, 0, $use_cache); + if(count($enforceoasis) > 0){ + foreach($enforceoasis as $enforce){ + for($i = 1; $i <= 90; $i++){ + $ownunit['u'.$i] += $enforce['u'.$i]; + } + } + } + + $enforceoasis1 = $database->getOasisEnforce($base, 1, $use_cache); + if(count($enforceoasis1) > 0){ + foreach($enforceoasis1 as $enforce){ + for($i = 1; $i <= 90; $i++){ + $ownunit['u'.$i] += $enforce['u'.$i]; + } + } + } + + $movement = $database->getVillageMovement($base); + if(!empty($movement)){ + for($i = 1; $i <= 90; $i++){ + if(!isset($ownunit['u' . $i])){ + $ownunit['u'.$i] = 0; + } + + $ownunit['u'.$i] += (isset($movement['u'.$i]) ? $movement['u'.$i] : 0); + } + } + + $prisoners = $database->getPrisoners($base, 1); + if(!empty($prisoners)){ + foreach($prisoners as $prisoner){ + $owner = $database->getVillageField($base, "owner"); + $ownertribe = $database->getUserField($owner, "tribe", 0); + $start = ($ownertribe - 1) * 10 + 1; + $end = ($ownertribe * 10); + for($i = $start; $i <= $end; $i++){ + $j = $i - $start + 1; + $ownunit['u'.$i] += $prisoner['t'.$j]; + } + $ownunit['hero'] += $prisoner['t11']; + } + } + return $ownunit; + } + + // Production of a single resource (1=wood, 2=clay, 3=iron, 4=crop) for the + // given village field layout, used by the bounty/production catch-up. The + // four resources only differ by: the production-building data global, the + // booster building field-type(s) whose 'attri' adds a % bonus, and the oasis + // bonus slot (resourceType - 1). Crop additionally has two boosters + // (grain mill type 8, then bakery type 9 applied on the running total) and + // the gold-club +25% crop bonus (b4) keyed on the village owner. + private function bountyGetResourceProd($resArray, $oasisNumber, $resourceType) { + global $bid1, $bid2, $bid3, $bid4, $bid5, $bid6, $bid7, $bid8, $bid9, $database; + + $prodBid = [1 => $bid1, 2 => $bid2, 3 => $bid3, 4 => $bid4][$resourceType]; + $boosterBid = [5 => $bid5, 6 => $bid6, 7 => $bid7, 8 => $bid8, 9 => $bid9]; + // Booster field-types per resource, in application order (crop: mill then bakery). + $boosterTypes = [1 => [5], 2 => [6], 3 => [7], 4 => [8, 9]][$resourceType]; + + $prod = 0; + $holders = []; + $boosterLevels = array_fill_keys($boosterTypes, 0); + for($i = 1; $i <= 38; $i++) { + if($resArray['f'.$i.'t'] == $resourceType) $holders[] = 'f'.$i; + if(isset($boosterLevels[$resArray['f'.$i.'t']])) $boosterLevels[$resArray['f'.$i.'t']] = $resArray['f'.$i]; + } + + foreach($holders as $holder) $prod += $prodBid[$resArray[$holder]]['prod']; + + foreach($boosterTypes as $bt) { + $level = $boosterLevels[$bt]; + if($level >= 1) $prod += $prod / 100 * (isset($boosterBid[$bt][$level]['attri']) ? $boosterBid[$bt][$level]['attri'] : 0); + } + + $oasisIndex = $resourceType - 1; + if($oasisNumber[$oasisIndex] > 0) $prod += $prod * 0.25 * $oasisNumber[$oasisIndex]; + + if($resourceType == 4 && !empty($resArray['vref']) && is_numeric($resArray['vref'])) { + $who = $database->getVillageField($resArray['vref'], "owner"); + $croptrue = $database->getUserField($who, "b4", 0); + if($croptrue > time()) $prod *= 1.25; + } + + return round($prod * SPEED); + } +} diff --git a/GameEngine/Automation/AutomationBuildQueue.php b/GameEngine/Automation/AutomationBuildQueue.php new file mode 100644 index 00000000..b75f5b32 --- /dev/null +++ b/GameEngine/Automation/AutomationBuildQueue.php @@ -0,0 +1,309 @@ +query_return( + "SELECT + id, wid, field, level, type, timestamp + FROM + ".TB_PREFIX."bdata + WHERE + timestamp < $time and master = 0" + ); + + // preload village data + $vilIDs = []; + foreach($res as $indi) { + $vilIDs[$indi['wid']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getProfileVillages($vilIDs, 5); + $database->getEnforceVillage($vilIDs, 0); + + // complete buildings + foreach($res as $indi) { + // store village ID for later for statistical updates + $villageData = $database->getVillageFields($indi['wid'],'owner, maxcrop, maxstore, starv, pop'); + $villageOwner = $villageData['owner']; + $villagesAffected[] = (int) $indi['wid']; + $fieldsToSet = []; + + $q = "UPDATE ".TB_PREFIX."fdata SET f".$indi['field']." = ".$indi['level'].", f".$indi['field']."t = ".$indi['type']." WHERE vref = ".(int) $indi['wid']; + + if($database->query($q)) { + // this will be the level we brought the building to now + $level = $indi['level']; + + // TODO: magic numbers into constants (for building types below) + + // update capacity if we updated a warehouse or a granary + if (in_array($indi['type'], [10, 11, 38, 39])) { + [$fieldDbName, $max] = $this->updateStorageCapacity($indi['type'], $level, $villageData); + $fieldsToSet[$fieldDbName] = $max; + } + + // if we updated Embassy, update maximum members that the alliance can take + if($indi['type'] == 18) Automation::updateMax($villageOwner); + + // World Wonder completion handling (Natar attacks, winner lock, last-upgrade time) + if ($indi['type'] == 40) $this->completeWorldWonder($indi); + + // TODO: find out what exactly these conditions are for + // no special military conditioning for Teutons and Gauls + if ($database->getUserField($villageOwner, "tribe", 0) != 1) $loopconUpdates[$indi['wid']] = ''; + else + { + // special condition for Roman military buildings + if ($indi['field'] > 18) $loopconUpdates[$indi['wid']] = ' AND field > 18'; + else $loopconUpdates[$indi['wid']] = ' AND field < 19'; + } + + $dbIdsToDelete[] = (int) $indi['id']; + } + + //Update starvation data + $database->addStarvationData($indi['wid']); + + // update the requested fields, all at once + $database->setVillageFields($indi['wid'], array_keys($fieldsToSet), array_values($fieldsToSet)); + } + + // update statistical data for affected villages + foreach ($villagesAffected as $affected_id) $this->recountPop($affected_id, false); + + // update data that can be done in one swoop instead of using multiple update queries + // no special checks for Romans + foreach ($loopconUpdates as $villageId => $updateCondition) { + $database->query( + "UPDATE + ".TB_PREFIX."bdata + SET + loopcon = 0 + WHERE + loopcon = 1 AND + master = 0 AND + wid = ".$villageId.$updateCondition); + } + + // delete all processed entries + if (count($dbIdsToDelete)) { + $database->query( "DELETE FROM " . TB_PREFIX . "bdata WHERE id IN(" . implode( ',', $dbIdsToDelete ) . ")" ); + } + } + + /** + * Handle the side effects of completing a World Wonder (type 40) build: + * launch the Natar attack waves, lock out further winners at level 100, + * and record the last upgrade time. + */ + private function completeWorldWonder($indi) { + global $database; + + if (($indi['level'] % 5 == 0 || $indi['level'] > 95) && $indi['level'] != 100) { + $this->startNatarAttack($indi['level'], $indi['wid'], $indi['timestamp']); + } + + //now can't be more than one winner if ww to level 100 is build by 2 users or more on same time + if ($indi['level'] == 100) { + mysqli_query($database->dblink,"TRUNCATE ".TB_PREFIX."bdata"); + } + + // Update ww last finish upgrade + $qW = "UPDATE ".TB_PREFIX."fdata set ww_lastupdate = ".time()." where vref = ".(int) $indi['wid']; + $database->query($qW); + } + + private function researchComplete() { + global $database; + + $time = time(); + $deleteIDs = []; + $tdata = []; + $abdata = []; + + $q = "SELECT tech, vref, id FROM ".TB_PREFIX."research where timestamp < $time"; + $dataarray = $database->query_return($q); + + foreach($dataarray as $data) { + $sort_type = substr($data['tech'],0,1); + switch($sort_type) { + case "t": + if (!isset($tdata[$data['vref']])) $tdata[$data['vref']] = []; + $tdata[$data['vref']][] = $data['tech'].' = 1'; + break; + case "a": + case "b": + if (!isset($abdata[$data['vref']])) $abdata[$data['vref']] = []; + $abdata[$data['vref']][] = $data['tech']." = ".$data['tech']." + 1"; + break; + } + $deleteIDs[] = (int) $data['id']; + } + + // execute queries with consolidated research data + if (count($tdata)) { + foreach ( $tdata as $vid => $preparedData ) { + $q = "UPDATE ".TB_PREFIX."tdata SET ".implode(', ', $preparedData)." WHERE vref = ".$vid; + $database->query($q); + } + } + + if (count($abdata)) { + foreach ( $abdata as $vid => $preparedData ) { + $q = "UPDATE ".TB_PREFIX."abdata SET ".implode(', ', $preparedData)." WHERE vref = ".$vid; + $database->query($q); + } + } + + if (count($deleteIDs)) { + $q = "DELETE FROM " . TB_PREFIX . "research where id IN(" . implode( ', ', $deleteIDs ) . ")"; + $database->query( $q ); + } + } + + private function demolitionComplete() { + global $database; + + $varray = $database->getDemolition(); + foreach($varray as $vil) { + if ($vil['lvl'] < 0) { + $database->delDemolition($vil['vref'], true); + continue; + } + if ($vil['timetofinish'] <= time()) { + $type = $database->getFieldType($vil['vref'],$vil['buildnumber']); + $level = $database->getFieldLevel($vil['vref'],$vil['buildnumber']); + + $newLevel = max(0, $level - 1); + + $buildarray = $GLOBALS["bid".$type]; + + if ($type == 10 || $type == 38) { + $database->query(" + UPDATE ".TB_PREFIX."vdata + SET + `maxstore` = IF(`maxstore` - ".$buildarray[$level]['attri']." <= ".STORAGE_BASE.", ".STORAGE_BASE.", `maxstore` - ".$buildarray[$level]['attri'].") + WHERE + wref=".(int) $vil['vref']); + } + + if ($type == 11 || $type == 39) { + $database->query(" + UPDATE ".TB_PREFIX."vdata + SET + `maxcrop` = IF(`maxcrop` - ".$buildarray[$level]['attri']." <= ".STORAGE_BASE.", ".STORAGE_BASE.", `maxcrop` - ".$buildarray[$level]['attri'].") + WHERE + wref=".(int) $vil['vref']); + } + + if ($level == 1) $clear = ",f".$vil['buildnumber']."t=0"; + else $clear = ""; + + if ($database->getVillageField($vil['vref'], 'natar') == 1 && $type == 40) $clear = ""; //fix by ronix - fixed by iopietro + $q = "UPDATE ".TB_PREFIX."fdata SET f".$vil['buildnumber']."=".$newLevel." ".$clear." WHERE vref=".(int)$vil['vref']; + $database->query($q); + + $pop = $this->getPop($type, $newLevel); + $database->modifyPop($vil['vref'], $pop[0], 1); + $this->procClimbers($database->getVillageField($vil['vref'], 'owner')); + $database->delDemolition($vil['vref'], true); + + if ($type == 18) Automation::updateMax($database->getVillageField($vil['vref'], 'owner')); + } + } + + } + + private function MasterBuilder() { + global $database; + + $q = "SELECT id, wid, type, level, field, timestamp FROM ".TB_PREFIX."bdata WHERE master = 1"; + $array = $database->query_return($q); + + foreach($array as $master) { + $owner = $database->getVillageField($master['wid'], 'owner'); + $tribe = $database->getUserField($owner, 'tribe', 0); + $villwood = $database->getVillageField($master['wid'], 'wood'); + $villclay = $database->getVillageField($master['wid'], 'clay'); + $villiron = $database->getVillageField($master['wid'], 'iron'); + $villcrop = $database->getVillageField($master['wid'], 'crop'); + $type = $master['type']; + $level = $master['level']; + $buildarray = $GLOBALS["bid".$type]; + $buildwood = $buildarray[$level]['wood']; + $buildclay = $buildarray[$level]['clay']; + $buildiron = $buildarray[$level]['iron']; + $buildcrop = $buildarray[$level]['crop']; + $ww = count($database->getBuildingByType($master['wid'], 40)); + + if($tribe == 1){ + if($master['field'] < 19){ + $bdata = $database->getDorf1Building($master['wid']); + $bdataTotal = count($bdata); + $bbdata = count($database->getDorf2Building($master['wid'])); + }else{ + $bdata = $database->getDorf2Building($master['wid']); + $bdataTotal = count($bdata); + $bbdata = count($database->getDorf1Building($master['wid'])); + } + }else{ + $bdata = array_merge($database->getDorf1Building($master['wid']), $database->getDorf2Building($master['wid'])); + $bdataTotal = $bbdata = count($bdata); + } + + if($database->getUserField($owner, 'plus', 0) > time() || $ww > 0){ + if($bbdata < 2) $inbuild = 2; + else $inbuild = 1; + } + else $inbuild = 1; + + $usergold = $database->getUserField($owner, 'gold', 0); + + if($bdataTotal < $inbuild && $buildwood <= $villwood && $buildclay <= $villclay && $buildiron <= $villiron && $buildcrop <= $villcrop && $usergold > 0){ + $time = $master['timestamp'] + time(); + + if(!empty($bdata)){ + foreach($bdata as $masterLoop) $time += ($masterLoop['timestamp'] - time()); + } + + if($bdataTotal == 0) $database->updateBuildingWithMaster($master['id'], $time, 0); + else $database->updateBuildingWithMaster($master['id'], $time, 1); + + $database->updateUserField($owner, 'gold', --$usergold, 1); + $database->modifyResource($master['wid'], $buildwood, $buildclay, $buildiron, $buildcrop, 0); + } + } + } +} diff --git a/GameEngine/Automation/AutomationHero.php b/GameEngine/Automation/AutomationHero.php new file mode 100644 index 00000000..e3a53619 --- /dev/null +++ b/GameEngine/Automation/AutomationHero.php @@ -0,0 +1,212 @@ +processArrivals(); + $adventures->processReturns(); + $adventures->generateOffersBatch(); + + $auctions = new HeroAuction(); + $auctions->processFinished(); + $auctions->seedNpcAuctions(); + } + + private function updateHero() { + global $database, $hero_levels; + + $harray = $database->getHero(); + if(!empty($harray)){ + // first of all, prepare all unit data at once for these heroes + $heroVillageIDs = []; + foreach($harray as $hdata) { + $heroVillageIDs[] = $hdata['wref']; + } + + // load data for those prepared IDs + $unitData = $database->getUnit($heroVillageIDs); + + // now do the math + $lastUpdateIDs = []; + $timeNow = time(); + foreach($harray as $hdata){ + $columns = []; + $columnValues = []; + $modes = []; + $lastUpdateTime = $timeNow; + + // 1. passive HP regeneration + $newHealth = $this->calculateHealthRegen($hdata); + + // 2. level-up + score points + $this->mergeHeroColumns($columns, $columnValues, $modes, $this->calculateLevelUp($hdata)); + + // 3. revive completion (forces health to 100 and stamps the update time) + $revive = $this->handleReviveCompletion($hdata); + $this->mergeHeroColumns($columns, $columnValues, $modes, $revive); + if ($revive['health'] !== null) $newHealth = $revive['health']; + if ($revive['lastUpdate'] !== null) $lastUpdateTime = $revive['lastUpdate']; + + // 4. training completion (does not touch health) + $training = $this->handleTrainingCompletion($hdata); + $this->mergeHeroColumns($columns, $columnValues, $modes, $training); + if ($training['lastUpdate'] !== null) $lastUpdateTime = $training['lastUpdate']; + + // update health, if needed + if ($newHealth > -1) { + $columns[] = 'health'; + $columnValues[] = $newHealth; + $modes[] = null; + } + + if ($lastUpdateTime != $timeNow) { + // last update timestamp + $columns[] = 'lastupdate'; + $columnValues[] = $lastUpdateTime; + $modes[] = null; + } else { + // leave same last update values for multiple heroes to the end + $lastUpdateIDs[] = $hdata['heroid']; + } + + if (count($columns)) $database->modifyHero($columns, $columnValues, $hdata['heroid'], $modes); + } + + if (count($lastUpdateIDs)) { + mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "hero SET lastupdate = $timeNow WHERE heroid IN(".implode(', ', $lastUpdateIDs).")"); + } + } + } + + // Append a hero column fragment (['columns'=>[], 'values'=>[], 'modes'=>[]]) + // onto the running modifyHero() arrays, preserving order. + private function mergeHeroColumns(&$columns, &$columnValues, &$modes, $fragment) { + foreach ($fragment['columns'] as $k => $col) { + $columns[] = $col; + $columnValues[] = $fragment['values'][$k]; + $modes[] = $fragment['modes'][$k]; + } + } + + // Passive HP regeneration: returns the new health value, or -1 if unchanged. + private function calculateHealthRegen($hdata) { + if((time()-$hdata['lastupdate']) >= 1){ + if($hdata['health'] < 100 and $hdata['health'] > 0){ + if(SPEED <= 10) $speed = SPEED; + else if(SPEED <= 100) $speed = ceil(SPEED / 10); + else $speed = ceil(SPEED / 100); + + $reg = $hdata['health'] + $hdata['regeneration'] * 5 * $speed / 86400 * (time() - $hdata['lastupdate']); + + return ($reg <= 100) ? $reg : 100; + } + } + return -1; + } + + // Level-up + score points. Returns a column fragment. + private function calculateLevelUp($hdata) { + global $hero_levels; + + $columns = []; + $values = []; + $modes = []; + + $herolevel = $hdata['level']; + $newLevel = - 1; + $scorePoints = false; + for ($i = $herolevel + 1; $i < 100; $i++){ + if($hdata['experience'] >= $hero_levels[$i]){ + $newLevel = $i; + if ($i < 99) $scorePoints = true; + } + } + + // upgrade hero to a new level, if needed + if ($newLevel > -1) { + $columns[] = 'level'; + $values[] = $newLevel; + $modes[] = null; + } + + // add as many points as needed, if we're below level 100 + if ($scorePoints) { + $columns[] = 'points'; + $values[] = (5 * ($newLevel - $herolevel)); + $modes[] = 1; + } + + return ['columns' => $columns, 'values' => $values, 'modes' => $modes, 'health' => null, 'lastUpdate' => null]; + } + + // Revive completion: marks the hero unit alive and clears the revive flag. + // Returns a column fragment plus the health (100) and lastUpdate overrides. + private function handleReviveCompletion($hdata) { + global $database; + + if($hdata['trainingtime'] < time() && $hdata['inrevive'] == 1){ + mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "units SET hero = 1 WHERE vref = ".(int) $hdata['wref'].""); + + return [ + 'columns' => ['dead', 'inrevive', 'inrevive'], + 'values' => [0, 0, 0], + 'modes' => [null, null, null], + 'health' => 100, + 'lastUpdate' => (int) $hdata['trainingtime'], + ]; + } + + return ['columns' => [], 'values' => [], 'modes' => [], 'health' => null, 'lastUpdate' => null]; + } + + // Training completion: marks the hero unit alive and clears the training flag. + // Returns a column fragment plus the lastUpdate override (health untouched). + private function handleTrainingCompletion($hdata) { + global $database; + + if($hdata['trainingtime'] < time() && $hdata['intraining'] == 1){ + mysqli_query($database->dblink,"UPDATE " . TB_PREFIX . "units SET hero = 1 WHERE vref = ".(int) $hdata['wref']); + + return [ + 'columns' => ['dead', 'intraining'], + 'values' => [0, 0], + 'modes' => [null, null], + 'health' => null, + 'lastUpdate' => (int) $hdata['trainingtime'], + ]; + } + + return ['columns' => [], 'values' => [], 'modes' => [], 'health' => null, 'lastUpdate' => null]; + } +} diff --git a/GameEngine/Automation/AutomationMarket.php b/GameEngine/Automation/AutomationMarket.php new file mode 100644 index 00000000..084f9eb9 --- /dev/null +++ b/GameEngine/Automation/AutomationMarket.php @@ -0,0 +1,179 @@ +delTradeRoute(); + } + + private function TradeRoute() { + global $database; + $time = time(); + $q = "SELECT `from`, wood, clay, iron, crop, wid, deliveries, id FROM ".TB_PREFIX."route where timestamp < $time"; + $dataarray = $database->query_return($q); + + $vilIDs = []; + foreach($dataarray as $data) { + $vilIDs[$data['wid']] = true; + $vilIDs[$data['from']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getVillageByWorldID($vilIDs); + + foreach($dataarray as $data) { + $targettribe = $database->getUserField($database->getVillageField($data['from'], "owner"), "tribe", 0); + $this->sendResource2($data['wood'], $data['clay'], $data['iron'], $data['crop'], $data['from'], $data['wid'], $targettribe, $data['deliveries']); + $database->editTradeRoute($data['id'], "timestamp", 86400, 1); + } + } + + private function marketComplete() { + // Two independent phases share a single cutoff timestamp: sort_type = 0 + // new trade deliveries, then sort_type = 2 resending of resources. + $time = microtime(true); + $this->marketCompleteDeliveries($time); + $this->marketCompleteResends($time); + } + + private function marketCompleteDeliveries($time) { + global $database, $units; + + $q = "SELECT s.wood, s.clay, s.iron, s.crop, `to`, `from`, endtime, merchant, send, moveid FROM ".TB_PREFIX."movement m, ".TB_PREFIX."send s WHERE m.ref = s.id AND m.proc = 0 AND sort_type = 0 AND endtime < $time"; + $dataarray = $database->query_return($q); + + foreach($dataarray as $data) { + $userData_from = $database->getUserFields($database->getVillageField($data['from'], "owner"), "alliance, tribe", 0); + $userData_to = $database->getUserFields($database->getVillageField($data['to'], "owner"), "alliance, tribe", 0); + + if($data['wood'] >= $data['clay'] && $data['wood'] >= $data['iron'] && $data['wood'] >= $data['crop']) $sort_type = 10; + elseif($data['clay'] >= $data['wood'] && $data['clay'] >= $data['iron'] && $data['clay'] >= $data['crop']) $sort_type = 11; + elseif($data['iron'] >= $data['wood'] && $data['iron'] >= $data['clay'] && $data['iron'] >= $data['crop']) $sort_type = 12; + elseif($data['crop'] >= $data['wood'] && $data['crop'] >= $data['clay'] && $data['crop'] >= $data['iron']) $sort_type = 13; + + $to = $database->getMInfo($data['to']); + $from = $database->getMInfo($data['from']); + + $ownally = $userData_from['alliance']; + $targetally = $userData_to['alliance']; + + // Report filter preferences (#198): skip merchant-transfer notices + // according to the saved checkboxes of the involved players. + // v4 -> recipient: no report for transfers to own villages + // v6 -> recipient: no report for transfers from foreign villages + // v5 -> sender: no report for transfers to foreign villages + $ownTransfer = ($from['owner'] == $to['owner']); + $skipRecipient = $ownTransfer ? !empty($userData_to['v4']) : !empty($userData_to['v6']); + if(!$skipRecipient) { + $database->addNotice($to['owner'],$to['wref'],$targetally,$sort_type,''.addslashes($from['name']).' send resources to '.addslashes($to['name']).'',''.$from['owner'].','.$from['wref'].','.$data['wood'].','.$data['clay'].','.$data['iron'].','.$data['crop'].'',$data['endtime']); + } + if(!$ownTransfer && empty($userData_from['v5'])) { + $database->addNotice($from['owner'],$to['wref'],$ownally,$sort_type,''.addslashes($from['name']).' send resources to '.addslashes($to['name']).'',''.$from['owner'].','.$from['wref'].','.$data['wood'].','.$data['clay'].','.$data['iron'].','.$data['crop'].'',$data['endtime']); + } + $database->modifyResource($data['to'],$data['wood'],$data['clay'],$data['iron'],$data['crop'],1); + + // Push protection: record completed cross-player deliveries so the + // admin dashboard can compute 7-day resource balance. Best-effort; + // skips own-village transfers internally. + if (!$ownTransfer) { + PushProtection::logTransfer( + (int)$from['wref'], (int)$to['wref'], + (int)$from['owner'], (int)$to['owner'], + (int)$data['wood'], (int)$data['clay'], (int)$data['iron'], (int)$data['crop'], + (int)$data['endtime'] + ); + } + $targettribe = $userData_to["tribe"]; + $endtime = $units->getWalkingTroopsTime($data['from'], $data['to'], 0, 0, [$targettribe], 0) + $data['endtime']; + $database->addMovement(2, $data['to'], $data['from'], $data['merchant'], time(), $endtime, $data['send'], $data['wood'], $data['clay'], $data['iron'], $data['crop']); + $database->setMovementProc($data['moveid']); + } + } + + private function marketCompleteResends($time) { + global $database; + + $q1 = "SELECT send, moveid, `to`, wood, clay, iron, crop, `from` FROM ".TB_PREFIX."movement WHERE proc = 0 and sort_type = 2 and endtime < $time"; + $dataarray1 = $database->query_return($q1); + + $vilIDs = []; + foreach($dataarray1 as $data1) { + $vilIDs[$data1['to']] = true; + $vilIDs[$data1['from']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getVillageByWorldID($vilIDs); + + foreach($dataarray1 as $data1) { + $database->setMovementProc($data1['moveid']); + if($data1['send'] > 1){ + $targettribe1 = $database->getUserFields($database->getVillageField($data1['to'],"owner"),"alliance, tribe",0)['tribe']; + $send = $data1['send']-1; + $this->sendResource2($data1['wood'],$data1['clay'],$data1['iron'],$data1['crop'],$data1['to'],$data1['from'],$targettribe1,$send); + } + } + } + + private function sendResource2($wtrans, $ctrans, $itrans, $crtrans, $from, $to, $tribe, $send) { + global $bid17, $bid28, $database, $units; + + $availableWood = $database->getWoodAvailable($from); + $availableClay = $database->getClayAvailable($from); + $availableIron = $database->getIronAvailable($from); + $availableCrop = $database->getCropAvailable($from); + + if($availableWood + $availableClay + $availableIron + $availableCrop > 0) + { + if($availableWood < $wtrans) $wtrans = $availableWood; + if($availableClay < $ctrans) $ctrans = $availableClay; + if($availableIron < $itrans) $itrans = $availableIron; + if($availableCrop < $crtrans) $crtrans = $availableCrop; + + $merchant2 = ($this->getTypeLevel(17, $from) > 0)? $this->getTypeLevel(17, $from) : 0; + $used2 = $database->totalMerchantUsed($from, false); + $merchantAvail2 = $merchant2 - $used2; + $carrymap = array(1 => 500, 2 => 1000, 3 => 750, 6 => 500, 7 => 750, 8 => 500, 9 => 750); + $maxcarry2 = isset($carrymap[$tribe]) ? $carrymap[$tribe] : 750; + $maxcarry2 *= TRADER_CAPACITY; + + if($this->getTypeLevel(28, $from) != 0) { + $maxcarry2 *= $bid28[$this->getTypeLevel(28, $from)]['attri'] / 100; + } + + $resource = [$wtrans, $ctrans, $itrans, $crtrans]; + $reqMerc = ceil((array_sum($resource) - 0.1) / $maxcarry2); + + if($merchantAvail2 > 0 && $reqMerc <= $merchantAvail2) { + if($database->getVillageState($to)) { + $timetaken = $units->getWalkingTroopsTime($from, $to, 0, 0, [$tribe], 0); + $res = $resource[0] + $resource[1] + $resource[2] + $resource[3]; + if($res > 0){ + $reference = $database->sendResource($resource[0], $resource[1], $resource[2], $resource[3], $reqMerc, 0); + $database->modifyResource($from, $resource[0], $resource[1], $resource[2], $resource[3], 0); + $database->addMovement(0, $from, $to, $reference, microtime(true), microtime(true) + $timetaken, $send); + } + } + } + } + } +} diff --git a/GameEngine/Automation/AutomationMedals.php b/GameEngine/Automation/AutomationMedals.php new file mode 100644 index 00000000..8f658886 --- /dev/null +++ b/GameEngine/Automation/AutomationMedals.php @@ -0,0 +1,228 @@ +shouldAwardMedalsNow(); + if ($time === null) { + return; + } + + // Exclude BANNED (0), MH (8), ADMIN (9) + $userFilter = "id > 5 AND access NOT IN (0,8,9)"; + + $week = $this->getNextMedalWeek('medal'); + $allyweek = $this->getNextMedalWeek('allimedal'); + + $this->awardPlayerMedals($week, $userFilter); + $this->resetWeeklyStats('users', $userFilter, "ap=0, dp=0, Rc=0, clp=0, RR=0"); + $this->awardAllianceMedals($allyweek); + $this->resetWeeklyStats('alidata', '', "ap=0, dp=0, RR=0, clp=0"); + + // Update last awarded time + $database->query("UPDATE ".TB_PREFIX."config SET lastgavemedal=".(int)$time); + } + + // Returns the next "lastgavemedal" stamp to write if medals are due now, or + // null to skip this run. On the very first run after server start it only + // schedules the next run (and returns null). + private function shouldAwardMedalsNow() { + global $database; + + $giveMedal = false; + $time = null; + $q = "SELECT lastgavemedal FROM ".TB_PREFIX."config"; + $result = mysqli_query($database->dblink, $q); + if ($result) { + $row = mysqli_fetch_assoc($result); + $stime = strtotime(START_DATE) - strtotime(date('d.m.Y')) + strtotime(START_TIME); + + if ($row['lastgavemedal'] == 0 && $stime < time()) { + // First run after server start - schedule next run + $setDays = round(MEDALINTERVAL / 86400); + $newtime = $setDays < 7? strtotime(($setDays + 1).' day midnight') : strtotime('next monday'); + $database->query("UPDATE ".TB_PREFIX."config SET lastgavemedal = ".(int)$newtime); + } elseif ($row['lastgavemedal']!= 0) { + $time = $row['lastgavemedal'] + MEDALINTERVAL; + $giveMedal = $row['lastgavemedal'] < time(); + } + } + + if (!($giveMedal && MEDALINTERVAL > 0)) { + return null; + } + return $time; + } + + // Next week number for a medal table (medal / allimedal). + private function getNextMedalWeek($table) { + global $database; + + $q = "SELECT week FROM ".TB_PREFIX."$table ORDER BY week DESC LIMIT 1"; + $res = mysqli_query($database->dblink, $q); + return $res && mysqli_num_rows($res)? (mysqli_fetch_assoc($res)['week'] + 1) : 1; + } + + // Insert a user medal row. + private function insertUserMedal($userid, $category, $place, $week, $points, $img) { + global $database; + + $q = "INSERT INTO ".TB_PREFIX."medal (userid, categorie, plaats, week, points, img) VALUES (". + (int)$userid.", ".(int)$category.", ".(int)$place.", ".(int)$week.", '".mysqli_real_escape_string($database->dblink, $points)."', '".mysqli_real_escape_string($database->dblink, $img)."')"; + mysqli_query($database->dblink, $q); + } + + // Insert an alliance medal row. + private function insertAllianceMedal($allyid, $cat, $place, $week, $points, $img) { + global $database; + + $q = "INSERT INTO ".TB_PREFIX."allimedal (allyid, categorie, plaats, week, points, img) VALUES (".(int)$allyid.", '".(int)$cat."', ".(int)$place.", '".(int)$week."', '".mysqli_real_escape_string($database->dblink, $points)."', '".mysqli_real_escape_string($database->dblink, $img)."')"; + mysqli_query($database->dblink, $q); + } + + // Award the top 10 users for a stat field (one category). + private function awardTopMedals($field, $category, $imgPrefix, $week, $userFilter) { + global $database; + + $q = "SELECT id, $field FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY $field DESC, id DESC LIMIT 10"; + $res = mysqli_query($database->dblink, $q); + $i = 0; + while ($row = mysqli_fetch_array($res)) { + $i++; + $this->insertUserMedal($row['id'], $category, $i, $week, $row[$field], $imgPrefix.$i); + } + } + + // Award milestone ribbons for 3/5/10 appearances in a top3 or top10 category. + private function awardMilestoneMedals($field, $sourceCat, $topLimit, $targetCat, $imgBase, $week, $userFilter) { + global $database; + + $res = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY $field DESC, id DESC LIMIT 10"); + while ($u = mysqli_fetch_array($res)) { + $cnt = mysqli_fetch_row(mysqli_query($database->dblink, + "SELECT COUNT(*) FROM ".TB_PREFIX."medal WHERE userid=".(int)$u['id']." AND categorie=".(int)$sourceCat." AND plaats<=".(int)$topLimit))[0]; + + $map = ['3' => ['Three', $imgBase.'0_1'], '5' => ['Five', $imgBase.'1_1'], '10' => ['Ten', $imgBase.'2_1']]; + if (isset($map[$cnt])) { + $this->insertUserMedal($u['id'], $targetCat, 0, $week, $map[$cnt][0], $map[$cnt][1]); + } + } + } + + // Player medals: top 10 of each category, the attack+defense bonus, then milestones. + private function awardPlayerMedals($week, $userFilter) { + global $database; + + // Top 10 for each category + $this->awardTopMedals('ap', 1, 't2_', $week, $userFilter); // Attackers of the week (cat 1) + $this->awardTopMedals('dp', 2, 't3_', $week, $userFilter); // Defenders of the week (cat 2) + $this->awardTopMedals('Rc', 3, 't1_', $week, $userFilter); // Climbers of the week (cat 3) + $this->awardTopMedals('clp', 10, 't6_', $week, $userFilter); // Rank climbers of the week (cat 10) + $this->awardTopMedals('RR', 4, 't4_', $week, $userFilter); // Robbers of the week (cat 4) + + // --- Bonus: player in both top10 attack AND defense (cat 5) --- + $topAttackers = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY ap DESC, id DESC LIMIT 10"); + while ($a = mysqli_fetch_array($topAttackers)) { + $topDefenders = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."users WHERE $userFilter ORDER BY dp DESC, id DESC LIMIT 10"); + while ($d = mysqli_fetch_array($topDefenders)) { + if ($a['id'] == $d['id']) { + $cnt = mysqli_fetch_row(mysqli_query($database->dblink, "SELECT COUNT(*) FROM ".TB_PREFIX."medal WHERE userid=".(int)$a['id']." AND categorie=5"))[0]; + if ($cnt <= 2) { + $texts = [0 => '', 1 => 'twice ', 2 => 'three times ']; + $this->insertUserMedal($a['id'], 5, 0, $week, $texts[$cnt], 't22'.$cnt.'_1'); + } + } + } + } + + // --- Milestone ribbons for 3/5/10 times in top3 or top10 --- + // Attackers milestones + $this->awardMilestoneMedals('ap', 1, 3, 6, 't12', $week, $userFilter); // top3 attackers + $this->awardMilestoneMedals('ap', 1, 10, 12, 't13', $week, $userFilter); // top10 attackers + // Defenders milestones + $this->awardMilestoneMedals('dp', 2, 3, 7, 't14', $week, $userFilter); + $this->awardMilestoneMedals('dp', 2, 10, 13, 't15', $week, $userFilter); + // Climbers milestones + $this->awardMilestoneMedals('Rc', 3, 3, 8, 't10', $week, $userFilter); + $this->awardMilestoneMedals('Rc', 3, 10, 14, 't11', $week, $userFilter); + // Rank climbers milestones + $this->awardMilestoneMedals('clp', 10, 3, 11, 't20', $week, $userFilter); + $this->awardMilestoneMedals('clp', 10, 10, 16, 't21', $week, $userFilter); + // Robbers milestones + $this->awardMilestoneMedals('RR', 4, 3, 9, 't16', $week, $userFilter); + $this->awardMilestoneMedals('RR', 4, 10, 15, 't17', $week, $userFilter); + } + + // Alliance medals: top 10 of each category, then the attack+defense bonus. + private function awardAllianceMedals($allyweek) { + global $database; + + $allyCats = [ + ['ap', 1, 'a2_'], + ['dp', 2, 'a3_'], + ['RR', 4, 'a4_'], + ['clp', 3, 'a1_'] + ]; + foreach ($allyCats as [$field, $cat, $img]) { + $res = mysqli_query($database->dblink, "SELECT id, $field FROM ".TB_PREFIX."alidata ORDER BY $field DESC, id DESC LIMIT 10"); + $i = 0; + while ($r = mysqli_fetch_array($res)) { $i++; $this->insertAllianceMedal($r['id'], $cat, $i, $allyweek, $r[$field], $img.$i); } + } + + // Alliance bonus for attack+defense + $resA = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."alidata ORDER BY ap DESC, id DESC LIMIT 10"); + while ($a = mysqli_fetch_array($resA)) { + $resD = mysqli_query($database->dblink, "SELECT id FROM ".TB_PREFIX."alidata ORDER BY dp DESC, id DESC LIMIT 10"); + while ($d = mysqli_fetch_array($resD)) { + if ($a['id'] == $d['id']) { + $cnt = mysqli_fetch_row(mysqli_query($database->dblink, "SELECT COUNT(*) FROM ".TB_PREFIX."allimedal WHERE allyid=".(int)$a['id']." AND categorie=5"))[0]; + if ($cnt <= 2) { + $texts = [0 => '', 1 => 'twice ', 2 => 'three times ']; + $this->insertAllianceMedal($a['id'], 5, 0, $allyweek, $texts[$cnt], 't22'.$cnt.'_1'); + } + } + } + } + } + + // Reset the weekly stat columns for every id in a table (optionally filtered). + private function resetWeeklyStats($table, $where, $setClause) { + global $database; + + $ids = []; + $sql = "SELECT id FROM ".TB_PREFIX.$table; + if ($where !== '') $sql .= " WHERE ".$where; + $res = mysqli_query($database->dblink, $sql); + while ($r = mysqli_fetch_row($res)) { $ids[] = (int)$r[0]; } + if ($ids) { + mysqli_query($database->dblink, "UPDATE ".TB_PREFIX.$table." SET ".$setClause." WHERE id IN(".implode(',', $ids).")"); + } + } +} diff --git a/GameEngine/Automation/AutomationNatarsWW.php b/GameEngine/Automation/AutomationNatarsWW.php new file mode 100644 index 00000000..cffd347a --- /dev/null +++ b/GameEngine/Automation/AutomationNatarsWW.php @@ -0,0 +1,215 @@ +getNatarTroopTable(); + if (isset($troops[$level])) $units = $troops[$level]; + else return false; + + // get the capital village from the natars + $query = mysqli_query($database->dblink,'SELECT `wref` FROM `' . TB_PREFIX . 'vdata` WHERE `owner` = 3 and `capital` = 1 LIMIT 1') or die(mysqli_error($database->dblink)); + $row = mysqli_fetch_assoc($query); + + // start the attacks + $endtime = $time + round(86400 / INCREASE_SPEED); + + // -.- + $vid = (int) $vid; + mysqli_query($database->dblink,'INSERT INTO `' . TB_PREFIX . 'ww_attacks` (`vid`, `attack_time`) VALUES (' . $vid . ', ' . $endtime . ')'); + mysqli_query($database->dblink,'INSERT INTO `' . TB_PREFIX . 'ww_attacks` (`vid`, `attack_time`) VALUES (' . $vid . ', ' . ($endtime + 1) . ')'); + + // two waves: the second one targets the WW (catapult target 40) one second later + $this->launchNatarWave($row['wref'], $vid, $units[0], 0, $time, $endtime); + $this->launchNatarWave($row['wref'], $vid, $units[1], 40, $time, $endtime + 1); + } + + /** + * Fire a single Natar attack wave at a World Wonder village. + * $unitRow is one troop row [t2, t3, t5, t6, t7, t8]; $ctar1 is the + * catapult target slot (0 for the first wave, 40 for the second). + */ + private function launchNatarWave($source, $vid, $unitRow, $ctar1, $time, $arrival) { + global $database; + + $ref = $database->addAttack($source, 0, $unitRow[0], $unitRow[1], 0, $unitRow[2], $unitRow[3], $unitRow[4], $unitRow[5], 0, 0, 0, 3, $ctar1, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20); + $database->addMovement(3, $source, $vid, $ref, $time, $arrival); + } + + /** + * Hard-coded Natar offensive waves indexed by World Wonder level. + * Each level holds two waves, each a troop row [t2, t3, t5, t6, t7, t8]. + */ + private function getNatarTroopTable() { + return [5 => [[3412, 2814, 4156, 3553, 9, 0], [35, 0, 77, 33, 17, 10]], + 10 => [[4314, 3688, 5265, 4621, 13, 0], [65, 0, 175, 77, 28, 17]], + 15 => [[4645, 4267, 5659, 5272, 15, 0], [99, 0, 305, 134, 40, 25]], + 20 => [[6207, 5881, 7625, 7225, 22, 0], [144, 0, 456, 201, 56, 36]], + 25 => [[6004, 5977, 7400, 7277, 23, 0], [152, 0, 499, 220, 58, 37]], + 30 => [[7073, 7181, 8730, 8713, 27, 0], [183, 0, 607, 268, 69, 45]], + 35 => [[7090, 7320, 8762, 8856, 28, 0], [186, 0, 620, 278, 70, 45]], + 40 => [[7852, 6967, 9606, 8667, 25, 0], [146, 0, 431, 190, 60, 37]], + 45 => [[8480, 8883, 10490, 10719, 35, 0], [223, 0, 750, 331, 83, 54]], + 50 => [[8522, 9038, 10551, 10883, 35, 0], [224, 0, 757, 335, 83, 54]], + 55 => [[8931, 8690, 10992, 10624, 32, 0], [219, 0, 707, 312, 84, 54]], + 60 => [[12138, 13013, 15040, 15642, 51, 0], [318, 0, 1079, 477, 118, 76]], + 65 => [[13397, 14619, 16622, 17521, 58, 0], [345, 0, 1182, 522, 127, 83]], + 70 => [[16323, 17665, 20240, 21201, 70, 0], [424, 0, 1447, 640, 157, 102]], + 75 => [[20739, 22796, 25746, 27288, 91, 0], [529, 0, 1816, 803, 194, 127]], + 80 => [[21857, 24180, 27147, 28914, 97, 0], [551, 0, 1898, 839, 202, 132]], + 85 => [[22476, 25007, 27928, 29876, 100, 0], [560, 0, 1933, 855, 205, 134]], + 90 => [[31345, 35053, 38963, 41843, 141, 0], [771, 0, 2668, 1180, 281, 184]], + 95 => [[31720, 35635, 39443, 42506, 144, 0], [771, 0, 2671, 1181, 281, 184]], + 96 => [[32885, 37007, 40897, 44130, 150, 0], [795, 0, 2757, 1219, 289, 190]], + 97 => [[32940, 37099, 40968, 44235, 150, 0], [794, 0, 2755, 1219, 289, 190]], + 98 => [[33521, 37691, 41686, 44953, 152, 0], [812, 0, 2816, 1246, 296, 194]], + 99 => [[36251, 40861, 45089, 48714, 165, 0], [872, 0, 3025, 1338, 317, 208]]]; + } + + private function checkWWAttacks() { + global $database; + + $query = mysqli_query($database->dblink,'SELECT vid, attack_time FROM `' . TB_PREFIX . 'ww_attacks` WHERE `attack_time` <= ' . time()); + while ($row = mysqli_fetch_assoc($query)) + { + // delete the attack + $query3 = mysqli_query($database->dblink,'DELETE FROM `' . TB_PREFIX . 'ww_attacks` WHERE `vid` = ' . (int) $row['vid'] . ' AND `attack_time` = ' . (int) $row['attack_time']); + } + } + + /** + * Create the Natars account and spawn artifacts + * + */ + + private function spawnNatars(){ + global $database; + + //Check if Natars account is already created and if the time + //is come and we have to create Natars and spawn their artifacts + if($database->areArtifactsSpawned() || strtotime(START_DATE) + (NATARS_SPAWN_TIME * 86400) > time()) return; + + //Create the Natars account and his capital + $this->artifacts->createNatars(); + + //Write the system message + $database->displaySystemMessage(ARTEFACT); + } + + /** + * Spawn WW Villages + * + */ + + private function spawnWWVillages(){ + global $database; + + //Check if Natars account has already been created, if WW villages have already been spawned + //and if it's the time to spawn them or not + if(!$database->areArtifactsSpawned() || $database->areWWVillagesSpawned() || strtotime(START_DATE) + (NATARS_WW_SPAWN_TIME * 86400) > time()) return; + + //Create WW villages + $this->artifacts->createWWVillages(); + + //Write the system message + $database->displaySystemMessage(WWVILLAGEMSG); + } + + /** + * Spawn WW Building plans + * + */ + + private function spawnWWBuildingPlans(){ + global $database; + + //Check if Natars account is already spawned, if WW building plans have already been spawned + //and if it's the time to spawn them or not + if(!$database->areArtifactsSpawned() || $database->areArtifactsSpawned(true) || strtotime(START_DATE) + (NATARS_WW_BUILDING_PLAN_SPAWN_TIME * 86400) > time()) return; + + //Create WW building plans + $this->artifacts->createWWBuildingPlans(); + + //Set the system message to contain the infos of the WW building plans + $database->displaySystemMessage(PLAN_INFO); + } + + /** + * Automatically activate all artifacts that need to be activated + * + */ + + private function activateArtifacts() { + global $database; + + //Check if there's at least one artifact, if not, return + if(!$database->areArtifactsSpawned()) return; + + //Activate the artifacts that need to be activated + $this->artifacts->activateArtifacts(); + } + + private function artefactOfTheFool() { + global $database; + + $time = time(); + $q = "SELECT id, size FROM " . TB_PREFIX . "artefacts where type = 8 AND active = 1 AND del = 0 AND lastupdate <= ".($time - (86400 / (SPEED == 2 ? 1.5 : (SPEED == 3 ? 2 : SPEED)))); + $array = $database->query_return($q); + if ($array) { + foreach($array as $artefact) { + $kind = rand(1, 7); + + while($kind == 6) $kind = rand(1, 7); + + if($artefact['size'] != 3) $bad_effect = rand(0, 1); + else $bad_effect = 0; + + switch($kind) { + case 1: + $effect = rand(1, 5); + break; + case 2: + $effect = rand(1, 3); + break; + case 3: + $effect = rand(3, 10); + break; + case 4: + case 5: + $effect = rand(2, 4); + break; + case 7: + $effect = rand(1, 6); + break; + } + mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."artefacts SET kind = ". (int) $kind. ", bad_effect = $bad_effect, effect2 = $effect, lastupdate = $time WHERE id = ".(int) $artefact['id']); + } + } + } +} diff --git a/GameEngine/Automation/AutomationStarvation.php b/GameEngine/Automation/AutomationStarvation.php new file mode 100644 index 00000000..c680f1da --- /dev/null +++ b/GameEngine/Automation/AutomationStarvation.php @@ -0,0 +1,290 @@ +starvationUpdateAllVillages(); + + // 2. Load villages with negative production. + $starvarray = $this->starvationGetVillagesWithDeficit(); + if (empty($starvarray)) return; + + $vilIDs = array_column($starvarray, 'wref'); + + // 3. Prepare caches to reduce queries. + $this->starvationPrepareCaches($vilIDs); + + foreach ($starvarray as $starv) { + $this->starvationProcessVillage($starv, $time); + } + } + + /** + * Update the starvation table for all villages. + **/ + + private function starvationUpdateAllVillages() { + global $database; + $starvarray = $database->getProfileVillages(0, 7); + foreach($starvarray as $starv) { + $database->addStarvationData($starv['wref']); + } + } + + /** + * Return villages with crop deficit. + **/ + + private function starvationGetVillagesWithDeficit() { + global $database; + return $database->getStarvation(); + } + + /** + * Prepare troop caches. + **/ + + 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); + } + + /** + * Process a single village for starvation. + **/ + + private function starvationProcessVillage($starv, $time) { + global $database, $technology; + + $unitarrays = $this->getAllUnits($starv['wref']); + // Original upkeep formula + $upkeep = $starv['pop'] + $technology->getUpkeep($unitarrays, 0, $starv['wref']); + + $troopData = $this->starvationFindFirstTroopGroup($starv); + if (empty($troopData['troops'])) { + // No troops exist, only check reset. + $this->starvationCheckReset($starv, $upkeep); + return; + } + + $starvingTroops = $troopData['troops']; + $type = $troopData['type']; + $subtype = $troopData['subtype']; + + $timedif = $time - $starv['starvupdate']; + $cropProd = $database->getCropProdstarv($starv['wref']) - $starv['starv']; + + if($cropProd < 0){ + // Deficit calculation + $starvsec = (abs($cropProd) / 3600); + $difcrop = ($timedif * $starvsec); + $oldcrop = $database->getVillageField($starv['wref'], 'crop'); + + // Use granary crop first for consumption. + 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){ + $this->starvationKillTroops($starv, $starvingTroops, $type, $subtype, $difcrop, $upkeep, $time); + } + } + + $this->starvationCheckReset($starv, $upkeep); + } + + /** + * Locate the first troop group eligible for starvation. + * Processing order: oasis enforcement → village enforcement → prisoners → own units → attacks. + **/ + + 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]; + } + + /** + * Kill troops according to crop deficit. + **/ + + 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; + // Original per-unit crop consumption formula unchanged. + $unitIndex = $special ? $maxtype + ($tribe - 1) * 10 : $maxtype; + $difcrop -= $GLOBALS['u'.$unitIndex]['crop']; + } else break; + } + + $totalKilledUnits = array_sum($killedUnits); + $newCrop = 0; + + // Determine whether the hero dies. + 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); + } + } + + /** + * Apply starvation changes to the database. + **/ + + 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: // own units + $database->modifyUnit($starv['wref'], array_keys($killedUnits), array_values($killedUnits), [0]); + break; + case 3: // moving attacks. + 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]); + } + + /** + * Verify whether starvation reset is allowed. + **/ + + 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/Automation/AutomationTraining.php b/GameEngine/Automation/AutomationTraining.php new file mode 100644 index 00000000..f0617d54 --- /dev/null +++ b/GameEngine/Automation/AutomationTraining.php @@ -0,0 +1,177 @@ +getFieldLevelInVillage($vref, '46'); + $bighospital = $database->getFieldLevelInVillage($vref, '48'); + if($hospital <= 0 && $bighospital <= 0) return; + + $share = $bighospital > 0 ? 0.50 : 0.40; + $capacity = $bighospital > 0 ? $bighospital * 60 : $hospital * 30; + + $stored = 0; + $current = $database->getWounded($vref); + if(!empty($current)) { + for($i = 1; $i <= 90; $i++) $stored += isset($current['u'.$i]) ? (int)$current['u'.$i] : 0; + } + $free = $capacity - $stored; + if($free <= 0) return; + + $units = []; $amounts = []; + foreach($deadByUnit as $i => $deadAmt) { + if(empty($deadAmt)) continue; + $wounded = (int)floor($deadAmt * $share); + if($wounded <= 0) continue; + if($wounded > $free) $wounded = $free; + $units[] = $i; + $amounts[] = $wounded; + $free -= $wounded; + if($free <= 0) break; + } + if(!empty($units)) $database->addWounded($vref, $units, $amounts); + } + + /** + * Proceseaza coada de vindecare: unitatile vindecate se intorc in sat + * incremental (una la fiecare 'eachtime' secunde), ca la antrenare. + */ + private function healingComplete() { + global $database; + + $time = time(); + $healinglist = $database->getHealingDue($time); + foreach($healinglist as $heal) { + $healed = 1; + if($heal['eachtime'] > 0) { + $healed += (int)floor(($time - $heal['timestamp2']) / $heal['eachtime']); + } + if($healed > $heal['amt']) $healed = (int)$heal['amt']; + if($healed <= 0) continue; + + $database->modifyUnit($heal['vref'], [$heal['unit']], [$healed], [1]); + + $remaining = (int)$heal['amt'] - $healed; + if($remaining <= 0) { + $database->deleteHealing($heal['id']); + } else { + $database->updateHealing($heal['id'], $remaining, (int)$heal['timestamp2'] + $healed * (int)$heal['eachtime']); + } + } + } + + private function trainingComplete() { + global $database, $technology; + + $time = time(); + $trainlist = $database->getTrainingList(); + if(count($trainlist) > 0){ + // preload village data + $vilIDs = []; + foreach($trainlist as $train){ + $vilIDs[$train['vref']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getProfileVillages($vilIDs, 5); + $database->cacheResourceLevels($vilIDs); + $database->getUnit($vilIDs); + $database->getEnforceVillage($vilIDs, 0 ); + $database->getMovement(3, $vilIDs, 0); + $database->getMovement(4, $vilIDs, 1); + $database->getMovement(5, $vilIDs, 0); + $database->getOasisEnforce($vilIDs, 0); + $database->getOasisEnforce($vilIDs, 1); + $database->getPrisoners($vilIDs, 1); + + // calculate training updates + foreach($trainlist as $train){ + $timepast = $train['timestamp2'] - $time; + $pop = $train['pop']; + $valuesUpdated = false; + if($timepast <= 0 && $train['amt'] > 0) { + $valuesUpdated = true; + if($train['eachtime'] > 0){ + $timepast2 = $time - $train['timestamp2']; + $trained = 1; + while($timepast2 >= $train['eachtime']){ + $timepast2 -= $train['eachtime']; + $trained += 1; + } + + if($trained > $train['amt']) $trained = $train['amt']; + } + else $trained = $train['amt']; + + if($train['unit'] > 1000 && $train['unit'] != 99){ + $database->modifyUnit($train['vref'], [$train['unit'] - 1000], [$trained], [1]); + } + else $database->modifyUnit($train['vref'], [$train['unit']], [$trained], [1]); + + $database->updateTraining($train['id'], $trained, $trained * $train['eachtime']); + + if($train['amt'] - $trained <= 0) $database->trainUnit($train['id'], 0, 0, 0, 0, 1); + } + + if ($valuesUpdated) call_user_func(get_class($database).'::clearUnitsCache'); + + //Update starvation data + $database->addStarvationData($train['vref']); + } + } + } + + private function celebrationComplete() { + global $database; + + $varray = $database->getCel(); + foreach($varray as $vil){ + $id = $vil['wref']; + $type = $vil['type']; + $user = $vil['owner']; + $cp = ($type == 1) ? 500 : 2000; + $database->clearCel($id); + $database->setCelCp($user, $cp); + } + } + + /** + * Expires Mead-Festivals (Brewery, building 35). Unlike celebrationComplete() + * this grants no reward — the festival only gated the temporary combat + * bonus / chief penalty / catapult randomization while it was active. + */ + private function festivalComplete() { + global $database; + + $varray = $database->getFestivals(); + foreach($varray as $vil){ + $database->clearFestival($vil['wref']); + } + } +} diff --git a/GameEngine/Automation/AutomationTroopMovements.php b/GameEngine/Automation/AutomationTroopMovements.php new file mode 100644 index 00000000..9f916cb3 --- /dev/null +++ b/GameEngine/Automation/AutomationTroopMovements.php @@ -0,0 +1,385 @@ +query_return($q); + + if ($dataarray && count($dataarray)) { + // preload village data + $vilIDs = []; + $tos = []; + $froms = []; + foreach($dataarray as $data) { + $vilIDs[$data['from']] = true; + $vilIDs[$data['to']] = true; + $tos[$data['to']] = true; + $froms[$data['from']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getProfileVillages($vilIDs, 5); + $database->getUnit($vilIDs); + $database->getEnforce(array_keys($tos), array_keys($froms)); + $database->getVillageByWorldID($vilIDs); + + // calculate reinforcements data + foreach($dataarray as $data) { + if (!$this->claimMovementRecord($data['moveid'])) { + continue; + } + + $isoasis = $database->isVillageOases($data['to']); + if($isoasis == 0){ + $to = $database->getMInfo($data['to']); + $toF = $database->getVillage($data['to']); + $DefenderID = $to['owner']; + $targettribe = $database->getUserField($DefenderID, "tribe", 0); + $conqureby = 0; + }else{ + $to = $database->getOMInfo($data['to']); + $toF = $database->getOasisV($data['to']); + $DefenderID = $to['owner']; + $targettribe = $database->getUserField($DefenderID, "tribe", 0); + $conqureby = $toF['conqured']; + } + + if($data['from'] == 0){ + // flow 1: a "village of the elders" reinforcement returning home + $this->completeReinforcementFromElders($data, $to); + }else{ + // flow 2: a standard reinforcement delivery (hero transfer + enforce merge + report) + $this->completeReinforcementDelivery($data, $to, $isoasis, $DefenderID); + } + + //Update starvation data + $database->addStarvationData($data['to']); + + //check empty reinforcement in rally point + $e_units = ''; + for ($i = 1; $i <= 90; $i++) $e_units.= 'u'.$i.'= 0 AND '; + + $e_units.= 'hero = 0'; + $q = "DELETE FROM ".TB_PREFIX."enforcement WHERE ".$e_units." AND (vref=".(int) $data['to']." OR `from`=".(int) $data['to'].")"; + $database->query($q); + } + } + } + + // Flow 1 of sendreinfunitsComplete(): a "village of the elders" reinforcement + // (from = 0) returning home. $targetally / $AttackArrivalTime are intentionally + // left undefined here, exactly as in the original inline code. + private function completeReinforcementFromElders($data, $to) { + global $database; + + $DefenderID = $database->getVillageField($data['to'], "owner"); + $database->addEnforce(['from' => $data['from'], 'to' => $data['to'], 't1' => 0, 't2' => 0, 't3' => 0, 't4' => 0, 't5' => 0, 't6' => 0, 't7' => 0, 't8' => 0, 't9' => 0, 't10' => 0, 't11' => 0]); + $reinf = $database->getEnforce($data['to'], $data['from']); + $database->modifyEnforce($reinf['id'], 31, 1, 1); + $data_fail = '0,0,4,1,0,0,0,0,0,0,0,0,0,0'; + $database->addNotice($to['owner'], $to['wref'], (isset($targetally) ? $targetally : 0), 8, 'village of the elders reinforcement ' . addslashes($to['name']), $data_fail, isset($AttackArrivalTime) ? $AttackArrivalTime : time()); + } + + // Flow 2 of sendreinfunitsComplete(): a standard reinforcement delivery. Handles + // the lone-hero transfer between own villages, the enforcement merge-or-create, + // and the reinforcement notices. $ownally / $targetally / $AttackArrivalTime are + // intentionally left undefined (guarded by isset()), exactly as in the original. + private function completeReinforcementDelivery($data, $to, $isoasis, $DefenderID) { + global $database; + + //set base things + $from = $database->getMInfo($data['from']); + $fromF = $database->getVillage($data['from']); + $AttackerID = $from['owner']; + $owntribe = $database->getUserField($AttackerID,"tribe",0); + + $HeroTransfer = $troopsPresent = 0; + for($i = 1;$i <= 10; $i++) { + if($data['t'.$i] > 0) { + $troopsPresent = 1; + break; + } + } + + //check if the hero is present and we're not sending him to an occupied oasis + //only add hero if we're sending him alone + if($data['t11'] > 0 && !$isoasis && !$troopsPresent) { + //check if we're sending a hero between own villages + if($AttackerID == $DefenderID) { + //check if there's a Mansion at target village + if($this->getTypeLevel(37, $data['to']) > 0){ + //don't reinforce, addunit instead + $database->modifyUnit($data['to'], ["hero"], [1], [1]); + $heroid = $database->getHeroField($DefenderID, 'heroid'); + $database->modifyHero("wref", $data['to'], $heroid); + $HeroTransfer = 1; + } + } + } + + if($data['t11'] > 0 || $troopsPresent) { + $this->mergeOrCreateEnforcement($data, $owntribe, $HeroTransfer); + } + + //send rapport + $unitssend_att = ''.$data['t1'].','.$data['t2'].','.$data['t3'].','.$data['t4'].','.$data['t5'].','.$data['t6'].','.$data['t7'].','.$data['t8'].','.$data['t9'].','.$data['t10'].','.$data['t11'].''; + $data_fail = ''.$from['wref'].','.$from['owner'].','.$owntribe.','.$unitssend_att.''; + + + if($isoasis == 0) $to_name = $to['name']; + else $to_name = "Oasis ".$database->getVillageField($to['conqured'],"name"); + + $database->addNotice($from['owner'],$from['wref'],(isset($ownally) ? $ownally : 0),8,''.addslashes($from['name']).' reinforcement '.addslashes($to_name).'',$data_fail,(isset($AttackArrivalTime) ? $AttackArrivalTime : time())); + if($from['owner'] != $to['owner']) { + $database->addNotice($to['owner'],$to['wref'],(isset($targetally) ? $targetally : 0),8,''.addslashes($from['name']).' reinforcement '.addslashes($to_name).'',$data_fail,(isset($AttackArrivalTime) ? $AttackArrivalTime : time())); + } + } + + // Flow 3 of sendreinfunitsComplete(): merge the incoming troops into an existing + // enforcement record at the target, or create a new one. When the hero was already + // transferred as a unit ($HeroTransfer), his t11 is temporarily zeroed for the + // merge and restored afterwards, exactly as in the original. + private function mergeOrCreateEnforcement($data, $owntribe, $HeroTransfer) { + global $database; + + $temphero = $data['t11']; + if ($HeroTransfer) $data['t11'] = 0; + //check if there is defence from town in to town + $check = $database->getEnforce($data['to'], $data['from']); + if (!isset($check['id'])) $database->addEnforce($data); + else + { + //yes + $start = ($owntribe - 1) * 10 + 1; + $end = ($owntribe * 10); + + //add unit. + $t_units = ''; + for($i = $start, $j = 1; $i <= $end; $i++, $j++) + { + $t_units .= "u".$i." = u".$i." + ".$data['t'.$j].(($j > 9) ? '' : ', '); + } + + $q = "UPDATE ".TB_PREFIX."enforcement set $t_units where id =".(int) $check['id']; + $database->query($q); + $database->modifyEnforce($check['id'], 'hero', $data['t11'], 1); + } + $data['t11'] = $temphero; + } + + private function returnunitsComplete() { + global $database, $technology; + + $time = time(); + $q = " + SELECT + `to`, `from`, moveid, starttime, endtime, wood, clay, iron, crop, + t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11 + FROM + ".TB_PREFIX."movement, + ".TB_PREFIX."attacks + WHERE + ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id + AND + ".TB_PREFIX."movement.proc = 0 + AND + ".TB_PREFIX."movement.sort_type = 4 + AND + endtime < $time"; + $dataarray = $database->query_return($q); + + if ($dataarray && count($dataarray)) { + // preload village data + $vilIDs = []; + foreach($dataarray as $data) { + $vilIDs[$data['from']] = true; + $vilIDs[$data['to']] = true; + } + $database->getProfileVillages(array_keys($vilIDs), 5); + $database->getOasisEnforce($vilIDs, 0); + $database->getOasisEnforce($vilIDs, 1); + + foreach($dataarray as $data) { + if (!$this->claimMovementRecord($data['moveid'])) { + continue; + } + + $tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0); + $u = $tribe == 1 ? "" : $tribe - 1; + $database->modifyUnit( + $data['to'], + [$u."1", $u."2", $u."3", $u."4", $u."5", $u."6", $u."7", $u."8", $u."9", $tribe."0", "hero"], + [$data['t1'], $data['t2'], $data['t3'], $data['t4'], $data['t5'], $data['t6'], $data['t7'], $data['t8'], $data['t9'], $data['t10'], $data['t11']], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ); + + //If there's at least 1 resource, add it to the village + if($data['wood'] + $data['clay'] + $data['iron'] + $data['crop'] > 0){ + $database->modifyResource($data['to'], $data['wood'], $data['clay'], $data['iron'], $data['crop'], 1); + } + + //Update starvation data + $database->addStarvationData($data['to']); + } + + $this->pruneResource(); + } + + // Settlers + $q = "SELECT `to`, moveid FROM ".TB_PREFIX."movement where ref = 0 and proc = '0' and sort_type = '4' and endtime < $time"; + $dataarray = $database->query_return($q); + if ($dataarray && count($dataarray)) { + foreach($dataarray as $data) { + if (!$this->claimMovementRecord($data['moveid'])) { + continue; + } + + $tribe = $database->getUserField($database->getVillageField($data['to'], "owner"), "tribe", 0); + $database->modifyUnit($data['to'], [$tribe."0"], [3], [1]); + + //If a settling is canceled, add 750 for each resource type + $database->modifyResource($data['to'], 750, 750, 750, 750, 1); + } + } + } + + private function sendSettlersComplete() { + global $database; + + $time = microtime(true); + $q = "SELECT `to`, `from`, moveid, starttime, ref FROM ".TB_PREFIX."movement where proc = 0 and sort_type = 5 and endtime < $time"; + + $dataarray = $database->query_return($q); + $fieldIDs = []; + $addUnitsWrefs = []; + $addTechWrefs = []; + $addABTechWrefs = []; + $time = microtime(true); + $types = []; + $froms = []; + $tos = []; + $refs = []; + $times = []; + $endtimes = []; + + // preload village data + $vilIDs = []; + foreach($dataarray as $data) { + $vilIDs[$data['from']] = true; + $vilIDs[$data['to']] = true; + } + $vilIDs = array_keys($vilIDs); + $database->getProfileVillages($vilIDs, 5); + $database->getVillageByWorldID($vilIDs); + + foreach($dataarray as $data) { + if (!$this->claimMovementRecord($data['moveid'])) { + continue; + } + + $ownerID = $database->getUserField($database->getVillageField($data['from'], "owner"), "id", 0); + $to = $database->getMInfo($data['from']); + $user = addslashes($database->getUserField($to['owner'], 'username', 0)); + $taken = $database->getVillageState($data['to']); + if($taken != 1){ + $fieldIDs[] = $data['to']; + $database->addVillage($data['to'], $to['owner'], $user, '0'); + $database->addResourceFields($data['to'], $database->getVillageType($data['to'])); + $addUnitsWrefs[] = $data['to']; + $addTechWrefs[] = $data['to']; + $addABTechWrefs[] = $data['to']; + + $exp1 = $database->getVillageField($data['from'], 'exp1'); + $exp2 = $database->getVillageField($data['from'], 'exp2'); + $exp3 = $database->getVillageField($data['from'], 'exp3'); + + if($exp1 == 0){ + $exp = 'exp1'; + $value = $data['to']; + }elseif($exp2 == 0){ + $exp = 'exp2'; + $value = $data['to']; + }else{ + $exp = 'exp3'; + $value = $data['to']; + } + + $database->setVillageField($data['from'], $exp, $value); + + // Report: new village founded (issue #178) + $ncoor = $database->getCoor($data['to']); + $database->addNotice($to['owner'], $data['to'], 0, 24, 'New village founded', ($ncoor['x'] ?? 0) . ',' . ($ncoor['y'] ?? 0), time()); + + // Milestone: first player ever to settle their 2nd village. + // Checked right after the new village row exists, so the + // COUNT below already includes it. + if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { + $villageCount = $database->countVillages($to['owner']); + if ($villageCount == 2) { + $database->recordMilestoneIfFirst('second_village', $to['owner'], $data['to']); + } + if ($villageCount == 5) { + $database->recordMilestoneIfFirst('five_villages', $to['owner'], $data['to']); + } + } + }else{ + // here must come movement from returning settlers + $types[] = 4; + $froms[] = $data['to']; + $tos[] = $data['from']; + $refs[] = $data['ref']; + $times[] = $time; + $endtimes[] = $time + ($time - $data['starttime']); + + // Report: valley already occupied, settlers returning (issue #178) + $fcoor = $database->getCoor($data['to']); + $database->addNotice($to['owner'], $data['to'], 0, 25, 'Settlers returned - valley occupied', ($fcoor['x'] ?? 0) . ',' . ($fcoor['y'] ?? 0), time()); + } + } + + $database->addMovement($types, $froms, $tos, $refs, $times, $endtimes); + $database->setFieldTaken($fieldIDs); + $database->addUnits($addUnitsWrefs); + $database->addTech($addTechWrefs); + $database->addABTech($addABTechWrefs); + + } +} diff --git a/GameEngine/Automation/AutomationVillageUpkeep.php b/GameEngine/Automation/AutomationVillageUpkeep.php new file mode 100644 index 00000000..874c7dc4 --- /dev/null +++ b/GameEngine/Automation/AutomationVillageUpkeep.php @@ -0,0 +1,384 @@ +getResourceLevel($vid, $use_cache); + $popTot = 0; + + for ($i = 1; $i <= 40; $i++) { + $lvl = $fdata["f".$i]; + $building = $fdata["f".$i."t"]; + if($building) $popTot += $this->buildingPOP($building, $lvl); + } + + Building::recountCP($database, $vid); + $q = "UPDATE ".TB_PREFIX."vdata set pop = $popTot where wref = $vid"; + mysqli_query($database->dblink, $q); + $owner = $database->getVillageField($vid, "owner"); + + // Milestone: first player ever to reach 1000 total population, + // summed across all their villages. recountPop() is the single + // funnel every population-changing event (building, demolishing, + // founding/conquering a village) already passes through, so this + // is the one place that's guaranteed to catch the threshold being + // crossed regardless of which village/action caused it. + // Excludes owner 3 (Natars) — see Artifacts::NATARS_UID — same + // convention already used elsewhere in this file (e.g. the + // "fix natar report by ronix" check a few hundred lines below). + if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES && $owner > 0 && $owner != 3) { + $totalPop = (int) $database->getVSumField($owner, 'pop', false); + if ($totalPop >= 1000) { + $database->recordMilestoneIfFirst('population_1000', $owner, $vid); + } + } + + $this->procClimbers($owner); + + return $popTot; + } + + function buildingPOP($f, $lvl){ + $name = "bid".$f; + global $$name; + + $popT = 0; + $dataarray = $$name; + + for ($i = 0; $i <= $lvl; $i++) { + $popT += ((isset($dataarray[$i]) && isset($dataarray[$i]['pop'])) ? $dataarray[$i]['pop'] : 0); + } + return $popT; + } + + private function loyaltyRegeneration() { + global $database; + + $array = []; + $array = $database->getProfileVillages(0, 6); + if(!empty($array)) { + foreach($array as $loyalty) { + if (($t25_level = $this->getTypeLevel(25, $loyalty['wref'])) >= 1) { + $value = $t25_level; + }elseif(($t26_level = $this->getTypeLevel(26, $loyalty['wref'])) >= 1){ + $value = $t26_level; + } + else $value = 0; + + if($value > 0){ + $newloyalty = min(100, $loyalty['loyalty'] + $value * (time() - $loyalty['lastupdate2']) / 3600); + $q = "UPDATE ".TB_PREFIX."vdata SET loyalty = $newloyalty, lastupdate2=".time()." WHERE wref = '".$loyalty['wref']."'"; + $database->query($q); + } + } + } + + $array = []; + $q = "SELECT conqured, loyalty, lastupdated, wref FROM ".TB_PREFIX."odata WHERE loyalty < 100"; + $array = $database->query_return($q); + if(!empty($array)) { + foreach($array as $loyalty) { + $value = $this->getTypeLevel(37, $loyalty['conqured']); + + if($value > 0){ + $newloyalty = min(100, $loyalty['loyalty'] + $value * (time() - $loyalty['lastupdated']) / 3600); + $q = "UPDATE ".TB_PREFIX."odata SET loyalty = $newloyalty, lastupdated=".time()." WHERE wref = '".$loyalty['wref']."'"; + $database->query($q); + } + } + } + } + + public function getTypeLevel($tid, $vid) { + global $database; + + $keyholder = []; + + $resourcearray = $database->getResourceLevel($vid); + foreach(array_keys($resourcearray, $tid) as $key) { + if(strpos($key,'t')) { + $key = preg_replace("/[^0-9]/", '', $key); + array_push($keyholder, $key); + } + } + + $element = count($keyholder); + if($element >= 2) { + if($tid <= 4) { + $temparray = []; + for($i = 0; $i <= $element - 1; $i++) { + array_push($temparray,$resourcearray['f'.$keyholder[$i]]); + } + foreach ($temparray as $key => $val) { + if ($val == max($temparray)) $target = $key; + } + } + else { + $target = 0; + for($i = 1; $i <= $element - 1; $i++) { + if($resourcearray['f'.$keyholder[$i]] > $resourcearray['f'.$keyholder[$target]]) { + $target = $i; + } + } + } + } + else if($element == 1) $target = 0; + else return 0; + + if(!empty($keyholder[$target])) return $resourcearray['f'.$keyholder[$target]]; + else return 0; + } + + // Clamp resources/storage up to their minimums for the given table (vdata or + // odata): negative resources back to 0 and maxstore/maxcrop back to + // STORAGE_BASE. This floor pass is identical for both tables, so both + // pruneResource() (vdata) and pruneOResource() (odata) share it. The $table + // argument is an internal constant string, never user input. + private function pruneResourceMinimums($table) { + global $database; + + $database->query("UPDATE + ".TB_PREFIX.$table." + SET + wood = IF(wood < 0, 0, wood), + clay = IF(clay < 0, 0, clay), + iron = IF(iron < 0, 0, iron), + crop = IF(crop < 0, 0, crop), + maxstore = IF(maxstore < ".STORAGE_BASE.", ".STORAGE_BASE.", maxstore), + maxcrop = IF(maxcrop < ".STORAGE_BASE.", ".STORAGE_BASE.", maxcrop) + WHERE + maxstore < ".STORAGE_BASE." OR + maxcrop < ".STORAGE_BASE." OR + wood < 0 OR + clay < 0 OR + iron < 0 OR + crop < 0"); + } + + private function pruneOResource() { + if(!ALLOW_BURST) { + $this->pruneResourceMinimums('odata'); + } + } + private function pruneResource() { + global $database; + + if(!ALLOW_BURST) { + $this->pruneResourceMinimums('vdata'); + + $database->query("UPDATE + ".TB_PREFIX."vdata + SET + wood = IF(wood > maxstore, maxstore, wood), + clay = IF(clay > maxstore, maxstore, clay), + iron = IF(iron > maxstore, maxstore, iron), + crop = IF(crop > maxcrop, maxcrop, crop) + WHERE + wood > maxstore OR + clay > maxstore OR + iron > maxstore OR + crop > maxcrop"); + } + } + + private function culturePoints() { + global $database; + + $database->updateVSumField('cp'); + } + + /** + * Recompute a warehouse/granary capacity after a build completion. + * Returns [$fieldDbName, $max]: the vdata column to update and its new value. + */ + private function updateStorageCapacity($type, $level, $villageData) { + global $bid10, $bid11, $bid38, $bid39; + + $fieldDbName = (in_array($type, [10, 38]) ? 'maxstore' : 'maxcrop'); + $max = $villageData[$fieldDbName]; + + if($level == 1 && $max == STORAGE_BASE) $max = STORAGE_BASE; + + if ($level != 1) $max -= ${'bid'.$type}[$level - 1]['attri'] * STORAGE_MULTIPLIER; + + $max += ${'bid'.$type}[$level]['attri'] * STORAGE_MULTIPLIER; + + return [$fieldDbName, $max]; + } + + private function getPop($tid, $level) { + $name = "bid".$tid; + global $$name; + + $dataarray = $$name; + $pop = $dataarray[($level + 1)]['pop']; + $cp = $dataarray[($level + 1)]['cp']; + return [$pop, $cp]; + } + + private function updateORes($bountywid) { + global $database; + + $oasisInfoArray = $database->getOasisV($bountywid); + $timepast = time() - $oasisInfoArray['lastupdated']; + $nwood = (OASIS_WOOD_PRODUCTION / 3600) * $timepast; + $nclay = (OASIS_CLAY_PRODUCTION / 3600) * $timepast; + $niron = (OASIS_IRON_PRODUCTION / 3600) * $timepast; + $ncrop = (OASIS_CROP_PRODUCTION / 3600) * $timepast; + $database->modifyOasisResource($bountywid, $nwood, $nclay, $niron, $ncrop, 1); + $database->updateOasis($bountywid); + } + + private function updateRes($bountywid) { + global $database, $technology; + + //Get village infos + $villageInfoArray = $database->getVillage($bountywid); + + //Get building and resource fields array + $resArray = $database->getResourceLevel($bountywid, false); + + //Get oasis array + $oasisArray = $database->getOasis($bountywid); + + //Get an array with the numbers of the oasis + $numberOfOasis = $this->bountysortOasis($oasisArray); + + //Set the village population (if WW Villages, it's halved) + $villagePopulation = !$villageInfoArray['natar'] ? $villageInfoArray['pop'] : round($villageInfoArray['pop'] / 2); + + //Get the upkeep of the village + $upkeep = $technology->getUpkeep($this->getAllUnits($bountywid), 0, $bountywid); + + //Calculate the produced resources + $timepast = time() - $villageInfoArray['lastupdate']; + $nwood = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 1) / 3600) * $timepast; + $nclay = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 2) / 3600) * $timepast; + $niron = ($this->bountyGetResourceProd($resArray, $numberOfOasis, 3) / 3600) * $timepast; + $ncrop = (($this->bountyGetResourceProd($resArray, $numberOfOasis, 4) - $villagePopulation - $upkeep) / 3600) * $timepast; + $database->modifyResource($bountywid, $nwood, $nclay, $niron, $ncrop, 1); + $database->updateVillage($bountywid); + } + + private function getsort_typeLevel($tid, $resarray) { + $keyholder = []; + + foreach(array_keys($resarray, $tid) as $key) { + if(strpos($key, 't')) { + $key = preg_replace("/[^0-9]/", '', $key); + array_push($keyholder, $key); + } + } + + $element = count($keyholder); + if($element >= 2) { + if($tid <= 4) { + $temparray = []; + + for($i = 0; $i <= $element - 1; $i++) { + array_push($temparray, $resarray['f'.$keyholder[$i]]); + } + + foreach ($temparray as $key => $val) { + if ($val == max($temparray)) $target = $key; + } + } + } + else if($element == 1) $target = 0; + else return 0; + + if(!empty($keyholder[$target])) return $resarray['f'.$keyholder[$target]]; + else return 0; + } + + private function updateStore() { + global $database, $bid10, $bid38, $bid11, $bid39; + + $result = mysqli_query($database->dblink, 'SELECT * FROM `' . TB_PREFIX . 'fdata`'); + + mysqli_begin_transaction($database->dblink); + while ($row = mysqli_fetch_assoc($result)) + { + $ress = $crop = 0; + for ($i = 19; $i < 40; ++$i) + { + //Warehouse + if ($row['f' . $i . 't'] == 10) + { + $ress += ((isset($bid10[$row['f' . $i]]) && isset($bid10[$row['f' . $i]]['attri'])) ? $bid10[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); + } + + //Great warehouse + if ($row['f' . $i . 't'] == 38) + { + $ress += ((isset($bid38[$row['f' . $i]]) && isset($bid38[$row['f' . $i]]['attri'])) ? $bid38[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); + } + + //Granary + if ($row['f' . $i . 't'] == 11) + { + $crop += ((isset($bid11[$row['f' . $i]]) && isset($bid11[$row['f' . $i]]['attri'])) ? $bid11[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); + } + + //Great granary + if ($row['f' . $i . 't'] == 39) + { + $crop += ((isset($bid39[$row['f' . $i]]) && isset($bid39[$row['f' . $i]]['attri'])) ? $bid39[$row['f' . $i]]['attri'] * STORAGE_MULTIPLIER : 0); + } + } + + // no need for update, since we didn't find any warehouses or granaries + // and maximums would have been set to correct values inside prune* functions already + if ($ress == 0 && $crop == 0) continue; + + // maxstore nor maxcrop can go below the minimum threshold + if ($ress < STORAGE_BASE) $ress = STORAGE_BASE; + if ($crop < STORAGE_BASE) $crop = STORAGE_BASE; + + mysqli_query($database->dblink,'UPDATE `' . TB_PREFIX . 'vdata` SET `maxstore` = ' . (int) $ress . ', `maxcrop` = ' . (int) $crop . ' WHERE `wref` = ' . (int) $row['vref']); + } + mysqli_commit($database->dblink); + } + + private function regenerateOasisTroops() { + global $database; + + $timeFinal = time() - NATURE_REGTIME; + $q = "SELECT wref FROM " . TB_PREFIX . "odata where conqured = 0 and lastupdated2 < $timeFinal"; + $array = $database->query_return($q); + if (count($array)) { + $ids = []; + foreach($array as $oasis) $ids[] = $oasis['wref']; + $database->regenerateOasisUnits($ids, true); + } + } +} diff --git a/GameEngine/Automation/index.php b/GameEngine/Automation/index.php new file mode 100644 index 00000000..63970c0d --- /dev/null +++ b/GameEngine/Automation/index.php @@ -0,0 +1,40 @@ + + +
+
+ +

404 - File not found

+ + Not Found
+ +

We looked 404 times already but can't find anything, Not even an X marking the spot.

+ +

This system is not complete yet. So the page probably does not exist.

+ +
+ +
+
\ No newline at end of file diff --git a/GameEngine/Database.php b/GameEngine/Database.php index 7c92ff42..e2729840 100755 --- a/GameEngine/Database.php +++ b/GameEngine/Database.php @@ -3,22 +3,24 @@ ################################################################################# ## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ## ## --------------------------------------------------------------------------- ## -## Project: TravianZ ## -## Version: 14.05.2026 ## -## Filename Database.php ## -## Developed by: Mr.php , Advocaite , brainiacX , yi12345 , Shadow , ronix ## -## Reworked by: martinambrus ## -## Refactored by: Shadow (cata7007@gmail.com) ## -## ## -## Adăugat cache generic și contoare centralizate ## -## Corectat mysqli_fetch_all pentru PHP 7.2+ (returnează mereu array) ## -## Refactorizat checkAllianceEmbassiesStatus în metode mai mici ## -## ## -## License: TravianZ Project ## -## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## -## URLs: https://travianz.org ## -## https://github.com/Shadowss/TravianZ ## -## ## +## Filename : Database.php ## +## Type : Database Cache System and Methods ## +## --------------------------------------------------------------------------- ## +## Developed by : Mr.php , Advocaite , brainiacX , yi12345 , Shadow , ronix ## +## Refactored by : Shadow & Ferywir ## +## Reworked by : martinambrus ## +## Thanks to : InCube, Akakori, Elmar & Kirilloid ## +## Split&Refactor : Shadow ## +## Phase S1 : Methods split into 14 traits (GameEngine/Database/) ## +## --------------------------------------------------------------------------- ## +## Contact : cata7007@gmail.com ## +## Project : TravianZ ## +## URLs: : https://travianz.org ## +## GitHub : https://github.com/Shadowss/TravianZ ## +## --------------------------------------------------------------------------- ## +## License : TravianZ Project ## +## Copyright : TravianZ (c) 2010-2026. All rights reserved. ## +## --------------------------------------------------------------------------- ## ################################################################################# global $autoprefix; @@ -42,10 +44,44 @@ if (!$autoloader_found) { include_once("config.php"); +// === Phase S1: the MYSQLi_DB class has been split into domain-specific traits (GameEngine/Database/) === +// The traits are in the global namespace, so they are included explicitly (the autoloader only maps the App\ namespace). +include_once __DIR__ . '/Database/DatabaseConnectionCore.php'; +include_once __DIR__ . '/Database/DatabaseUserQueries.php'; +include_once __DIR__ . '/Database/DatabaseVillageQueries.php'; +include_once __DIR__ . '/Database/DatabaseForumQueries.php'; +include_once __DIR__ . '/Database/DatabaseAllianceQueries.php'; +include_once __DIR__ . '/Database/DatabaseMessageQueries.php'; +include_once __DIR__ . '/Database/DatabaseMarketQueries.php'; +include_once __DIR__ . '/Database/DatabaseBuildingQueries.php'; +include_once __DIR__ . '/Database/DatabaseTroopQueries.php'; +include_once __DIR__ . '/Database/DatabaseMovementQueries.php'; +include_once __DIR__ . '/Database/DatabaseHeroQueries.php'; +include_once __DIR__ . '/Database/DatabaseStatisticsQueries.php'; +include_once __DIR__ . '/Database/DatabaseArtefactQueries.php'; +include_once __DIR__ . '/Database/DatabaseSystemQueries.php'; + use App\Database\IDbConnection; use App\Utils\Math; class MYSQLi_DB implements IDbConnection { + // === Phase S1: class methods grouped by domain === + use DatabaseConnectionCore; + use DatabaseUserQueries; + use DatabaseVillageQueries; + use DatabaseForumQueries; + use DatabaseAllianceQueries; + use DatabaseMessageQueries; + use DatabaseMarketQueries; + use DatabaseBuildingQueries; + use DatabaseTroopQueries; + use DatabaseMovementQueries; + use DatabaseHeroQueries; + use DatabaseStatisticsQueries; + use DatabaseArtefactQueries; + use DatabaseSystemQueries; + + private /** @@ -413,29 +449,6 @@ class MYSQLi_DB implements IDbConnection { /** @var array Cache statements pregătite */ private $stmtCache = []; - - private function countQueryType(string $sql): void { - $trim = ltrim($sql); - if (stripos($trim, 'SELECT') === 0) $this->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 === @@ -476,8746 +489,6 @@ class MYSQLi_DB implements IDbConnection { // we will operate in UTF8 mysqli_query($this->dblink,"SET NAMES 'UTF8'"); } - - /** - * {@inheritDoc} - * @see \App\Database\IDbConnection::connect() - */ - public function connect() { - $host = (string) $this->hostname; - $port = (int) $this->port; - if ($port <= 0) $port = 3306; - - // If host is in form host:port (common in env files), split it once. - if (strpos($host, ':') !== false && substr_count($host, ':') === 1) { - $hostPort = explode(':', $host, 2); - if (isset($hostPort[0], $hostPort[1]) && is_numeric($hostPort[1])) { - $host = $hostPort[0]; - $port = (int) $hostPort[1]; - } - } - - $attempts = 15; - $waitMicros = 1000000; // 1 second - - for ($i = 0; $i < $attempts; $i++) { - try { - $this->dblink = mysqli_connect($host, $this->username, $this->password, $this->dbname, $port); - } catch (\Throwable $exception) { - $this->dblink = false; - } - - if ($this->dblink instanceof \mysqli) { - return true; - } - - // During container startup DB may still be booting. - if ($i < $attempts - 1) { - usleep($waitMicros); - } - } - - return false; - } - - /** - * {@inheritDoc} - * @see \App\Database\IDbConnection::disconnect() - */ - public function disconnect() { - if ($this->dblink) { - if (!$this->dblink->close()) { - return false; - } - - $this->dblink = null; - } - - return true; - } - - /** - * {@inheritDoc} - * @see \App\Database\IDbConnection::reconnect() - */ - public function reconnect() { - $this->disconnect(); - return $this->connect(); - } - - /** - * {@inheritDoc} - * @see \App\Database\IDbConnection::query_new() - */ - public function query_new($statement, ...$params) { - if ($prep = mysqli_prepare($this->dblink, $statement)) { - // if we're doing a multi-update/insert/delete query, - // we'll need to mark it as such - $is_multi_query = false; - - // determine the nature of this query - preg_match('/[^AZ-az]*(\()?[^AZ-az]*SELECT/i', $statement, $select_matches); - preg_match('/[^AZ-az]*(\()?[^AZ-az]*DELETE/i', $statement, $delete_matches); - preg_match('/[^AZ-az]*(\()?[^AZ-az]*INSERT/i', $statement, $insert_matches); - preg_match('/[^AZ-az]*(\()?[^AZ-az]*REPLACE/i', $statement, $replace_matches); - preg_match('/[^AZ-az]*(\()?[^AZ-az]*UPDATE/i', $statement, $update_matches); - - // a single array parameter means that we're batching multiple - // value feeds for a single prepared statement, so we just use - // the first array value to actually prepare the statement - // and determine all the binding types - if (count($params) == 1) { - $paramsArray = $params[0]; - $is_multi_query = true; - } else { - $paramsArray = $params; - // convert method parameters into an array, - // so we can reuse it in both cases - when we're executing - // just a single prepared statement and also when we're - // batching up multiple values for an insert/update/delete statement - $params = [$params]; - } - - // determine and prepare parameter types - $types = []; - foreach ($paramsArray as $param) { - // default to string, change if neccessary - $paramType = 's'; - - if (Math::isInt($param)) { - $paramType = 'i'; - } else if (Math::isFloat($param)) { - $paramType = 'd'; - } - - $types[] = $paramType; - } - - // dynamically bind each data batch using previously - // defined parameters - $implodedNames = [implode('', $types)]; - $outputValues = []; - - foreach ($params as $dataBatch) { - $bind_names = $implodedNames; - for ($i=0; $iselectQueryCount++; - $queryResult = []; - - // read metadata, so we know what fields we were actually selecting - // and can prepare our temporary variables to read them into - $resultMetaData = mysqli_stmt_result_metadata($prep); - - $stmtRow = array(); - $rowReferences = array(); - while ($field = mysqli_fetch_field($resultMetaData)) { - $rowReferences[] = &$stmtRow[$field->name]; - } - mysqli_free_result($resultMetaData); - - // now call bind_result with all our variables to recive the data prepared - call_user_func_array(array($prep, 'bind_result'), $rowReferences); - - // prepare the array-ed result - while(mysqli_stmt_fetch($prep)){ - $row = array(); - foreach($stmtRow as $key => $value){ - $row[$key] = $value; - } - $queryResult[] = $row; - } - - // free the result - mysqli_stmt_free_result($prep); - - $outputValues[] = $queryResult; - } else { - throw new Exception('Failed to execute an SQL statement!'); - } - } - } - - // free the prepared statement - mysqli_stmt_close($prep); - - // return the expected result - if (count($select_matches)) { - // if there is only a single result, return it alone - if (count($outputValues) === 1) { - return $outputValues[0]; - } else { - // otherwise, return all the data - return $outputValues; - } - } - } else { - throw new Exception('Failed to prepare an SQL statement!'); - } - - return false; - } - - /** - * {@inheritDoc} - * @see \App\Database\IDbConnection::is_connected() - */ - public function is_connected() { - return ($this->dblink ? true : false); - } - - - /*************************** - Function to calc oasis bonus - References: lietuvis10 - ***************************/ - - public function getBestOasisCropBonus($x, $y) { - $x = (int)$x; - $y = (int)$y; - - // Adjust oasis type codes if your fork differs: - // - 50% crop only: type IN (12) - // - 25% crop (pure or mixed w/ wood/clay/iron): type IN (4,9,10,11) - $sql = "SELECT COALESCE(SUM(bonus), 0) AS total FROM (SELECT CASE - WHEN o.type IN (12) THEN 50 WHEN o.type IN (4,9,10,11) THEN 25 ELSE 0 - END AS bonus FROM " . TB_PREFIX . "wdata w JOIN " . TB_PREFIX . "odata o ON o.wref = w.id - WHERE w.fieldtype = 0 AND ABS(w.x - $x) <= 3 AND ABS(w.y - $y) <= 3 AND o.type IN (12,4,9,10,11) - ORDER BY bonus DESC LIMIT 3) t"; - - $q = mysqli_query($this->dblink, $sql); - $row = mysqli_fetch_assoc($q); - $total = (int)($row['total'] ?? 0); - if ($total > 150) $total = 150; // safety cap - return $total; -} - - /*************************** - Function to process MYSQLi->fetch_all (Only exist in MYSQL) - References: Result - ***************************/ - function mysqli_fetch_all($result) { - // REFACTORIZAT: garantăm array pentru PHP 7.2+, eliminăm escape pe resource - $all = []; - if($result instanceof \mysqli_result) { - while($row = mysqli_fetch_assoc($result)) { - $all[] = $row; - } - 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); - } - - /*************************** - Function to do free query - References: Query - ***************************/ - function query($query) { - // REFACTORIZAT: contorizare centralizată - $this->countQueryType($query); - return mysqli_query($this->dblink,$query); - } - - function RemoveXSS($val) { - list($val) = $this->escape_input($val); - - return htmlspecialchars($val, ENT_QUOTES); - } - - /** - * Returns a value previously cached from the database, if present. - * - * @param $arrayVariable array Reference to the static array in Database class to use for the lookup. - * @param $arrayFieldName string The actual array field name to look a cached value for. - * - * @return Returns the requested cached value or null if it's not cached yet. - */ - private static function returnCachedContent(&$arrayVariable, $arrayStructure) { - if (!isset($arrayVariable[$arrayStructure])) { - $arrayVariable[$arrayStructure] = []; - } - - if (isset($arrayVariable[$arrayStructure]) && !empty($arrayVariable[$arrayStructure])) { - return $arrayVariable[$arrayStructure]; - } - else return null; - } - - /** - * Clears cached village data, so after automation is run, we can re-load new data (like resource levels etc) - * to be displayed in the front-end. - */ - public static function clearVillageCache() { - self::$villageFieldsCache = []; - self::$villageFieldsCacheByWorldID = []; - } - - /** - * Returns a string value safely escaped to be used in mysqli_query() method. - * - * @param $value string The value to sanitize. - * - * @return string Returns a sanitized string, safe for SQL queries. - */ - function escape($value) { - if (is_string($value)) { - $value = stripslashes( $value ); - return mysqli_real_escape_string($this->dblink, $value); - } else { - return $value; - } - } - - /** - * Returns a list of safely escaped values which can be used to re-retrieve - * them in a list() method. - * - * @example list($username, $password) = $database->escape_input($username, $password); - * - * @return array Returns an array with all items sanitized and safe to be used in SQL statements. - */ - function escape_input() { - $numargs = func_num_args(); - $arg_list = func_get_args(); - $ret = []; - - for ($i = 0; $i < $numargs; $i++) { - if (is_string($arg_list[$i])) { - $arg_list[$i] = stripslashes($arg_list[$i]); - $res[] = mysqli_real_escape_string($this->dblink, $arg_list[$i]); - } else { - $res[] = $arg_list[$i]; - } - } - - return $res; - } - - function return_link() { - return $this->dblink; - } - - function register($username, $password, $email, $tribe, $act, $uid = 0, $desc = null) { - $username = trim($username); - $email = filter_var($email, FILTER_VALIDATE_EMAIL) ?: ''; - $tribe = (int)$tribe; - $uid = (int)$uid; - $desc = $desc ?? ''; - $time = time(); - $access = USER; - - $startTime = strtotime(START_DATE) - strtotime(date('d.m.Y')) + strtotime(START_TIME); - $protectionTime = $uid != 3 ? (($startTime > $time) ? $startTime : $time) + PROTECTION : 0; - - // încercăm varianta cu is_bcrypt (PHP 8.3) - $stmt = $this->dblink->prepare( - "INSERT INTO `".TB_PREFIX."users` - (id, username, password, access, email, timestamp, tribe, act, protect, lastupdate, regtime, desc2, is_bcrypt) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)" - ); - $is_bcrypt = 1; - $stmt->bind_param("issisiiiiiisi", $uid, $username, $password, $access, $email, $time, $tribe, $act, $protectionTime, $time, $time, $desc, $is_bcrypt); - - if($stmt->execute()){ - $id = $stmt->insert_id ?: $uid; - $stmt->close(); - $this->grantRegistrationGold($id); - return $id; - } - $stmt->close(); - - // fallback pentru DB vechi fără coloana is_bcrypt - $stmt2 = $this->dblink->prepare( - "INSERT INTO `".TB_PREFIX."users` - (id, username, password, access, email, timestamp, tribe, act, protect, lastupdate, regtime, desc2) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" - ); - $stmt2->bind_param("issisiiiiiis", $uid, $username, $password, $access, $email, $time, $tribe, $act, $protectionTime, $time, $time, $desc); - - if($stmt2->execute()){ - $id = $stmt2->insert_id ?: $uid; - $stmt2->close(); - $this->grantRegistrationGold($id); - return $id; - } - $stmt2->close(); - return false; -} - /** - * Registration bonus gold (NEW_FUNCTION_REGISTRATION_GOLD). - * Grants a one-time gold bonus to a freshly created player account. Called - * from register() right after the users row is inserted, so it covers every - * registration path (email activation, instant registration, admin-created - * users). No-ops when disabled, amount <= 0, or system account (id <= 3). - */ - private function grantRegistrationGold($id) { - $id = (int) $id; - if ($id <= 3) { - return; // system accounts: admin / nature / natars - } - if (!defined('NEW_FUNCTION_REGISTRATION_GOLD') || !NEW_FUNCTION_REGISTRATION_GOLD) { - return; - } - $amount = defined('NEW_FUNCTION_REGISTRATION_GOLD_VALUE') ? (int) NEW_FUNCTION_REGISTRATION_GOLD_VALUE : 0; - if ($amount <= 0) { - return; - } - $this->modifyGold($id, $amount, 1); // mode 1 = add - - // Best-effort audit trail. The village does not exist yet, so wid = 0. - if (defined('LOG_GOLD_FIN') && LOG_GOLD_FIN) { - try { - $now = time(); - $details = 'Registration bonus'; - $stmt = $this->dblink->prepare( - "INSERT INTO `".TB_PREFIX."gold_fin_log` (wid, uid, action, gold, time, details) - VALUES (0, ?, 'Registration bonus Gold', ?, ?, ?)" - ); - if ($stmt) { - $stmt->bind_param("iiis", $id, $amount, $now, $details); - $stmt->execute(); - $stmt->close(); - } - } catch (\Throwable $e) { - // swallow: logging must never block account creation - } - } - } - - function activate($username, $password, $email, $tribe, $locate, $act, $act2) { - $username = trim($username); - $email = filter_var($email, FILTER_VALIDATE_EMAIL) ?: ''; - $tribe = (int)$tribe; - $locate = (int)$locate; - $time = time(); - $access = USER; - - $stmt = $this->dblink->prepare( - "INSERT INTO `".TB_PREFIX."activate` - (username,password,access,email,tribe,timestamp,location,act,act2) - VALUES (?,?,?,?,?,?,?,?,?)" - ); - $stmt->bind_param("ssisiiiss", $username, $password, $access, $email, $tribe, $time, $locate, $act, $act2); - - if($stmt->execute()){ - $id = $stmt->insert_id; - $stmt->close(); - return $id; - } - $stmt->close(); - return false; -} - - function unreg($username) { - list($username) = $this->escape_input($username); - - $q = "DELETE from " . TB_PREFIX . "activate where username = '$username'"; - return mysqli_query($this->dblink,$q); - } - - /** - * IP ban lookup (issue #185). - * Returns the active ban row for the given packed binary IP, or false. - * Uses a prepared statement and tolerates the table not existing yet - * (older installs): in that case it simply reports "not banned". - * - * @param string $ipBinary Packed IP (inet_pton output), 4 or 16 bytes. - * @return array|false - */ - function ipBanActive($ipBinary) { - if (!is_string($ipBinary) || $ipBinary === '') { - return false; - } - try { - $now = time(); - $stmt = $this->dblink->prepare( - "SELECT id, ip_text, reason, end FROM `".TB_PREFIX."banlist_ip` - WHERE ip = ? AND active = 1 AND (end IS NULL OR end = 0 OR end > ?) LIMIT 1" - ); - if (!$stmt) { - return false; // table missing / prepare failed (non-throwing mysqli) - } - $stmt->bind_param("si", $ipBinary, $now); - $stmt->execute(); - $res = $stmt->get_result(); - $row = $res ? $res->fetch_assoc() : null; - $stmt->close(); - return $row ?: false; - } catch (\Throwable $e) { - return false; // table missing (throwing mysqli) → treat as not banned - } - } - function deleteReinf($id) { - list($id) = $this->escape_input((int) $id); - - $q = "DELETE from " . TB_PREFIX . "enforcement where id = '$id'"; - mysqli_query($this->dblink,$q); - self::clearReinforcementsCache(); - } - - function updateResource($vid, $what, $number) { - $vid = (int) $vid; - - if (!is_array($what)) { - $what = [$what]; - $number = [$number]; - } - - $pairs = []; - foreach ($what as $index => $whatValue) { - $pairs[] = $this->escape($whatValue) . ' = ' . (Math::isInt($number[$index]) ? $number[$index] : '"'.$this->escape($number[$index]).'"'); - } - - $q = "UPDATE " . TB_PREFIX . "vdata SET ".implode(', ', $pairs)." WHERE wref = $vid"; - $result = mysqli_query($this->dblink,$q); - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - public function hasBeginnerProtection($vid) { - list($vid) = $this->escape_input($vid); - $q = "SELECT u.protect FROM ".TB_PREFIX."users u,".TB_PREFIX."vdata v WHERE u.id=v.owner AND v.wref=".(int) $vid." LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - if(!empty($dbarray)) { - if(time()<$dbarray[0]) { - return true; - } else { - return false; - } - } else { - return false; - } -} - - function updateUserField($ref, $field, $value, $switch) { - list($ref) = $this->escape_input($ref); - - if (!is_array($field)) { - $field = [$field]; - $value = [$value]; - } - - $pairs = []; - foreach ($field as $i => $f) { - $pairs[] = $this->escape($f) . ' = ' . (is_numeric($value[$i]) ? $value[$i] : "'".$this->escape($value[$i])."'"); - } - - $q = !$switch - ? "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE username = '$ref'" - : "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE id = ".(int)$ref; - - $ret = mysqli_query($this->dblink, $q); - - // === 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) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT id from " . TB_PREFIX . "users where sit1 = $uid or sit2 = $uid"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - //TODO: Remove this function to use the more general one - // no need to cache this method - function getVilWref($x, $y) { - list($x, $y) = $this->escape_input((int) $x, (int) $y); - - $q = "SELECT id FROM " . TB_PREFIX . "wdata where x = $x AND y = $y LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['id']; - } - - /** - * Converts from coordinates to village IDs - * - * @param array $coordinatesArray The coordinates array, containing the coordinates which need to be converted - * @return array Returns the converted coordinates - */ - - function getVilWrefs($coordinatesArray) { - list($coordinatesArray) = $this->escape_input($coordinatesArray); - - if(!is_array($coordinatesArray[0])) $coordinatesArray = [$coordinatesArray]; - - $conditions = []; - foreach($coordinatesArray as $coordinate){ - $conditions[] = "(x = ".round($coordinate[0])." AND y = ".round($coordinate[1]).")"; - } - - $q = "SELECT id FROM " . TB_PREFIX . "wdata WHERE ".implode(" OR ", $conditions); - $result = mysqli_query($this->dblink, $q); - - while($row = mysqli_fetch_assoc($result)) $wids[] = $row['id']; - return $wids; - } - - function removeMeSit($uid, $uid2) { - list($uid, $uid2) = $this->escape_input((int) $uid, (int) $uid2); - - $q = "UPDATE " . TB_PREFIX . "users set sit1 = 0 where id = $uid and sit1 = $uid2"; - mysqli_query($this->dblink,$q); - $q2 = "UPDATE " . TB_PREFIX . "users set sit2 = 0 where id = $uid and sit2 = $uid2"; - mysqli_query($this->dblink,$q2); - } - - function getUserField($ref, $field, $mode, $use_cache = true) { - // update for Multihunter's username and ID - if (($mode && $ref == '') || (!$mode && $ref == 0)) { - $ref = 'Multihunter'; - $mode = 1; - } - - // return all data, don't waste time by selecting fields one by one - $userArray = $this->getUserArray($ref, ($mode ? 0 : 1), $use_cache); - $result = (isset($userArray[$field]) ? $userArray[$field] : null); - - if ($result) { - // will return the result - } elseif($field=="username") { - $result = "[?]"; - } else { - $result = 0; - } - - return $result; - } - - function getUserFields($ref, $fields, $mode, $use_cache = true) { - // update for Multihunter's username and ID - if (($mode && $ref == '') || (!$mode && $ref == 0)) { - $ref = 'Multihunter'; - $mode = 1; - } - - // return all data, don't waste time by selecting fields one by one - return $this->getUserArray($ref, ($mode ? 0 : 1), $use_cache); - } - - // no need to cache this method - function getInvitedUser($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT * FROM " . TB_PREFIX . "users where invited = $uid order by regtime desc"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function getVrefField($ref, $field, $use_cache = true) { - return $this->getVillage($ref, 0, $use_cache)[$field]; - } - - // no need to cache this method - function getVrefCapital($ref) { - $vdata = $this->getProfileVillages($ref); - - foreach($vdata as $village){ - if($village['capital']) return $village; - } - return false; - } - - // no need to cache this method - function getStarvation() { - return $this->getProfileVillages(0, 2); - } - - // no need to cache this method - function getActivateField($ref, $field, $mode) { - list($ref, $field, $mode) = $this->escape_input($ref, $field, $mode); - - if(!$mode) { - $q = "SELECT $field FROM " . TB_PREFIX . "activate where id = " . (int) $ref . " LIMIT 1"; - } else { - $q = "SELECT $field FROM " . TB_PREFIX . "activate where username = '$ref' LIMIT 1"; - } - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray[$field]; - } - - function login($username, $password) { - static $cachedResult = null; - - if ($cachedResult !== null) { - return $cachedResult; - } - - list($username, $password) = $this->escape_input($username, $password); - $q = "SELECT id,password,sessid,is_bcrypt FROM " . TB_PREFIX . "users where username = '$username'"; - $result = mysqli_query($this->dblink,$q); - - // if we didn't update the database for bcrypt hashes yet... - if (mysqli_error($this->dblink) != '') { - $q = "SELECT id, password,sessid,0 as is_bcrypt FROM " . TB_PREFIX . "users where username = '$username' LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $bcrypt_update_done = false; - } else { - $bcrypt_update_done = true; - } - - $dbarray = mysqli_fetch_array($result); - - // even if we didn't do a DB conversion for bcrypt passwords, - // we still need to check if this password wasn't encrypted via password_hash, - // since all methods were updated to use that instead of md5 and therefore - // new passwords in DB will be bcrypt already even without the is_bcrypt field present - $bcrypted = true; - $pwOk = password_verify($password, $dbarray['password']); - - if (!$pwOk && !$dbarray['is_bcrypt']) { - $pwOk = ($dbarray['password'] == md5($password)); - $bcrypted = false; - } - - if($pwOk) { - // update password to bcrypt, if correct - if (!$dbarray['is_bcrypt'] && !$bcrypted) { - mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "users SET password = '".password_hash($password, PASSWORD_BCRYPT,['cost' => 12])."'".($bcrypt_update_done ? ', is_bcrypt = 1' : '')." where id = ".(int) $dbarray['id']); - } - $cachedResult = true; - } else { - $cachedResult = false; - } - - return $cachedResult; - } - - function sitterLogin($username, $password) { - list($username, $password) = $this->escape_input($username, $password); - - $q = "SELECT sit1,sit2 FROM " . TB_PREFIX . "users where username = '$username' and access != " . BANNED ." LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - if($dbarray['sit1'] != 0) { - $q2 = "SELECT password FROM " . TB_PREFIX . "users where id = " . (int) $dbarray['sit1'] . " and access != " . BANNED . " LIMIT 1"; - $result2 = mysqli_query($this->dblink,$q2); - $dbarray2 = mysqli_fetch_array($result2); - } - if($dbarray['sit2'] != 0) { - $q3 = "SELECT password FROM " . TB_PREFIX . "users where id = " . (int) $dbarray['sit2'] . " and access != " . BANNED . " LIMIT 1"; - $result3 = mysqli_query($this->dblink,$q3); - $dbarray3 = mysqli_fetch_array($result3); - } - if($dbarray['sit1'] != 0 || $dbarray['sit2'] != 0) { - if(password_verify($password, $dbarray2['password']) || password_verify($password, $dbarray3['password'])) { - return true; - } else { - return false; - } - } else { - return false; - } - } - - function setDeleting($uid, $mode) { - list($uid, $mode) = $this->escape_input((int) $uid, $mode); - - $time = time() + 72 * 3600; - if(!$mode) { - $q = "INSERT into " . TB_PREFIX . "deleting values ($uid,$time)"; - } else { - $q = "DELETE FROM " . TB_PREFIX . "deleting where uid = $uid"; - } - mysqli_query($this->dblink,$q); - } - - function isDeleting($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT timestamp from " . TB_PREFIX . "deleting where uid = $uid LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return isset($dbarray['timestamp']) ? (int)$dbarray['timestamp'] : 0; - } - - function modifyGold($userid, $amt, $mode) { - list($userid, $amt, $mode) = $this->escape_input((int) $userid, (int) $amt, $mode); - - if(!$mode) $q = "UPDATE " . TB_PREFIX . "users set gold = gold - $amt where id = $userid"; - else $q = "UPDATE " . TB_PREFIX . "users set gold = gold + $amt where id = $userid"; - - return mysqli_query($this->dblink,$q); - } - - /** - * Retrieves the user array via Username or ID - * - * @param int $ref The user ID or the username - * @param int $mode 0 --> Search by username, 1 --> Search by user ID - * @param bool $use_cache Will use the cache if true - * @return array Returns the user array - */ - - function getUserArray($ref, $mode, $use_cache = true) { - list($ref, $mode) = $this->escape_input($ref, $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)) { - return $cachedValue; - } - - 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); - return self::$fieldsCache[$ref.$mode]; - } - - function activeModify($username, $mode) { - list($username, $mode) = $this->escape_input($username, $mode); - - $time = time(); - if(!$mode) { - $q = "INSERT into " . TB_PREFIX . "active VALUES ('$username',$time)"; - } else { - $q = "DELETE FROM " . TB_PREFIX . "active where username = '$username'"; - } - return mysqli_query($this->dblink,$q); - } - - function addActiveUser($username, $time) { - list($username, $time) = $this->escape_input($username, $time); - - $q = "REPLACE into " . TB_PREFIX . "active values ('$username',$time)"; - if(mysqli_query($this->dblink,$q)) { - return true; - } else { - return false; - } - } - - function updateActiveUser($username, $time) { - static $updated = false; - - if ($updated) { - return; - } - - list($username, $time) = $this->escape_input($username, $time); - - $res1 = $this->addActiveUser($username, $time); - $q = "UPDATE " . TB_PREFIX . "users set timestamp = $time where username = '$username'"; - $res2 = mysqli_query($this->dblink,$q); - if($res1 && $res2) { - $updated = true; - return true; - } else { - return false; - } - } - - function submitProfile($uid, $gender, $location, $birthday, $desc1, $desc2) { - $uid = (int)$uid; - $gender = (int)$gender; - $location = mb_substr(trim($location), 0, 30, 'UTF-8'); - $birthday = trim($birthday); - // Issue #250: cap profile descriptions (BBCode is rendered as HTML on - // display) so a single field cannot store an oversized payload. - $desc1 = mb_substr(trim($desc1), 0, 3000, 'UTF-8'); - $desc2 = mb_substr(trim($desc2), 0, 3000, 'UTF-8'); - - $stmt = $this->dblink->prepare( - "UPDATE `".TB_PREFIX."users` - SET gender = ?, location = ?, birthday = ?, desc1 = ?, desc2 = ? - WHERE id = ? LIMIT 1" - ); - $stmt->bind_param("issssi", $gender, $location, $birthday, $desc1, $desc2, $uid); - $stmt->execute(); - $stmt->close(); - return true; - } - - function gpack($uid, $gpack) { - list($uid, $gpack) = $this->escape_input((int) $uid, $gpack); - - $q = "UPDATE " . TB_PREFIX . "users set gpack = '$gpack' where id = $uid"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function GetOnline($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT sit FROM " . TB_PREFIX . "online WHERE uid = $uid LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['sit']; - } - - function UpdateOnline($mode, $name = "", $time = "", $uid = 0) { - list($mode, $name, $time, $uid) = $this->escape_input($mode, $name, $time, (int) $uid); - - global $session; - if($mode == "login") { - $q = "INSERT IGNORE INTO " . TB_PREFIX . "online (name, uid, time, sit) VALUES ('$name', $uid, '" . time() . "', 0)"; - return mysqli_query($this->dblink,$q); - } else if($mode == "sitter") { - $q = "INSERT IGNORE INTO " . TB_PREFIX . "online (name, uid, time, sit) VALUES ('$name', $uid, '" . time() . "', 1)"; - return mysqli_query($this->dblink,$q); - } else { - $q = "DELETE FROM " . TB_PREFIX . "online WHERE name ='" . $this->escape($session->username) . "'"; - return mysqli_query($this->dblink,$q); - } - } - - /** - * Generates a list of "free to take" villages - * - * @param int $sector The map sector, + | -, - | + , + | +, - | - (0 and > 3, 1, 2, 3) - * @param int $mode 0 if villages need be generated under certain filters, 1 if not - * @param bool $respect_gametime If is false, we generate user base really anywhere - * and that means we can generate farms closer to the middle of the map as well. - * Otherwise we'd only generate farms at corner edges in late game, which - * sucks for people in the middle who registered too soon - * @param int $numberOfVillages Number of villages which need to be generated - * @return array Return the generated villages - */ - - function generateBase($sector, $mode = 0, $numberOfVillages = 1) { - list($sector, $mode, $numberOfVillages) = $this->escape_input((int) $sector, (int) $mode, (int)$numberOfVillages); - - // don't let SQL time out when 30-500 seconds (depending on php.ini) is not enough - @set_time_limit(0); - $num_rows = $count = 0; - $villages = []; - $time = time(); - - while ($numberOfVillages > 0) { - switch($mode){ - case 0: - $daysPassedFromStart = ($time - strtotime(START_DATE) - strtotime(date('d.m.Y')) + strtotime(START_TIME)) / 86400; - - $radiusMin = min(round(pow(2 * ($daysPassedFromStart / 5 * SPEED), 2)), round(pow(WORLD_MAX * 0.8, 2)) + round(pow(WORLD_MAX * 0.8, 2))); - $radiusMax = min(round(pow(4 * ($daysPassedFromStart / 5 * SPEED), 2)) + pow($count, 2), pow(WORLD_MAX, 2) + pow(WORLD_MAX, 2)); - break; - - case 1: - default: - $radiusMin = 1; - $radiusMax = pow(WORLD_MAX, 2); - break; - - case 2: //Small artifacts & WW building plans - $radiusMin = round(pow(WORLD_MAX * 0.50, 2)); - $radiusMax = round(pow(WORLD_MAX * 0.75, 2)); - break; - - case 3: //Large artifacts - $radiusMin = round(pow(WORLD_MAX * 0.35, 2)); - $radiusMax = round(pow(WORLD_MAX * 0.55, 2)); - break; - - case 4: //Unique artifacts - $radiusMin = round(pow(WORLD_MAX * 0.05, 2)); - $radiusMax = round(pow(WORLD_MAX * 0.25, 2)); - break; - - case 5: //WW villages - $radiusMin = round(pow(WORLD_MAX * 0.8, 2)); - $radiusMax = round(pow(WORLD_MAX, 2)); - break; - } - - // The four sectors must be mutually exclusive: any tile sitting on an - // axis (x = 0 or y = 0) used to satisfy two of the predicates below - // (e.g. "x <= 0" and "x >= 0" both matched x = 0). When a batch was - // spread over several sectors (WW villages, artifact villages), - // occupied = 0 was only flipped at the very end by setFieldTaken(), - // so two sectors could each pick the SAME axis tile and assign its - // wref to two villages. The duplicate addVillage() insert then hit - // the vdata PRIMARY KEY(wref) and aborted generateVillages() before - // setFieldTaken() ran, leaving the villages in vdata (visible in the - // Natars profile) but never marked on the map (issue #301). Using - // strict bounds on one side of each axis partitions the plane so a - // tile belongs to exactly one sector. - switch($sector){ - case 1: $newSector = "x < 0 AND y >= 0"; break; // - | + - case 2: $newSector = "x >= 0 AND y > 0"; break; // + | + - case 3: $newSector = "x <= 0 AND y < 0"; break; // - | - - default: $newSector = "x > 0 AND y <= 0"; // + | - - } - - //Choose villages beetween two circumferences, by using their formula (x^2 + y^2 = r^2) - $q = "SELECT id FROM ".TB_PREFIX."wdata WHERE fieldtype = 3 AND ($newSector) AND (POWER(x, 2) + POWER(y, 2) >= $radiusMin AND POWER(x, 2) + POWER(y, 2) <= $radiusMax) AND occupied = 0 ORDER BY RAND() LIMIT $numberOfVillages"; - $result = mysqli_query($this->dblink, $q); - - //Prevent an infinite loop - $resultedRows = mysqli_num_rows($result); - if($resultedRows == 0 && $count >= WORLD_MAX * 2) break; - - //Fill the villages array - $villages = array_merge($villages, $this->mysqli_fetch_all($result)); - - $num_rows += $resultedRows; - $numberOfVillages -= $resultedRows; - $count++; - - } - - foreach($villages as $village) $wids[] = $village['id']; - - return $num_rows == 1 ? $wids[0] : $wids; - } - - function setFieldTaken($id) { - if(empty($id)) return; - if (!is_array($id)) { - $id = [$id]; - } - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - - $q = "UPDATE " . TB_PREFIX . "wdata SET occupied = 1 WHERE id IN(". implode(', ', $id).")"; - return mysqli_query($this->dblink,$q); - } - - /** - * Creates new villages - * - * @param array $villageArrays The array of the villages which have to be created - * @param int $uid The user ID - * @param string $username The username of the future owner - * @param array $troopsArray The troops that need to be added in the village(s) - * @param array $buildingsArray The buildings that need to be created in the village(s) - * @return array Returns the created villages ID - */ - - function generateVillages($villageArrays, $uid, $username, $troopsArray = null, $buildingsArray = null){ - $uid = (int)$uid; - $username = trim($username); - - $wids = $takenWids = $countedWids = $generatedWids = $i = []; - - //Count each kid in its own array, to check how many villages must be created - foreach($villageArrays as $village){ - if($village['wid'] == 0) $countedWids[$village['mode']][$village['kid']]++; - } - - //Generate the number of desired village for each kid - //and merge them with the more general "wids" array - foreach($countedWids as $mode => $totalCount){ - foreach($totalCount as $sector => $count){ - $generatedWids = $this->generateBase($sector, $mode, $count); - // Bug fix: previously merged every sector's wids into one flat - // $wids[$mode] list and consumed it with a single shared counter - // per mode, regardless of which sector a wid actually came from. - // As soon as a batch needed more than one sector (e.g. WW - // villages spread across all 4 quadrants), villages were handed - // wids strictly in array order, not matching their own kid — so - // most ended up in the wrong quadrant. Keying by [mode][sector] - // keeps each sector's wids separate. - $wids[$mode][$sector] = !is_array($generatedWids) ? [$generatedWids] : $generatedWids; - if(empty($i[$mode][$sector])) $i[$mode][$sector] = 0; - } - } - - //Create the villages - foreach($villageArrays as $village){ - - //Check if the village wid isn't already set and assing one among the generated ones - if($village['wid'] == 0) $village['wid'] = $wids[$village['mode']][$village['kid']][$i[$village['mode']][$village['kid']]++]; - - //Merge the wids into an unique array - $takenWids[] = $village['wid']; - $villageTypes[] = $village['type']; - - //Add the village and its buildings - $this->addVillage($village['wid'], $uid, $username, $village['capital'], $village['pop'], $village['name'], $village['natar']); - } - - //Create tables for the just generated villages - $this->addResourceFields($takenWids, $villageTypes, $buildingsArray); - $this->setFieldTaken($takenWids); - $this->addUnits($takenWids, $troopsArray); - $this->addTech($takenWids); - $this->addABTech($takenWids); - - return count($takenWids) > 1 ? $takenWids : $takenWids[0]; - } - - /** - * - * Create a village - * - * @param int $wid The village ID - * @param int $uid The User ID, the village's owner - * @param string $username The username - * @param int $capital 1 if it's a capital village, 0 otherwise - * @param int $pop The default village population - * @param string $villageName The default village name - * @return bool Returns true if the query was successful, false otherwise - */ - - function addVillage($wid, $uid, $username, $capital, $pop = 2, $villageName = null, $isNatar = 0) { - $wid = (int)$wid; - $uid = (int)$uid; - $capital = (int)$capital; - $pop = (int)$pop; - $isNatar = (int)$isNatar; - $username = trim($username); - - $total = count($this->getVillagesID($uid)); - if (empty($villageName)) { - // fără backslash, fără htmlentities – doar text curat - $villageName = $username . "'s village" . ($total >= 1 ? " " . ($total + 1) : ""); - } - - $time = time(); - $storage = STORAGE_BASE; - - $stmt = $this->dblink->prepare( - "INSERT INTO `".TB_PREFIX."vdata` - (wref, owner, name, capital, pop, cp, celebration, wood, clay, iron, maxstore, crop, maxcrop, lastupdate, created, natar) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - ); - $cp = 1; $celebration = 0; $wood = 750; $clay = 750; $iron = 750; $crop = 750; - $stmt->bind_param("iisiiiiiiiiiiiii", - $wid, $uid, $villageName, $capital, $pop, $cp, $celebration, - $wood, $clay, $iron, $storage, $crop, $storage, $time, $time, $isNatar - ); - $result = $stmt->execute(); - $stmt->close(); - return $result; -} - - /** - * - * Add the buildings tables to a specified village(s), and its relative buildings - * - * @param mixed $vid The village ID(s) - * @param mixed $type int if there's only one village, array if there are multiple villages - * @param array $buildingsArray divided in two portion, which contains the types (unidimensional array) and the values of the - * buildings that need to be added (bidimensional array) - * @return bool Return true if the query was successful, false otherwise - */ - - function addResourceFields($vids, $types, $buildingsArray = null) { - list($vids, $types, $buildingsArray) = $this->escape_input($vids, $types, $buildingsArray); - - if(!is_array($vids)){ - $vids = [$vids]; - $types = [$types]; - } - - //Set the default villages structure (resources fields and main building) - $defaultVillage = "vref,f1t,f2t,f3t,f4t,f5t,f6t,f7t,f8t,f9t,f10t,f11t,f12t,f13t,f14t,f15t,f16t,f17t,f18t" - .($buildingsArray != null ? ",".implode(",",$buildingsArray[0]) : ",f26,f26t"); - $defaultValues = []; - - //Select the village type and assemble the building values - foreach($vids as $index => $vid){ - $stringValues = ""; - $stringValues .= "(".$vid.","; - switch($types[$index]) { - case 1: $stringValues .= "4,4,1,4,4,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 2: $stringValues .= "3,4,1,3,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 3: $stringValues .= "1,4,1,3,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 4: $stringValues .= "1,4,1,2,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 5: $stringValues .= "1,4,1,3,1,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 6: $stringValues .= "4,4,1,3,4,4,4,4,4,4,4,4,4,4,4,2,4,4"; break; - case 7: $stringValues .= "1,4,4,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 8: $stringValues .= "3,4,4,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 9: $stringValues .= "3,4,4,1,1,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 10: $stringValues .= "3,4,1,2,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - case 11: $stringValues .= "3,1,1,3,1,4,4,3,3,2,2,3,1,4,4,2,4,4"; break; - case 12: $stringValues .= "1,4,1,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; - default: $stringValues .= "4,4,1,4,4,2,3,4,4,3,3,4,4,1,4,2,1,2"; - } - - $stringValues .= $buildingsArray != null ? ",".implode(",",$buildingsArray[1][$index]).")" : ",1,15)"; - $defaultValues[] = $stringValues; - } - - $q = "INSERT INTO " . TB_PREFIX . "fdata ($defaultVillage) values".implode(",",$defaultValues); - return mysqli_query($this->dblink, $q); - } - - function isVillageOases($wref, $use_cache = true) { - // retirieve form cache - return $this->getVillageByWorldID($wref, $use_cache)['oasistype']; - } - - public function VillageOasisCount($vref, $use_cache = true) { - list($vref) = $this->escape_input((int) $vref); - - // 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::$oasisCountCache, $vref)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT count(*) FROM `".TB_PREFIX."odata` WHERE conqured=". $vref; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - - self::$oasisCountCache[$vref] = $row[0]; - return self::$oasisCountCache[$vref]; - } - - public function countOasisTroops($vref, $use_cache = true) { - list($vref) = $this->escape_input((int) $vref); - - // 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::$oasisTroopsCountCache, $vref)) && !is_null($cachedValue)) { - return $cachedValue; - } - - //count oasis troops: $troops_o - $troops_o = 0; - $o_unit = $this->getUnit($vref, $use_cache); - - for ( $i = 1; $i < 51; $i ++ ) { - $troops_o += $o_unit[ 'u'.$i ]; - } - - $troops_o += $o_unit['hero']; - $o_unit2 = $this->getEnforceVillage($vref, 0, $use_cache); - - foreach ($o_unit2 as $o_unit) { - for ( $i = 1; $i < 51; $i ++ ) { - $troops_o += $o_unit[ 'u'.$i ]; - } - - $troops_o += $o_unit['hero']; - } - - self::$oasisTroopsCountCache[$vref] = $troops_o; - return self::$oasisTroopsCountCache[$vref]; - } - - /** - * Calculates the distance from a village to another - * - * @param int $coorx1 X coordinate of the first village - * @param int $coory1 Y coordinate of the second village - * @param int $coorx2 X coordinate of the first village - * @param int $coory2 Y coordinate of the second village - * @return int Returns the calculated distance - */ - - public function getDistance($coorx1, $coory1, $coorx2, $coory2) { - $max = 2 * WORLD_MAX + 1; - $x1 = intval($coorx1); - $y1 = intval($coory1); - $x2 = intval($coorx2); - $y2 = intval($coory2); - $distanceX = min(abs($x2 - $x1), abs($max - abs($x2 - $x1))); - $distanceY = min(abs($y2 - $y1), abs($max - abs($y2 - $y1))); - return round(sqrt(pow($distanceX, 2) + pow($distanceY, 2)), 1); - } - - public function canConquerOasis($vref, $wref, $use_cache = true) { - list($vref,$wref) = $this->escape_input($vref,$wref); - - // 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::$oasisConquerableCache, $vref.$wref)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $AttackerFields = $this->getResourceLevel( $vref, $use_cache ); - for ( $i = 19; $i <= 38; $i ++ ) { - if ( $AttackerFields[ 'f' . $i . 't' ] == 37 ) { - $HeroMansionLevel = $AttackerFields[ 'f' . $i ]; - } - } - if ( $this->VillageOasisCount( $vref ) < floor( ( $HeroMansionLevel - 5 ) / 5 ) ) { - $OasisInfo = $this->getOasisInfo( $wref ); - //fix by ronix - if ( - $OasisInfo['conqured'] == 0 || - $OasisInfo['conqured'] != 0 && - intval( $OasisInfo['loyalty'] ) < ( 99 / min(3, (4 - $this->VillageOasisCount($OasisInfo['conqured'], $use_cache))) ) - ) { - $CoordsVillage = $this->getCoor( $vref ); - $CoordsOasis = $this->getCoor( $wref ); - $max = 2 * WORLD_MAX + 1; - $x1 = intval( $CoordsOasis['x'] ); - $y1 = intval( $CoordsOasis['y'] ); - $x2 = intval( $CoordsVillage['x'] ); - $y2 = intval( $CoordsVillage['y'] ); - $distanceX = min( abs( $x2 - $x1 ), abs( $max - abs( $x2 - $x1 ) ) ); - $distanceY = min( abs( $y2 - $y1 ), abs( $max - abs( $y2 - $y1 ) ) ); - - if ( $distanceX <= 3 && $distanceY <= 3 ) { - self::$oasisConquerableCache[ $vref . $wref ] = 1; //can - } else { - self::$oasisConquerableCache[ $vref . $wref ] = 2; //can but not in 7x7 field - } - - } else { - self::$oasisConquerableCache[ $vref . $wref ] = 3; //loyalty >0 - } - - } else { - self::$oasisConquerableCache[ $vref . $wref ] = 0; //req level hero mansion - } - - return self::$oasisConquerableCache[ $vref . $wref ]; - } - - public function conquerOasis($vref,$wref) { - list($wref) = $this->escape_input((int) $wref); - - $vinfo = $this->getVillage($vref); - $uid = (int) $vinfo['owner']; - $q = "UPDATE `".TB_PREFIX."odata` SET conqured=".(int) $vref. ",loyalty=100,lastupdated=".time().",owner=$uid,name='Occupied Oasis' WHERE wref=".$wref; - return mysqli_query($this->dblink,$q); - } - - public function modifyOasisLoyalty($wref) { - list($wref) = $this->escape_input((int) $wref); - - if($this->isVillageOases($wref) != 0) { - $OasisInfo = $this->getOasisInfo($wref); - if($OasisInfo['conqured'] != 0) { - $LoyaltyAmendment = floor(100 / min(3,(4-$this->VillageOasisCount($OasisInfo['conqured'])))); - $q = "UPDATE `".TB_PREFIX."odata` SET loyalty=loyalty-$LoyaltyAmendment, lastupdated=".time()." WHERE wref=".$wref; - $result=mysqli_query($this->dblink,$q); - return $OasisInfo['loyalty']-$LoyaltyAmendment; - } - } - } - - function regenerateOasisUnits($wid, $automation = false) { - global $autoprefix; - - if (is_array($wid)) $wid = '(' . implode('),(', $wid) . ')'; - else $wid = '(' . (int) $wid . ')'; - - // load the oasis regeneration (in-game) and units generation (during install) SQL file - // and replace village IDs for the given $wid - $str = file_get_contents($autoprefix."var/db/datagen-oasis-troops-regen.sql"); - $str = preg_replace(["'%PREFIX%'", "'%VILLAGEID%'", "'%NATURE_REG_TIME%'"], [TB_PREFIX, $wid, ($automation ? NATURE_REGTIME : -1)], $str); - $result = $this->dblink->multi_query($str); - - // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work - while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} - - if (!$result) return false; - - return true; - } - - /** - * Remove all oasis of a specified village if the mode is 1, if it's 0, then it'll remove only the selected oasis - * - * @param mixed $wref The village ID(s) (mode = 1)/oasis ID (mode = 0) of the oasis owner - * @return bool Returns true if the query was successful, false otherwise - */ - - function removeOases($wref, $mode = 0) { - list($wref) = $this->escape_input((int) $wref); - - if(!is_array($wref)) $wref = [$wref]; - $wrefs = implode(",", $wref); - - $q = "UPDATE ".TB_PREFIX."odata SET conqured = 0, owner = 2, name = 'Unoccupied Oasis' WHERE ".(!$mode ? "wref IN($wrefs)" : "conqured IN($wrefs)"); - return mysqli_query($this->dblink,$q); - } - - /*************************** - Function to retrieve type of village via ID - References: Village ID - ***************************/ - function getVillageType($wref, $use_cache = true) { - // retrieve this value from the full village data cache - return $this->getVillageByWorldID($wref, $use_cache)['fieldtype']; - } - - /***************************************** - Function to retrieve if is occupied via ID - References: Village ID - *****************************************/ - function getVillageState($wref, $use_cache = true) { - // retrieve this value from the full village data cache - if ($use_cache && ($cachedValue = self::returnCachedContent(self::$villageFieldsCacheByWorldID, $wref)) && !is_null($cachedValue)) { - return (((int)($cachedValue['occupied'] ?? 0)) != 0 || ((int)($cachedValue['oasistype'] ?? 0)) != 0); - } else { - $vil = $this->getVillageByWorldID($wref, $use_cache); - return (((int)($vil['occupied'] ?? 0)) != 0 || ((int)($vil['oasistype'] ?? 0)) != 0); - } - } - - /** - * Get the first free village, if there's one - * - * @param array $wids The village IDs - * @return int Returns the wid of the first free village, if they're all taken, returns 0 - */ - - function getFreeVillage($wids){ - list($wids) = $this->escape_input($wids); - - if(!is_array($wids)) $wids = [$wids]; - - $q = "SELECT id FROM ".TB_PREFIX."wdata WHERE id IN(".implode(",", $wids).") AND occupied = 0 AND oasistype = 0 LIMIT 1"; - $result = mysqli_query($this->dblink, $q); - return mysqli_num_rows($result) > 0 ? mysqli_fetch_array($result)[0] : 0; - } - - // no need to refactor this method - function getProfileMedal($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT id,categorie,plaats,week,img,points from " . TB_PREFIX . "medal where userid = $uid and del = 0 order by id desc"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - - } - - // no need to refactor this method - function getProfileMedalAlly($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT id,categorie,plaats,week,img,points from " . TB_PREFIX . "allimedal where allyid = $uid and del = 0 order by id desc"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - - } - - function getVillageID($uid, $use_cache = true) { - // load cached value - return $this->getVillagesID($uid, $use_cache)[0]; - } - - function getVillagesID($uid, $use_cache = true) { - list($uid) = $this->escape_input((int) $uid); - - // 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::$villageIDsCache, $uid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $array = $this->getProfileVillages($uid, 0, $use_cache); - $newarray = array(); - - for($i = 0; $i < count($array); $i++) { - array_push($newarray, $array[$i]['wref']); - } - - self::$villageIDsCache[$uid] = $newarray; - return self::$villageIDsCache[$uid]; - } - - function getVillagesID2($uid, $use_cache = true) { - list($uid) = $this->escape_input((int) $uid); - - // 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::$villageIDsCacheSimple, $uid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $array = $this->getProfileVillages($uid, 0, $use_cache); - self::$villageIDsCacheSimple[$uid] = $array; - - return self::$villageIDsCacheSimple[$uid]; - } - - function findAlreadyCachedVillageData($vid, $mode) { - // check if we don't actually have this data cached already in one of the other modes - for ($i = 0; $i <= 4; $i++) { - if ($mode !== $i && isset(self::$villageFieldsCache[$vid.$i])) { - // loop through cached values - foreach (self::$villageFieldsCache[$vid.$i] as $index => $value) { - // check for existing record with our requested ID/name/owner... - switch ($mode) { - case 0: if ($value['wref'] == $vid) { - return $value; - } - break; - - case 1: if ($value['name'] == $vid) { - return $value; - } - break; - - case 2: if ($value['owner'] == $vid) { - return $value; - } - break; - - case 3: if ((isset($value['owner']) && isset($value['capital'])) && $value['owner'] == $vid && $value['capital'] == 1) { - return $value; - } - break; - - case 4: if ($value['owner'] == 4) { - return $value; - } - break; - } - } - } - } - - return false; - } - - function getVillage($vid, $mode = 0, $use_cache = true) { - // 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::$villageFieldsCache, ((int) $vid).$mode)) && !is_null($cachedValue)) { - return $cachedValue; - } - - if ($use_cache && ($altCachedContentSearch = $this->findAlreadyCachedVillageData($vid, $mode))) { - return $altCachedContentSearch; - } - - switch ($mode) { - // by WREF - case 0: $vid = (int) $vid; - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE wref = $vid LIMIT 1"; - break; - - // by name - case 1: $name = $this->escape($vid); - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE `name` = '$name' LIMIT 1"; - break; - - // by owner ID - case 2: $vid = (int) $vid; - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = $vid LIMIT 1"; - break; - - // by owner ID and capital = 1 - case 3: $vid = (int) $vid; - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = $vid AND capital = 1 LIMIT 1"; - break; - - // by owner = Taskmaster - case 4: $vid = (int) $vid; - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = 4 LIMIT 1"; - break; - } - - $result = mysqli_query($this->dblink,$q); - - self::$villageFieldsCache[$vid.$mode] = mysqli_fetch_array($result, MYSQLI_ASSOC); - return self::$villageFieldsCache[$vid.$mode]; - } - - function getProfileVillages($uid, $mode = 0, $use_cache = true) { - $arrayPassed = is_array($uid); - - if (!$arrayPassed) { - $uid = [(int) $uid]; - } else { - foreach ($uid as $index => $uidValue) { - $uid[$index] = (int) $uidValue; - } - } - - if (!count($uid)) { - return []; - } - - // first of all, check if we should be using cache - if ($use_cache && !$arrayPassed && ($cachedValue = self::returnCachedContent(self::$userVillagesCache, $uid[0].$mode)) && !is_null($cachedValue)) { - return $cachedValue; - } - - // if we've given a number of villages to preload, remove those that already are - if ($use_cache && $arrayPassed) { - $newIDs = []; - foreach ($uid as $id) { - if (!isset(self::$userVillagesCache[$id.$mode])) { - $newIDs[] = $id; - } - } - $uid = $newIDs; - } - - // nothing left to cache, return the full cache - if (!count($uid)) { - return self::$userVillagesCache; - } - - switch ($mode) { - // by owner ID - case 0: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner IN(".implode(', ', $uid).") ORDER BY pop DESC"; - break; - - // capital villages where owner is a real player (i.e. not Natars etc.) - case 1: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE capital = 1 and owner > 5"; - break; - - // villages with starvation data - case 2: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE starv != 0 and owner != 3"; - break; - - // field distance calculator query - case 3: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner > 4 and wref != ".$uid[0]; - break; - - // villages in need of celebration data update - case 4: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE celebration < ".$uid[0]." AND celebration != 0"; - break; - - // by vref ID - case 5: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE wref IN(".implode(', ', $uid).")"; - break; - - // by loyalty updates required - case 6: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE loyalty < 100"; - break; - - // villages without starvation data, Support, Nature, Natars, Taskmaster, Multihunter are all excluded - case 7: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE starv = 0 and owner > 5"; - break; - } - - $result = mysqli_query($this->dblink,$q); - - if (!$arrayPassed) { - $result = $this->mysqli_fetch_all($result); - self::$userVillagesCache[ $uid[0].$mode ] = $result; - - // cache each village individually into the fields cache as well - foreach ($result as $v) { - $amode = 0; - self::$villageFieldsCache[((int) $v['wref']).$amode] = $v; - } - } else { - // we're preloading, cache all the data individually - if (mysqli_num_rows($result)) { - $amode = 0; - while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ) ) { - if ( ! isset( self::$userVillagesCache[ $row['owner'].$mode ] ) ) { - self::$userVillagesCache[ $row['owner'].$mode ] = []; - } - - self::$userVillagesCache[ $row['owner'].$mode ][] = $row; - self::$villageFieldsCache[((int) $row['wref']).$amode] = $row; - } - - // just return the full cache if we've given an array of IDs to load villages for - $result = self::$userVillagesCache; - } - } - - return $result; - } - - function cacheVillageByWorldIDs($uid, $mode = 0) { - if (!is_array($uid)) { - $uid = [(int) $uid]; - } else { - foreach ($uid as $index => $uidValue) { - $uid[$index] = (int) $uidValue; - } - } - - $result = mysqli_query($this->dblink, " - SELECT - * - FROM - " . TB_PREFIX . "wdata as wdata - LEFT JOIN " . TB_PREFIX . "vdata as vdata ON wdata.id = vdata.wref - WHERE vdata.owner IN(".implode('', $uid).")" - ); - - if (mysqli_num_rows($result)) { - $result = $this->mysqli_fetch_all($result); - - $amode = 0; - foreach ($result as $row) { - self::$villageFieldsCacheByWorldID[$row['id']] = $row; - - // cache village fields by wref as well, for future use - if (!isset(self::$villageFieldsCache[((int) $row['wref']).$amode])) { - self::$villageFieldsCache[ ( (int) $row['wref'] ) . $amode ] = $row; - } - } - } - } - - function getVillageByWorldID($vid, $use_cache = true) { - $array_passed = is_array($vid); - - if (!$array_passed) { - $vid = [(int) $vid]; - } else { - foreach ($vid as $index => $ivdValue) { - $vid[$index] = (int) $ivdValue; - } - } - - if (!count($vid)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$villageFieldsCacheByWorldID[$vid[0]]) && is_array(self::$villageFieldsCacheByWorldID[$vid[0]]) && !count(self::$villageFieldsCacheByWorldID[$vid[0]])) { - return self::$villageFieldsCacheByWorldID[$vid[0]]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newVIDs = []; - foreach ($vid as $key) { - if (!isset(self::$villageFieldsCacheByWorldID[$key])) { - $newVIDs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newVIDs)) { - return self::$villageFieldsCacheByWorldID; - } else { - // update remaining IDs to select and cache - $vid = $newVIDs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageFieldsCacheByWorldID, $vid[0])) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "wdata where id IN(".implode(', ', $vid).")"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$villageFieldsCacheByWorldID[$vid[0]] = (isset($result[0]) && is_array($result[0])) ? $result[0] : []; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - self::$villageFieldsCacheByWorldID[$record['id']] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no reinforcements were found for these villages - foreach ($vid as $key) { - if (!isset(self::$villageFieldsCacheByWorldID[$key])) { - self::$villageFieldsCacheByWorldID[$key] = []; - } - } - } - - return ($array_passed ? self::$villageFieldsCacheByWorldID : self::$villageFieldsCacheByWorldID[$vid[0]]); - } - - public function getVillageBattleData($vid, $use_cache = true) { - list($vid) = $this->escape_input((int) $vid); - - // 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::$villageBattleDataCache, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT u.id,u.tribe,v.capital,f.f40 AS wall FROM ".TB_PREFIX."users u,".TB_PREFIX."fdata f,".TB_PREFIX."vdata v WHERE u.id=v.owner AND f.vref=v.wref AND v.wref=".$vid." LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - - self::$villageBattleDataCache[$vid] = mysqli_fetch_array($result, MYSQLI_ASSOC); - return self::$villageBattleDataCache[$vid]; - } - - function getOasisV($vid, $use_cache = true) { - list($vid) = $this->escape_input((int) $vid); - - // 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::$oasisFieldsCache, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "odata where wref = $vid LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - - self::$oasisFieldsCache[$vid] = mysqli_fetch_array($result, MYSQLI_ASSOC); - return self::$oasisFieldsCache[$vid]; - } - - function getMInfo($id, $use_cache = true) { - $array_passed = is_array($id); - - if (!$array_passed) { - $id = [(int) $id]; - } else { - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - if (!count($id)) { - return []; - } - - // first of all, check if we should be using cache and whether the data - // required is already cached - if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$worldAndVillageDataCache, $id[0])) && !is_null($cachedValue)) { - return $cachedValue; - } else if ($use_cache && $array_passed) { - // only select the world IDs we haven't cached yet - $newIDs = []; - foreach ($id as $key) { - if (!isset(self::$worldAndVillageDataCache[$key])) { - $newIDs[] = $key; - } - } - - // everything's cached, return the whole cache - if (!count($newIDs)) { - return self::$worldAndVillageDataCache; - } - $id = $newIDs; - } - - $q = "SELECT * FROM " . TB_PREFIX . "wdata left JOIN " . TB_PREFIX . "vdata ON " . TB_PREFIX . "vdata.wref = " . TB_PREFIX . "wdata.id where " . TB_PREFIX . "wdata.id IN(".implode(', ', $id).")"; - $result = mysqli_query($this->dblink,$q); - - // preserve the original MYSQLI_BOTH semantics (numeric + associative keys) - $rows = []; - if ($result) { - while ($row = mysqli_fetch_array($result, MYSQLI_BOTH)) { - $rows[] = $row; - } - } - - // return a single value - if (!$array_passed) { - self::$worldAndVillageDataCache[$id[0]] = isset($rows[0]) ? $rows[0] : null; - return self::$worldAndVillageDataCache[$id[0]]; - } - - // cache each returned record by its world ID (wdata.id) - foreach ($rows as $record) { - self::$worldAndVillageDataCache[$record['id']] = $record; - } - - return self::$worldAndVillageDataCache; - } - - function getOMInfo($id, $use_cache = true) { - list($id) = $this->escape_input((int) $id); - - // 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::$worldAndOasisDataCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "wdata left JOIN " . TB_PREFIX . "odata ON " . TB_PREFIX . "odata.wref = " . TB_PREFIX . "wdata.id where " . TB_PREFIX . "wdata.id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - - self::$worldAndOasisDataCache[$id] = mysqli_fetch_array($result); - return self::$worldAndOasisDataCache[$id]; - } - - function getOasis($vid, $use_cache = true) { - list($vid) = $this->escape_input((int) $vid); - - // 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::$oasisFieldsCacheByConqueredID, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "odata where conqured = $vid"; - $result = mysqli_query($this->dblink,$q); - - self::$oasisFieldsCacheByConqueredID[$vid] = $this->mysqli_fetch_all($result); - return self::$oasisFieldsCacheByConqueredID[$vid]; - } - - function getOasisInfo($wid, $use_cache = true) { - return $this->getOasisV($wid, $use_cache); - } - - function getVillageField($ref, $field, $use_cache = true) { - // return all data, don't waste time by selecting fields one by one - $villageArray = $this->getVillage($ref, 0, $use_cache); - $result = (isset($villageArray[$field]) ? $villageArray[$field] : null); - - if($result){ - // will return the result - }elseif($field=="name"){ - $result = "[?]"; - }else $result = 0; - - return $result; - } - - 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); - } - - function getOasisField($ref, $field, $use_cache = true) { - // return all data, don't waste time by selecting fields one by one - $oasisArray = $this->getOasisV($ref, $use_cache); - return (isset($oasisArray[$field]) ? $oasisArray[$field] : null); - } - - 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); - } - - function setVillageField($ref, $field, $value) { - if (!is_array($field)) { - $field = [$field]; - $value = [$value]; - } - - $pairs = []; - foreach ($field as $index => $fieldValue) { - $newValue = ((Math::isInt($value[$index]) || Math::isFloat($value[$index])) ? $value[$index] : '"'.$this->escape($value[$index]).'"'); - $pairs[] = $this->escape($fieldValue).' = '.$newValue; - - // update cache - if (isset(self::$villageFieldsCache[$ref])) { - self::$villageFieldsCache[$ref][$fieldValue] = $newValue; - } - } - - $q = "UPDATE " . TB_PREFIX . "vdata SET ".implode(', ', $pairs)." WHERE wref = ".(int) $ref; - return mysqli_query($this->dblink,$q); - } - - function setVillageFields($ref, $fields, $values) { - list($ref, $fields, $values) = $this->escape_input((int) $ref, $fields, $values); - - if (!count($fields)) { - return; - } - - // build the field-value query parts - $fieldValues = []; - foreach ($fields as $id => $fieldName) { - $fieldValues[] = $this->escape($fieldName).' = '.((Math::isInt($values[$id]) || Math::isFloat($values[$id])) ? $values[$id] : '"'.$this->escape($values[$id]).'"'); - } - - $q = "UPDATE " . TB_PREFIX . "vdata set ".implode(', ', $fieldValues)." where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - function setVillageLevel($ref, $fields, $values) { - list($ref, $fields, $values) = $this->escape_input((int) $ref, $fields, $values); - - // build the field-value query parts - $fieldValues = []; - - if (!is_array($fields)) { - $fields = [$fields]; - $values = [$values]; - } - - foreach ($fields as $id => $fieldName) { - $fieldValues[] = $this->escape($fieldName).' = '.((Math::isInt($values[$id]) || Math::isFloat($values[$id])) ? $values[$id] : '"'.$this->escape($values[$id]).'"'); - } - - $q = "UPDATE " . TB_PREFIX . "fdata set ".implode(', ', $fieldValues)." where vref = " . $ref; - return mysqli_query($this->dblink,$q); - } - - function cacheResourceLevels($vids) { - if (!is_array($vids)) { - $vids = [$vids]; - } - - $newVids = []; - foreach ($vids as $index => $vidValue) { - $vids[ $index ] = (int) $vidValue; - - // don't cache what's cached - if (!isset(self::$resourceLevelsCache[$vids[ $index ]])) { - $newVids[] = $vids[ $index ]; - } - } - $vids = $newVids; - - if (!count($vids)) { - return []; - } - - $q = "SELECT * FROM " . TB_PREFIX . "fdata WHERE vref IN(".implode(', ', $vids).")"; - $result = mysqli_query($this->dblink,$q); - - foreach ( $this->mysqli_fetch_all( $result ) as $row ) { - self::$resourceLevelsCache[ $row['vref'] ] = $row; - } - - return self::$resourceLevelsCache; - } - - function getResourceLevel($vid, $use_cache = true) { - list($vid) = $this->escape_input((int) $vid); - - // 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::$resourceLevelsCache, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * from " . TB_PREFIX . "fdata where vref = $vid"; - $result = mysqli_query($this->dblink,$q); - - self::$resourceLevelsCache[$vid] = mysqli_fetch_assoc($result); - return self::$resourceLevelsCache[$vid]; - } - - public static function clearResourseLevelsCache() { - self::$resourceLevelsCache = []; - self::$fieldLevelsInVillageSearchCache = []; - self::$fieldLevelsCache = []; - } - - function getAdminLog() { - $q = "SELECT id,user,log,time from " . TB_PREFIX . "admin_log where id != 0 ORDER BY id DESC"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - //fix market log - function getMarketLog() { - $q = "SELECT id,wid,log from " . TB_PREFIX . "market_log where id != 0 ORDER BY id ASC"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function getMarketLogVillage($village) { - list($village) = $this->escape_input((int) $village); - - $q = "SELECT wref,owner,name from " . TB_PREFIX . "vdata where wref =$village "; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - function getMarketLogUsers($id_user) { - list($id_user) = $this->escape_input((int) $id_user); - - $q = "SELECT id,username from " . TB_PREFIX . "users where id = $id_user "; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - //end fix - - function getCoor($wref, $use_cache = true) { - // retirieve form cache - return $this->getVillageByWorldID($wref, $use_cache); - } - - /** - * Get shared forums (and confederation forums), based on user ID and alliance ID - * - * @param int $uid The user ID - * @param int $alliance The alliance ID - * @return array Returns all user's shared forums - */ - - function getSharedForums($uid, $alliance){ - list($uid, $alliance) = $this->escape_input((int) $uid, (int) $alliance); - - $allianceForums = $confForums = $closedForums = []; - - $q = "SELECT * FROM - ".TB_PREFIX."forum_cat - WHERE - display_to_alliances - LIKE - '%,$alliance,%' - OR - display_to_alliances - LIKE - '%,$alliance%' - OR - display_to_alliances - LIKE - '%$alliance,%' - OR - display_to_alliances - = - '$alliance' - OR - display_to_users - LIKE - '%,$uid,%' - OR - display_to_users - LIKE - '%,$uid%' - OR - display_to_users - LIKE - '%$uid,%' - OR - display_to_users - = - '$uid' - "; - $result = mysqli_query($this->dblink, $q); - if(!empty($result)){ - while($row = mysqli_fetch_assoc($result)) { - switch($row['forum_area']){ - case 0: $allianceForums[] = $row; break; - case 2: $confForums[] = $row; break; - case 3: $closedForums[] = $row; break; - } - } - } - - //Get the alliance confederation forums - if($alliance > 0){ - $confederations = $this->diplomacyExistingRelationships($alliance); - if(!empty($confederations)){ - foreach($confederations as $confederation){ - if($confederation['type'] == 1){ - $confederationForums = $this->ForumCat($confederation['alli1'] == $alliance ? $confederation['alli2'] : $confederation['alli1'], 1); - if(!empty($confederationForums)){ - foreach($confederationForums as $forum){ - if($forum['forum_area'] == 2) $confForums[] = $forum; - } - } - } - } - } - } - - return ['alliance' => $allianceForums, 'confederation' => $confForums, 'closed' => $closedForums]; - } - - /** - * Get the total amount of the wanted forum, based on alliance and the forum area - * - * @param int $forumArea The forum Area 0 = alliance, 1 = public, 2 = confederation, 3 = closed - * @param int $ally The alliance ID - * @return int Returns the total amount of the wanted forum - */ - - function countForums($forumArea, $ally){ - list($forumArea, $ally) = $this->escape_input((int) $forumArea, (int) $ally); - - $q = "SELECT Count(*) as Total FROM ".TB_PREFIX."forum_cat WHERE ".($ally != -1 ? "alliance = $ally AND" : "")." forum_area = $forumArea"; - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC ); - return $result['Total']; - } - - // no need to refactor this method - function CheckForum($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "forum_cat where alliance = $id"; - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - // no need to refactor this method - function CountCat($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_topic where cat = '$id'"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - return $row[0]; - } - - // no need to refactor this method - function LastTopic($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' order by post_date"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function CheckLastTopic($id, $use_cache = true) { - list($id) = $this->escape_input($id); - - // 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::$lastTopicCheckCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_topic where cat = $id"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - if($result['Total']) { - self::$lastTopicCheckCache[$id] = true; - } else { - self::$lastTopicCheckCache[$id] = false; - } - - return self::$lastTopicCheckCache[$id]; - } - - function CheckLastPost($id, $use_cache = true) { - list($id) = $this->escape_input($id); - - // 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::$lastPostForTopicCheckCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_post where topic = $id"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - if ($result['Total']) { - self::$lastPostForTopicCheckCache[$id] = true; - } else { - self::$lastPostForTopicCheckCache[$id] = false; - } - - return self::$lastPostForTopicCheckCache[$id]; - } - - function LastPost($id, $use_cache = true) { - list($id) = $this->escape_input($id); - - // 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::$lastPostForTopicCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * from " . TB_PREFIX . "forum_post where topic = '$id'"; - $result = mysqli_query($this->dblink,$q); - - self::$lastPostForTopicCache[$id] = $this->mysqli_fetch_all($result); - return self::$lastPostForTopicCache[$id]; - } - - function CountTopic($id, $use_cache = true) { - list($id) = $this->escape_input($id); - - // 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::$topicCountCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_post where owner = '$id'"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - - $qs = "SELECT count(id) FROM " . TB_PREFIX . "forum_topic where owner = '$id'"; - $results = mysqli_query($this->dblink,$qs); - $rows = mysqli_fetch_row($results); - - self::$topicCountCache[$id] = $row[0] + $rows[0]; - return self::$topicCountCache[$id]; - } - - // no need to cache this method - function CountPost($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_post where topic = '$id'"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - return $row[0]; - } - - // no need to cache this method - function ForumCat($id, $mode = 0) { - list($id, $mode) = $this->escape_input($id, $mode); - - $q = "SELECT * from " . TB_PREFIX . "forum_cat where alliance = '$id' ".(!$mode ? "OR forum_area = 1" : "")." ORDER BY sorting DESC, id"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ForumCatEdit($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT * from " . TB_PREFIX . "forum_cat where id = '$id'"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ForumCatAlliance($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT alliance from " . TB_PREFIX . "forum_cat where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink, $q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['alliance']; - } - - // no need to cache this method - function ForumCatName($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT forum_name from " . TB_PREFIX . "forum_cat where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['forum_name']; - } - - // no need to cache this method - function CheckCatTopic($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_topic where cat = $id"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - // no need to cache this method - function CheckResultEdit($alli) { - list($alli) = $this->escape_input($alli); - - $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_edit where alliance = $alli"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - // no need to cache this method - function CheckCloseTopic($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT close from " . TB_PREFIX . "forum_topic where id = '$id' LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['close']; - } - - function CheckEditRes($alli, $use_cache = true) { - list($alli) = $this->escape_input($alli); - - // 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::$editResultsCache, $alli)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT result from " . TB_PREFIX . "forum_edit where alliance = '$alli' LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - - self::$editResultsCache[$alli] = $dbarray['result']; - return self::$editResultsCache[$alli]; - } - - function CreatResultEdit($alli, $result) { - list($alli, $result) = $this->escape_input($alli, $result); - - $q = "INSERT into " . TB_PREFIX . "forum_edit values (0,'$alli','$result')"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - function UpdateResultEdit($alli, $result) { - list($alli, $result) = $this->escape_input($alli, $result); - - $date = time(); - $q = "UPDATE " . TB_PREFIX . "forum_edit set result = '$result' where alliance = '$alli'"; - return mysqli_query($this->dblink,$q); - } - - function getVillageType2($wref, $use_cache = true) { - // retirieve form cache - return $this->getVillageByWorldID($wref, $use_cache)['oasistype']; - } - - // no need to cache this method - function checkVilExist($wref) { - list($wref) = $this->escape_input((int) $wref); - - // first of all, check if this exists in our cache already - and if so, we don't need an extra query - $mode = 0; - if (isset(self::$villageFieldsCache[((int) $wref).$mode])) { - return true; - } - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "vdata where wref = '$wref'"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - - return $result['Total']; - } - - // no need to cache this method - function checkOasisExist($wref) { - list($wref) = $this->escape_input((int) $wref); - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "odata where wref = '$wref'"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - if($result['Total']) { - return true; - } else { - return false; - } - } - - function MoveForum($id, $area, $ally, $mode){ - list($id, $area, $ally, $mode) = $this->escape_input((int) $id, (int) $area, (int) $ally, $mode); - - $q = "UPDATE - ".TB_PREFIX."forum_cat - SET - sorting = (SELECT * FROM(SELECT ".(!$mode ? "MIN" : "MAX")."(sorting) FROM ".TB_PREFIX."forum_cat WHERE forum_area = $area ".($area != 1 ? "AND alliance = $ally" : "")." AND id != $id) f) ".(!$mode ? "-" : "+")." 1 - WHERE - id = $id"; - return mysqli_query($this->dblink, $q); - } - - function UpdateEditTopic($id, $title, $cat) { - list($id, $title, $cat) = $this->escape_input((int) $id, $title, $cat); - - $q = "UPDATE " . TB_PREFIX . "forum_topic set title = '$title', cat = '$cat' where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function UpdateEditForum($id, $name, $des, $alliances, $users) { - list($id, $name, $des, $alliances, $users) = $this->escape_input((int) $id, $name, $des, $alliances, $users); - - $q = "UPDATE " . TB_PREFIX . "forum_cat SET forum_name = '$name', forum_des = '$des', display_to_alliances = '$alliances', display_to_users = '$users' WHERE id = $id"; - return mysqli_query($this->dblink,$q); - } - - function StickTopic($id, $mode) { - list($id, $mode) = $this->escape_input((int) $id, (int) $mode); - - $q = "UPDATE " . TB_PREFIX . "forum_topic SET stick = $mode WHERE id = $id"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function ForumCatTopic($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' AND stick = '' ORDER BY post_date desc"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ForumCatTopicStick($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' AND stick = '1' ORDER BY post_date desc"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ShowTopic($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * from " . TB_PREFIX . "forum_topic where id = $id"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ShowPost($id) { - list($id) = $this->escape_input($id); - - $q = "SELECT * from " . TB_PREFIX . "forum_post where topic = '$id' ORDER BY id ASC"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function ShowPostEdit($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * from " . TB_PREFIX . "forum_post where id = $id"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function CreatForum($owner, $alli, $name, $des, $area, $alliances, $users) { - list($owner, $alli, $name, $des, $area, $alliances, $users) = $this->escape_input($owner, $alli, $name, $des, $area, $alliances, $users); - - $q = "INSERT into " . TB_PREFIX . "forum_cat values (0, 0,'$owner','$alli','$name','$des','$area','$alliances','$users')"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - function CreatTopic($title, $post, $cat, $owner, $alli, $ends) { - list($title, $post, $cat, $owner, $alli, $ends) = $this->escape_input($title, $post, (int) $cat, (int) $owner, (int) $alli, (int) $ends); - - $date = time(); - $q = "INSERT into " . TB_PREFIX . "forum_topic values (0,'$title','$post',$date, $date, $cat, $owner, $alli, $ends, 0, 0)"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - /************************* - FORUM SUREY - *************************/ - - function createSurvey($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends) { - list($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends) = $this->escape_input($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends); - - $q = "INSERT into " . TB_PREFIX . "forum_survey (topic,title,option1,option2,option3,option4,option5,option6,option7,option8,ends) values ('$topic','$title','$option1','$option2','$option3','$option4','$option5','$option6','$option7','$option8','$ends')"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getSurvey($topic) { - list($topic) = $this->escape_input((int) $topic); - - $q = "SELECT * FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - return mysqli_fetch_array($result); - } - - // no need to cache this method - function checkSurvey($topic) { - list($topic) = $this->escape_input((int) $topic); - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "forum_survey where topic = $topic"; - $result = mysqli_fetch_array( mysqli_query( $this->dblink, $q ), MYSQLI_ASSOC ); - - if ( $result['Total'] ) { - return true; - } else { - return false; - } - } - - function Vote($topic, $num, $text) { - list($topic, $num, $text) = $this->escape_input((int) $topic, (int) $num, $text); - - $q = "UPDATE " . TB_PREFIX . "forum_survey set vote".$num." = vote".$num." + 1, voted = '$text' where topic = ".$topic.""; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function checkVote($topic, $uid) { - list( $topic, $uid ) = $this->escape_input( (int) $topic, $uid ); - - $q = "SELECT voted FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; - $result = mysqli_query( $this->dblink, $q ); - $array = mysqli_fetch_array( $result ); - $text = $array['voted']; - - if ( preg_match( '/,' . $uid . ',/', $text ) ) { - return true; - } else { - return false; - } - } - - // no need to cache this method - function getVoteSum($topic) { - list( $topic ) = $this->escape_input( (int) $topic ); - - $q = "SELECT * FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; - $result = mysqli_query( $this->dblink, $q ); - $array = mysqli_fetch_array( $result ); - $sum = 0; - - for ( $i = 1; $i <= 8; $i ++ ) { - $sum += $array[ 'vote' . $i ]; - } - - return $sum; - } - - - /************************* - FORUM SUREY - *************************/ - - function CreatPost($post, $tids, $owner, $fid2 = 0) { - global $message, $session; - list($post, $tids, $owner, $fid2) = $this->escape_input($post, (int) $tids, $owner, (int) $fid2); - - $date = time(); - $q = "INSERT into " . TB_PREFIX . "forum_post values (0,'$post',$tids,'$owner','$date')"; - mysqli_query($this->dblink,$q); - $postID = mysqli_insert_id($this->dblink); - - // create a message notification for each person subscribed to this topic - // ... for now it's everyone who ever posted there, there is no real un/subscription yet - if(NEW_FUNCTIONS_FORUM_POST_MESSAGE){ - if ($fid2 !== 0) { - $q = "SELECT DISTINCT owner FROM ".TB_PREFIX . "forum_post WHERE topic = $tids"; - $result = mysqli_query($this->dblink, $q); - if ($result->num_rows) { - while ($row = mysqli_fetch_assoc($result)) { - if ($row['owner'] != $owner) { - $this->sendMessage( - (int) $row['owner'], - 4, - rc_tok('MSG_FORUM_NEW_TITLE'), - rc_tok( - 'MSG_FORUM_NEW_BODY', - rtrim(SERVER, '/')."/spieler.php?uid=".(int) $session->uid, - $this->escape($session->username), - rtrim(SERVER, '/')."/allianz.php?s=2&pid=2&fid2=$fid2&tid=$tids" - ), - 0, - 0, - 0, - 0, - 0, - true); - } - } - } - } - } - return $postID; - } - - function UpdatePostDate($id) { - list($id) = $this->escape_input((int) $id); - - $date = time(); - $q = "UPDATE " . TB_PREFIX . "forum_topic set post_date = '$date' where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function EditUpdateTopic($id, $post) { - list($id, $post) = $this->escape_input((int) $id, $post); - - $q = "UPDATE " . TB_PREFIX . "forum_topic set post = '$post' where id = $id"; - - return mysqli_query($this->dblink, $q); - } - - function EditUpdatePost($id, $post) { - list($id, $post) = $this->escape_input((int) $id, $post); - - $q = "UPDATE " . TB_PREFIX . "forum_post set post = '$post' where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function LockTopic($id, $mode) { - list($id, $mode) = $this->escape_input((int) $id, $mode); - - $q = "UPDATE " . TB_PREFIX . "forum_topic set close = '$mode' where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function DeleteCat($id) { - list($id) = $this->escape_input($id); - - $qs = "DELETE from " . TB_PREFIX . "forum_cat where id = '$id'"; - $q = "DELETE from " . TB_PREFIX . "forum_topic where cat = '$id'"; - $q2="SELECT id from ".TB_PREFIX."forum_topic where cat ='$id'"; - $result = mysqli_query($this->dblink,$q2); - if (!empty($result)) { - $array=$this->mysqli_fetch_all($result); - $toDelete = []; - foreach($array as $ss) { - $toDelete[] = $ss['id']; - } - $this->DeleteSurvey($toDelete); - } - mysqli_query($this->dblink,$qs); - return mysqli_query($this->dblink,$q); - } - - function DeleteSurvey($id) { - if (!is_array($id)) { - $id = [$id]; - } - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - - $qs = "DELETE from " . TB_PREFIX . "forum_survey where topic IN(".implode(', ', $id).")"; - return mysqli_query($this->dblink,$qs); - } - - function DeleteTopic($id) { - list($id) = $this->escape_input($id); - - $qs = "DELETE from " . TB_PREFIX . "forum_topic where id = '$id'"; - return mysqli_query($this->dblink,$qs); - } - - function DeletePost($id) { - list($id) = $this->escape_input($id); - - $q = "DELETE from " . TB_PREFIX . "forum_post where id = '$id'"; - return mysqli_query($this->dblink,$q); - } - - function getAllianceName($id, $use_cache = true) { - $alliance = $this->getAlliance($id, $use_cache); - if (!is_array($alliance) || !isset($alliance['tag'])) { - return null; - } - return $alliance['tag']; - } - - // no need to cache this method - - function getAlliancePermission($uid, $field, $alliance) { - $uid = (int)$uid; - $alliance = (int)$alliance; - - // whitelist câmpuri permise - $allowed_fields = ['ap1','ap2','ap3','ap4','ap5','ap6','ap7','ap8','ap9','ap10','owner','admin','rank']; - - if (!in_array($field, $allowed_fields)) { - error_log("Invalid field in getAlliancePermission: $field"); - return false; - } - - $q = "SELECT `$field` FROM " . TB_PREFIX . "ali_permission WHERE uid = $uid AND alliance = $alliance LIMIT 1"; - - $result = mysqli_query($this->dblink, $q); - - if (!$result) { - error_log("SQL Error in getAlliancePermission: " . mysqli_error($this->dblink) . " | Query: $q"); - return false; - } - - if (mysqli_num_rows($result) == 0) { - return false; - } - - $row = mysqli_fetch_assoc($result); - - return $row[$field]; - } - - function getAlliance($id, $use_cache = true) { - $id = (int) $id; - - // 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::$allianceDataCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * from " . TB_PREFIX . "alidata where id = $id"; - $result = mysqli_query($this->dblink,$q); - - self::$allianceDataCache[$id] = mysqli_fetch_assoc($result); - return self::$allianceDataCache[$id]; - } - - function setAlliName($aid, $name, $tag) { - list($aid, $name, $tag) = $this->escape_input((int) $aid, $name, $tag); - $name = $this->RemoveXSS($name); - $tag = $this->RemoveXSS($tag); - - $q = "UPDATE " . TB_PREFIX . "alidata set name = '$name', tag = '$tag' where id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function isAllianceOwner($id, $use_cache = true) { - $id = (int) $id; - - // 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::$allianceOwnerCheckCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT id from " . TB_PREFIX . "alidata where leader = ". $id; - $result = mysqli_query($this->dblink,$q); - if(mysqli_num_rows($result)) { - $result = mysqli_fetch_assoc($result); - $result = $result['id']; - } - else $result = false; - - self::$allianceOwnerCheckCache[$id] = $result; - return self::$allianceOwnerCheckCache[$id]; - } - - function countAllianceMembers($aid, $use_cache = true) { - $aid = (int) $aid; - - // 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::$allianceMembersCountCache, $aid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT Count(*) as Total from ".TB_PREFIX."users WHERE alliance = ".$aid; - $membersCount = $this->query_return($q); - - self::$allianceMembersCountCache[$aid] = $membersCount[0]['Total']; - return self::$allianceMembersCountCache[$aid]; - } - - // no need to cache this method - function aExist($ref, $type) { - list($ref, $type) = $this->escape_input($ref, $type); - - $q = "SELECT $type FROM " . TB_PREFIX . "alidata where $type = '$ref'"; - $result = mysqli_query($this->dblink,$q); - return mysqli_num_rows($result); - } - - function modifyPoints($aid, $points, $amt) { - $aid = (int) $aid; - - if (!is_array($points)) { - $points = [$points]; - $amt = [$amt]; - } - - $updates = []; - foreach ($points as $index => $value) { - $value = $this->escape($value); - $updates[] = $value.' = ' . $value . ' + ' . (int) $amt[$index]; - } - - $q = "UPDATE " . TB_PREFIX . "users SET ".implode(', ', $updates)." WHERE id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function modifyPointsAlly($aid, $points, $amt) { - $aid = (int) $aid; - - if (!is_array($points)) { - $points = [$points]; - $amt = [$amt]; - } - - $updates = []; - foreach ($points as $index => $value) { - $value = $this->escape($value); - $updates[] = $value.' = ' . $value . ' + ' . (int) $amt[$index]; - } - - $q = "UPDATE " . TB_PREFIX . "alidata SET ".implode(', ', $updates)." WHERE id = $aid"; - return mysqli_query($this->dblink,$q); - } - - /***************************************** - Function to create an alliance - References: - *****************************************/ - function createAlliance($tag, $name, $uid, $max) { - list($tag, $name, $uid, $max) = $this->escape_input($tag, $name, (int) $uid, (int) $max); - $tag = $this->RemoveXSS($tag); - $name = $this->RemoveXSS($name); - - $q = "INSERT into " . TB_PREFIX . "alidata values (0,'$name','$tag',$uid,0,0,0,'','',$max,0,0,0,0,0,0,0,0,0)"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - function procAllyPop($aid) { - list($aid) = $this->escape_input($aid); - - $ally = $this->getAlliance($aid); - $memberlist = $this->getAllMember($ally['id']); - $oldrank = 0; - $memberIDs = []; - - foreach($memberlist as $member) { - $memberIDs[] = $member['id']; - } - - $data = $this->getVSumField($memberIDs,"pop"); - - if (count($data)) { - foreach ($data as $row) { - $oldrank += $row['Total']; - } - } - - if($ally['oldrank'] != $oldrank){ - if($ally['oldrank'] < $oldrank) { - $totalpoints = $oldrank - $ally['oldrank']; - $this->addclimberrankpopAlly($ally['id'], $totalpoints); - $this->updateoldrankAlly($ally['id'], $oldrank); - } else - if($ally['oldrank'] > $oldrank) { - $totalpoints = $ally['oldrank'] - $oldrank; - $this->removeclimberrankpopAlly($ally['id'], $totalpoints); - $this->updateoldrankAlly($ally['id'], $oldrank); - } - } - } - - /***************************************** - Function to insert an alliance new - References: - *****************************************/ - function insertAlliNotice($aid, $notice) { - list($aid, $notice) = $this->escape_input($aid, $notice); - - $time = time(); - $q = "INSERT into " . TB_PREFIX . "ali_log values (0,'$aid','$notice',$time)"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - /***************************************** - Function to delete alliance if empty - References: - *****************************************/ - function deleteAlliance($aid) { - list($aid) = $this->escape_input((int) $aid); - - $result = mysqli_fetch_array(mysqli_query($this->dblink,"SELECT Count(*) as Total FROM " . TB_PREFIX . "users where alliance = $aid"), MYSQLI_ASSOC); - if ($result['Total'] == 0) { - // remove the alliance - $q = "DELETE FROM " . TB_PREFIX . "alidata WHERE id = $aid"; - mysqli_query($this->dblink,$q); - - // remove all permissions for that alliance - $q = "DELETE FROM " . TB_PREFIX . "ali_permission WHERE alliance = $aid"; - mysqli_query($this->dblink,$q); - - // remove all logs for that alliance - $q = "DELETE FROM " . TB_PREFIX . "ali_log WHERE aid = $aid"; - mysqli_query($this->dblink,$q); - - // remove all medals for that alliance - $q = "DELETE FROM " . TB_PREFIX . "allimedal WHERE allyid = $aid"; - mysqli_query($this->dblink,$q); - - // remove all invitations for that alliance - $q = "DELETE FROM " . TB_PREFIX . "ali_invite WHERE alliance = $aid"; - mysqli_query($this->dblink,$q); - } - } - - /***************************************** - Function to read all alliance news - References: - *****************************************/ - function readAlliNotice($aid) { - list($aid) = $this->escape_input((int) $aid); - - $q = "SELECT * from " . TB_PREFIX . "ali_log where aid = $aid ORDER BY date DESC"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - /***************************************** - Function to create alliance permissions - References: ID, notice, description - *****************************************/ - function createAlliPermissions($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8) { - list($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8) = $this->escape_input($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8); - - - $q = "INSERT into " . TB_PREFIX . "ali_permission values(0,'$uid','$aid','$rank','$opt1','$opt2','$opt3','$opt4','$opt5','$opt6','$opt7','$opt8')"; - mysqli_query($this->dblink,$q); - - // update cache - $insertID = mysqli_insert_id($this->dblink); - self::$alliancePermissionsCache[$uid.$aid] = [ - 'id' => $insertID, - 'uid' => $uid, - 'alliance' => $aid, - 'rank' => $rank, - 'opt1' => $opt1, - 'opt2' => $opt2, - 'opt3' => $opt3, - 'opt4' => $opt4, - 'opt5' => $opt5, - 'opt6' => $opt6, - 'opt7' => $opt7, - 'opt8' => $opt8 - ]; - - return $insertID; - } - - /***************************************** - Function to update alliance permissions - References: - *****************************************/ - function deleteAlliPermissions($uid) { - list($uid) = $this->escape_input($uid); - - $q = "DELETE from " . TB_PREFIX . "ali_permission where uid = '$uid'"; - return mysqli_query($this->dblink,$q); - } - /***************************************** - Function to update alliance permissions - References: - *****************************************/ - - function updateAlliPermissions($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7){ - list($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7) = $this->escape_input((int)$uid, (int)$aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7); - // update cache - if (isset(self::$alliancePermissionsCache[$uid.$aid])) { - self::$alliancePermissionsCache[ $uid . $aid ]['rank'] = $rank; - self::$alliancePermissionsCache[ $uid . $aid ]['opt1'] = $opt1; - self::$alliancePermissionsCache[ $uid . $aid ]['opt2'] = $opt2; - self::$alliancePermissionsCache[ $uid . $aid ]['opt3'] = $opt3; - self::$alliancePermissionsCache[ $uid . $aid ]['opt4'] = $opt4; - self::$alliancePermissionsCache[ $uid . $aid ]['opt5'] = $opt5; - self::$alliancePermissionsCache[ $uid . $aid ]['opt6'] = $opt6; - self::$alliancePermissionsCache[ $uid . $aid ]['opt7'] = $opt7; - self::$alliancePermissionsCache[ $uid . $aid ]['opt8'] = $opt8; - } - $q = "UPDATE " . TB_PREFIX . "ali_permission SET `rank` = '$rank',opt1 = '$opt1', opt2 = '$opt2', opt3 = '$opt3', opt4 = '$opt4', opt5 = '$opt5', opt6 = '$opt6', opt7 = '$opt7' WHERE uid = $uid AND alliance = $aid LIMIT 1"; - $result = mysqli_query($this->dblink, $q); - if(!$result) { - die("SQL ERROR: " . mysqli_error($this->dblink) . "

" . $q); - } - return true; - } - - /***************************************** - Function to read alliance permissions - References: ID, notice, description - *****************************************/ - function getAlliPermissions($uid, $aid, $use_cache = true) { - list($uid, $aid) = $this->escape_input((int) $uid, (int) $aid); - - // 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::$alliancePermissionsCache, $uid.$aid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "ali_permission where uid = $uid && alliance = $aid"; - $result = mysqli_query($this->dblink,$q); - - self::$alliancePermissionsCache[$uid.$aid] = mysqli_fetch_assoc($result); - return self::$alliancePermissionsCache[$uid.$aid]; - } - - /***************************************** - Function to update an alliance description and notice - References: ID, notice, description - *****************************************/ - function submitAlliProfile($aid, $notice, $desc) { - list($aid, $notice, $desc) = $this->escape_input((int) $aid, $notice, $desc); - - - $q = "UPDATE " . TB_PREFIX . "alidata SET `notice` = '$notice', `desc` = '$desc' where id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function diplomacyInviteAdd($alli1, $alli2, $type) { - list($alli1, $alli2, $type) = $this->escape_input((int) $alli1, (int) $alli2, $type); - - $q = "INSERT INTO " . TB_PREFIX . "diplomacy (alli1,alli2,type,accepted) VALUES ($alli1,$alli2," . (int)intval($type) . ",0)"; - return mysqli_query($this->dblink,$q); - } - - function diplomacyOwnOffers($sessionAlliance) { - list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); - - $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE alli1 = $sessionAlliance AND accepted = 0"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getAllianceID($name) { - list($name) = $this->escape_input($name); - - $q = "SELECT id FROM " . TB_PREFIX . "alidata WHERE tag ='" . $this->RemoveXSS($name) . "' LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['id']; - } - - function diplomacyCancelOffer($id, $sessionAlliance) { - list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); - - $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE id = $id AND alli1 = $sessionAlliance"; - return mysqli_query($this->dblink,$q); - } - - function diplomacyInviteAccept($id, $sessionAlliance) { - list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); - - $q = "UPDATE " . TB_PREFIX . "diplomacy SET accepted = 1 WHERE id = $id AND alli2 = $sessionAlliance"; - return mysqli_query($this->dblink,$q); - } - - function diplomacyInviteDenied($id, $sessionAlliance) { - list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); - - $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE id = $id AND alli2 = $sessionAlliance"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function diplomacyInviteCheck($sessionAlliance) { - list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); - - $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE alli2 = $sessionAlliance AND accepted = 0"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function diplomacyInviteCheck2($ally1, $ally2) { - list($ally1, $ally2) = $this->escape_input((int) $ally1, (int) $ally2); - - $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $ally1 OR alli2 = $ally1) AND (alli1 = $ally2 OR alli2 = $ally2)"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getAllianceDipProfile($aid, $type) { - list($aid, $type) = $this->escape_input($aid, $type); - $q = "SELECT alli1, alli2 FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '$type' AND accepted = '1' OR alli2 = '$aid' AND type = '$type' AND accepted = '1'"; - $array = $this->query_return($q); - $text = ""; - - if($array){ - foreach($array as $row){ - if($row['alli1'] == $aid) $alliance = $this->getAlliance($row['alli2']); - elseif($row['alli2'] == $aid) $alliance = $this->getAlliance($row['alli1']); - $text .= ""; - $text .= "" . $alliance['tag'] . "
"; - } - } - if(strlen($text) == 0){ - $text = "-
"; - } - return $text; - } - - // no need to cache this method - function getAllianceWar($aid) { - list($aid) = $this->escape_input($aid); - $q = "SELECT alli1, alli2 FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '3' OR alli2 = '$aid' AND type = '3' AND accepted = '1'"; - $array = $this->query_return($q); - $text = ""; - - if ($array) { - foreach($array as $row){ - if($row['alli1'] == $aid){ - $alliance = $this->getAlliance($row['alli2']); - }elseif($row['alli2'] == $aid){ - $alliance = $this->getAlliance($row['alli1']); - } - $text .= ""; - $text .= "".$alliance['tag']."
"; - } - } - if(strlen($text) == 0){ - $text = "-
"; - } - return $text; - } - - function getAllianceAlly($aid, $type, $use_cache = true) { - list($aid, $type) = $this->escape_input($aid, $type); - - // 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::$allianceAlliesCache, $aid.$type)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM ".TB_PREFIX."diplomacy WHERE (alli1 = '$aid' or alli2 = '$aid') AND (type = '$type' AND accepted = '1')"; - $result = mysqli_query($this->dblink,$q); - - self::$allianceAlliesCache[$aid.$type] = $this->mysqli_fetch_all($result); - return self::$allianceAlliesCache[$aid.$type]; - } - - // no need to cache this method - function getAllianceWar2($aid) { - list($aid) = $this->escape_input($aid); - $q = "SELECT * FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '3' OR alli2 = '$aid' AND type = '3' AND accepted = '1'"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function diplomacyExistingRelationships($sessionAlliance) { - list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); - - $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $sessionAlliance OR alli2 = $sessionAlliance) AND accepted = 1"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function diplomacyCancelExistingRelationship($id, $sessionAlliance) { - list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); - - $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $sessionAlliance OR alli2 = $sessionAlliance) AND id = $id "; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function diplomacyCheckLimits($aid, $type) { - list($aid, $type) = $this->escape_input((int) $aid, (int) $type); - if($type == 3) return true; - - $q = "SELECT Count(case when alli1 = $aid then 1 end) as Total1, Count(case when alli2 = $aid then 1 end) as Total2 FROM " . TB_PREFIX . "diplomacy WHERE type = $type"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - return $result['Total1'] < 3 && $result['Total2'] < 3; - } - - function setAlliForumdblink($aid, $dblink) { - list($aid, $dblink) = $this->escape_input((int) $aid, $dblink); - - $q = "UPDATE " . TB_PREFIX . "alidata SET `forumlink` = '$dblink' WHERE id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function getUserAlliance($id, $use_cache = true) { - list($id) = $this->escape_input((int) $id); - - // 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::$userAllianceCache, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT " . TB_PREFIX . "alidata.tag from " . TB_PREFIX . "users join " . TB_PREFIX . "alidata where " . TB_PREFIX . "users.alliance = " . TB_PREFIX . "alidata.id and " . TB_PREFIX . "users.id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - if($dbarray['tag'] == "") { - self::$userAllianceCache[$id] = "-"; - } else { - self::$userAllianceCache[$id] = $dbarray['tag']; - } - - return self::$userAllianceCache[$id]; - } - - 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 ? '-' : '+'); - - $q = " - UPDATE " . TB_PREFIX . "vdata - SET - wood = IF(wood $sign $wood < 0, 0, IF(wood $sign $wood > maxstore, maxstore, wood $sign $wood)), - clay = IF(clay $sign $clay < 0, 0, IF(clay $sign $clay > maxstore, maxstore, clay $sign $clay)), - iron = IF(iron $sign $iron < 0, 0, IF(iron $sign $iron > maxstore, maxstore, iron $sign $iron)), - crop = IF(crop $sign $crop < 0, 0, IF(crop $sign $crop > maxcrop, maxcrop, crop $sign $crop)) - WHERE - wref = " . $vid ; - - return mysqli_query( $this->dblink, $q); - } - - function setMaxStoreForVillage($vid, $maxLevel) { - $vid = (int) $vid; - $maxLevel = (int) $maxLevel; - - $this->query(" - UPDATE - ".TB_PREFIX."vdata - SET - `maxstore` = IF( `maxstore` - $maxLevel < ".STORAGE_BASE.", ".STORAGE_BASE.", `maxstore` - $maxLevel ) - WHERE - wref=$vid"); - } - - function setMaxCropForVillage($vid, $maxLevel) { - $vid = (int) $vid; - $maxLevel = (int) $maxLevel; - - $this->query(" - UPDATE - ".TB_PREFIX."vdata - SET - `maxcrop` = IF( `maxcrop` - $maxLevel < ".STORAGE_BASE.", ".STORAGE_BASE.", `maxcrop` - $maxLevel ) - WHERE - wref=$vid"); - } - - function modifyOasisResource($vid, $wood, $clay, $iron, $crop, $mode) { - list($vid, $wood, $clay, $iron, $crop, $mode) = $this->escape_input((int) $vid, (int) $wood, (int) $clay, (int) $iron, (int) $crop, $mode); - - $negativeResources = false; - $checkres = $this->getOasisV($vid); - - if (!$mode) { - $nwood = $checkres['wood'] - $wood; - $nclay = $checkres['clay'] - $clay; - $niron = $checkres['iron'] - $iron; - $ncrop = $checkres['crop'] - $crop; - - $negativeResources = $nwood < 0 || $nclay < 0 || $niron < 0 || $ncrop < 0; - - $dwood = ($nwood < 0) ? 0 : $nwood; - $dclay = ($nclay < 0) ? 0 : $nclay; - $diron = ($niron < 0) ? 0 : $niron; - $dcrop = ($ncrop < 0) ? 0 : $ncrop; - } else { - $nwood = $checkres['wood'] + $wood; - $nclay = $checkres['clay'] + $clay; - $niron = $checkres['iron'] + $iron; - $ncrop = $checkres['crop'] + $crop; - $dwood = ($nwood > $checkres['maxstore']) ? $checkres['maxstore'] : $nwood; - $dclay = ($nclay > $checkres['maxstore']) ? $checkres['maxstore'] : $nclay; - $diron = ($niron > $checkres['maxstore']) ? $checkres['maxstore'] : $niron; - $dcrop = ($ncrop > $checkres['maxcrop']) ? $checkres['maxcrop'] : $ncrop; - } - - if (!$negativeResources) { - $q = "UPDATE " . TB_PREFIX . "odata SET wood = $dwood, clay = $dclay, iron = $diron, crop = $dcrop WHERE wref = ".$vid; - return mysqli_query($this->dblink, $q); - } - else return false; - } - - function getFieldLevelInVillage($vid, $fieldType, $use_cache = true) { - $vid = (int) $vid; - - // first of all, check if we should be using cache and whether the field - // required is already cached. NB: use isset() rather than the generic - // returnCachedContent(), which treats a cached level of 0 as "empty" and - // re-queries it on every call — buildings the village does not own - // (level 0, very common) would otherwise never be cached. - if ($use_cache && isset(self::$fieldLevelsInVillageSearchCache[$vid.$fieldType])) { - return self::$fieldLevelsInVillageSearchCache[$vid.$fieldType]; - } - - // $fieldType can be both, integer and string, to be used in the IN statement, - // so we need to handle it correctly here - if (!Math::isInt($fieldType)) { - $fieldType = $this->escape($fieldType); - } - - // please don't scream... - // with the current table structure, there really IS NOT another way - // (except for stored procedures, which we can't rely on to be allowed on the server) - $result = mysqli_query($this->dblink, 'SELECT '. - 'CASE '. - 'WHEN `f1t` IN ('.$fieldType.') THEN `f1` '. - 'WHEN `f2t` IN ('.$fieldType.') THEN `f2` '. - 'WHEN `f3t` IN ('.$fieldType.') THEN `f3` '. - 'WHEN `f4t` IN ('.$fieldType.') THEN `f4` '. - 'WHEN `f5t` IN ('.$fieldType.') THEN `f5` '. - 'WHEN `f6t` IN ('.$fieldType.') THEN `f6` '. - 'WHEN `f7t` IN ('.$fieldType.') THEN `f7` '. - 'WHEN `f8t` IN ('.$fieldType.') THEN `f8` '. - 'WHEN `f9t` IN ('.$fieldType.') THEN `f9` '. - 'WHEN `f10t` IN ('.$fieldType.') THEN `f10` '. - 'WHEN `f11t` IN ('.$fieldType.') THEN `f11` '. - 'WHEN `f12t` IN ('.$fieldType.') THEN `f12` '. - 'WHEN `f13t` IN ('.$fieldType.') THEN `f13` '. - 'WHEN `f14t` IN ('.$fieldType.') THEN `f14` '. - 'WHEN `f15t` IN ('.$fieldType.') THEN `f15` '. - 'WHEN `f16t` IN ('.$fieldType.') THEN `f16` '. - 'WHEN `f17t` IN ('.$fieldType.') THEN `f17` '. - 'WHEN `f18t` IN ('.$fieldType.') THEN `f18` '. - 'WHEN `f19t` IN ('.$fieldType.') THEN `f19` '. - 'WHEN `f20t` IN ('.$fieldType.') THEN `f20` '. - 'WHEN `f21t` IN ('.$fieldType.') THEN `f21` '. - 'WHEN `f22t` IN ('.$fieldType.') THEN `f22` '. - 'WHEN `f23t` IN ('.$fieldType.') THEN `f23` '. - 'WHEN `f24t` IN ('.$fieldType.') THEN `f24` '. - 'WHEN `f25t` IN ('.$fieldType.') THEN `f25` '. - 'WHEN `f26t` IN ('.$fieldType.') THEN `f26` '. - 'WHEN `f27t` IN ('.$fieldType.') THEN `f27` '. - 'WHEN `f28t` IN ('.$fieldType.') THEN `f28` '. - 'WHEN `f29t` IN ('.$fieldType.') THEN `f29` '. - 'WHEN `f30t` IN ('.$fieldType.') THEN `f30` '. - 'WHEN `f31t` IN ('.$fieldType.') THEN `f31` '. - 'WHEN `f32t` IN ('.$fieldType.') THEN `f32` '. - 'WHEN `f33t` IN ('.$fieldType.') THEN `f33` '. - 'WHEN `f34t` IN ('.$fieldType.') THEN `f34` '. - 'WHEN `f35t` IN ('.$fieldType.') THEN `f35` '. - 'WHEN `f36t` IN ('.$fieldType.') THEN `f36` '. - 'WHEN `f37t` IN ('.$fieldType.') THEN `f37` '. - 'WHEN `f38t` IN ('.$fieldType.') THEN `f38` '. - 'WHEN `f39t` IN ('.$fieldType.') THEN `f39` '. - 'WHEN `f40t` IN ('.$fieldType.') THEN `f40` '. - 'WHEN `f99t` IN ('.$fieldType.') THEN `f99` '. - 'ELSE 0 '. - 'END AS level '. - 'FROM `'.TB_PREFIX.'fdata` '. - 'WHERE '. - '`vref` = '.$vid.' '. - 'AND ('. - '`f1t` IN ('.$fieldType.') OR '. - '`f2t` IN ('.$fieldType.') OR '. - '`f3t` IN ('.$fieldType.') OR '. - '`f4t` IN ('.$fieldType.') OR '. - '`f5t` IN ('.$fieldType.') OR '. - '`f6t` IN ('.$fieldType.') OR '. - '`f7t` IN ('.$fieldType.') OR '. - '`f8t` IN ('.$fieldType.') OR '. - '`f9t` IN ('.$fieldType.') OR '. - '`f10t` IN ('.$fieldType.') OR '. - '`f11t` IN ('.$fieldType.') OR '. - '`f12t` IN ('.$fieldType.') OR '. - '`f13t` IN ('.$fieldType.') OR '. - '`f14t` IN ('.$fieldType.') OR '. - '`f15t` IN ('.$fieldType.') OR '. - '`f16t` IN ('.$fieldType.') OR '. - '`f17t` IN ('.$fieldType.') OR '. - '`f18t` IN ('.$fieldType.') OR '. - '`f19t` IN ('.$fieldType.') OR '. - '`f20t` IN ('.$fieldType.') OR '. - '`f20t` IN ('.$fieldType.') OR '. - '`f21t` IN ('.$fieldType.') OR '. - '`f22t` IN ('.$fieldType.') OR '. - '`f23t` IN ('.$fieldType.') OR '. - '`f24t` IN ('.$fieldType.') OR '. - '`f25t` IN ('.$fieldType.') OR '. - '`f26t` IN ('.$fieldType.') OR '. - '`f27t` IN ('.$fieldType.') OR '. - '`f28t` IN ('.$fieldType.') OR '. - '`f29t` IN ('.$fieldType.') OR '. - '`f30t` IN ('.$fieldType.') OR '. - '`f30t` IN ('.$fieldType.') OR '. - '`f31t` IN ('.$fieldType.') OR '. - '`f32t` IN ('.$fieldType.') OR '. - '`f33t` IN ('.$fieldType.') OR '. - '`f34t` IN ('.$fieldType.') OR '. - '`f35t` IN ('.$fieldType.') OR '. - '`f36t` IN ('.$fieldType.') OR '. - '`f37t` IN ('.$fieldType.') OR '. - '`f38t` IN ('.$fieldType.') OR '. - '`f39t` IN ('.$fieldType.') OR '. - '`f40t` IN ('.$fieldType.') OR '. - '`f99t` IN ('.$fieldType.')) '. - 'LIMIT 1'); - $row = mysqli_fetch_array($result, MYSQLI_ASSOC); - - self::$fieldLevelsInVillageSearchCache[$vid.$fieldType] = isset($row['level']) ? (int)$row['level'] : 0; - return self::$fieldLevelsInVillageSearchCache[$vid.$fieldType]; - } - - function getFieldLevel($vid, $field, $use_cache = true) { - list($vid, $field) = $this->escape_input((int) $vid, $field); - - // 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::$resourceLevelsCache, $vid.$field)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT f" . $field . " from " . TB_PREFIX . "fdata where vref = $vid LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $row = $result ? mysqli_fetch_array($result, MYSQLI_ASSOC) : null; - - self::$resourceLevelsCache[$vid.$field] = (is_array($row) && array_key_exists("f".$field, $row)) ? (int)$row["f" . $field] : 0; - return self::$resourceLevelsCache[$vid.$field]; - } - - function getSingleFieldTypeCount($uid, $field, $lvlComparisonSign = '=', $lvl = false, $use_cache = true) { - $uid = (int) $uid; - $field = (int) $field; - $lvl = ($lvl === false ? $lvl : (int) $lvl); - - if (!in_array($lvlComparisonSign, ['=', '<', '>', '>=', '<=', '!='])) { - $lvlComparisonSign = '='; - } - - // 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::$singleFieldTypeCountCache, $uid.$field.$lvlComparisonSign.($lvl ? 1 : 0))) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = " - SELECT - Count(*) as Total - FROM - ".TB_PREFIX."fdata f - LEFT JOIN ".TB_PREFIX."vdata v ON f.vref = v.wref - LEFT JOIN ".TB_PREFIX."users u ON v.owner = u.id - WHERE - u.id = ".$uid." - AND - ( - (f1t = ".$field.($lvl !== false ? ' AND f1 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f2t = ".$field.($lvl !== false ? ' AND f2 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f3t = ".$field.($lvl !== false ? ' AND f3 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f4t = ".$field.($lvl !== false ? ' AND f4 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f5t = ".$field.($lvl !== false ? ' AND f5 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f6t = ".$field.($lvl !== false ? ' AND f6 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f7t = ".$field.($lvl !== false ? ' AND f7 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f8t = ".$field.($lvl !== false ? ' AND f8 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f9t = ".$field.($lvl !== false ? ' AND f9 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f10t = ".$field.($lvl !== false ? ' AND f10 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f11t = ".$field.($lvl !== false ? ' AND f11 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f12t = ".$field.($lvl !== false ? ' AND f12 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f13t = ".$field.($lvl !== false ? ' AND f13 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f14t = ".$field.($lvl !== false ? ' AND f14 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f15t = ".$field.($lvl !== false ? ' AND f15 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f16t = ".$field.($lvl !== false ? ' AND f16 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f17t = ".$field.($lvl !== false ? ' AND f17 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f18t = ".$field.($lvl !== false ? ' AND f18 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f19t = ".$field.($lvl !== false ? ' AND f19 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f20t = ".$field.($lvl !== false ? ' AND f20 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f21t = ".$field.($lvl !== false ? ' AND f21 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f22t = ".$field.($lvl !== false ? ' AND f22 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f23t = ".$field.($lvl !== false ? ' AND f23 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f24t = ".$field.($lvl !== false ? ' AND f24 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f25t = ".$field.($lvl !== false ? ' AND f25 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f26t = ".$field.($lvl !== false ? ' AND f26 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f27t = ".$field.($lvl !== false ? ' AND f27 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f28t = ".$field.($lvl !== false ? ' AND f28 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f29t = ".$field.($lvl !== false ? ' AND f29 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f30t = ".$field.($lvl !== false ? ' AND f30 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f31t = ".$field.($lvl !== false ? ' AND f31 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f32t = ".$field.($lvl !== false ? ' AND f32 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f33t = ".$field.($lvl !== false ? ' AND f33 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f34t = ".$field.($lvl !== false ? ' AND f34 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f35t = ".$field.($lvl !== false ? ' AND f35 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f36t = ".$field.($lvl !== false ? ' AND f36 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f37t = ".$field.($lvl !== false ? ' AND f37 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f38t = ".$field.($lvl !== false ? ' AND f38 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f39t = ".$field.($lvl !== false ? ' AND f39 '.$lvlComparisonSign.' '.$lvl : '').") - OR (f40t = ".$field.($lvl !== false ? ' AND f40 '.$lvlComparisonSign.' '.$lvl : '').") - )"; - - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result); - - self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)] = $row["Total"]; - return self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)]; - } - - function getFieldType($vid, $field, $use_cache = true) { - list($vid, $field) = $this->escape_input((int) $vid, $field); - - // 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::$fieldTypeCache, $vid.$field)) && !is_null($cachedValue)) { - return $cachedValue; - } - - if ($field && $vid) { - $q = "SELECT f" . $field . "t from " . TB_PREFIX . "fdata where vref = $vid LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result); - self::$fieldTypeCache[$vid.$field] = $row["f" . $field . "t"]; - } else { - self::$fieldTypeCache[$vid.$field] = 0; - } - - return self::$fieldTypeCache[$vid.$field]; - } - - // no need to cache this method - function getFieldDistance($wid) { - list($wid) = $this->escape_input((int) $wid); - - $array = $this->getProfileVillages($wid, 3); - $coor = $this->getCoor($wid); - $x1 = intval($coor['x']); - $y1 = intval($coor['y']); - $prevdist = 0; - $array2 = $this->getVillage(0, 4); - $vill = $array2['wref']; - - if ($array && count($array)){ - foreach($array as $village){ - $coor2 = $this->getCoor($village['wref']); - $max = 2 * WORLD_MAX + 1; - $x2 = intval($coor2['x']); - $y2 = intval($coor2['y']); - $distanceX = min(abs($x2 - $x1), abs($max - abs($x2 - $x1))); - $distanceY = min(abs($y2 - $y1), abs($max - abs($y2 - $y1))); - $dist = sqrt(pow($distanceX, 2) + pow($distanceY, 2)); - if($dist < $prevdist or $prevdist == 0){ - $prevdist = $dist; - $vill = $village['wref']; - } - } - } - return $vill; - } - - function updateVSumField($field) { - list($field) = $this->escape_input($field); - - //fix by ronix - if (SPEED >10) { - $speed = 10; - } else { - $speed = SPEED; - } - - // cultural points to gain during a day - $dur_day = (86400/SPEED); - - if ($dur_day < 3600) { - $dur_day = 3600; - } - - $q = " - UPDATE " . TB_PREFIX . "users as users - SET cp = cp + ( - ( SELECT sum($field) FROM " . TB_PREFIX . "vdata as vdata WHERE vdata.owner = users.id ".($field == 'cp' ? ' AND vdata.natar = 0' : '')." ) * - (UNIX_TIMESTAMP() - lastupdate) / $dur_day - ), - lastupdate = UNIX_TIMESTAMP() - WHERE - lastupdate < (UNIX_TIMESTAMP() - 600) - "; // recount every 10 minutes - - mysqli_query($this->dblink, $q); - } - - function getVSumField($uid, $field, $use_cache = true) { - list($field) = $this->escape_input($field); - - $array_passed = is_array($uid); - if (!$array_passed) { - $uid = [(int) $uid]; - } else { - foreach ($uid as $index => $uidValue) { - $uid[$index] = (int) $uidValue; - } - } - - if (!count($uid)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$userSumFieldCache, $uid[0].$field)) && !is_null($cachedValue)) { - return $cachedValue; - } - - if($field != "cp"){ - $q = "SELECT owner, MIN(lastupdate), SUM(" . $field . ") as Total FROM " . TB_PREFIX . "vdata where owner IN(".implode(', ', $uid).") GROUP BY owner"; - }else{ - $q = "SELECT owner, MIN(lastupdate), SUM(" . $field . ") as Total FROM " . TB_PREFIX . "vdata where owner IN(".implode(', ', $uid).") and natar = 0 GROUP BY owner"; - } - - $result = mysqli_query($this->dblink,$q); - - // return a single value - if (!$array_passed) { - $row = mysqli_fetch_row( $result ); - self::$userSumFieldCache[$row[0].$field] = $row[2]; - } else { - $result = $this->mysqli_fetch_all($result); - if ($result && count($result)) { - foreach ( $result as $record ) { - self::$userSumFieldCache[ $record['owner'] . $field ] = $record['Total']; - } - } - } - - return ($array_passed ? $result : self::$userSumFieldCache[$uid[0].$field]); - } - - function updateVillage($vid) { - list($vid) = $this->escape_input((int) $vid); - - $time = time(); - $q = "UPDATE " . TB_PREFIX . "vdata set lastupdate = $time where wref = $vid"; - return mysqli_query($this->dblink,$q); - } - - - function updateOasis($vid) { - list($vid) = $this->escape_input((int) $vid); - - $time = time(); - $q = "UPDATE " . TB_PREFIX . "odata set lastupdated = $time where wref = $vid"; - return mysqli_query($this->dblink,$q); - } - - function setVillageName($vid, $name) { - $vid = (int)$vid; - $name = trim($name); - if ($name === '') return false; - $name = mb_substr($name, 0, 30, 'UTF-8'); - - $stmt = $this->dblink->prepare( - "UPDATE `".TB_PREFIX."vdata` SET name = ? WHERE wref = ? LIMIT 1" - ); - $stmt->bind_param("si", $name, $vid); - $result = $stmt->execute(); - $stmt->close(); - return $result; - } - - function modifyPop($vid, $pop, $mode) { - list($vid, $pop, $mode) = $this->escape_input((int) $vid, (int) $pop, $mode); - - if(!$mode) { - $q = "UPDATE " . TB_PREFIX . "vdata set pop = pop + $pop where wref = $vid"; - } else { - $q = "UPDATE " . TB_PREFIX . "vdata set pop = pop - $pop where wref = $vid"; - } - return mysqli_query($this->dblink,$q); - } - - function addCP($ref, $cp) { - list($ref, $cp) = $this->escape_input((int) $ref, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "vdata set cp = cp + $cp where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - function addCel($ref, $cel, $type) { - list($ref, $cel, $type) = $this->escape_input((int) $ref, (int) $cel, (int) $type); - - $q = "UPDATE " . TB_PREFIX . "vdata set celebration = $cel, type= $type where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getCel() { - return $this->getProfileVillages(time(), 4); - } - - function clearCel($ref) { - list($ref) = $this->escape_input((int) $ref); - - $q = "UPDATE " . TB_PREFIX . "vdata set celebration = 0, type = 0 where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - function setCelCp($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "users set cp = cp + $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - /** - * Mead-Festival (Brewery, building 35 — Teutons only). - * Mirrors addCel()/getCel()/clearCel() above, but the festival grants no - * culture points: it only gates the temporary combat bonus, the chief - * persuasion penalty and the catapult randomization while it is active. - */ - function addFestival($ref, $end) { - list($ref, $end) = $this->escape_input((int) $ref, (int) $end); - - $q = "UPDATE " . TB_PREFIX . "vdata set festival = $end where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getFestivals() { - $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE festival < " . time() . " AND festival != 0"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function clearFestival($ref) { - list($ref) = $this->escape_input((int) $ref); - - $q = "UPDATE " . TB_PREFIX . "vdata set festival = 0 where wref = $ref"; - return mysqli_query($this->dblink,$q); - } - - /** - * Delete a single village or multiple ones - * - * @param mixed $wref The Village ID(s) - */ - - function DelVillage($wref){ - list($wref) = $this->escape_input($wref); - global $units; - - //Check if we've to delete a single village or multiple ones - if(!is_array($wref)) $wref = [$wref]; - - //Create the list of village IDs - $wrefs = implode(", ", $wref); - - $this->clearExpansionSlot($wref); - $q = "DELETE FROM ".TB_PREFIX."abdata where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."bdata where wid IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."market where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."research where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."tdata where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."fdata where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."training where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."units where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."farmlist where wref IN($wrefs)"; - $this->query($q); - $q = "UPDATE ".TB_PREFIX."artefacts SET del = 1 where vref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."raidlist where towref IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."route where wid IN($wrefs) OR `from` IN($wrefs)"; - $this->query($q); - $q = "DELETE FROM ".TB_PREFIX."movement where proc = 0 AND ((`to` IN($wrefs) AND sort_type = 4) OR (`from` IN($wrefs) AND sort_type = 3))"; - $this->query($q); - $this->removeOases($wref, 1); - - // In-flight attacks still heading for the deleted village(s) must be - // bounced straight home. Query them directly instead of using - // getMovement(3, $wref, 1): when passed an array, getMovement() returns - // the WHOLE movement cache (a map of "type.village.mode" => record list), - // not the flat record list this loop expects — so $movedata['moveid'] / - // ['starttime'] were NULL and the follow-up waves were never bounced, - // leaving them stuck at sort_type=3/proc=0 and looping forever once the - // village is gone. Pre-dates the sendunitsComplete() split (issue #298). - $q = "SELECT * FROM ".TB_PREFIX."movement, ".TB_PREFIX."attacks - WHERE ".TB_PREFIX."movement.`to` IN($wrefs) - AND ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id - AND ".TB_PREFIX."movement.proc = 0 - AND ".TB_PREFIX."movement.sort_type = 3 - ORDER BY endtime ASC"; - $getmovement = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q)); - - $moveIDs = []; - $time = microtime(true); - $types = []; - $froms = []; - $tos = []; - $refs = []; - $times = []; - $endtimes = []; - - foreach($getmovement as $movedata) { - $time2 = $time - $movedata['starttime']; - $moveIDs[] = $movedata['moveid']; - $types[] = 4; - $froms[] = $movedata['to']; - $tos[] = $movedata['from']; - $refs[] = $movedata['ref']; - $times[] = $time; - $endtimes[] = $time+$time2; - } - - $this->setMovementProc(implode(', ', $moveIDs)); - $this->addMovement($types, $froms, $tos, $refs, $times, $endtimes); - - $q = "DELETE FROM ".TB_PREFIX."enforcement WHERE `from` IN($wrefs)"; - $this->query($q); - - //check return enforcement from del village - foreach($wref as $w) $units->returnTroops($w); - - $q = "DELETE FROM ".TB_PREFIX."vdata WHERE `wref` IN($wrefs)"; - $this->query($q); - - if (mysqli_affected_rows($this->dblink) > 0) { - $q = "UPDATE ".TB_PREFIX."wdata set occupied = 0 where id IN($wrefs)"; - $this->query($q); - - // clear expansion slots, if this village is an expansion of any other village - $this->clearExpansionSlot($wref, 1); - - $getprisoners = $this->getPrisoners($wref); - foreach($getprisoners as $pris) { - $troops = 0; - for($i = 1; $i < 12; $i++) $troops += $pris['t'.$i]; - $this->modifyUnit($pris['wref'], ["99o"], [$troops], [0]); - $this->deletePrisoners($pris['id']); - } - - $getprisoners = $this->getPrisoners($wref, 1); - foreach($getprisoners as $pris) { - $troops = 0; - for($i = 1; $i < 12; $i++) $troops += $pris['t'.$i]; - $this->modifyUnit($pris['wref'], ["99o"], [$troops], [0]); - $this->deletePrisoners($pris['id']); - } - } - } - - /** - * Clear the expansion slots of a specified village(s) - * - * @param mixed $id The village ID(s) - * @param number $mode 0 If there's the need to clear all expansion slots of a village, - * 1 if there's the need to clear a single expansion slot of a village - */ - - function clearExpansionSlot($id, $mode = 0) { - // acceptă int sau array, fără (int) pe array - if(!is_array($id)) { - $id = [$id]; - } - // curățare sigură – doar numere - $id = array_map('intval', $id); - $ids = implode(",", $id); - - if(!$ids) return; - - if(!$mode){ - // ștergem sloturile DIN satul care se distruge - $pairs = []; - for($i = 1; $i <= 3; $i++) $pairs[] = 'exp'.$i.' = 0'; - $q = "UPDATE ".TB_PREFIX."vdata SET ".implode(',', $pairs)." WHERE wref IN($ids)"; - }else{ - // ștergem referința DIN satul părinte - $q = " - UPDATE ".TB_PREFIX."vdata - SET - exp1 = IF(exp1 IN($ids), 0, exp1), - exp2 = IF(exp2 IN($ids), 0, exp2), - exp3 = IF(exp3 IN($ids), 0, exp3) - WHERE - exp1 IN($ids) OR - exp2 IN($ids) OR - exp3 IN($ids)"; - } - mysqli_query($this->dblink, $q); - } - - // no need to cache this method - function getInvitation($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where uid = $uid"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getInvitation2($uid, $aid) { - list($uid, $aid) = $this->escape_input((int) $uid, (int) $aid); - - $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where uid = $uid and alliance = $aid"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getAliInvitations($aid) { - list($aid) = $this->escape_input((int) $aid); - - $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where alliance = $aid && accept = 0"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function sendInvitation($uid, $alli, $sender) { - list($uid, $alli, $sender) = $this->escape_input((int) $uid, (int) $alli, (int) $sender); - - $time = time(); - $q = "INSERT INTO " . TB_PREFIX . "ali_invite values (0,$uid,$alli,$sender,$time,0)"; - return mysqli_query($this->dblink,$q); - } - - function removeInvitation($id) { - list($id) = $this->escape_input((int) $id); - - $q = "DELETE FROM " . TB_PREFIX . "ali_invite where id = $id"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getUnreadMessagesCount($uid) { - $uid = (int) $uid; - - $ids = [$uid]; - - if (($this->getUserField($uid, 'access', 0) == ADMIN) && ADMIN_RECEIVE_SUPPORT_MESSAGES) { - $ids[] = 1; - } - - if ($this->getUserField($uid, 'access', 0) == MULTIHUNTER) { - $ids[] = 5; - } - - $q = 'SELECT Count(*) as numUnread FROM '.TB_PREFIX.'mdata WHERE target IN('.implode(', ', $ids).') AND viewed = 0'; - return mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC)['numUnread']; - } - - // no need to cache this method - function getUnreadNoticesCount($uid) { - $uid = (int) $uid; - - return mysqli_fetch_array(mysqli_query($this->dblink, ' - SELECT Count(*) as numUnread FROM '.TB_PREFIX.'ndata WHERE uid = '.$uid.' AND viewed = 0' - ), MYSQLI_ASSOC)['numUnread']; - } - - function sendMessage($client, $owner, $topic, $message, $send, $alliance, $player, $coor, $report, $skip_escaping = false) { - if (!$skip_escaping) { - list($client, $owner, $topic, $message, $send, $alliance, $player, $coor, $report) = $this->escape_input((int) $client, (int) $owner, $topic, $message, (int) $send, (int) $alliance, (int) $player, (int) $coor, (int) $report); - } - - $time = time(); - - // add this message to the query cache, so we save some queries - // if we need to send multiple messages at once - self::$sendMessageQueryCache[] = "(0,$client,$owner,'$topic','$message',0,0,$send,$time,0,0,$alliance,$player,$coor,$report)"; - - // check if we don't have too many messages to be sent out cached, - // in which case we'll flush the cache and start again - $retValue = true; - if (count(self::$sendMessageQueryCache) >= self::$sendMessageQueryCacheMaxRecords) { - $retValue = mysqli_query($this->dblink, "INSERT INTO " . TB_PREFIX . "mdata VALUES " . implode(', ', self::$sendMessageQueryCache)); - self::$sendMessageQueryCache = []; - } - - return $retValue; - } - - public function sendPendingMessages() { - if (count(self::$sendMessageQueryCache)) { - mysqli_query($this->dblink, "INSERT INTO " . TB_PREFIX . "mdata VALUES " . implode(', ', self::$sendMessageQueryCache)); - } - } - - function setArchived($id) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - $q = "UPDATE " . TB_PREFIX . "mdata set archived = 1 where id IN(".implode(', ', $id).")"; - return mysqli_query($this->dblink,$q); - } - - function setNorm($id) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - $q = "UPDATE " . TB_PREFIX . "mdata set archived = 0 where id IN(".implode(',', $id).")"; - return mysqli_query($this->dblink,$q); - } - -/*************************** -Function to get messages -Mode 1: Get inbox -Mode 2: Get sent -Mode 3: Get message -Mode 4: Set viewed -Mode 5: Remove message -Mode 6: Retrieve archive -References: User ID/Message ID, Mode -***************************/ - // no need to cache this method - function getMessage($id, $mode) { - global $session; - - $mode = (int) $mode; - $mode_updated = false; - // update $id if we should show Support messages for Admins and we are an admin - if ( - $session->access == ADMIN - && ADMIN_RECEIVE_SUPPORT_MESSAGES - && in_array($mode, [1,2,6,9,10,11]) - ) { - $id = $id . ', 1'; - $mode_updated = true; - } - - // update $id if we should show Multihunter messages for the current player - if ( - $session->access == MULTIHUNTER - && in_array($mode, [1,2,6,9,10,11]) - ) { - $id = $id . ', 5'; - $mode_updated = true; - } - - if (in_array($mode, [5,7,8])) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - } else { - if (!$mode_updated) { - $id = (int) $id; - } - } - - switch($mode) { - case 1: - $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE target IN($id) and send = 0 and archived = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - case 2: - $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE owner IN($id) ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - case 3: - $q = "SELECT * FROM " . TB_PREFIX . "mdata where id = $id"; - break; - case 4: - $show_target = $session->uid; - if ($session->access == ADMIN && ADMIN_RECEIVE_SUPPORT_MESSAGES) $show_target .= ',1'; - if ($session->access == MULTIHUNTER) $show_target .= ',5'; - - $q = "UPDATE " . TB_PREFIX . "mdata set viewed = 1 where id = $id AND target IN(".$show_target.")"; - break; - case 5: - $q = "UPDATE " . TB_PREFIX . "mdata set deltarget = 1, viewed = 1 where id IN(".implode(', ', $id).")"; - break; - case 6: - $q = "SELECT * FROM " . TB_PREFIX . "mdata where target IN($id) and send = 0 and archived = 1 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - case 7: - $q = "UPDATE " . TB_PREFIX . "mdata set delowner = 1 where id IN(".implode(', ', $id).")"; - break; - case 8: - $q = "UPDATE " . TB_PREFIX . "mdata set deltarget = 1, delowner = 1, viewed = 1 where id IN(".implode(', ', $id).")"; - break; - case 9: - $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE target IN($id) and send = 0 and archived = 0 and deltarget = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - case 10: - $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE owner IN($id) and delowner = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - case 11: - $q = "SELECT * FROM " . TB_PREFIX . "mdata where target IN($id) and send = 0 and archived = 1 and deltarget = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - break; - } - - if($mode <= 3 || $mode == 6 || $mode > 8) { - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - else return mysqli_query($this->dblink,$q); - } - - function unarchiveNotice($id) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - $q = "UPDATE " . TB_PREFIX . "ndata set ntype = archive, archive = 0 where id IN(".implode(',', $id).")"; - return mysqli_query($this->dblink,$q); - } - - function archiveNotice($id) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - $q = "update " . TB_PREFIX . "ndata set archive = ntype, ntype = 9 where id IN(".implode(',', $id).")"; - return mysqli_query($this->dblink,$q); - } - - function removeNotice($id) { - if (!is_array($id)) { - $id = [$id]; - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - $q = "UPDATE " . TB_PREFIX . "ndata set del = 1,viewed = 1 where id IN(".implode(',', $id).")"; - return mysqli_query($this->dblink,$q); - } - - function noticeViewed($id) { - list($id) = $this->escape_input((int) $id); - - $q = "UPDATE " . TB_PREFIX . "ndata set viewed = 1 where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function addNotice($uid, $toWref, $ally, $type, $topic, $data, $time = 0) { - list($uid, $toWref, $ally, $type, $topic, $data, $time) = $this->escape_input((int) $uid, (int) $toWref, (int) $ally, (int) $type, $topic, $data, (int) $time); - - //We don't need to send reports to Nature or Natars - if($uid == 2 || $uid == 3) return; - if($time == 0) $time = time(); - - $q = "INSERT INTO " . TB_PREFIX . "ndata (id, uid, toWref, ally, topic, ntype, data, time, viewed) values (0,'$uid','$toWref','$ally','$topic',$type,'$data',$time,0)"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getNotice($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT * FROM " . TB_PREFIX . "ndata where uid = $uid and del = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function getNotice2($id, $field = null, $use_cache = true) { - list($id, $field) = $this->escape_input((int) $id, $field); - - // 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::$noticesCacheById, $id)) && !is_null($cachedValue)) { - return $cachedValue[$field]; - } - - $q = "SELECT * FROM " . TB_PREFIX . "ndata where `id` = $id ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC')." LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - - self::$noticesCacheById[$id] = $dbarray; - return is_null($field) ? self::$noticesCacheById[$id] : self::$noticesCacheById[$id][$field]; - } - - function getUnViewNotice($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT * FROM " . TB_PREFIX . "ndata where uid = $uid AND viewed=0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - /** - * Delete expired trade routes - * - */ - - function delTradeRoute() { - $time = time(); - $q = "DELETE from " . TB_PREFIX . "route where timeleft < $time"; - return mysqli_query($this->dblink, $q); - } - - function createTradeRoute($uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time) { - list($uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time) = $this->escape_input((int) $uid,(int) $wid,(int) $from,(int) $r1,(int) $r2,(int) $r3,(int) $r4,(int) $start,(int) $deliveries,(int) $merchant,(int) $time); - - $x = "UPDATE " . TB_PREFIX . "users SET gold = gold - 2 WHERE id = " . $uid; - mysqli_query( $this->dblink, $x ); - $timeleft = time() + 604800; - $q = "INSERT into " . TB_PREFIX . "route values (0,$uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time,$timeleft)"; - - return mysqli_query( $this->dblink, $q ); - } - - // no need to cache this method - function getTradeRoute($from) { - list($from) = $this->escape_input((int) $from); - - $q = "SELECT * FROM " . TB_PREFIX . "route where `from` = $from ORDER BY timestamp ASC"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getTradeRoute2($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * FROM " . TB_PREFIX . "route where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray; - } - - // no need to cache this method - function getTradeRouteUid($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT uid FROM " . TB_PREFIX . "route where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['uid']; - } - - function editTradeRoute($id,$column,$value,$mode) { - list($id,$column,$value,$mode) = $this->escape_input((int) $id,$column,(int) $value,$mode); - - if ( ! $mode ) { - $q = "UPDATE " . TB_PREFIX . "route set $column = $value where id = $id"; - } else { - $q = "UPDATE " . TB_PREFIX . "route set $column = $column + $value where id = $id"; - } - - return mysqli_query( $this->dblink, $q ); - } - - function deleteTradeRoute($id) { - list($id) = $this->escape_input((int) $id); - - $q = "DELETE FROM " . TB_PREFIX . "route where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function deleteTradeRoutesByVillage($id) { - list($id) = $this->escape_input((int) $id); - - $q = "DELETE FROM " . TB_PREFIX . "route where `from` = $id"; - return mysqli_query($this->dblink,$q); - } - - function addBuilding($wid, $field, $type, $loop, $time, $master, $level) { - list($wid, $field, $type, $loop, $time, $master, $level) = $this->escape_input((int) $wid, $field, (int) $type, (int) $loop, (int) $time, (int) $master, (int) $level); - - $x = "UPDATE " . TB_PREFIX . "fdata SET f" . $field . "t=" . $type . " WHERE vref=" . $wid; - mysqli_query($this->dblink,$x); - $q = "INSERT into " . TB_PREFIX . "bdata values (0, $wid, $field, $type, $loop, $time, $master, $level)"; - return mysqli_query($this->dblink,$q); - } - - function getBuildLock($wid) { - $wid = (int) $wid; - $result = mysqli_query($this->dblink, "SELECT GET_LOCK('build_village_$wid', 10) AS locked"); - $row = mysqli_fetch_assoc($result); - return $row['locked'] == 1; - } - - function releaseBuildLock($wid) { - $wid = (int) $wid; - mysqli_query($this->dblink, "SELECT RELEASE_LOCK('build_village_$wid')"); - } - - function getResearchLock($wid) { - $wid = (int) $wid; - $result = mysqli_query($this->dblink, "SELECT GET_LOCK('research_village_$wid', 10) AS locked"); - $row = mysqli_fetch_assoc($result); - return $row['locked'] == 1; - } - - function releaseResearchLock($wid) { - $wid = (int) $wid; - mysqli_query($this->dblink, "SELECT RELEASE_LOCK('research_village_$wid')"); - } - - function getTrainingLock($wid) { - $wid = (int) $wid; - $result = mysqli_query($this->dblink, "SELECT GET_LOCK('train_village_$wid', 10) AS locked"); - $row = mysqli_fetch_assoc($result); - return $row['locked'] == 1; - } - - function releaseTrainingLock($wid) { - $wid = (int) $wid; - mysqli_query($this->dblink, "SELECT RELEASE_LOCK('train_village_$wid')"); - } - - function getEnforceLock($id) { - $id = (int) $id; - $result = mysqli_query($this->dblink, "SELECT GET_LOCK('enforce_$id', 10) AS locked"); - $row = mysqli_fetch_assoc($result); - return $row['locked'] == 1; - } - - function releaseEnforceLock($id) { - $id = (int) $id; - mysqli_query($this->dblink, "SELECT RELEASE_LOCK('enforce_$id')"); - } - - /** - * Get the time required to build a specified building - * - * @param int $id The ID where the building is located - * @param int $tid The type of the building - * @param int $plus The construction queue count - * @param int $wref The village ID - * @param array $buildingArray The array containing the buildings in the village - * @return int Returns the building time - */ - - function getBuildingTime($id, $tid, $plus, $wref, $buildingArray) { - list($id, $tid, $plus, $wref, $buildingArray) = $this->escape_input((int) $id, (int) $tid, (int) $plus, (int) $wref, $buildingArray); - global ${'bid'.$tid}, $bid15; - - $dataArray = ${'bid'.$tid}; - - //Check if we've the main building or not - $mainBuilding = $this->getFieldLevelInVillage($wref, 15); - if($tid == 15){ - if($mainBuilding == 0) return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] / SPEED * 5); - else return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] / SPEED); - }else{ - if($mainBuilding > 0) { - return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] * ($bid15[$mainBuilding]['attri'] / 100) / SPEED); - } - else return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] * 5 / SPEED); - } - } - - /** - * Called when removing a queued building by a player or because destroyed by catapults - * - * @param int $d The ID of the building which needs to be deleted - * @param int $tribe The tribe of the player - * @param int $wid The village ID of the player - * @param array $fieldsArray Optional, the array containing the village building/resource fields - * @return bool Returns true if the building was delete successfully, false otherwise - */ - - function removeBuilding($d, $tribe, $wid, $fieldsArray = []) { - list($d, $tribe, $wid, $fieldsArray) = $this->escape_input((int) $d, (int) $tribe, (int) $wid, $fieldsArray); - - //Variables initialization - $jobToDelete = []; - $canBeRemoved = true; - $time = time(); - $newTime = $loopTime = 0; - if(empty($fieldsArray)) $fieldsArray = $this->getResourceLevel($wid); - $jobs = $this->getJobsOrderByID($wid); - - //Search the job which needs to be deleted - foreach($jobs as $job){ - //We need to modify waiting loop orders - if(!empty($jobToDelete) && $job['loopcon'] == 1 && ($tribe != 1 || ($tribe == 1 && (($jobToDelete['field'] <= 18 && $job['field'] <= 18) || ($jobToDelete['field'] >= 19 && $job['field'] >= 19))))){ - - //Does this job have the same field of the deleted one? - $sameBuilding = $jobToDelete['field'] == $job['field'] ? 1 : 0; - - //Can the building be completely removed from the village? - if($sameBuilding && $canBeRemoved) $canBeRemoved = !$sameBuilding; - - //Get the time required to upgrade the building at the given level - $newTime = $this->getBuildingTime( - $job['field'], - $job['type'], - $job['level'] - $fieldsArray['f'.$job['field']] - $sameBuilding, - $wid, - $fieldsArray); - - //Increase the looptime - $loopTime += $newTime; - - //Update the values - $q = "UPDATE - " .TB_PREFIX. "bdata - SET - ".($job['master'] ? "" : "loopcon = 0,")." - timestamp = ".($job['master'] ? $newTime : $loopTime + $time)." - ".($sameBuilding ? ", level = level - 1" : "")." - WHERE - id = ".$job['id']; - mysqli_query($this->dblink, $q); - - } - - //We found the job that needs to be deleted - if($job['id'] == $d) $jobToDelete = $job; - } - - if($canBeRemoved && $jobToDelete['field'] > 18 && $jobToDelete['field'] != 99 && $jobToDelete['level'] - 1 == 0){ - $this->setVillageLevel($wid, ["f".$jobToDelete['field']."t"], [0]); - } - - $q = "DELETE FROM " . TB_PREFIX . "bdata where id = $d"; - return mysqli_query($this->dblink, $q); - } - - function addDemolition($wid, $field) { - list($wid, $field) = $this->escape_input((int) $wid, (int) $field); - - global $building, $village, $session; - - $fLevel = $this->getFieldLevel($wid, $field); - - // check if we're not demolishing an Embassy if the player is in an alliance - if ($this->getFieldType($wid,$field) == 18 && $session->alliance) { - - // get field level, alliance members count and the minimum - // level of Embassy to be able to hold this number of people - $membersCount = $this->countAllianceMembers($session->alliance); - $minEmbassyLevel = $this->getMinEmbassyLevel($membersCount); - $isOwner = $this->isAllianceOwner($session->uid) == $session->alliance; - - // make sure minimum Embassy level is 3 of the player is alliance owner - if ($isOwner && $minEmbassyLevel < 3) { - $minEmbassyLevel = 3; - } - - // check if this user is the founder of the alliance - // and whether we're not trying to demolish under the lowest level - // which can hold current number of members - if ($fLevel == $minEmbassyLevel && $session->alliance && $isOwner) { - // check if we have any other players in this alliance left - if ($membersCount > 1) { - // check if this player has only 1 last Embassy on a sufficient level - if ($this->getSingleFieldTypeCount($session->uid, 18, '>=', $minEmbassyLevel) == 1) { - // cannot demolish Embassy further until the player quits the alliance, - // as they are founder and there are still other players in the alliance, - // thus destroying Embassy would evict this player from the alliance - // and leave a new random leader - return 18; - } - } - } - } - - $q = "DELETE FROM ".TB_PREFIX."bdata WHERE field=$field AND wid=$wid"; - mysqli_query($this->dblink,$q); - $uprequire = $building->resourceRequired($field,$village->resarray['f'.$field.'t'],0); - $newLevel = max(0, $fLevel - 1); - $q = "INSERT INTO ".TB_PREFIX."demolition VALUES (".$wid.",".$field.",".$newLevel.",".(time() + floor($uprequire['time'] / 2)).")"; - mysqli_query($this->dblink,$q); - - return true; - } - - // no need to cache this method - function getDemolition($wid = 0) { - list($wid) = $this->escape_input((int) $wid); - - if($wid) { - $q = "SELECT * FROM " . TB_PREFIX . "demolition WHERE vref=" . $wid; - } else { - $q = "SELECT * FROM " . TB_PREFIX . "demolition WHERE timetofinish<=" . time(); - } - $result = mysqli_query($this->dblink,$q); - if(!empty($result)) { - return $this->mysqli_fetch_all($result); - } else { - return NULL; - } - } - - function finishDemolition($wid) { - list($wid) = $this->escape_input((int) $wid); - - $q = "UPDATE " . TB_PREFIX . "demolition SET timetofinish=" . time() . " WHERE vref=" . $wid; - $result= mysqli_query($this->dblink,$q); - return mysqli_affected_rows($this->dblink); - } - - function delDemolition($wid, $checkEmbassy = false) { - $wid = (int) $wid; - - if ($checkEmbassy) { - // check if we've demolished an Embassy - // and select the user it belonged to as well, - // so we can potentially evict them from the alliance - // and remove it - if they don't have any more Embassies - // or if the they are founder and they have no more lvl 3+ Embassies - $q = ' - SELECT - u.id, u.username, u.alliance, d.buildnumber, d.lvl - FROM - '.TB_PREFIX.'demolition d - LEFT JOIN '.TB_PREFIX.'vdata v ON d.vref = v.wref - LEFT JOIN '.TB_PREFIX.'users u ON u.id = v.owner - WHERE d.vref = '.$wid; - - $res = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - foreach ($res as $key) { - // if this building being demolished is an Embassy or was demolished completely - // and the player is in an alliance, check and update their alliance status - if (($key['alliance'] > 0) && ($key['lvl'] == 0 || $this->getFieldType($wid, $key['buildnumber']) == 18)) { - $this->checkAllianceEmbassiesStatus($key, true); - } - } - } - - $q = "DELETE FROM " . TB_PREFIX . "demolition WHERE vref=" . $wid; - return mysqli_query($this->dblink,$q); - } - - /** - * Returns a minimum level for an Embassy in order to accomodate - * the given number of members. - * - * @param int $membersCount Number of members for an alliance to accomodate. - * Maximum = 60 - * - * @return number Returns the level of Embassy required to accomodate - * the given number of members. - */ - public function getMinEmbassyLevel($membersCount) { - $membersCount = (int) $membersCount; - - if ($membersCount > 60) { - $membersCount = 60; - } - - if ($membersCount < 0) { - $membersCount = 0; - } - - return ceil((20 / 60) * $membersCount); - } - - /*** - * Returns the number of members an alliance can hold - * with the current level of leader's Embassy. - * - * @param int $embassyLevel Level of leader's Embassy building. - * - * @return number Returns the number of members an alliance - * can hold with the current level of leader's Embassy. - */ - public function getAllianceCapacity($embassyLevel) { - $embassyLevel = (int) $embassyLevel; - - if ($embassyLevel > 20) { - $embassyLevel = 20; - } - - if ($embassyLevel < 0) { - $embassyLevel = 0; - } - - // ceil is not really necessary but to make sure - // decimals won't crack this up, it's here - return ceil((60 / 20) * $embassyLevel); - } - - /** - * Checks and potentially updates the status of a player-alliance - * relationship given the user input. - * - * @param array $userData Data of the user for which we want to check - * the player-alliance relationship. - * @param boolean $demolition Determines whether the request came from - * a buiding demolition (true) or from a battle - * report (false). - * - * @return boolean Returns TRUE if there was no change - * to the player-alliance relationship - * FALSE if the player was an alliance - * leader and the alliance was destroyed - * and 0 when the player was evicted from - * the alliance due to Embassy damage. - */ - 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; - } - - $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); - } - } - - // === METODE NOI EXTRAS DIN checkAllianceEmbassiesStatus === - - 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ță - } - - // nu mai are ambasadă - evict - $this->evictUserFromAlliance($userData['id']); - $this->deleteAlliPermissions($userData['id']); - - $msgTitle = $demolition ? rc_tok('MSG_LEFT_ALLIANCE_TITLE') : rc_tok('MSG_FORCED_LEAVE_TITLE'); - $msgBody = $demolition - ? rc_tok('MSG_LEFT_DEMOLITION_BODY', $userData['username']) - : rc_tok('MSG_LEFT_ATTACK_BODY', $userData['username']); - - $this->sendMessage($userData['id'], 4, $msgTitle, $this->escape($msgBody), 0,0,0,0,0,true); - - return $demolition ? true : 0; - } - - 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 - - $hasSufficientEmbassy = $this->getSingleFieldTypeCount($userData['id'], 18, '>=', $minLevel, false, $use_cache) >= 1; - $needsAction = ($userData['lvl'] <= $minLevel) && !$hasSufficientEmbassy; - - if (!$needsAction) { - return true; - } - - $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); - } - } - - 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ță - } - - private function disbandAllianceOnDemolition(array $ownerData, array $members): bool { - $evicts = []; - foreach ($members as $member) { - $evicts[] = $member['id']; - $isOwner = ($member['id'] == $ownerData['id']); - $title = rc_tok('MSG_DISBAND_TITLE'); - $body = $isOwner - ? rc_tok('MSG_DISBAND_OWNER_BODY', $ownerData['username']) - : rc_tok('MSG_DISBAND_MEMBER_BODY', $member['username']); - - $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; - } - - 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; - } - } - } - - 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 = rc_tok('MSG_PROMOTE_BODY', $newLeaderName, $oldLeaderId, $oldLeaderData['username']); - $title = rc_tok('MSG_NOW_ALLIANCE_LEADER_TITLE'); - } else { - $msg = $customMessage; - $title = rc_tok('MSG_NOW_LEADER_TITLE'); - } - $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 = rc_tok('MSG_DISPERSE_TITLE'); - if ($isOwner) { - $body = ($membersCount > 1) - ? rc_tok('MSG_DISPERSE_OWNER_BODY_MANY', $ownerData['username'], $membersCount) - : rc_tok('MSG_DISPERSE_OWNER_BODY_FEW', $ownerData['username']); - } else { - $body = rc_tok('MSG_DISPERSE_MEMBER_BODY', $m['username'], $membersCount); - } - $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 = rc_tok('MSG_NEW_LEADER_TITLE'); - $body = $isOwner - ? rc_tok('MSG_NEWLEADER_OWNER_BODY', $ownerData['username'], count($members), $newLeaderId) - : rc_tok('MSG_NEWLEADER_MEMBER_BODY', $m['username'], $newLeaderId); - $this->sendMessage($m['id'], 4, $title, $this->escape($body), 0,0,0,0,0,true); - } - - if (!$keepOwner) { - $this->evictUserFromAlliance($ownerData['id']); - $msg = rc_tok('MSG_FORCED_LEAVE_BODY', $ownerData['username'], $newLeaderId); - $this->sendMessage($ownerData['id'], 4, rc_tok('MSG_FORCED_LEAVE_TITLE'), $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); - - Automation::updateMax($this->getVillageField($vid,"owner")); - $allianceStatus = $this->checkAllianceEmbassiesStatus([ - 'id' => $userData['id'], - 'alliance' => $userData["alliance"], - 'username' => $userData["username"], - 'lvl' => $current_level - ], false, $use_cache); - - if ($allianceStatus === false) return ' ' . rc_tok('MSG_ALLIANCE_DISPERSED_STATUS'); - else if ($allianceStatus === 0) return ' ' . rc_tok('MSG_FORCED_LEAVE_STATUS'); - else return ''; // all is good, no need to append additional alliance-related text - } - - function isThereAWinner(){ - $q = "SELECT Count(*) as Total FROM ".TB_PREFIX."fdata WHERE f99 = 100 and f99t = 40"; - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - /** - * Modify or delete a building being constructed/in queue - * - * @param int The village ID - * @param int $field The field where the building is located - * @param array $levels The new level of the building and the old one - * @param int $tribe The player's tribe - */ - - function modifyBData($wid, $field, $levels, $tribe){ - list($wid, $field, $levels, $tribe) = $this->escape_input((int) $wid, (int) $field, (int) $levels, (int) $tribe); - - if($levels[0] == 0){ - $q = "SELECT id FROM " .TB_PREFIX. "bdata WHERE wid = $wid AND field = $field"; - $orders = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q)); - foreach($orders as $order) $this->removeBuilding($order['id'], $tribe, $wid); - } - else mysqli_query($this->dblink, $q = "UPDATE " .TB_PREFIX. "bdata SET level = level - $levels[1] + $levels[0] WHERE wid = $wid AND field = $field"); - } - - private function getBData($wid, $use_cache = true, $orderByID = false) { - $wid = (int) $wid; - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && isset(self::$buildingsUnderConstructionCache[$wid]) && is_array(self::$buildingsUnderConstructionCache[$wid]) && !count(self::$buildingsUnderConstructionCache[$wid])) { - return []; - } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$buildingsUnderConstructionCache, $wid)) && !is_null($cachedValue)) { - return self::$buildingsUnderConstructionCache[$wid]; - } - - $q = "SELECT * FROM " . TB_PREFIX . "bdata where wid = $wid order by ".(!$orderByID ? "master,timestamp" : "id")." ASC"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - self::$buildingsUnderConstructionCache[$wid] = $result; - return $result; - } - - // do not cache output, as building jobs can change when using instant build (PLUS) etc. - function getJobs($wid) { - return $this->getBData($wid, false); - } - - function getJobsOrderByID($wid) { - return $this->getBData($wid, false, true); - } - - function FinishWoodcutter($wid) { - $bdata = $this->getBData($wid); - $time = time()-1; - - // find our woodcutter - $dbarray = []; - foreach ($bdata as $row) { - if ($row['type'] == 1) { - $dbarray = $row; - break; - } - } - - // no woodcutters? just return - if (!count($dbarray)) { - return; - } - - // make it complete - $q = "UPDATE ".TB_PREFIX."bdata SET timestamp = $time WHERE id = ".$dbarray['id']; - $this->query($q); - - $tribe = $this->getUserField($this->getVillageField($wid, "owner"), "tribe", 0); - - // find first field that's the next one in the loop after our finished woodcutter - $dbarray2 = []; - foreach ($bdata as $row) { - if ($row['loopcon'] == 1 && ($tribe == 1 ? $row['field'] >= 19 : true)) { - $dbarray2 = $row; - break; - } - } - - // if found, update it's finish time by subtracting the resulting time for our woodcutter, - // which is now finished - if (count($dbarray2)){ - $wc_time = $dbarray['timestamp']; - $q2 = "UPDATE ".TB_PREFIX."bdata SET timestamp = timestamp - $wc_time WHERE id = ".$dbarray2['id']; - $this->query($q2); - } - } - - function getMasterJobs($wid) { - // cache data - $bdata = $this->getBData($wid); - - // return all master jobs - $data = []; - foreach ($bdata as $row) { - if ($row['master'] == 1) $data[] = $row; - } - - return $data; - } - - function getMasterJobsByField($wid,$field) { - // cache data - $bdata = $this->getBData($wid); - - // return all master jobs for the requested field - $data = []; - foreach ($bdata as $row) { - if ($row['master'] == 1 && $row['field'] == $field) { - $data[] = $row; - } - } - - return $data; - } - - function getBuildingByField($wid,$field) { - // cache data - $bdata = $this->getBData($wid); - - // return all non-master jobs for the requested field - $data = []; - foreach ($bdata as $row) { - if ($row['master'] == 0 && $row['field'] == $field) { - $data[] = $row; - } - } - - return $data; - } - - // no need to cache this method - function getBuildingByField2($wid,$field) { - list($wid,$field) = $this->escape_input((int) $wid,(int) $field); - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "bdata where wid = $wid and field = $field and master = 0"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - return $result['Total']; - } - - function getBuildingByType($wid,$type) { - // cache data - $bdata = $this->getBData($wid); - $type = (strpos($type, ',') === false ? [(int) $type] : explode(',', str_replace(' ', '', $this->escape($type)))); - - // return all jobs which are of the requested type - $data = []; - foreach ($bdata as $row) { - if (in_array($row['field'], $type)) { - $data[] = $row; - } - } - - return $data; - } - - function getBuildingByType2($wid,$type) { - $wid = (int) $wid; - - if (!is_array($type)) { - $type = [$type]; - } else { - foreach ($type as $index => $typeValue) { - $type[$index] = (int) $typeValue; - } - } - - $q = "SELECT CONCAT(type, \"=\", Count(type)) FROM " . TB_PREFIX . "bdata where wid = $wid and type IN(".implode(', ', $type).") and master = 0 GROUP BY type"; - $result = mysqli_query($this->dblink, $q); - $newresult = []; - - if (mysqli_num_rows($result)) { - while ($row = mysqli_fetch_array($result, MYSQLI_NUM)) { - if ($row[0]) { - $val = explode( '=', $row[0] ); - $newresult[ $val[0] ] = $val[1]; - } - } - - $result = $newresult; - - } else { - $result = []; - } - - return $result; - } - - function getDorf1Building($wid) { - // cache data - $bdata = $this->getBData($wid); - - // return all non-master jobs for field type under 19 - $data = []; - foreach ($bdata as $row) { - if ($row['master'] == 0 && $row['field'] < 19) $data[] = $row; - } - - return $data; - } - - function getDorf2Building($wid) { - // cache data - $bdata = $this->getBData($wid); - - // return all non-master jobs for field type above 18 - $data = []; - foreach ($bdata as $row) { - if ($row['master'] == 0 && $row['field'] > 18) $data[] = $row; - } - - return $data; - } - - function updateBuildingWithMaster($id, $time, $loop) { - list($id, $time, $loop) = $this->escape_input((int) $id, (int) $time, (int) $loop); - - $q = "UPDATE " . TB_PREFIX . "bdata SET master = 0, timestamp = ".$time.", loopcon = ".$loop." WHERE id = ".$id.""; - return mysqli_query($this->dblink,$q); - } - - function getVillageByName($name, $use_cache = true) { - return $this->getVillage($name, 1, $use_cache)['wref']; - } - - /** - * Build the village-name autocomplete suggestions for the rally point and - * the marketplace, honouring the player's auto-completion preferences - * (issue #198): - * v1 -> own villages - * v2 -> villages in the surroundings of the active village - * v3 -> villages of the player's alliance members - * System accounts (Nature, Natars, Multihunter, ...) are excluded. - * - * @param int $uid Player id (own villages / self). - * @param int $alliance Player's alliance id (0 = none). - * @param int $x Active village X coordinate (vicinity center). - * @param int $y Active village Y coordinate (vicinity center). - * @param bool $v1 Include own villages. - * @param bool $v2 Include surrounding villages. - * @param bool $v3 Include alliance villages. - * @param int $radius Vicinity radius in fields (v2). - * @param int $limit Max number of names returned. - * @return string[] Distinct village names. - */ - function getAutoCompleteVillages($uid, $alliance, $x, $y, $v1, $v2, $v3, $radius = 25, $limit = 100) { - $uid = (int) $uid; - $alliance = (int) $alliance; - $x = (int) $x; - $y = (int) $y; - $radius = (int) $radius; - $limit = (int) $limit; - - $joins = "JOIN " . TB_PREFIX . "users u ON u.id = v.owner "; - $conds = []; - - if ($v1) { - $conds[] = "v.owner = $uid"; - } - if ($v3 && $alliance > 0) { - $conds[] = "u.alliance = $alliance"; - } - if ($v2) { - $joins .= "JOIN " . TB_PREFIX . "wdata w ON w.id = v.wref "; - $conds[] = "(ABS(w.x - $x) <= $radius AND ABS(w.y - $y) <= $radius)"; - } - - if (!count($conds)) { - return []; - } - - $q = "SELECT DISTINCT v.name FROM " . TB_PREFIX . "vdata v " . - $joins . - "WHERE u.id > 5 AND (" . implode(' OR ', $conds) . ") " . - "ORDER BY v.name LIMIT $limit"; - - $result = mysqli_query($this->dblink, $q); - $names = []; - if ($result) { - while ($row = mysqli_fetch_assoc($result)) { - $names[] = $row['name']; - } - } - return $names; - } - - function getVillageByOwner($uid, $use_cache = true) { - $uid = (int) $uid; - - // 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::$villageDataByOwnerCache, $uid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = 'SELECT * FROM `' . TB_PREFIX . 'vdata` WHERE `owner` = ' . $uid . ' LIMIT 1'; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - - self::$villageDataByOwnerCache[$uid] = $result; - return self::$villageDataByOwnerCache[$uid]; - } - - /*************************** - Function to set accept flag on market - References: id - ***************************/ - function setMarketAcc($id) { - if (!is_array($id)) { - $id = [$id]; - } - - foreach ($id as $index => $value) { - $id[$index] = (int) $value; - } - - $q = "UPDATE " . TB_PREFIX . "market set accept = 1 where id IN(".implode(', ', $id ).")"; - return mysqli_query($this->dblink,$q); - } - - /*************************** - Function to send resource to other village - Mode 0: Send - Mode 1: Cancel - References: Wood/ID, Clay, Iron, Crop, Mode - ***************************/ - function sendResource($ref, $clay, $iron, $crop, $merchant, $mode) { - // always prepare for multiple inserts at once - if (!is_array($ref)) { - $ref = [$ref]; - $clay = [$clay]; - $iron = [$iron]; - $crop = [$crop]; - $merchant = [$merchant]; - } - - $pairs = []; - foreach ($ref as $index => $refValue) { - if(!$mode) { - $pairs[] = '(0, ' . (int) $refValue . ', ' . (int) $clay[$index] . ', ' . (int) $iron[$index] . ', ' . (int) $crop[$index] . ', ' . (int) $merchant[$index] . ')'; - } else { - $pairs[] = (int) $refValue; - } - } - - if(!$mode) { - $q = "INSERT INTO " . TB_PREFIX . "send VALUES ".implode(', ', $pairs); - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } else { - $q = "DELETE FROM " . TB_PREFIX . "send WHERE id IN(".implode(', ', $pairs).")"; - return mysqli_query($this->dblink,$q); - } - } - - /*************************** - Function to get resources back if you delete offer - References: VillageRef (vref) - Made by: Dzoki - ***************************/ - - function getResourcesBack($vref, $gtype, $gamt) { - list($vref, $gtype, $gamt) = $this->escape_input((int) $vref, (int) $gtype, (int) $gamt); - - //Xtype (1) = wood, (2) = clay, (3) = iron, (4) = crop - if($gtype == 1) { - $q = "UPDATE " . TB_PREFIX . "vdata SET `wood` = `wood` + $gamt WHERE wref = $vref"; - return mysqli_query($this->dblink,$q); - } else - if($gtype == 2) { - $q = "UPDATE " . TB_PREFIX . "vdata SET `clay` = `clay` + $gamt WHERE wref = $vref"; - return mysqli_query($this->dblink,$q); - } else - if($gtype == 3) { - $q = "UPDATE " . TB_PREFIX . "vdata SET `iron` = `iron` + $gamt WHERE wref = $vref"; - return mysqli_query($this->dblink,$q); - } else - if($gtype == 4) { - $q = "UPDATE " . TB_PREFIX . "vdata SET `crop` = `crop` + $gamt WHERE wref = $vref"; - return mysqli_query($this->dblink,$q); - } - } - - /*************************** - Function to get info about offered resources - References: VillageRef (vref) - Made by: Dzoki - ***************************/ - - function getMarketField($vref, $id, $field, $use_cache = true) { - list($vref, $id, $field) = $this->escape_input($vref, $id, $field); - - // 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::$marketFieldCache, $vref.$field)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "market WHERE id = $id AND vref = $vref"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - - self::$marketFieldCache[$vref.$field] = $dbarray[$field]; - return self::$marketFieldCache[$vref.$field]; - } - - function removeAcceptedOffer($id) { - list($id) = $this->escape_input((int) $id); - - $q = "DELETE FROM " . TB_PREFIX . "market where id = $id"; - $result = mysqli_query($this->dblink,$q); - return mysqli_fetch_assoc($result); - } - - /** - * Function to add market offer - * - * Mode 0: Add - * Mode 1: Cancel - * References: Village, Give, Amt, Want, Amt, Time, Alliance, Mode - */ - - function addMarket($vid, $gtype, $gamt, $wtype, $wamt, $time, $alliance, $merchant, $mode) { - list($vid, $gtype, $gamt, $wtype, $wamt, $time, $alliance, $merchant, $mode) = $this->escape_input((int) $vid, (int) $gtype, (int) $gamt, (int) $wtype, (int) $wamt, (int) $time, (int) $alliance, (int) $merchant, $mode); - - if(!$mode) { - $q = "INSERT INTO " . TB_PREFIX . "market values (0,$vid,$gtype,$gamt,$wtype,$wamt,0,$time,$alliance,$merchant)"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } else { - $q = "DELETE FROM " . TB_PREFIX . "market where id = $gtype and vref = $vid"; - return mysqli_query($this->dblink,$q); - } - } - - /*************************** - Function to get market offer - References: Village, Mode - ***************************/ - // no need to cache this method - function getMarket($vid, $mode) { - list($vid, $mode) = $this->escape_input((int) $vid, $mode); - - $alliance = (int) $this->getUserField($this->getVillageField($vid, "owner"), "alliance", 0); - if(!$mode) { - $q = "SELECT * FROM " . TB_PREFIX . "market where vref = $vid and accept = 0"; - } else { - $q = "SELECT * FROM " . TB_PREFIX . "market where vref != $vid and alliance = $alliance or vref != $vid and alliance = 0 and accept = 0"; - } - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - /*************************** - Function to get market offer - References: ID - ***************************/ - // no need to cache this method - function getMarketInfo($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * FROM " . TB_PREFIX . "market where id = $id"; - $result = mysqli_query($this->dblink,$q); - return mysqli_fetch_assoc($result); - } - - function setMovementProc($moveid) { - if (!Math::isInt($moveid)) { - list($moveid) = $this->escape_input($moveid); - } - - if(empty($moveid)) return; - - // rather than re-selecting data and updating cache here, let's just - // flush the cache and let it re-cach itself as neccessary - self::$marketMovementCache = []; - - $q = "UPDATE " . TB_PREFIX . "movement set proc = 1 where moveid IN($moveid)"; - return mysqli_query($this->dblink,$q); - } - - /*************************** - Function to retrieve used merchant - References: Village - ***************************/ - function totalMerchantUsed($vid, $use_cache = true) { - list($vid) = $this->escape_input((int) $vid); - - // 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::$merchantsUseCountCache, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - self::$merchantsUseCountCache[$vid] = mysqli_fetch_array(mysqli_query($this->dblink, ' - SELECT - IFNULL((SELECT sum('.TB_PREFIX.'send.merchant) FROM '.TB_PREFIX.'send, '.TB_PREFIX.'movement WHERE '.TB_PREFIX.'movement.`from` = '.$vid.' AND '.TB_PREFIX.'send.id = '.TB_PREFIX.'movement.ref AND '.TB_PREFIX.'movement.proc = 0 AND sort_type = 0), 0) + - IFNULL((SELECT sum(ref) FROM '.TB_PREFIX.'movement WHERE sort_type = 2 AND '.TB_PREFIX.'movement.`to` = '.$vid.' AND proc = 0), 0) + - IFNULL((SELECT sum(merchant) FROM '.TB_PREFIX.'market WHERE vref = '.$vid.' AND accept = 0), 0) - as merchants_used' - ), MYSQLI_ASSOC)['merchants_used']; - - return self::$merchantsUseCountCache[$vid]; - } - - /*************************** - Function to acquire/release MySQL advisory lock for merchant operations - Prevents race conditions when sending merchants concurrently - ***************************/ - function getMerchantLock($vid, $timeout = 10) - { - $lockName = TB_PREFIX . 'merchant_' . (int)$vid; - $result = mysqli_query($this->dblink, "SELECT GET_LOCK('$lockName', $timeout) AS lock_acquired"); - $row = mysqli_fetch_assoc($result); - return $row['lock_acquired'] == 1; - } - - function releaseMerchantLock($vid) - { - $lockName = TB_PREFIX . 'merchant_' . (int)$vid; - mysqli_query($this->dblink, "SELECT RELEASE_LOCK('$lockName')"); - } - - function getMovement($type, $village, $mode, $use_cache = true) { - $array_passed = is_array($village); - - if (!$array_passed) { - $village = [(int) $village]; - } else { - foreach ($village as $index => $villageValue) { - $village[$index] = (int) $villageValue; - } - } - - if (!count($village)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$marketMovementCache[$type.$village[0].$mode]) && is_array(self::$marketMovementCache[$type.$village[0].$mode]) && !count(self::$marketMovementCache[$type.$village[0].$mode])) { - return self::$marketMovementCache[$type.$village[0].$mode]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newIDs = []; - foreach ($village as $key) { - if (!isset(self::$marketMovementCache[$type.$key.$mode])) { - $newIDs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newIDs)) { - return self::$marketMovementCache; - } else { - // update remaining IDs to select and cache - $village = $newIDs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$marketMovementCache, $type.$village[0].$mode)) && !is_null($cachedValue)) { - // special case when we have empty arrays cached for this cache only - return ($array_passed ? self::$marketMovementCache: $cachedValue); - } - - $time = time(); - if(!$mode) { - $where = "from"; - } else { - $where = "to"; - } - switch($type) { - case 0: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "send where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "send.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 0 ORDER BY endtime ASC"; - break; - case 1: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "send where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "send.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 6 ORDER BY endtime ASC"; - break; - case 2: - $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 2 ORDER BY endtime ASC"; - break; - case 3: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; - break; - case 4: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 4 ORDER BY endtime ASC"; - break; - case 5: - $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 5 and proc = 0 ORDER BY endtime ASC"; - break; - case 6: - $q = "SELECT * FROM " . TB_PREFIX . "movement," . TB_PREFIX . "odata, " . TB_PREFIX . "attacks where " . TB_PREFIX . "odata.wref IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.to IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "attacks.attack_type != 1 and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; - //$q = "SELECT * FROM " . TB_PREFIX . "movement," . TB_PREFIX . "odata, " . TB_PREFIX . "attacks where " . TB_PREFIX . "odata.conqured IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.to = " . TB_PREFIX . "odata.wref and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; - break; - case 7: - $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 4 and ref = 0 and proc = 0 ORDER BY endtime ASC"; - break; - case 8: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 and " . TB_PREFIX . "attacks.attack_type = 1 ORDER BY endtime ASC"; - break; - // T4 hero port: hero travelling to an adventure (plain select, - // ref points to hero_adventure - NOT to attacks, so no join). - case 20: - $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 20 and proc = 0 ORDER BY endtime ASC"; - break; - // T4 hero port: hero returning from an adventure. - case 21: - $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 21 and proc = 0 ORDER BY endtime ASC"; - break; - case 34: - $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 or " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 4 ORDER BY endtime ASC"; - break; - default: - return []; - } - - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$marketMovementCache[$type.$village[0].$mode] = $result; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - self::$marketMovementCache[ $type . $record[ $where ] . $mode ][] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no movements were found for these villages - foreach ($village as $key) { - if (!isset(self::$marketMovementCache[$type.$key.$mode])) { - self::$marketMovementCache[$type.$key.$mode] = []; - } - } - } - - return ($array_passed ? self::$marketMovementCache : self::$marketMovementCache[$type.$village[0].$mode]); - } - - function addA2b($ckey, $timestamp, $to, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type) { - list($ckey, $timestamp, $to, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type) = $this->escape_input($ckey, (int) $timestamp, (int) $to, (int) $t1, (int) $t2, (int) $t3, (int) $t4, (int) $t5, (int) $t6, (int) $t7, (int) $t8, (int) $t9, (int) $t10, (int) $t11, (int) $type); - - $q = "INSERT INTO " . TB_PREFIX . "a2b (ckey,time_check,to_vid,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,type) VALUES ('$ckey', '$timestamp', '$to', '$t1', '$t2', '$t3', '$t4', '$t5', '$t6', '$t7', '$t8', '$t9', '$t10', '$t11', '$type')"; - mysqli_query($this->dblink,$q); - return mysqli_insert_id($this->dblink); - } - - function remA2b($id) { - $id = (int) $id; - - $q = "DELETE FROM " . TB_PREFIX . "a2b WHERE id = $id"; - return mysqli_query($this->dblink,$q); - } - - function claimA2b($id, $ckey) { - $id = (int)$id; - list($ckey) = $this->escape_input($ckey); - - $q = "DELETE FROM " . TB_PREFIX . "a2b WHERE id = $id AND ckey = '".$ckey."' LIMIT 1"; - mysqli_query($this->dblink, $q); - - return (mysqli_affected_rows($this->dblink) === 1); - } - - // no need to cache this method - function getA2b($ckey) { - list($ckey) = $this->escape_input($ckey); - - $q = "SELECT * from " . TB_PREFIX . "a2b where ckey = '" . $ckey . "'"; - $result = mysqli_query($this->dblink,$q); - if($result) return mysqli_fetch_assoc($result); - else return false; - } - - function addMovement($type, $from, $to, $ref, $time, $endtime, $send = 1, $wood = 0, $clay = 0, $iron = 0, $crop = 0, $ref2 = 0) { - // always prepare for multiple inserts at once - if (!is_array($type)) { - $type = [$type]; - $from = [$from]; - $to = [$to]; - $ref = [$ref]; - $time = [$time]; - $endtime = [$endtime]; - $send = [$send]; - $wood = [$wood]; - $clay = [$clay]; - $iron = [$iron]; - $crop = [$crop]; - $ref2 = [$ref2]; - } - - $counter = 0; - $pairs = []; - - foreach ($type as $index => $typeValue) { - $pairs[] = '(0, '.(int) $typeValue.', '.(int) $from[$index].', '.(int) $to[$index].', '.(int) $ref[$index].', '.(int) $ref2[$index].', '.(int) $time[$index].', '.(int) $endtime[$index].', 0, '.(int) $send[$index].', '.(int) $wood[$index].', '.(int) $clay[$index].', '.(int) $iron[$index].', '.(int) $crop[$index].')'; - - if ($counter++ > 25) { - $q = "INSERT INTO " . TB_PREFIX . "movement (moveid, sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop) VALUES ".implode(', ', $pairs); - mysqli_query($this->dblink,$q); - - $pairs = []; - $counter = 0; - } - } - - if ($counter > 0) { - $q = "INSERT INTO " . TB_PREFIX . "movement (moveid, sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop) VALUES " . implode( ', ', $pairs ); - return mysqli_query( $this->dblink, $q ); - } else { - return true; - } - } - - function addAttack($vid, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type, $ctar1, $ctar2, $spy,$b1=0,$b2=0,$b3=0,$b4=0,$b5=0,$b6=0,$b7=0,$b8=0) { - if (!is_array($vid)) { - $vid = [$vid]; - $t1 = [$t1]; - $t2 = [$t2]; - $t3 = [$t3]; - $t4 = [$t4]; - $t5 = [$t5]; - $t6 = [$t6]; - $t7 = [$t7]; - $t8 = [$t8]; - $t9 = [$t9]; - $t10 = [$t10]; - $t11 = [$t11]; - $type = [$type]; - $ctar1 = [$ctar1]; - $ctar2 = [$ctar2]; - $spy = [$spy]; - $b1 = [$b1]; - $b2 = [$b2]; - $b3 = [$b3]; - $b4 = [$b4]; - $b5 = [$b5]; - $b6 = [$b6]; - $b7 = [$b7]; - $b8 = [$b8]; - } - - $values = []; - foreach ($vid as $index => $vidValue) { - $values[] = '(0, '.(int) $vidValue.', '.(int) $t1[$index].', '.(int) $t2[$index].', '.(int) $t3[$index].', '. - (int) $t4[$index].', '.(int) $t5[$index].', '.(int) $t6[$index].', '.(int) $t7[$index].', '. - (int) $t8[$index].', '.(int) $t9[$index].', '.(int) $t10[$index].', '.(int) $t11[$index]. - ', '.(int) $type[$index].', '.(int) $ctar1[$index].', '.(int) $ctar2[$index].', '. - (int) $spy[$index].', '.(int) $b1[$index].', '.(int) $b2[$index].', '.(int) $b3[$index]. - ', '.(int) $b4[$index].', '.(int) $b5[$index].', '.(int) $b6[$index].', '.(int) $b7[$index]. - ', '.(int) $b8[$index].')'; - } - - $q = "INSERT INTO " . TB_PREFIX . "attacks VALUES ".implode(', ', $values); - mysqli_query($this->dblink,$q); - - return (count($vid) == 1 ? mysqli_insert_id($this->dblink) : true); - } - - function modifyAttack($aid, $unit, $amt) { - list($aid, $unit, $amt) = $this->escape_input((int) $aid, $unit, (int) $amt); - - $unit = 't' . $unit; - $q = "UPDATE " . TB_PREFIX . "attacks set $unit = $unit - $amt where id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function modifyAttack2($aid, $unit, $amt, $mode = 1) { - list($aid, $unit, $amt) = $this->escape_input((int) $aid, $unit, $amt); - - if (!is_array($unit)) { - $unit = [$unit]; - $amt = [$amt]; - } - - $pairs = []; - foreach ($unit as $index => $unitValue) { - $unitValue = 't' . $this->escape($unitValue); - $pairs[] = $unitValue . ' = ' . $unitValue . (($mode) ? ' + ' : ' - ') . (int) $amt[$index]; - } - - $q = "UPDATE " . TB_PREFIX . "attacks SET ".implode(', ', $pairs)." WHERE id = $aid"; - return mysqli_query($this->dblink,$q); - } - - function modifyAttack3($aid, $units) { - list($aid, $units) = $this->escape_input((int) $aid, $units); - - $q = "UPDATE ".TB_PREFIX."attacks set $units WHERE id = $aid"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getVRanking() { - $q = "SELECT v.wref,v.name,v.owner,v.pop FROM " . TB_PREFIX . "vdata AS v," . TB_PREFIX . "users AS u WHERE v.owner=u.id AND u.tribe IN(1,2,3".(SHOW_NATARS ? ',5' : '').") AND v.wref != '' AND u.access<" . (INCLUDE_ADMIN ? "10" : "8"); - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function getARanking($use_cache = true) { - // 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::$allianceRankingCache, 0)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT id,name,tag,oldrank,Aap,Adp FROM " . TB_PREFIX . "alidata where id != '' ORDER BY id DESC"; - $result = mysqli_query($this->dblink,$q); - - self::$allianceRankingCache[0] = $this->mysqli_fetch_all($result); - return self::$allianceRankingCache[0]; - } - - // no need to cache this method - function getUserByTribe($tribe) { - list($tribe) = $this->escape_input((int) $tribe); - $q = "SELECT * FROM " . TB_PREFIX . "users where tribe = $tribe"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getUserByAlliance($aid) { - list($aid) = $this->escape_input((int) $aid); - $q = "SELECT * FROM " . TB_PREFIX . "users where alliance = $aid"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getHeroRanking() { - $q = "SELECT * FROM " . TB_PREFIX . "hero WHERE dead = 0"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function getAllMember($aid, $limit = 0, $use_cache = true) { - list($aid) = $this->escape_input((int) $aid); - - // 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::$allianceMembersCache, $aid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "users where alliance = $aid order by (SELECT sum(pop) FROM " . TB_PREFIX . "vdata WHERE owner = " . TB_PREFIX . "users.id) desc, " . TB_PREFIX . "users.id desc".($limit > 0 ? ' LIMIT '.(int) $limit : ''); - $result = mysqli_query($this->dblink,$q); - - self::$allianceMembersCache[$aid] = $this->mysqli_fetch_all($result); - return self::$allianceMembersCache[$aid]; - } - - function getAllMember2($aid) { - return $this->getAllMember($aid, 1); - } - - /** - * Add the unit table(s) and troops if presents - * - * @param mixed $vid The villaged ID(s) - * @param array $troopsArray divided in two portion, which contains the types (unidimensional array) and the values of the - * troops that need to be added (bidimensional array) - * @return bool Returns true if the query was successful, false otherwise - */ - - function addUnits($vid, $troopsArray = null) { - list($vid) = $this->escape_input($vid); - - if(empty($vid)) return; - if (!is_array($vid)) $vid = [$vid]; - $types = ""; - $typeKeys = []; - $values = []; - - if($troopsArray != null){ - $typeKeys = $troopsArray[0]; - $values = $troopsArray[1]; - - $types = ",u".implode(",u", $typeKeys); - } - - foreach ($vid as $index => $vidValue) $vid[$index] = (int) $vidValue.($troopsArray != null ? ",".implode(",", $values[$index]) : ""); - - $duplicateUpdate = ""; - if(!empty($typeKeys)){ - $duplicateColumns = []; - foreach($typeKeys as $typeKey){ - $typeKey = (int) $typeKey; - $duplicateColumns[] = "u".$typeKey."=VALUES(u".$typeKey.")"; - } - $duplicateUpdate = " ON DUPLICATE KEY UPDATE ".implode(', ', $duplicateColumns); - }else{ - $duplicateUpdate = " ON DUPLICATE KEY UPDATE vref=vref"; - } - - $q = "INSERT into " . TB_PREFIX . "units (vref$types) values (".implode('),(', $vid).")".$duplicateUpdate; - return mysqli_query($this->dblink,$q); - } - - function getUnit($vid, $use_cache = true) { - $array_passed = is_array($vid); - - if (!$array_passed) { - $singleVillage = true; - $vid = [$vid]; - } else { - foreach ($vid as $index => $vidValue) { - $vid[$index] = (int) $vidValue; - } - } - - $returnArray = []; - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$unitsCache, (int) $vid[0])) && !is_null($cachedValue)) { - return $cachedValue; - } else if ($use_cache && $array_passed) { - $newIDs = []; - foreach ($vid as $villageID) { - // don't cache what we don't need to cache - if (isset(self::$unitsCache[$villageID])) { - $returnArray[$villageID] = self::$unitsCache[$villageID]; - } else { - // add the uncached ID, so we can select and cache it - $newIDs[] = $villageID; - } - } - $vid = $newIDs; - - // nothing to cache? return what we have - if (!count($vid)) { - return $returnArray; - } - } - - $q = "SELECT * from " . TB_PREFIX . "units where vref IN(".implode(', ', $vid).")"; - $result = mysqli_query($this->dblink,$q); - $resCount = 0; - $vidCount = count($vid); - - if (!empty($result) && ($resCount = mysqli_num_rows($result)) && $resCount) { - while ($row = mysqli_fetch_assoc($result)) { - self::$unitsCache[$row['vref']] = $row; - $returnArray[$row['vref']] = $row; - } - } else { - // fill everything with nulls - foreach ($vid as $id) { - self::$unitsCache[$id] = null; - $returnArray[$id] = null; - } - } - - // check if we're not missing any return values - if ($vidCount != $resCount) { - // fill-in the gaps, as it would mean some of the IDs we got were not found - // (which is super-strange, but it's still a mathematical possibility) - foreach ($vid as $id) { - if (!isset($returnArray[$id])) { - $returnArray[$id] = null; - } - } - } - - return (!isset($singleVillage) ? $returnArray : reset($returnArray)); - } - - // no need to cache this method - function getUnitsNumber($vid, $mode = 1, $use_cache = false) { - list( $vid ) = $this->escape_input( (int) $vid ); - - $dbarray = $this->getUnit( $vid ); - $totalunits = 0; - for ( $i = 1; $i <= 90; $i ++ ) { - $totalunits += $dbarray[ 'u' . $i ]; - } - - $totalunits += $dbarray['hero']; - if(!$mode) return $totalunits; - - $movingunits = $this->getVillageMovement( $vid ); - $reinforcingunits = $this->getEnforceArray( $vid, 1 ); - $owner = $this->getVillageField( $vid, "owner" ); - $ownertribe = $this->getUserField( $owner, "tribe", 0 ); - $start = ( $ownertribe - 1 ) * 10 + 1; - $end = ( $ownertribe * 10 ); - - for ( $i = $start; $i <= $end; $i ++ ) { - $totalunits += $movingunits[ 'u' . $i ] ?? 0; - $totalunits += $reinforcingunits[ 'u' . $i ] ?? 0; - } - - $totalunits += $movingunits['hero'] ?? 0; - $totalunits += $reinforcingunits['hero'] ?? 0; - - return $totalunits; - } - - function getHero($uid=0, $all=0, $include_dead = false, $use_cache = true) { - list($uid,$all) = $this->escape_input((int) $uid,$all); - - // 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::$heroCache, $uid.$all.($include_dead ? 1 : 0))) && !is_null($cachedValue)) { - return $cachedValue; - } - - if ($all) { - $q = "SELECT * FROM ".TB_PREFIX."hero WHERE uid=$uid ORDER BY lastupdate DESC"; - } elseif (!$uid) { - $q = "SELECT * FROM ".TB_PREFIX."hero"; - } else { - $q = "SELECT * FROM ".TB_PREFIX."hero WHERE ".($include_dead ? '' : "dead=0 AND ")."uid=$uid LIMIT 1"; - } - - $result = mysqli_query($this->dblink,$q); - if (!empty($result)) { - self::$heroCache[$uid.$all.($include_dead ? 1 : 0)] = $this->mysqli_fetch_all($result); - } else { - self::$heroCache[$uid.$all.($include_dead ? 1 : 0)] = null; - } - - return self::$heroCache[$uid.$all.($include_dead ? 1 : 0)]; - } - - function getHeroField($uid,$field, $use_cache = true) { - list($uid,$field) = $this->escape_input((int) $uid,$field); - - // 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::$heroFieldCache, $uid.$field)) && !is_null($cachedValue)) { - return $cachedValue[$field]; - } - - $q = "SELECT * FROM ".TB_PREFIX."hero WHERE uid = $uid AND dead = 0"; - $result = mysqli_query($this->dblink,$q); - - self::$heroFieldCache[$uid.$field] = $this->mysqli_fetch_all($result)[0]; - return self::$heroFieldCache[$uid.$field][$field]; - } - - function modifyHero($column,$value,$heroid,$mode=null) { - if (!is_array($column)) { - $column = [$column]; - $value = [$value]; - $mode = [$mode]; - } - - $pairs = []; - foreach ($column as $index => $columnValue) { - if($mode[$index] === null) { - $pairs[] = "$columnValue = ".(Math::isInt($value[$index]) ? $value[$index] : '"'.$this->escape($value[$index]).'"'); - } elseif($mode[$index]=1) { - $pairs[] = "$columnValue = $columnValue + ".(int) $value[$index]; - } else { - $pairs[] = "$columnValue = $columnValue - ".(int) $value[$index]; - } - } - - $q = "UPDATE `".TB_PREFIX."hero` SET ".implode(', ', $pairs)." WHERE heroid = $heroid"; - return mysqli_query($this->dblink,$q); - } - - function modifyHeroXp($column,$value,$heroid) { - list($column,$value,$heroid) = $this->escape_input($column,(int) $value,(int) $heroid); - - $q = "UPDATE ".TB_PREFIX."hero SET $column = $column + $value WHERE heroid=$heroid"; - return mysqli_query($this->dblink,$q); - } - - function addTech($vid) { - if(empty($vid)) return; - if (!is_array($vid)) { - $vid = [$vid]; - } - - foreach ($vid as $index => $vidValue) { - $vid[$index] = (int) $vidValue; - } - - $q = "INSERT INTO " . TB_PREFIX . "tdata (vref) VALUES (".implode('),(', $vid).")"; - return mysqli_query($this->dblink,$q); - } - - function addABTech($vid) { - if(empty($vid)) return; - if (!is_array($vid)) { - $vid = [$vid]; - } - - foreach ($vid as $index => $vidValue) { - $vid[$index] = (int) $vidValue; - } - - self::$abTechCache = []; - $q = "INSERT INTO " . TB_PREFIX . "abdata (vref) VALUES (".implode('),(', $vid).")"; - return mysqli_query($this->dblink,$q); - } - - function getABTech($vid, $use_cache = true) { - $array_passed = is_array($vid); - - if (!$array_passed) { - $vid = [(int) $vid]; - } else { - foreach ($vid as $index => $ivdValue) { - $vid[$index] = (int) $ivdValue; - } - } - - if (!count($vid)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$abTechCache[$vid[0]]) && is_array(self::$abTechCache[$vid[0]]) && !count(self::$abTechCache[$vid[0]])) { - return self::$abTechCache[$vid[0]]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newVIDs = []; - foreach ($vid as $key) { - if (!isset(self::$abTechCache[$key])) { - $newVIDs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newVIDs)) { - return self::$abTechCache; - } else { - // update remaining IDs to select and cache - $vid = $newVIDs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$abTechCache, $vid[0])) && !is_null($cachedValue)) { - // special case when we have empty arrays cached for this cache only - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "abdata where vref IN(".implode(', ', $vid).")"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$abTechCache[$vid[0]] = $result[0]; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - self::$abTechCache[ $record['vref']] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no reinforcements were found for these villages - foreach ($vid as $key) { - if (!isset(self::$abTechCache[$key])) { - self::$abTechCache[$key] = []; - } - } - } - - return ($array_passed ? self::$abTechCache : self::$abTechCache[$vid[0]]); - } - - function addResearch($vid, $tech, $time) { - list($vid, $tech, $time) = $this->escape_input((int) $vid, $tech, (int) $time); - - self::$researchingCache = []; - $q = "INSERT into " . TB_PREFIX . "research values (0,$vid,'$tech',$time)"; - return mysqli_query($this->dblink,$q); - } - - function getResearching($vid, $use_cache = true) { - $vid = (int) $vid; - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && isset(self::$researchingCache[$vid]) && is_array(self::$researchingCache[$vid]) && !count(self::$researchingCache[$vid])) { - return self::$researchingCache[$vid]; - } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$researchingCache, $vid)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "research where vref = $vid ORDER BY timestamp ASC"; - $result = mysqli_query($this->dblink,$q); - $researchingCache[$vid] = $this->mysqli_fetch_all($result); - return $researchingCache[$vid]; - } - - function checkIfResearched($vref, $unit, $use_cache = true) { - list($vref, $unit) = $this->escape_input((int) $vref, $unit); - - // 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::$isResearchedCache, $vref)) && !is_null($cachedValue)) { - return $cachedValue[$unit]; - } - - $q = "SELECT * FROM " . TB_PREFIX . "tdata WHERE vref = $vref LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result, MYSQLI_ASSOC); - - self::$isResearchedCache[$vref] = $dbarray; - return self::$isResearchedCache[$vref][$unit]; - } - - function getTech($vid) { - // this is a somewhat non-ideal, externally non-changeable way of caching - // but since we're only ever going to be calling this from Village constructor - // for our current village, this will more than suffice - static $cachedData = []; - $vid = (int) $vid; - - if (isset($cachedData[$vid])) { - return $cachedData[$vid]; - } - - $q = "SELECT * from " . TB_PREFIX . "tdata where vref = $vid"; - $result = mysqli_query($this->dblink,$q); - $cachedData[$vid] = mysqli_fetch_assoc($result); - - return $cachedData[$vid]; - } - - // no need to cache this method - - // ==================== HOSPITAL (raniti + vindecare) ==================== - - function getWounded($vid) { - list($vid) = $this->escape_input((int)$vid); - $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "hospital WHERE vref = $vid"); - return $result ? mysqli_fetch_array($result, MYSQLI_ASSOC) : null; - } - - function addWounded($vid, array $units, array $amounts) { - list($vid) = $this->escape_input((int)$vid); - $cols = []; $vals = []; $upd = []; - foreach($units as $k => $u) { - $u = (int)$u; $a = (int)$amounts[$k]; - if($a <= 0 || $u < 1 || $u > 90) continue; - $cols[] = "u$u"; $vals[] = $a; $upd[] = "u$u = u$u + $a"; - } - if(empty($cols)) return true; - $q = "INSERT INTO " . TB_PREFIX . "hospital (vref, " . implode(',', $cols) . ") VALUES ($vid, " . implode(',', $vals) . ") ON DUPLICATE KEY UPDATE " . implode(',', $upd); - return mysqli_query($this->dblink, $q); - } - - function deductWounded($vid, $unit, $amt) { - list($vid, $unit, $amt) = $this->escape_input((int)$vid, (int)$unit, (int)$amt); - return mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "hospital SET u$unit = GREATEST(u$unit - $amt, 0) WHERE vref = $vid"); - } - - function clearHospital($vid) { - list($vid) = $this->escape_input((int)$vid); - mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "hospital WHERE vref = $vid"); - mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "healing WHERE vref = $vid"); - } - - function getHealing($vid) { - list($vid) = $this->escape_input((int)$vid); - $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "healing WHERE vref = $vid ORDER BY id"); - $rows = []; - if($result) while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) $rows[] = $row; - return $rows; - } - - function getHealingDue($time) { - list($time) = $this->escape_input((int)$time); - $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "healing WHERE timestamp2 <= $time AND amt > 0"); - $rows = []; - if($result) while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) $rows[] = $row; - return $rows; - } - - function healUnit($vid, $unit, $amt, $each) { - list($vid, $unit, $amt, $each) = $this->escape_input((int)$vid, (int)$unit, (int)$amt, (int)$each); - $now = time(); - $q = "INSERT INTO " . TB_PREFIX . "healing (vref, unit, amt, timestamp, eachtime, timestamp2) VALUES ($vid, $unit, $amt, $now, $each, " . ($now + $each) . ")"; - return mysqli_query($this->dblink, $q); - } - - function updateHealing($id, $amt, $timestamp2) { - list($id, $amt, $timestamp2) = $this->escape_input((int)$id, (int)$amt, (int)$timestamp2); - return mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "healing SET amt = $amt, timestamp2 = $timestamp2 WHERE id = $id"); - } - - function deleteHealing($id) { - list($id) = $this->escape_input((int)$id); - return mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "healing WHERE id = $id"); - } - - function getTraining($vid) { - list($vid) = $this->escape_input((int) $vid); - - $q = "SELECT * FROM " . TB_PREFIX . "training where vref = $vid ORDER BY id"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function trainUnit($vid, $unit, $amt, $pop, $each, $mode) { - list($vid, $unit, $amt, $pop, $each, $mode) = $this->escape_input((int) $vid, (int) $unit, (int) $amt, (int) $pop, (int) $each, $mode); - - global $technology; - - if(!$mode) { - // Rutare generica pe tipul unitatii (u1-u90 + offsetul +1000 pentru cladirile mari) - global $unitsbytype; - $isGreat = $unit > 1000; - $baseUnit = $isGreat ? $unit - 1000 : $unit; - - if($baseUnit == 99) $queued = $technology->getTrainingList(8); - elseif(in_array($baseUnit, $unitsbytype['expansion'])) $queued = $technology->getTrainingList(4); - elseif(in_array($baseUnit, $unitsbytype['siege'])) $queued = $technology->getTrainingList($isGreat ? 7 : 3); - elseif(in_array($baseUnit, $unitsbytype['cavalry'])) $queued = $technology->getTrainingList($isGreat ? 6 : 2); - else $queued = $technology->getTrainingList($isGreat ? 5 : 1); - - $now = time(); - $uid = $this->getVillageField($vid, "owner"); - $each = $this->getArtifactsValueInfluence($uid, $vid, 5, $each); - - $time2 = $now + $each; - $time = $now + ($each * $amt); - if(count($queued) > 0){ - $time += $queued[count($queued) - 1]['timestamp'] - $now; - $time2 += $queued[count($queued) - 1]['timestamp'] - $now; - } - - $q = "INSERT INTO " . TB_PREFIX . "training values (0, $vid, $unit, $amt, $pop, $time, $each, $time2)"; - } - else $q = "DELETE FROM " . TB_PREFIX . "training where id = $vid"; - - return mysqli_query($this->dblink,$q); - } - - function updateTraining($id, $trained, $each) { - list($id, $trained, $each) = $this->escape_input((int) $id, (int) $trained, (int) $each); - - $q = "UPDATE " . TB_PREFIX . "training set amt = amt - $trained, timestamp2 = timestamp2 + $each where id = $id"; - return mysqli_query($this->dblink,$q); - } - - function modifyUnit($vref, $array_unit, $array_amt, $array_mode) { - list($vref, $array_unit, $array_amt, $array_mode) = $this->escape_input((int) $vref, $array_unit, $array_amt, $array_mode); - $i = -1; - $units=''; - $number = count($array_unit); - foreach($array_unit as $unit){ - if($unit == 230) $unit = 30; - if($unit == 231) $unit = 31; - if($unit == 120) $unit = 20; - if($unit == 121) $unit = 21; - if($unit =="hero") $unit = 'hero'; - else $unit = 'u' . $unit; - - ++$i; - //Fixed part of negative troops (double troops) - by InCube - $array_amt[$i] = (int) $array_amt[$i] < 0 ? 0 : $array_amt[$i]; - //Fixed part of negative troops (double troops) - by InCube - $units .= $unit.' = '.$unit.' '.(($array_mode[$i] == 1)? '+':'-').' '.($array_amt[$i] ? $array_amt[$i] : 0).(($number > $i+1) ? ', ' : ''); - } - $q = "UPDATE ".TB_PREFIX."units set $units WHERE vref = $vref"; - return mysqli_query($this->dblink, $q); - } - - function getEnforce($vid, $from, $use_cache = true) { - $array_passed = is_array($vid); - if (!$array_passed) { - $vid = [$vid]; - $from = [$from]; - } else { - foreach ($vid as $index => $vidValue) { - $vid[$index] = (int) $vidValue; - $from[$index] = (int) $from[$index]; - } - } - - if (!count($vid)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$villageFromReinforcementsCache[$vid[0].$from[0]]) && is_array(self::$villageFromReinforcementsCache[$vid[0].$from[0]]) && !count(self::$villageFromReinforcementsCache[$vid[0].$from[0]])) { - return self::$villageFromReinforcementsCache[$vid[0].$from[0]]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newVIDs = []; - $newFROMs = []; - foreach ($vid as $index => $vidValue) { - if (!isset(self::$villageFromReinforcementsCache[$vidValue.$from[$index]])) { - $newVIDs[] = $vidValue; - $newFROMs[] = $from[$index]; - } - } - - // everything's cached, just return the cache - if (!count($newVIDs)) { - return self::$villageFromReinforcementsCache; - } else { - // update remaining IDs to select and cache - $vid = $newVIDs; - $from = $newFROMs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageFromReinforcementsCache, $vid[0].$from[0])) && !is_null($cachedValue)) { - return $cachedValue; - } - - // build SELECT pairs - $pairs = []; - foreach ($vid as $index => $vidValue) { - $pairs[] = '(`from` = '.(int) $from[$index].' AND vref = '.(int) $vidValue.')'; - } - - $q = "SELECT * FROM " . TB_PREFIX . "enforcement WHERE ".implode(' OR ', $pairs); - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$villageFromReinforcementsCache[$vid[0].$from[0]] = $result[0]; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - self::$villageFromReinforcementsCache[$record['vref'].$record['from']] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no reinforcements were found for these villages - foreach ($vid as $index => $vidValue) { - if (!isset(self::$villageFromReinforcementsCache[$vidValue.$from[$index]])) { - self::$villageFromReinforcementsCache[$vidValue.$from[$index]] = []; - } - } - } - - return ($array_passed ? self::$villageFromReinforcementsCache : self::$villageFromReinforcementsCache[$vid[0].$from[0]]); - } - - function getOasisEnforce($ref, $mode=0, $use_cache = true) { - $array_passed = is_array($ref); - $mode = (int) $mode; - - if (!$array_passed) { - $ref = [(int) $ref]; - } else { - foreach ($ref as $index => $refValue) { - $ref[$index] = (int) $refValue; - } - } - - if (!count($ref)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$oasisReinforcementsCache[$ref[0].$mode]) && is_array(self::$oasisReinforcementsCache[$ref[0].$mode]) && !count(self::$oasisReinforcementsCache[$ref[0].$mode])) { - return self::$oasisReinforcementsCache[$ref[0].$mode]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newREFs = []; - foreach ($ref as $key) { - if (!isset(self::$oasisReinforcementsCache[$key.$mode])) { - $newREFs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newREFs)) { - return self::$oasisReinforcementsCache; - } else { - // update remaining IDs to select and cache - $ref = $newREFs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$oasisReinforcementsCache, $ref[0].$mode)) && !is_null($cachedValue)) { - // special case when we have empty arrays cached for this cache only - return $cachedValue; - } - - if (!$mode) { - $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.conqured IN(".implode(', ', $ref).") AND e.from NOT IN(".implode(', ', $ref).")"; - }else if ($mode == 1) { - $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.conqured IN(".implode(', ', $ref).")"; - } else if ($mode == 2) { - $q = "SELECT e.*,o.conqured,o.wref,o.high, o.owner as ownero, v.owner as ownerv FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref where o.conqured IN(".implode(', ', $ref).") AND o.owner<>v.owner"; - } else if ($mode == 3) { - $q = "SELECT e.*,o.conqured,o.wref,o.high, o.owner as ownero, v.owner as ownerv FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref where o.conqured IN(".implode(', ', $ref).") AND o.owner=v.owner"; - } - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$oasisReinforcementsCache[$ref[0].$mode] = $result; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - if ( ! isset( self::$oasisReinforcementsCache[ $record['conqured'] . $mode ] ) ) { - self::$oasisReinforcementsCache[ $record['conqured'] . $mode ] = []; - } - - self::$oasisReinforcementsCache[ $record['conqured'] . $mode ][] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no reinforcements were found for these villages - foreach ($ref as $key) { - if (!isset(self::$oasisReinforcementsCache[$key.$mode])) { - self::$oasisReinforcementsCache[$key.$mode] = []; - } - } - } - - return ($array_passed ? self::$oasisReinforcementsCache : self::$oasisReinforcementsCache[$ref[0].$mode]); - } - - function getOasisEnforceArray($id, $mode=0, $use_cache = true) { - list($id, $mode) = $this->escape_input((int) $id, $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::$oasisArrayReinforcementsCache, $id.$mode)) && !is_null($cachedValue)) { - return $cachedValue; - } - - if (!$mode) { - $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where e.id = $id"; - }else{ - $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.from=o.wref where e.id =$id"; - } - $result = mysqli_query($this->dblink,$q); - - self::$oasisArrayReinforcementsCache[$id.$mode] = mysqli_fetch_assoc($result); - return self::$oasisArrayReinforcementsCache[$id.$mode]; - } - - function addEnforce($data) { - list($data) = $this->escape_input($data); - - $q = "INSERT into " . TB_PREFIX . "enforcement (vref,`from`) values (" . (int) $data['to'] . "," . (int) $data['from'] . ")"; - mysqli_query($this->dblink,$q); - $id = mysqli_insert_id($this->dblink); - $owntribe = $this->getUserField($this->getVillageField($data['from'], "owner"), "tribe", 0); - $start = ($owntribe - 1) * 10 + 1; - $end = ($owntribe * 10); - //add unit - $j = 1; - $units = []; - $amounts = []; - $modes = []; - - for($i = $start; $i <= $end; $i++) { - $units[] = ($i < 0 ? 0 : $i); - $amounts[] = $data['t' . $j . '']; - $modes[] = 1; - $j++; - } - - // add hero - $units[] = 'hero'; - $amounts[] = $data['t11']; - $modes[] = 1; - - $this->modifyEnforce($id,$units, $amounts, $modes); - } - - function addEnforce2($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11) { - list($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11) = $this->escape_input($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11); - - $q = "INSERT into " . TB_PREFIX . "enforcement (vref,`from`) values (" . (int) $data['to'] . "," . (int) $data['from'] . ")"; - mysqli_query($this->dblink,$q); - $id = mysqli_insert_id($this->dblink); - $owntribe = $this->getUserField($this->getVillageField($data['from'], "owner"), "tribe", 0); - $start = ($owntribe - 1) * 10 + 1; - $end = ($owntribe * 10); - $start2 = ($tribe - 1) * 10 + 1; - $start3 = ($tribe - 1) * 10; - if($start3 == 0){ - $start3 = ""; - } - $end2 = ($tribe * 10); - //add unit - $j = 1; - - $units = []; - $amounts = []; - $modes = []; - - for($i = $start; $i <= $end; $i++) { - $units[] = ($i < 0 ? 0 : $i); - $amounts[] = $data['t' . $j . '']; - $modes[] = 1; - - $units[] = ($i < 0 ? 0 : $i); - $amounts[] = ${'dead'.$j}; - $modes[] = 0; - - $j++; - } - - // process heroes - $units[] = 'hero'; - $amounts[] = $data['t11']; - $modes[] = 1; - - $units[] = 'hero'; - $amounts[] = $dead11; - $modes[] = 0; - - $this->modifyEnforce($id,$units, $amounts, $modes); - } - - function modifyEnforce($id, $unit, $amt, $mode) { - $id = (int) $id; - - // prepare pairing array, even if we're not passing arrays, so we can use the same logic - $pairs = []; - if (!is_array($unit)) { - $unit = [$unit]; - $amt = [(int) $amt]; - $mode = [(int) $mode]; - } - - foreach ($unit as $index => $unitType) { - $unitType = ($unitType != 'hero' ? 'u' . $this->escape($unitType) : $unitType); - $pairs[] = $unitType . ' = ' . $unitType . (!(int) $mode[$index] ? ' - ' : ' + ') . (int) $amt[$index]; - } - - $q = "UPDATE " . TB_PREFIX . "enforcement SET ".implode(', ', $pairs)." WHERE id = $id"; - mysqli_query($this->dblink,$q); - - // clear enforce cache - self::$villageReinforcementsCache = []; - self::$villageFromReinforcementsCache = []; - self::$reinforcementsCache = []; - } - - function getEnforceArray($id, $mode, $use_cache = true) { - list($id, $mode) = $this->escape_input((int) $id, $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::$reinforcementsCache, $id.$mode)) && !is_null($cachedValue)) { - return $cachedValue; - } - - if(!$mode) { - $q = "SELECT * from " . TB_PREFIX . "enforcement where id = $id"; - } else { - $q = "SELECT * from " . TB_PREFIX . "enforcement where `from` = $id"; - } - $result = mysqli_query($this->dblink,$q); - - self::$reinforcementsCache[$id.$mode] = mysqli_fetch_assoc($result); - return self::$reinforcementsCache[$id.$mode]; - } - - function getEnforceVillage($id, $mode, $use_cache = true) { - $array_passed = is_array($id); - $mode = (int) $mode; - - if (!$array_passed) { - $id = [(int) $id]; - } else { - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - } - - if (!count($id)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$villageReinforcementsCache[$id[0].$mode]) && is_array(self::$villageReinforcementsCache[$id[0].$mode]) && !count(self::$villageReinforcementsCache[$id[0].$mode])) { - return self::$villageReinforcementsCache[$id[0].$mode]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newIDs = []; - foreach ($id as $key) { - if (!isset(self::$villageReinforcementsCache[$key.$mode])) { - $newIDs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newIDs)) { - return self::$villageReinforcementsCache; - } else { - // update remaining IDs to select and cache - $id = $newIDs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageReinforcementsCache, $id[0].$mode)) && !is_null($cachedValue)) { - // special case when we have empty arrays cached for this cache only - return $cachedValue; - } - - if(!$mode) { - $q = "SELECT * from " . TB_PREFIX . "enforcement where vref IN(".implode(', ', $id).")"; - } else if ($mode == 1) { - $q = "SELECT * from " . TB_PREFIX . "enforcement where `from` IN(".implode(', ', $id).")"; - } else if ($mode == 2) { - $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner<>v1.owner"; - } else if ($mode == 3) { - $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner=v1.owner"; - } else if ($mode == 4) { - $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner=v1.owner"; - } - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - self::$villageReinforcementsCache[$id[0].$mode] = $result; - } else { - if ($result && count($result)) { - foreach ( $result as $record ) { - if ( ! isset( self::$villageReinforcementsCache[ $record['vref'] . $mode ] ) ) { - self::$villageReinforcementsCache[ $record['vref'] . $mode ] = []; - } - - self::$villageReinforcementsCache[ $record['vref'] . $mode ][] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no reinforcements were found for these villages - foreach ($id as $key) { - if (!isset(self::$villageReinforcementsCache[$key.$mode])) { - self::$villageReinforcementsCache[$key.$mode] = []; - } - } - } - - return ($array_passed ? self::$villageReinforcementsCache : self::$villageReinforcementsCache[$id[0].$mode]); - } - - public static function clearReinforcementsCache() { - self::$reinforcementsCache = []; - self::$villageReinforcementsCache = []; - self::$villageFromReinforcementsCache = []; - self::$oasisArrayReinforcementsCache = []; - self::$oasisReinforcementsCache = []; - self::clearUnitsCache(); - } - - public static function clearUnitsCache() { - self::$unitsCache = []; - } - - // no need to cache this method - function getVillageMovement($id) { - list($id) = $this->escape_input($id); - - $vinfo = $this->getVillage($id); - $vtribe = $this->getUserField($vinfo['owner'], "tribe", 0); - $movingunits = []; - - $outgoingarray = $this->getMovement(3, $id, 0); - if(!empty($outgoingarray) && count($outgoingarray)) { - foreach($outgoingarray as $out) { - for($i = 1; $i <= 10; $i++) { - if (!isset($movingunits['u'.(($vtribe - 1) * 10 + $i)])) { - $movingunits['u'.(($vtribe - 1) * 10 + $i)] = 0; - } - - if (!isset($out['t'.$i])) $out['t'.$i] = 0; - $movingunits['u'.(($vtribe - 1) * 10 + $i)] += $out['t'.$i]; - } - - if (!isset($movingunits['hero'])) $movingunits['hero'] = 0; - if (!isset($out['t11'])) $out['t11'] = 0; - - $movingunits['hero'] += $out['t11']; - } - } - - $returningarray = $this->getMovement(4, $id, 1); - if(!empty($returningarray) && count($returningarray)) { - foreach($returningarray as $ret) { - for($i = 1; $i <= 10; $i++) { - if (!isset($movingunits['u'.(($vtribe - 1) * 10 + $i)])) { - $movingunits['u'.(($vtribe - 1) * 10 + $i)] = 0; - } - $movingunits['u'.(($vtribe - 1) * 10 + $i)] += $ret['t' . $i]; - } - - if (!isset($movingunits['hero'])) $movingunits['hero'] = 0; - $movingunits['hero'] += $ret['t11']; - } - } - - $settlerarray = $this->getMovement(5, $id, 0); - if(!empty($settlerarray)) { - if (!isset($movingunits['u'.($vtribe * 10)])) { - $movingunits['u'.($vtribe * 10)] = 0; - } - $movingunits['u'.($vtribe * 10)] += 3 * count($settlerarray); - } - return $movingunits; - } - - ################# -START- ################## - ## WORLD WONDER STATISTICS FUNCTIONS! ## - ############################################ - - /*************************** - Function to get user alliance name! - Made by: Dzoki - ***************************/ - - // no need to cache this method - function getUserAllianceID($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT alliance FROM " . TB_PREFIX . "users where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['alliance']; - } - - /*************************** - Function to get WW name - Made by: Dzoki - ***************************/ - - // no need to cache this method - function getWWName($vref) { - list($vref) = $this->escape_input((int) $vref); - - $q = "SELECT wwname FROM " . TB_PREFIX . "fdata WHERE vref = $vref LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return $dbarray['wwname']; - } - - /*************************** - Function to change WW name - Made by: Dzoki - ***************************/ - - function submitWWname($vref, $name) { - list($vref, $name) = $this->escape_input((int) $vref, $name); - - $q = "UPDATE " . TB_PREFIX . "fdata SET `wwname` = '$name' WHERE " . TB_PREFIX . "fdata.`vref` = $vref"; - return mysqli_query($this->dblink,$q); - } - - //medal functions - function addclimberrankpop($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "users set clp = clp + $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - function removeclimberrankpop($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "users set clp = clp - $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - function setclimberrankpop($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "users set clp = $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - function updateoldrank($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "users set oldrank = $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - // ALLIANCE MEDAL FUNCTIONS - function addclimberrankpopAlly($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "alidata set clp = clp + $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - function removeclimberrankpopAlly($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "alidata set clp = clp - $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - function updateoldrankAlly($user, $cp) { - list($user, $cp) = $this->escape_input((int) $user, (int) $cp); - - $q = "UPDATE " . TB_PREFIX . "alidata set oldrank = $cp where id = $user"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getTrainingList() { - $q = "SELECT * FROM " . TB_PREFIX . "training where vref IS NOT NULL"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - // no need to cache this method - function getNeedDelete() { - $time = time(); - $q = "SELECT uid FROM " . TB_PREFIX . "deleting where timestamp < $time"; - $result = mysqli_query($this->dblink,$q); - return $this->mysqli_fetch_all($result); - } - - function countUser($use_cache = true) { - // 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::$usersCountCache, 0)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT count(id) FROM " . TB_PREFIX . "users where id > 5"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - - self::$usersCountCache[0] = $row[0]; - return self::$usersCountCache[0]; - } - - function countAlli($use_cache = true) { - // 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::$allianceCountCache, 0)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT count(id) FROM " . TB_PREFIX . "alidata where id != 0"; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_row($result); - - self::$allianceCountCache[0] = $row[0]; - return self::$allianceCountCache[0]; - } - - //MARKET FIXES - function getWoodAvailable($wref, $use_cache = true) { - // return from cache - return $this->getVillage($wref, 0, $use_cache)['wood']; - } - - function getClayAvailable($wref, $use_cache = true) { - // return from cache - return $this->getVillage($wref, 0, $use_cache)['clay']; - } - - function getIronAvailable($wref, $use_cache = true) { - // return from cache - return $this->getVillage($wref, 0, $use_cache)['iron']; - } - - function getCropAvailable($wref, $use_cache = true) { - // return from cache - return $this->getVillage($wref, 0, $use_cache)['crop']; - } - - function Getowner($vid) { - // return from cache - return $this->getVillage($vid, 0, $use_cache)['owner']; - } - - /** - * Creates a database structure for the game. - * Used during installation. - * - * @return boolean|number Returns TRUE, FALSE or -1. True is for successful data import - * (from prepared SQL file), false is in case of an SQL error. - * -1 will be returned in case of any unexpected behavior - * and unhandled exceptions. - */ - - public function createDbStructure() { - global $autoprefix; - - try { - // check that we don't have the structure in place already - // (we'd have at least 1 user present, since 4 are being created by default - Support, Nature, Multihunter & Taskmaster) - try { - $data_exist = $this->query_return("SELECT * FROM " . TB_PREFIX . "users LIMIT 1"); - if ($data_exist && count($data_exist)) { - return false; - } - } catch (\Exception $e) { - - } - - // load the DB structure SQL file - $str = file_get_contents($autoprefix."var/db/struct.sql"); - $str = preg_replace("'%PREFIX%'", TB_PREFIX, $str); - $result = $this->dblink->multi_query($str); - - // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work - while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} - - if (!$result) { - return false; - } - } catch (\Exception $e) { - echo($e); - return -1; - } - - return true; - } - - /** - * Populates the game database with Map World Data (i.e. creates the whole - * world with X,Y coordinate squares and their types). - * - * Also populates oasis' table data for the squares where there are oasis. - * - * @return boolean|number Returns TRUE, FALSE or -1. True is for successful data import - * (from prepared SQL file), false is in case of an SQL error. - * -1 will be returned in case of any unexpected behavior - * and unhandled exceptions. - */ - public function populateWorldData() { - global $autoprefix; - - $wdataTable = TB_PREFIX . "wdata"; - $droppedIndexes = []; - - try { - // check if we don't already have world data - $data_exist = $this->query_return("SELECT * FROM " . $wdataTable . " LIMIT 1"); - if ($data_exist && count($data_exist)) { - return false; - } - - // Best-effort session tuning for faster bulk inserts. - $sessionTuningStatements = [ - "SET SESSION innodb_flush_log_at_trx_commit=2", - "SET SESSION sync_binlog=0", - "SET SESSION unique_checks=0", - "SET SESSION foreign_key_checks=0", - ]; - foreach ($sessionTuningStatements as $stmt) { - try { - mysqli_query($this->dblink, $stmt); - } catch (Throwable $e) { - // Ignore tuning failures, they are not required for correctness. - } - } - - // Temporarily drop secondary indexes before heavy INSERT .. SELECT. - // Recreated at the end to speed up map generation on larger worlds. - $indexesToDrop = ['occupied', 'fieldtype', 'x-y']; - foreach ($indexesToDrop as $indexName) { - try { - $escapedIndexName = mysqli_real_escape_string($this->dblink, $indexName); - $res = mysqli_query($this->dblink, "SHOW INDEX FROM `{$wdataTable}` WHERE Key_name = '{$escapedIndexName}'"); - if ($res && mysqli_num_rows($res) > 0) { - mysqli_query($this->dblink, "ALTER TABLE `{$wdataTable}` DROP INDEX `{$indexName}`"); - $droppedIndexes[$indexName] = true; - } - } catch (Throwable $e) { - // If we can't drop an index, continue with generation anyway. - } - } - - // load the data generation SQL file - $str = file_get_contents($autoprefix."var/db/datagen-world-data.sql"); - $str = preg_replace(["'%PREFIX%'", "'%WORLDSIZE%'"], [TB_PREFIX, WORLD_MAX], $str); - $result = $this->dblink->multi_query($str); - - // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work - while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} - - if (!$result) { - return -1; - } - - $result = $this->regenerateOasisUnits(-1); - if (!$result) { - return -1; - } - } catch (\Throwable $e) { - return -1; - } finally { - // Recreate dropped indexes to keep runtime query performance unchanged. - if (!empty($droppedIndexes)) { - try { - if (isset($droppedIndexes['occupied'])) { - mysqli_query($this->dblink, "CREATE INDEX `occupied` ON `{$wdataTable}` (`occupied`)"); - } - if (isset($droppedIndexes['fieldtype'])) { - mysqli_query($this->dblink, "CREATE INDEX `fieldtype` ON `{$wdataTable}` (`fieldtype`)"); - } - if (isset($droppedIndexes['x-y'])) { - mysqli_query($this->dblink, "CREATE INDEX `x-y` ON `{$wdataTable}` (`x`, `y`)"); - } - } catch (Throwable $e) { - // Best effort only; installation can proceed and indexes may be rebuilt manually if needed. - } - } - - // Restore conservative defaults for this session (best effort). - try { mysqli_query($this->dblink, "SET SESSION unique_checks=1"); } catch (Throwable $e) {} - try { mysqli_query($this->dblink, "SET SESSION foreign_key_checks=1"); } catch (Throwable $e) {} - try { mysqli_query($this->dblink, "SET SESSION innodb_flush_log_at_trx_commit=1"); } catch (Throwable $e) {} - try { mysqli_query($this->dblink, "SET SESSION sync_binlog=1"); } catch (Throwable $e) {} - } - - return true; - } - - - /*** Build/rebuild the croppers precompute table from wdata. */ - - public function TotalCroppers(): int { - $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; - $WDATA = $TBP . 'wdata'; - - $res = mysqli_query($this->dblink, "SELECT COUNT(*) AS cnt FROM `$WDATA` WHERE fieldtype IN (1,6)"); - if (!$res) { - throw new Exception('Count query failed: ' . mysqli_error($this->dblink)); - } - - $row = mysqli_fetch_assoc($res); - return (int)($row['cnt'] ?? 0); - } - - public function populateCroppers(int $countTotal = 0, bool $truncateFirst = false, int $batch = 20000, ?callable $reporter = null ): array { - @set_time_limit(0); - @ini_set('memory_limit', '1G'); - - $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; - $CROP_TABLE = $TBP . 'croppers'; - $WDATA = $TBP . 'wdata'; - - $total = 0; - $inTransaction = false; - - try { - if ($countTotal <= 0) { - $row = mysqli_fetch_assoc(mysqli_query($this->dblink, "SELECT COUNT(*) cnt FROM `$WDATA` WHERE fieldtype IN (1,6)")); - $countTotal = (int)($row['cnt'] ?? 0); - } - - if ($truncateFirst && !mysqli_query($this->dblink, "TRUNCATE TABLE `$CROP_TABLE`")) { - return ['ok'=>false,'msg'=>'TRUNCATE failed: '.mysqli_error($this->dblink)]; - } - - $sessionTuningStatements = [ - "SET SESSION innodb_flush_log_at_trx_commit=2", - "SET SESSION sync_binlog=0", - "SET SESSION unique_checks=0", - "SET SESSION foreign_key_checks=0", - ]; - foreach($sessionTuningStatements as $stmt){ - try { - mysqli_query($this->dblink, $stmt); - } catch (Throwable $e) { - // Ignore tuning failures, they are not required for correctness. - } - } - - if ($batch < 1000) $batch = 1000; - if ($batch > 100000) $batch = 100000; - if($countTotal < 1000) $sliceSize = 200; - elseif($countTotal < 5000) $sliceSize = 500; - else $sliceSize = 1000; - - $lastId = 0; - while (true) { - $res = mysqli_query( - $this->dblink, - "SELECT id AS wref, x, y, fieldtype - FROM `$WDATA` - WHERE fieldtype IN (1,6) AND id > $lastId - ORDER BY id ASC - LIMIT $batch" - ); - if (!$res) { - return ['ok'=>false,'msg'=>'SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; - } - - $rows = []; - while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } - if (!$rows) break; - - mysqli_begin_transaction($this->dblink); - $inTransaction = true; - - $n = count($rows); - for ($i = 0; $i < $n; $i += $sliceSize) { - $chunk = array_slice($rows, $i, $sliceSize); - $values = []; - - // Compute oasis crop bonus in batch for the whole chunk. - $bonusByWref = []; - $wrefs = []; - foreach ($chunk as $r) { - $wrefs[] = (int)$r['wref']; - } - - if (!empty($wrefs)) { - $bonusSql = "SELECT - c.id AS wref, - LEAST( - 150, - (50 * LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3)) + - (25 * LEAST( - GREATEST(3 - LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3), 0), - SUM(CASE WHEN od.type IN (4,9,10,11) THEN 1 ELSE 0 END) - )) - ) AS bonus - FROM `$WDATA` c - LEFT JOIN `$WDATA` o - ON o.fieldtype = 0 - AND o.x BETWEEN (c.x - 3) AND (c.x + 3) - AND o.y BETWEEN (c.y - 3) AND (c.y + 3) - LEFT JOIN `{$TBP}odata` od - ON od.wref = o.id - AND od.type IN (12,4,9,10,11) - WHERE c.id IN (".implode(',', $wrefs).") - GROUP BY c.id"; - - $bonusRes = mysqli_query($this->dblink, $bonusSql); - if (!$bonusRes) { - mysqli_rollback($this->dblink); - $inTransaction = false; - return ['ok'=>false,'msg'=>'BONUS SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; - } - - while ($bonusRow = mysqli_fetch_assoc($bonusRes)) { - $bonusByWref[(int)$bonusRow['wref']] = (int)($bonusRow['bonus'] ?? 0); - } - } - - foreach ($chunk as $r) { - $x = (int)$r['x']; - $y = (int)$r['y']; - $bonus = (int)($bonusByWref[(int)$r['wref']] ?? 0); - if ($bonus < 0) $bonus = 0; - if ($bonus > 150) $bonus = 150; - $values[] = sprintf("(%d,%d,%d,%d,%d)", (int)$r['wref'], $x, $y, (int)$r['fieldtype'], $bonus); - } - - if ($values) { - $sql = "INSERT INTO `$CROP_TABLE` - (`wref`,`x`,`y`,`fieldtype`,`best_oasis_bonus`) - VALUES ".implode(',', $values)." - ON DUPLICATE KEY UPDATE - `x`=VALUES(`x`), - `y`=VALUES(`y`), - `fieldtype`=VALUES(`fieldtype`), - `best_oasis_bonus`=VALUES(`best_oasis_bonus`)"; - if (!mysqli_query($this->dblink, $sql)) { - mysqli_rollback($this->dblink); - $inTransaction = false; - return ['ok'=>false,'msg'=>'INSERT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; - } - } - - $total += count($chunk); - if ($reporter) { - $pct = $countTotal ? min(100, (int)floor(($total / $countTotal) * 100)) : 0; - $reporter($total, $countTotal, $pct); - } - } - - mysqli_commit($this->dblink); - $inTransaction = false; - $lastId = (int)$rows[$n - 1]['wref']; - } - - foreach(["SET SESSION unique_checks=1", "SET SESSION foreign_key_checks=1"] as $stmt){ - try { - mysqli_query($this->dblink, $stmt); - } catch (Throwable $e) { - // Ignore restore failures if the engine does not support the setting. - } - } - - @mysqli_query($this->dblink, "ANALYZE TABLE `$CROP_TABLE`"); - if ($reporter) { $reporter($total, $countTotal, 100); } - return ['ok'=>true,'msg'=>'Croppers populated','processed'=>$total,'target'=>$countTotal]; - } catch (Throwable $e) { - if ($inTransaction) { - try { - mysqli_rollback($this->dblink); - } catch (Throwable $rollbackException) { - // Ignore rollback errors after fatal DB exceptions. - } - } - return ['ok'=>false,'msg'=>$e->getMessage(),'processed'=>$total,'target'=>$countTotal]; - } - } - - - // no need to cache, not used in any loops or more than once for each page load - public function getAvailableExpansionTraining() { - global $building, $session, $technology, $village; - - $vilData = $this->getVillage($village->wid); - $maxslots = (($vilData['exp1'] == 0 ? 1 : 0) + ($vilData['exp2'] == 0 ? 1 : 0) + ($vilData['exp3'] == 0 ? 1 : 0)); - $residence = $building->getTypeLevel(25); - $palace = $building->getTypeLevel(26); - - if($residence > 0) { - $maxslots -= (3 - floor($residence / 10)); - } - - if($palace > 0) { - $maxslots -= (3 - floor(($palace - 5) / 5)); - } - - // Command Center (Huni) - sloturi de expansiune ca Residence (nivel 10/20) - $commandcenter = $building->getTypeLevel(44); - if($commandcenter > 0) { - $maxslots -= (3 - floor($commandcenter / 10)); - } - - // Units at home - $q = "SELECT (u10+u20+u30+u60+u70+u80+u90) as R1, (u9+u19+u29+u59+u69+u79+u89) as R2 - FROM " . TB_PREFIX . "units - WHERE vref = " . (int)$village->wid; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result, MYSQLI_ASSOC); - $settlers = (int)$row['R1']; - $chiefs = (int)$row['R2']; - - // Movements - $settlers += 3 * count($this->getMovement(5, $village->wid, 0)); - - $current_movement = $this->getMovement(3, $village->wid, 0); - if(!empty($current_movement)) { - foreach($current_movement as $build) { - $settlers += (int)$build['t10']; - $chiefs += (int)$build['t9']; - } - } - - $current_movement = $this->getMovement(4, $village->wid, 1); - if(!empty($current_movement)) { - foreach($current_movement as $build) { - $settlers += (int)$build['t10']; - $chiefs += (int)$build['t9']; - } - } - - // FIX: Count ALL reinforcements properly (SUM over ALL rows) - $q = "SELECT COALESCE(SUM(u10+u20+u30+u60+u70+u80+u90),0) AS s - FROM " . TB_PREFIX . "enforcement - WHERE `from` = " . (int)$village->wid; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result, MYSQLI_ASSOC); - $settlers += (int)$row['s']; - - $q = "SELECT COALESCE(SUM(u9+u19+u29+u59+u69+u79+u89),0) AS c - FROM " . TB_PREFIX . "enforcement - WHERE `from` = " . (int)$village->wid; - $result = mysqli_query($this->dblink,$q); - $row = mysqli_fetch_array($result, MYSQLI_ASSOC); - $chiefs += (int)$row['c']; - - // Training queue (your existing logic) - $trainlist = $technology->getTrainingList(4); - if(!empty($trainlist)) { - foreach($trainlist as $train) { - if($train['unit'] % 10 == 0) { - $settlers += (int)$train['amt']; - } - if($train['unit'] % 10 == 9) { - $chiefs += (int)$train['amt']; - } - } - } - - // Trapped troops - $trappedTroops = $this->getPrisoners($village->wid, 1); - if(!empty($trappedTroops)){ - foreach($trappedTroops as $trapped){ - $settlers += (int)$trapped['t10']; - $chiefs += (int)$trapped['t9']; - } - } - - // Slot math (unchanged, but clamp to 0 to avoid negatives) - $settlerslots = ($maxslots * 3) - ($chiefs * 3) - $settlers; - $chiefslots = $maxslots - $chiefs - floor(($settlers + 2) / 3); - - if(!$technology->getTech(($session->tribe - 1) * 10 + 9)) { - $chiefslots = 0; - } - - if ($settlerslots < 0) $settlerslots = 0; - if ($chiefslots < 0) $chiefslots = 0; - - return ["chiefs" => $chiefslots, "settlers" => $settlerslots]; -} - - - /** - * Calculates how much artifacts affect troops speed, cranny efficency, etc. - * - * @param int $uid The User ID - * @param int $vid The village ID - * @param int $kind The kind of the artifact - * @param float $multiplicand The value which needs to be multiplied - * @return int Returns the new value, multiplied or divided by artifacts bonus or malus - */ - - function getArtifactsValueInfluence($uid, $vid, $kind, $multiplicand, $round = true){ - list($uid, $vid, $kind, $multiplicand, $round) = $this->escape_input((int) $uid,(int) $vid, $kind, $multiplicand, $round); - - $artefacts = $foolArefacts = []; - $multipliers = [1 => [4, 5, 3], 2 => [1/2, 1/3, 2/3], 3 => [5, 10, 3], 4 => [1/2, 1/2, 3/4], 5 => [1/2, 1/2, 3/4], 7 => [3, 6, 2]]; - - $artefacts[] = count($this->getOwnUniqueArtefactInfo2($vid, $kind, 1, 1)); //Village effect - $artefacts[] = count($this->getOwnUniqueArtefactInfo2($uid, $kind, 3, 0)); //Unique effect - $artefacts[] = count($this->getOwnUniqueArtefactInfo2($uid, $kind, 2, 0)); //Account effect - - $multiplier = 1; - for($i = 0; $i < count($artefacts); $i++) - { - if($artefacts[$i] > 0) { - $multiplier = $multipliers[$kind][$i]; - break; - } - } - - $foolArefacts[] = $this->getOwnUniqueArtefactInfo2($vid, 8, 1, 1); //Village effect - $foolArefacts[] = $this->getOwnUniqueArtefactInfo2($uid, 8, 3, 0); //Unique effect - - $foolEffect = 1; - for($i = 0; $i < count($foolArefacts); $i++) - { - if(count($foolArefacts[$i]) > 0 && $foolArefacts[$i]['kind'] == $kind) - { - $foolEffect = $foolArefacts[$i]['bad_effect'] == 1 ? 1 / $foolArefacts[$i]['effect2'] : $foolArefacts[$i]['effect2']; - break; - } - } - - if(in_array($kind, [2, 4, 5])) $foolEffect = 1 / $foolEffect; - - return !$round ? $multiplicand * $multiplier * $foolEffect : round($multiplicand * $multiplier * $foolEffect); - } - - /** - * Get the total artifacts sum, divided by small, great and unique, by kind - * - * @param int $uid The User ID - * @param int $vid The Village ID - * @param int $kind The kind of the artifact - * @return array Returns the total artifacts sum divided by size - */ - - function getArtifactsSumByKind($uid, $vid, $kind){ - list($uid, $vid, $kind) = $this->escape_input((int) $uid, (int) $vid, (int) $kind); - - $q = "SELECT SUM(IF((size = '1' AND vref = $vid) OR size > '1', 1, 0)) totals, - SUM(IF(size = '1' AND vref = $vid, 1, 0)) small, - SUM(IF(size = '2', 1, 0)) great, - SUM(IF(size = '3', 1, 0)) `unique` - FROM " . TB_PREFIX . "artefacts WHERE owner = ".$uid." AND active = 1 AND (type = $kind OR kind = $kind) AND del = 0"; - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result)[0]; - } - - /** - * Display a system message to all players - * - * @param string $message The text of the system message that will be written and displayed to all players - */ - - function displaySystemMessage($message){ - list($message) = $this->escape_input($message); - global $autoprefix; - - $myFile = $autoprefix."Templates/text.tpl"; - $fh = fopen($myFile, 'w'); - $text = file_get_contents($autoprefix."Templates/text_format.tpl"); - $text = preg_replace("'%TEKST%'", $message, $text); - fwrite($fh, $text); - - //Set "OK" to 1 to all players, so they can visualize the message - $this->setUsersOk(); - } - - /** - * Called when a system message is sent or Natars/Artifacts have been spawned - * - * @param int $value 1 to make a system message visible to all users, 0 to hide it - * @return bool Returns true if the query was successful, false otherwise - */ - - function setUsersOk($value = 1){ - list($value) = $this->escape_input((int) $value); - - $q = "UPDATE " . TB_PREFIX . "users SET ok = $value"; - return mysqli_query($this->dblink, $q); - } - - function addArtefacts($wids, $artifactsArray) { - list($wids, $artifactsArray) = $this->escape_input($wids, $artifactsArray); - - if(!is_array($wids)) $wids = [$wids]; - - $time = time(); - - foreach($artifactsArray as $index => $artifact){ - $values[] = "(".$wids[$index].",".$artifact['owner'].",".$artifact['type'].",".$artifact['size'].",".$time.",'".$artifact['name']."','".$artifact['desc']."','".$artifact['effect']."','".$artifact['img']."', 0)"; - } - - $q = "INSERT INTO `" . TB_PREFIX . "artefacts` (`vref`, `owner`, `type`, `size`, `conquered`, `name`, `desc`, `effect`, `img`, `active`) VALUES ".implode(",", $values); - return mysqli_query($this->dblink, $q); - } - - function getWWConstructionPlans($uid, $alliance = 0){ - list($uid, $alliance) = $this->escape_input((int) $uid, $alliance); - - if(!$alliance){ - $q = "SELECT - Count(*) as Total - FROM - ".TB_PREFIX."artefacts - WHERE - owner = ".$uid." AND type = 11 AND active = 1 AND del = 0"; - }else{ - $q = "SELECT - Count(*) as Total - FROM - ".TB_PREFIX."artefacts AS artefacts - INNER JOIN ".TB_PREFIX."users AS users - ON users.id != ".$uid." AND users.alliance = ".$alliance." AND artefacts.owner = users.id AND artefacts.type = 11 - WHERE - users.id > 4 AND artefacts.active = 1 AND artefacts.del = 0"; - } - - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - // no need to cache this method - function getOwnArtefactInfo($vref, $use_cache = true) { - // load the data - type is irrelevant, since the method caches all data - // then returns the one for our type - $this->getOwnArtefactInfoByType($vref, 1, $use_cache); - - // return what we've cached - return (self::$artefactInfoByTypeCache[$vref]); - } - - // no need to cache this method since its called one time only - function getOwnArtefactsInfo($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE owner = $uid AND del = 0"; - $result = mysqli_query($this->dblink, $q); - - return $this->mysqli_fetch_all($result); - } - - function getOwnArtefactInfoByType2($vref, $type, $use_cache = true) { - return $this->getOwnArtefactInfoByType($vref, $type, $use_cache); - } - - function getOwnArtefactInfoByType($vref, $type, $use_cache = true) { - $vref = (int) $vref; - $type = (int) $type; - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && isset(self::$artefactInfoByTypeCache[$vref]) && is_array(self::$artefactInfoByTypeCache[$vref]) && !count(self::$artefactInfoByTypeCache[$vref])) { - return []; - } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$artefactInfoByTypeCache, $vref)) && !is_null($cachedValue)) { - return (isset($cachedValue[$type]) ? $cachedValue[$type] : []); - } - - $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE vref = $vref AND del = 0 ORDER BY size"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // cache all types and return the requested one - if (count($result)) { - foreach ($result as $arteInfo) { - if (!isset(self::$artefactInfoByTypeCache[$arteInfo['vref']])) { - self::$artefactInfoByTypeCache[$arteInfo['vref']] = []; - } - - // we're sorting by size, thus we only need the first one per each type - if (isset(self::$artefactInfoByTypeCache[$arteInfo['vref']]) && !isset(self::$artefactInfoByTypeCache[$arteInfo['vref']][$arteInfo['type']])) { - self::$artefactInfoByTypeCache[$arteInfo['vref']][$arteInfo['type']] = $arteInfo; - } - } - } else { - self::$artefactInfoByTypeCache[$vref] = []; - } - - return (isset(self::$artefactInfoByTypeCache[$vref][$type]) ? self::$artefactInfoByTypeCache[$vref][$type] : []); - } - - function getOwnUniqueArtefactInfo2($id, $type, $size, $mode, $use_cache = true) { - list($id, $type, $size, $mode) = $this->escape_input((int) $id, (int) $type, (int) $size, $mode); - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && isset(self::$artefactDataCache[$id.$mode]) && is_array(self::$artefactDataCache[$id.$mode]) && !count(self::$artefactDataCache[$id.$mode])) { - return []; - } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$artefactDataCache, $id.$mode)) && !is_null($cachedValue)) { - return (isset($cachedValue[$size.$type]) ? $cachedValue[$size.$type] : []); - } - - $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE ".(!$mode ? 'owner' : 'vref')." = $id AND active = 1 AND del = 0"; - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // cache all types and return the requested one - if (count($result)) { - foreach ($result as $arteInfo) { - if (!isset(self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode])) { - self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode] = []; - } - - self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode][$arteInfo['size'].$arteInfo['type']] = $arteInfo; - } - } else { - self::$artefactDataCache[$id.$mode] = []; - } - - return (isset(self::$artefactDataCache[$id.$mode][$size.$type]) ? self::$artefactDataCache[$id.$mode][$size.$type] : []); - } - - /** - * Get deleted artifacts - * - * @return array Returns the deleted artifacts - */ - - function getDeletedArtifacts(){ - $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE del = 1"; - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result); - } - - function villageHasArtefact($vref) { - // this is a somewhat non-ideal, externally non-changeable way of caching - // but since we're only ever going to be calling this from a single point of Automation, - // this will more than suffice - static $cachedData = []; - $vref = (int) $vref; - - if (isset($cachedData[$vref])) { - return $cachedData[$vref]; - } - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "artefacts WHERE vref = $vref AND del = 0"; - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - $cachedData[$vref] = $result['Total']; - - return $cachedData[$vref]; - } - - /** - * ===================================================================== - * SERVER MILESTONES (NEW_FUNCTIONS_MILESTONES) - * ===================================================================== - * "First player on the server to..." achievements. Each milestone_key - * is recorded AT MOST ONCE, ever — the table's UNIQUE KEY on - * milestone_key does the actual "first wins" enforcement at the DB - * level via INSERT IGNORE, so this is race-condition-safe even if two - * players' actions are processed in the same cron batch: only one - * INSERT can ever succeed for a given key, no matter how many - * processes attempt it concurrently. - */ - - /** - * Attempts to record a milestone. Only the very first call for a given - * $key across the server's lifetime actually stores anything. - * - * @param string $key Unique milestone identifier, e.g. 'second_village' - * @param int $uid The user who achieved it - * @param int $vref The village where it happened (0 if not applicable) - * @param string $extra Optional free-form context (e.g. alliance name) - * @return bool true if THIS call is the one that recorded the milestone - */ - function recordMilestoneIfFirst($key, $uid, $vref = 0, $extra = '') { - list($key, $extra) = $this->escape_input((string) $key, (string) $extra); - list($uid, $vref) = [(int) $uid, (int) $vref]; - - $time = time(); - $q = "INSERT IGNORE INTO " . TB_PREFIX . "milestones (milestone_key, uid, vref, extra, achieved_time) - VALUES ('$key', $uid, $vref, '$extra', $time)"; - mysqli_query($this->dblink, $q); - - return mysqli_affected_rows($this->dblink) > 0; - } - - /** - * @return array All recorded milestones, keyed by milestone_key, each - * row enriched with the achiever's username and (if - * applicable) the village name. - */ - function getMilestones() { - $q = "SELECT m.milestone_key, m.uid, m.vref, m.extra, m.achieved_time, - u.username, v.name AS village_name - FROM " . TB_PREFIX . "milestones m - LEFT JOIN " . TB_PREFIX . "users u ON u.id = m.uid - LEFT JOIN " . TB_PREFIX . "vdata v ON v.wref = m.vref"; - $result = mysqli_query($this->dblink, $q); - $rows = $this->mysqli_fetch_all($result); - - $byKey = []; - foreach ($rows as $row) { - $byKey[$row['milestone_key']] = $row; - } - return $byKey; - } - - /** - * @param int $uid - * @return int How many villages this user currently owns. Deliberately - * uncached (always a fresh COUNT) since it's used right - * after a village INSERT to decide a one-time milestone. - */ - function countVillages($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = "SELECT COUNT(*) AS total FROM " . TB_PREFIX . "vdata WHERE owner = $uid"; - $result = mysqli_query($this->dblink, $q); - $row = mysqli_fetch_assoc($result); - - return $row ? (int) $row['total'] : 0; - } - - function claimArtefact($vref, $ovref, $id) { - list($vref, $ovref, $id) = $this->escape_input((int) $vref, (int) $ovref, (int) $id); - - $time = time(); - $q = "UPDATE " . TB_PREFIX . "artefacts SET vref = $vref, owner = $id, conquered = $time, active = 0 WHERE vref = $ovref"; - - if(mysqli_query($this->dblink, $q)) - { - $artifactInfo = reset($this->getOwnArtefactInfo($vref, false)); - $artifactID = $artifactInfo['id']; - - // Milestones: first artifact EVER captured by any player (any - // type, including the WW Building Plan), and — separately — - // the first WW Building Plan (artefacts.type == 11) specifically. - // claimArtefact() is the single function that transfers artifact - // ownership (both the "conquer the artifact's own village" path - // and the "hero carries it home" path call this), so hooking - // here covers every capture route with no risk of missing one. - if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { - $this->recordMilestoneIfFirst('first_artifact', $id, $vref); - if ((int)($artifactInfo['type'] ?? 0) === 11) { - $this->recordMilestoneIfFirst('first_ww_plan', $id, $vref); - } - } - - return $this->addArtifactsChronology($artifactID, $id, $vref, $time); - } - else return false; - } - - /** - * Retrieves the chronology of one specific artifact - * - * @param int $artefactid The id of the artifact - * @return array Returns the chronology for the passed artifact - */ - - function getArtifactsChronology($artifactID){ - list($artifactID) = $this->escape_input((int) $artifactID); - - $q = "SELECT * FROM " . TB_PREFIX . "artefacts_chrono WHERE artefactid = $artifactID ORDER BY conqueredtime ASC"; - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result); - } - - /** - * Stores when an artifact was conquered and who had conquered it - * - * @param int $artefactid The id of the artifact - * @param int $vref The vref of the village that has conquered the artifact - * @return bool Return true if the query was successful, false otherwise - */ - - function addArtifactsChronology($artifactID, $uid, $vref, $conqueredTime){ - list($artifactID, $uid, $vref, $conqueredTime) = $this->escape_input((int) $artifactID, (int) $uid, (int) $vref, (int) $conqueredTime); - - $q = "INSERT INTO " . TB_PREFIX . "artefacts_chrono (artefactid, uid, vref, conqueredtime) VALUES ('$artifactID', '$uid', '$vref', '$conqueredTime')"; - return mysqli_query($this->dblink, $q); - } - - /** - * @param mixed $size The integer/array which contains the artifacts size(s) - * @return int Returns if there are at least one not deleted artifact - */ - - function getArtifactsBysize($size){ - list($size) = $this->escape_input($size); - - if(!is_array($size)) $size = [$size]; - - $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE size IN(".implode(',', $size).") AND del = 0 ORDER BY id ASC"; - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result); - } - - /** - * @param bool $mode true: check if WW Building plans are already out, false: check if artifacts are already out - * @return int Returns if artifacts are already out or not - */ - - function areArtifactsSpawned($mode = false){ - list($mode) = $this->escape_input($mode); - - $q = "SELECT 1 FROM ".TB_PREFIX."artefacts".($mode ? " WHERE type = 11" : ""); - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - return $result; - } - - - /** - * Check if WW villages are already out or not - * - * @return int Returns if artifacts are already out or not - */ - - function areWWVillagesSpawned(){ - $q = "SELECT 1 FROM ".TB_PREFIX."vdata WHERE natar = 1"; - $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); - return $result; - } - - /** - * Get all inactive artifacts which can be activated - * - * @return bool Returns all inactive artifacts - */ - - function getInactiveArtifacts($time){ - list($time) = $this->escape_input($time); - - $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE active = 0 AND owner > 5 AND conquered <= $time AND del = 0 ORDER BY conquered ASC, size ASC"; - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result); - } - - /** - * Get the sum of active artifacts by user ID, divided by: total, great, small and unique - * - * @param int $uid The User ID of the player - * @param bool $mode True if you want only active artifacts, false if you want all artifacts - * @return array Returns the artifacts sum of the account, divided by: total, great, small and unique - */ - - function getOwnArtifactsSum($uid, $mode = false){ - list($uid) = $this->escape_input((int) $uid, $mode); - - $q = "SELECT Count(size) AS totals, - SUM(IF(size = '1', 1, 0)) small, - SUM(IF(size = '2', 1, 0)) great, - SUM(IF(size = '3', 1, 0)) `unique` - FROM " . TB_PREFIX . "artefacts WHERE owner = ".(int) $uid.($mode ? " AND active = 1 AND del = 0" : ""); - $result = mysqli_query($this->dblink, $q); - return $this->mysqli_fetch_all($result)[0]; - } - - /** - * Activate an artifact by his id - * - * @param int $id The id of the artifact - * @param int $mode 1 for activating an artifact, 0 for deactivating it - * @return bool Returns true if the query was successful, false otherwise - */ - - function activateArtifact($id, $mode = 1){ - list($id) = $this->escape_input((int) $id); - - $time = time(); - $q = "UPDATE " . TB_PREFIX . "artefacts SET active = $mode WHERE id = $id"; - return mysqli_query($this->dblink, $q); - } - - /** - * Get the newest active artifact by size - * - * @param int $size The size of the artifcat (village, account, unique) - * @return array Returns the newest active artifact infomations by size - */ - - function getNewestArtifactBySize($id, $size){ - list($id, $size) = $this->escape_input((int) $id, (int) $size); - - $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE active = 1 AND owner = $id AND size = $size AND del = 0 ORDER BY conquered DESC LIMIT 1"; - $result = mysqli_query($this->dblink, $q); - return mysqli_fetch_array($result); - } - - // no need to cache this method - public function canClaimArtifact($from, $vref, $size, $type) { - list($size, $type) = $this->escape_input((int) $size, (int) $type); - - $artifact = $this->getOwnArtefactInfo($from); - if (!empty($artifact)) return "Treasury is full. Your hero could not claim the artefact"; - - $uid = $this->getVillageField($from, "owner"); - $vuid = $this->getVillageField($vref, "owner"); - - $artifact = $this->getOwnArtifactsSum($uid); - - if ($artifact['totals'] < 3 || $uid == $vuid) { - $DefenderFields = $this->getResourceLevel( $vref ); - $defcanclaim = true; - - for ($i = 19; $i <= 38; $i++) { - if ($DefenderFields['f'.$i.'t'] == 27) { - $defTresuaryLevel = $DefenderFields['f'.$i]; - if ($defTresuaryLevel > 0) { - $defcanclaim = false; - return "Treasury has not been destroyed. Your hero could not claim the artefact"; - } - else $defcanclaim = true; - } - } - - $AttackerFields = $this->getResourceLevel( $from, 2 ); - - for($i = 19; $i <= 38; $i++) { - if($AttackerFields['f'.$i.'t'] == 27) { - $attTresuaryLevel = $AttackerFields['f'.$i]; - $villageartifact = $attTresuaryLevel >= 10; - $accountartifact = $attTresuaryLevel >= 20; - } - } - - if(($artifact['great'] > 0 || $artifact['unique'] > 0) && $size > 1 && $uid != $vuid) { - return "Max num. of great/unique artefacts. Your hero could not claim the artefact"; - } - - if(($size == 1 && ($villageartifact || $accountartifact)) || (($size == 2 || $size == 3) && $accountartifact)) { - return ""; - } - else return "Your level treasury is low. Your hero could not claim the artefact"; - } - else return "Max num. of artefacts. Your hero could not claim the artefact"; - } - - /** - * Get the informations of a single artifact - * - * @param int $id The artifact id - * @param int $del If 0, it will search not-deleted artifacts, and vice versa with 1 - * @return array Returns the artefact informations - */ - - function getArtefactDetails($id, $del = 0) { - list($id, $del) = $this->escape_input((int) $id, (int) $del); - - $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE id = ".$id." AND del = ".$del." LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - return mysqli_fetch_array($result); - } - - /** - * Update an artifact with a specified fields => values array - * - * @param int $id The artifact ID - * @param array $detailsArray Contains the fields to update and the relative values - * @return bool Returns true if the query was successful, false otherwise - */ - - function updateArtifactDetails($id, $detailsArray){ - list($id, $detailsArray) = $this->escape_input((int) $id, $detailsArray); - - $values = []; - foreach($detailsArray as $field => $value) $values[] = $field."=".$value; - - $q = "UPDATE ".TB_PREFIX."artefacts SET ".implode(",", $values)." WHERE id = $id"; - return mysqli_query($this->dblink, $q); - } - - // no need to cache this method - function getMovementById($id) { - list($id) = $this->escape_input((int) $id); - $q = "SELECT * FROM ".TB_PREFIX."movement WHERE moveid = ".$id; - $result = mysqli_query($this->dblink,$q); - $array = $this->mysqli_fetch_all($result); - return $array; - } - - // Rally-point attack marker (issue #245): a defender can tag an incoming - // attack green/yellow/red. The WHERE clause restricts the update to a - // movement whose target village (`to`) belongs to $uid, so a player can - // only mark attacks incoming on their own villages. - function setMovementMarker($moveid, $marker, $uid) { - $moveid = (int) $moveid; - $marker = (int) $marker; - $uid = (int) $uid; - if ($marker < 0 || $marker > 3 || $moveid <= 0 || $uid <= 0) { - return false; - } - $q = "UPDATE ".TB_PREFIX."movement SET marker = ".$marker. - " WHERE moveid = ".$moveid. - " AND `to` IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner = ".$uid.")"; - return mysqli_query($this->dblink, $q) && mysqli_affected_rows($this->dblink) > 0; - } - - // no need to cache this method - function getLinks($id) { - list($id) = $this->escape_input((int) $id); - $q = 'SELECT * FROM `' . TB_PREFIX . 'links` WHERE `userid` = ' . $id . ' ORDER BY `pos` ASC'; - return mysqli_query($this->dblink,$q); - } - - function removeLinks($id,$uid) { - list($id,$uid) = $this->escape_input((int) $id,(int) $uid); - $q = "DELETE FROM " . TB_PREFIX . "links WHERE `id` = ".$id." and `userid` = ".$uid; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getVilFarmlist($uid) { - list($uid) = $this->escape_input((int) $uid); - - $q = 'SELECT * FROM ' . TB_PREFIX . 'farmlist WHERE owner = '.$uid.' ORDER BY wref ASC LIMIT 1'; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - return ($dbarray['id'] ?? 0) > 0; - } - - // no need to cache this method - function getRaidList($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * FROM " . TB_PREFIX . "raidlist WHERE id = ".$id." LIMIT 1"; - $result = mysqli_query($this->dblink, $q); - return mysqli_fetch_array($result); - } - - /** - * Get all informations about a farm list - * - * @param int $id The farmlist ID - * @return array Returns the seleted farm list informations - */ - - function getFLData($id) { - list($id) = $this->escape_input((int) $id); - - $q = "SELECT * FROM " . TB_PREFIX . "farmlist where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - return mysqli_fetch_array($result); - } - - - function delFarmList($id, $owner) { - list($id, $owner) = $this->escape_input((int) $id, (int) $owner); - - $q = "DELETE FROM " . TB_PREFIX . "farmlist where id = $id and owner = $owner"; - if(mysqli_query($this->dblink, $q) && mysqli_affected_rows($this->dblink) > 0){ - $q = "DELETE FROM " . TB_PREFIX . "raidlist where lid = $id"; - return mysqli_query($this->dblink, $q); - } - return false; - } - - function delSlotFarm($id, $owner, $lid) { - list($id, $owner, $lid) = $this->escape_input((int) $id, (int) $owner, (int) $lid); - - $q = "DELETE FROM " . TB_PREFIX . "raidlist WHERE id = $id AND lid = $lid AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $lid AND owner = $owner)"; - return mysqli_query($this->dblink,$q); - } - - function createFarmList($wref, $owner, $name) { - list($wref, $owner, $name) = $this->escape_input($wref, $owner, $name); - - $q = "INSERT INTO " . TB_PREFIX . "farmlist (`wref`, `owner`, `name`) VALUES ('$wref', '$owner', '$name')"; - return mysqli_query($this->dblink,$q); - } - - function addSlotFarm($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6) { - list($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6) = $this->escape_input($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6); - - for($i = 1; $i <= 6; $i++) { - if (${'t'.$i} == '') { - ${'t'.$i} = 0; - } - } - $q = "INSERT INTO " . TB_PREFIX . "raidlist (`lid`, `towref`, `x`, `y`, `distance`, `t1`, `t2`, `t3`, `t4`, `t5`, `t6`) VALUES ('$lid', '$towref', '$x', '$y', '$distance', '$t1', '$t2', '$t3', '$t4', '$t5', '$t6')"; - return mysqli_query($this->dblink,$q); - } - - function editSlotFarm($eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6) { - list($eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6) = $this->escape_input((int) $eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6); - - for($i = 1; $i <= 6; $i++) { - if (${'t'.$i} == '') { - ${'t'.$i} = 0; - } - } - $q = "UPDATE " . TB_PREFIX . "raidlist SET lid = '$lid', towref = '$wref', x = '$x', y = '$y', distance = '$dist', t1 = '$t1', t2 = '$t2', t3 = '$t3', t4 = '$t4', t5 = '$t5', t6 = '$t6' WHERE id = $eid AND lid = $oldLid AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $lid AND owner = $owner) AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $oldLid AND owner = $owner)"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getArrayMemberVillage($uid) { - list($uid) = $this->escape_input((int) $uid); - $q = 'SELECT a.wref, a.name, b.x, b.y from '.TB_PREFIX.'vdata AS a left join '.TB_PREFIX.'wdata AS b ON b.id = a.wref where owner = '.$uid.' ORDER BY name ASC'; - $result = mysqli_query($this->dblink,$q); - $array = $this->mysqli_fetch_all($result); - return $array; - } - - function addPassword($uid, $npw, $cpw) { - list($uid, $npw, $cpw) = $this->escape_input((int) $uid, $npw, $cpw); - $q = "REPLACE INTO `" . TB_PREFIX . "password`(uid, npw, cpw) VALUES ($uid, '$npw', '$cpw')"; - mysqli_query($this->dblink,$q); - } - - function resetPassword($uid, $cpw) { - list($uid, $cpw) = $this->escape_input((int) $uid, $cpw); - $q = "SELECT npw FROM `" . TB_PREFIX . "password` WHERE uid = $uid AND cpw = '$cpw' AND used = 0 LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - $dbarray = mysqli_fetch_array($result); - - if(!empty($dbarray)) { - if(!$this->updateUserField($uid, 'password', password_hash($dbarray['npw'], PASSWORD_BCRYPT,['cost' => 12]), 1)) return false; - $q = "UPDATE `" . TB_PREFIX . "password` SET used = 1 WHERE uid = $uid AND cpw = '$cpw' AND used = 0"; - mysqli_query($this->dblink,$q); - return true; - } - - return false; - } - - function getCropProdstarv($wref, $use_cache = true) { - global $bid4, $bid8, $bid9, $technology; - - // 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::$cropProductionStarvationValueCache, $wref)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $basecrop = $grainmill = $bakery = $cropo = 0; - $owner = $this->getVrefField($wref, 'owner', $use_cache); - $bonus = $this->getUserField($owner, 'b4', 0); - - $buildarray = $this->getResourceLevel($wref); - $cropholder = []; - for($i = 1; $i <= 38; $i++){ - if($buildarray['f'.$i.'t'] == 4) array_push($cropholder, 'f'.$i); - if($buildarray['f'.$i.'t'] == 8) $grainmill = $buildarray['f'.$i]; - if($buildarray['f'.$i.'t'] == 9) $bakery = $buildarray['f'.$i]; - } - - $q = "SELECT type FROM `" . TB_PREFIX . "odata` WHERE conqured = ".(int) $wref; - $oasis = $this->query_return($q); - foreach($oasis as $oa){ - switch($oa['type']) { - case 3: - case 6: - case 9: - case 10: - case 11: - $cropo++; - break; - case 12: - $cropo += 2; - break; - } - } - - for($i = 0; $i <= count($cropholder) - 1; $i++){ - $basecrop += $bid4[$buildarray[$cropholder[$i]]]['prod']; - } - - $crop = $basecrop + $basecrop * 0.25 * $cropo; - - if($grainmill >= 1 || $bakery >= 1){ - $crop += $basecrop / 100 * ((isset($bid8[$grainmill]['attri']) ? $bid8[$grainmill]['attri'] : 0) + (isset($bid9[$bakery]['attri']) ? $bid9[$bakery]['attri'] : 0)); - } - if($bonus > time()) $crop *= 1.25; - - $crop *= SPEED; - - self::$cropProductionStarvationValueCache[$wref] = $crop; - return self::$cropProductionStarvationValueCache[$wref]; - } - - /** - * Adds the starvation data in villages with a negative value of crop - * - * @param int $wref The village ID where the crop is negative - */ - - public function addStarvationData($wref){ - global $technology; - - $getVillage = $this->getVillage($wref); - - // FIX: dacă satul nu există, ieși imediat - if (!$getVillage || !is_array($getVillage)) { - return; - } - - //Exlude Support, Nature, Natars, TaskMaster and Multihunter - if (($getVillage['owner'] ?? 0) > 5){ - $crop = $this->getCropProdstarv($wref, false); - $unitArrays = $technology->getAllUnits($wref, false, 0, false); - $villageUpkeep = $getVillage['pop'] + $technology->getUpkeep($unitArrays, 0, $wref); - $starv = $getVillage['starv']; - - if ($crop < $villageUpkeep){ - //Add starvation data - $fields = ['starv']; - $values = [$villageUpkeep]; - - //Update the starvupdate if it's set to 0 - if($getVillage['starvupdate'] == 0) { - $fields[] = 'starvupdate'; - $values[] = time(); - } - - //Update the starvation datas - $this->setVillageFields($wref, $fields, $values); - } - } - } - - //general statistics - - function addGeneralAttack($casualties) { - list($casualties) = $this->escape_input($casualties); - - $time = time(); - $q = "INSERT INTO " . TB_PREFIX . "general values (0,'$casualties','$time',1)"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function getAttackByDate($time) { - list($time) = $this->escape_input($time); - - $q = "SELECT time FROM " . TB_PREFIX . "general where shown = 1"; - $result = $this->query_return($q); - $attack = 0; - foreach($result as $general) { - if(date("j. M",$time) == date("j. M",$general['time'])){ - $attack += 1; - } - } - return $attack; - } - - // no need to cache this method - function getAttackCasualties($time) { - list($time) = $this->escape_input($time); - - $q = "SELECT time, casualties FROM " . TB_PREFIX . "general where shown = 1"; - $result = $this->query_return($q); - $casualties = 0; - foreach($result as $general){ - if(date("j. M",$time) == date("j. M",$general['time'])){ - $casualties += $general['casualties']; - } - } - return $casualties; - } - - //end general statistics - - function addFriend($uid, $column, $friend) { - list($uid, $column, $friend) = $this->escape_input((int) $uid, $column, (int) $friend); - - $q = "UPDATE " . TB_PREFIX . "users SET $column = $friend WHERE id = $uid"; - return mysqli_query($this->dblink,$q); - } - - function deleteFriend($uid, $column) { - list($uid, $column) = $this->escape_input((int) $uid, $column); - - $q = "UPDATE " . TB_PREFIX . "users SET $column = 0 WHERE id = $uid"; - return mysqli_query($this->dblink,$q); - } - - // no need to cache this method - function checkFriends($uid) { - list($uid) = $this->escape_input($uid); - global $session; - - $user = $this->getUserArray($uid, 1); - for($i = 0; $i <= 19; $i++){ - if($user['friend'.$i] == 0 && $user['friend'.$i.'wait'] == 0){ - for($j = $i + 1; $j <= 19; $j++){ - $k = $j - 1; - if($user['friend'.$j] != 0){ - $friend = $this->getUserField($uid, "friend".$j, 0); - $this->addFriend($uid, "friend".$k, $friend); - $this->deleteFriend($uid, "friend".$j); - } - - if($user['friend'.$j.'wait'] == 0){ - $friendwait = $this->getUserField($uid, "friend".$j."wait", 0); - $this->addFriend($session->uid, "friend".$k."wait", $friendwait); - $this->deleteFriend($uid, "friend".$j."wait"); - } - } - } - } - } - - function setVillageEvasion($vid) { - list($vid) = $this->escape_input($vid); - - $village = $this->getVillage((int) $vid); - if($village['evasion'] == 0){ - $q = "UPDATE " . TB_PREFIX . "vdata SET evasion = 1 WHERE wref = $vid"; - }else{ - $q = "UPDATE " . TB_PREFIX . "vdata SET evasion = 0 WHERE wref = $vid"; - } - return mysqli_query($this->dblink,$q); - } - - function addPrisoners($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) { - list($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) = $this->escape_input((int) $wid,(int) $from,(int) $t1,(int) $t2,(int) $t3,(int) $t4,(int) $t5,(int) $t6,(int) $t7,(int) $t8,(int) $t9,(int) $t10,(int) $t11); - - $q = "INSERT INTO " . TB_PREFIX . "prisoners values (0,$wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11)"; - mysqli_query($this->dblink,$q); - self::$prisonersCache = []; - return mysqli_insert_id($this->dblink); - } - - function updatePrisoners($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) { - list($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) = $this->escape_input((int) $wid,(int) $from,(int) $t1,(int) $t2,(int) $t3,(int) $t4,(int) $t5,(int) $t6,(int) $t7,(int) $t8,(int) $t9,(int) $t10,(int) $t11); - - $q = "UPDATE " . TB_PREFIX . "prisoners set t1 = t1 + $t1, t2 = t2 + $t2, t3 = t3 + $t3, t4 = t4 + $t4, t5 = t5 + $t5, t6 = t6 + $t6, t7 = t7 + $t7, t8 = t8 + $t8, t9 = t9 + $t9, t10 = t10 + $t10, t11 = t11 + $t11 where wref = $wid and ".TB_PREFIX."prisoners.from = $from"; - $res = mysqli_query($this->dblink,$q); - self::$prisonersCache = []; - return $res; - } - - /** - * Used to modify prisoners through the inserted id - * - * @param int $id The prisoner id where prisoners are in the database - * @param int $unit The type of the unit - * @param int $amount The amount of the unit you want to sum/subtract - * @param int $mode 0 for subtracting the inserted amount, 1 for adding it - * @return bool Returns false on failure and true on success - */ - - function modifyPrisoners($id, $units, $amount, $mode) { - list($id, $units, $amount, $mode) = $this->escape_input((int) $id, $units, $amount,(int) $mode); - - if (!is_array($units)) - { - $units = [$units]; - $amount = [$amount]; - } - - $prisoners = []; - foreach($units as $index => $unit) - { - $unit = 't'.$this->escape($unit); - $prisoners[] = $unit." = ".$unit.(!$mode ? " - " : " + ").(int)$amount[$index]; - } - - $q = "UPDATE " . TB_PREFIX . "prisoners set ".implode(', ', $prisoners)." WHERE id = $id"; - return mysqli_query($this->dblink,$q); - } - - function getPrisoners($wid, $mode = 0, $use_cache = true) { - $array_passed = is_array($wid); - $mode = (int) $mode; - - if (!$array_passed) { - $wid = [(int) $wid]; - } else { - foreach ($wid as $index => $widValue) { - $wid[$index] = (int) $widValue; - } - } - - if (!count($wid)) { - return []; - } - - // first of all, check if we should be using cache and whether the field - // required is already cached - if ($use_cache && !$array_passed && isset(self::$prisonersCache[$wid[0].$mode]) && is_array(self::$prisonersCache[$wid[0].$mode]) && !count(self::$prisonersCache[$wid[0].$mode])) { - return self::$prisonersCache[$wid[0].$mode]; - } else if ($use_cache && $array_passed) { - // check what we can return from cache - $newWIDs = []; - foreach ($wid as $key) { - if (!isset(self::$prisonersCache[$key.$mode])) { - $newWIDs [] = $key; - } - } - - // everything's cached, just return the cache - if (!count($newWIDs)) { - return self::$prisonersCache; - } else { - // update remaining IDs to select and cache - $wid = $newWIDs; - } - } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$prisonersCache, $wid[0].$mode)) && !is_null($cachedValue)) { - // special case when we have empty arrays cached for this cache only - return $cachedValue; - } - - if(!$mode) { - $q = "SELECT * FROM " . TB_PREFIX . "prisoners where wref IN(".implode(', ', $wid).")"; - }else { - $q = "SELECT * FROM " . TB_PREFIX . "prisoners where `from` IN(".implode(', ', $wid).")"; - } - $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); - - // return a single value - if (!$array_passed) { - if (count($result) == 1) { - $result = $result[0]; - } - self::$prisonersCache[$wid[0].$mode] = (count($result) ? [$result] : []); - } else { - if ($result && count($result)) { - foreach ($result as $record) { - $key = $record[($mode ? 'from' : 'wref')] . $mode; - if (!isset(self::$prisonersCache[$key])) { - self::$prisonersCache[$key] = []; - } - self::$prisonersCache[$key][] = $record; - } - } - - // check for any missing IDs and fill them in with blanks, - // since no prisoners were found for these villages - foreach ($wid as $key) { - if (!isset(self::$prisonersCache[$key.$mode])) { - self::$prisonersCache[$key.$mode] = []; - } - } -} - - return ($array_passed ? self::$prisonersCache : self::$prisonersCache[$wid[0].$mode]); - } - - function getPrisoners2($wid,$from, $use_cache = true) { - list($wid,$from) = $this->escape_input((int) $wid,(int) $from); - - // 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::$prisonersCacheByVillageAndFromIDs, $wid.$from)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "prisoners where wref = $wid and " . TB_PREFIX . "prisoners.from = $from"; - $result = mysqli_query($this->dblink,$q); - - self::$prisonersCacheByVillageAndFromIDs[$wid.$from] = $this->mysqli_fetch_all($result); - return self::$prisonersCacheByVillageAndFromIDs[$wid.$from]; - } - - function getPrisonersByID($id, $use_cache = true) { - list($id) = $this->escape_input((int) $id); - - // 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::$prisonersCacheByID, $id)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "prisoners where id = $id LIMIT 1"; - $result = mysqli_query($this->dblink,$q); - - self::$prisonersCacheByID[$id] = mysqli_fetch_array($result); - return self::$prisonersCacheByID[$id]; - } - - function getPrisoners3($from, $use_cache = true) { - list($from) = $this->escape_input((int) $from); - - // 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::$prisonersCacheByVillageAndFromIDs, $from)) && !is_null($cachedValue)) { - return $cachedValue; - } - - $q = "SELECT * FROM " . TB_PREFIX . "prisoners where " . TB_PREFIX . "prisoners.from = $from"; - $result = mysqli_query($this->dblink,$q); - - self::$prisonersCacheByVillageAndFromIDs[$from] = $this->mysqli_fetch_all($result); // FIX: scos $wid - return self::$prisonersCacheByVillageAndFromIDs[$from]; - } - - function deletePrisoners($id) { - if (!is_array($id)) { - $id = [$id]; - } - - foreach ($id as $index => $idValue) { - $id[$index] = (int) $idValue; - } - - $q = "DELETE FROM " . TB_PREFIX . "prisoners WHERE id IN(".implode(', ', $id).")"; - mysqli_query($this->dblink,$q); - - self::$prisonersCache = []; - } - - /***************************************** - Function to vacation mode - by advocaite - References: - *****************************************/ - - function setvacmode($uid, $days) { - // TODO: refactor vacation mode - list ($uid, $days) = $this->escape_input((int) $uid, (int) $days); - $days1 = 60 * 60 * 24 * $days; - $time = time() + $days1; - $q = "UPDATE " . TB_PREFIX . "users SET vac_mode = '1' , vac_time=" . $time . " WHERE id=" . $uid . ""; - $result = mysqli_query($this->dblink, $q); - return; - } - - function removevacationmode($uid){ - // TODO: refactor vacation mode - list ($uid) = $this->escape_input((int) $uid); - $q = "UPDATE " . TB_PREFIX . "users SET vac_mode = '0' , vac_time='0' WHERE id=" . $uid . ""; - $result = mysqli_query($this->dblink, $q); - return; - } - - function getvacmodexy($wref){ - // TODO: refactor vacation mode - list ($wref) = $this->escape_input((int) $wref); - $q = "SELECT id,oasistype,occupied FROM " . TB_PREFIX . "wdata where id = $wref"; - $result = mysqli_query($this->dblink, $q); - $dbarray = mysqli_fetch_array($result); - if ($dbarray['occupied'] != 0 && $dbarray['oasistype'] == 0) { - $q1 = "SELECT owner FROM " . TB_PREFIX . "vdata where wref = " . (int) $dbarray['id'] . ""; - $result1 = mysqli_query($this->dblink, $q1); - $dbarray1 = mysqli_fetch_array($result1); - if ($dbarray1['owner'] != 0) { - $q2 = "SELECT vac_mode,vac_time FROM " . TB_PREFIX . "users where id = " . (int) $dbarray1['owner'] . ""; - $result2 = mysqli_query($this->dblink, $q2); - $dbarray2 = mysqli_fetch_array($result2); - return $dbarray2['vac_mode'] == 1; - } - } - else - return - false; - } - - /***************************************** - Function to vacation mode - Remake & Refactor: Shadow - *****************************************/ - - function checkVacationRequirements(int $uid): array|bool{ - $uid = (int)$uid; - $now = time(); - $errors = []; - - // HELPERS : returns true if it finds rows - $exists = function(string $sql, array $params = []) { - $stmt = mysqli_prepare($this->dblink, $sql); - if ($params) { - $types = str_repeat('i', count($params)); - mysqli_stmt_bind_param($stmt, $types, ...$params); - } - mysqli_stmt_execute($stmt); - mysqli_stmt_store_result($stmt); - $found = mysqli_stmt_num_rows($stmt) > 0; - mysqli_stmt_close($stmt); - return $found; - }; - - // HELPERS : returns a single field - $fetchOne = function(string $sql, array $params = []) { - $stmt = mysqli_prepare($this->dblink, $sql); - if ($params) { - $types = str_repeat('i', count($params)); - mysqli_stmt_bind_param($stmt, $types, ...$params); - } - mysqli_stmt_execute($stmt); - $res = mysqli_stmt_get_result($stmt); - $row = mysqli_fetch_assoc($res); - mysqli_stmt_close($stmt); - return $row; - }; - - // 1. TROOPS MOVING - $sql = "SELECT 1 FROM ".TB_PREFIX."movement m - JOIN ".TB_PREFIX."vdata v ON v.wref = m.from - WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type=3 LIMIT 1"; - if ($exists($sql, [$uid, $now])) $errors[] = "TROOPS_MOVING"; - - // 2. INCOMING TROOPS - $sql = "SELECT 1 FROM ".TB_PREFIX."movement m - JOIN ".TB_PREFIX."vdata v ON v.wref = m.to - WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type IN (3,4) LIMIT 1"; - if ($exists($sql, [$uid, $now])) $errors[] = "INCOMING_TROOPS"; - - // 3. REINFORCEMENTS - $sql = "SELECT 1 FROM ".TB_PREFIX."enforcement e - WHERE (e.from IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner=?) - OR e.vref IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner=?)) - AND (e.u1+e.u2+e.u3+e.u4+e.u5+e.u6+e.u7+e.u8+e.u9+e.u10+ - e.u11+e.u12+e.u13+e.u14+e.u15+e.u16+e.u17+e.u18+e.u19+e.u20+ - e.u21+e.u22+e.u23+e.u24+e.u25+e.u26+e.u27+e.u28+e.u29+e.u30+ - e.u41+e.u42+e.u43+e.u44+e.u45+e.u46+e.u47+e.u48+e.u49+e.u50) > 0 - LIMIT 1"; - if ($exists($sql, [$uid, $uid])) $errors[] = "REINFORCEMENTS"; - - // 4. WONDER WORLD - $sql = "SELECT 1 FROM ".TB_PREFIX."fdata f - JOIN ".TB_PREFIX."vdata v ON v.wref=f.vref - WHERE v.owner=? AND f.f99t=40 LIMIT 1"; - if ($exists($sql, [$uid])) $errors[] = "WW"; - - // 5. ARTEFACTS - $sql = "SELECT 1 FROM ".TB_PREFIX."artefacts WHERE owner=? LIMIT 1"; - if ($exists($sql, [$uid])) $errors[] = "ARTEFACTS"; - - // 6 + 10. PROTECTION + ADMIN or MULTIHUNTER - $user = $fetchOne("SELECT protect, access FROM ".TB_PREFIX."users WHERE id=?", [$uid]); - if ($user) { - if ((int)$user['protect'] > $now) $errors[] = "PROTECTION"; - if (in_array((int)$user['access'], [8,9])) $errors[] = "NO_VACATION_ACCESS"; - } - - // 7. PRISONERS IN & OUT (Enemy troops trapped in your villages & Your troops trapped in enemy villages) - $sqlIn = "SELECT 1 FROM ".TB_PREFIX."prisoners p - JOIN ".TB_PREFIX."vdata v ON v.wref=p.wref - WHERE v.owner=? LIMIT 1"; - if ($exists($sqlIn, [$uid])) $errors[] = "PRISONERS_IN"; - - $sqlOut = "SELECT 1 FROM ".TB_PREFIX."prisoners p - JOIN ".TB_PREFIX."vdata v ON v.wref=p.from - WHERE v.owner=? LIMIT 1"; - if ($exists($sqlOut, [$uid])) $errors[] = "PRISONERS_OUT"; - - // 8. MARKETPLACE MOVING - $sql = "SELECT 1 FROM ".TB_PREFIX."movement m - JOIN ".TB_PREFIX."vdata v ON v.wref=m.from - WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type=0 LIMIT 1"; - if ($exists($sql, [$uid, $now])) $errors[] = "MARKET"; - - // 9. ACCOUNT DELETION - $del = $fetchOne("SELECT timestamp FROM ".TB_PREFIX."deleting WHERE uid=?", [$uid]); - if (!empty($del['timestamp']) && $del['timestamp'] > $now) { - $errors[] = "ACCOUNT_DELETION"; - } - return empty($errors) ? true : $errors; - } - - // no need to cache this method - function getHeroDeadReviveOrInTraining($id) { - $id = (int) $id; - - $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "hero WHERE `uid` = $id AND dead = 0 AND inrevive = 0 AND intraining = 0"; - $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); - return $result['Total'] > 0; - } - - /*************************** - Function to Kill hero if not found - Made by: Shadow and brainiacX - ***************************/ - function KillMyHero($id) { - list( $id ) = $this->escape_input( (int) $id ); - - $q = "UPDATE " . TB_PREFIX . "hero set dead = 1, intraining = 0, inrevive = 0, health = 0 where uid = " . $id . " AND dead = 0"; - return mysqli_query( $this->dblink, $q ); - } - - /*************************** - Function to find Hero place - Made by: ronix - ***************************/ - // no need to cache this method - function FindHeroInVil($wid) { - list($wid) = $this->escape_input($wid); - - $result = $this->query("SELECT hero FROM ".TB_PREFIX."units WHERE hero>0 AND vref='".$wid."' LIMIT 1"); - if (!empty($result)) { - $dbarray = mysqli_fetch_array($result); - if(isset($dbarray['hero'])) { - $this->query("UPDATE ".TB_PREFIX."units SET hero=0 WHERE vref='".$wid."'"); - unset($dbarray); - return true; - } - } - return false; - } - - // no need to cache this method - function FindHeroInDef($wid) { - list($wid) = $this->escape_input($wid); - - $delDef=true; - $result = $this->query_return("SELECT * FROM ".TB_PREFIX."enforcement WHERE hero>0 AND `from` = ".$wid); - if (!empty($result)) { - $dbarray = $result; - if(isset($dbarray['hero'])) { - $this->query("UPDATE ".TB_PREFIX."enforcement SET hero=0 WHERE `from` = ".$wid); - for ($i=0;$i<50;$i++) { - if($dbarray['u'.$i]>0) { - $delDef=false; - break; - } - } - if ($delDef) $this->deleteReinf($wid); - unset($dbarray); - return true; - } - } - return false; - } - - // no need to cache this method - function FindHeroInOasis($uid) { - list($uid) = $this->escape_input($uid); - - $delDef=true; - $dbarray = $this->query_return("SELECT e.*,o.conqured,o.owner FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.owner=".$uid." AND e.hero>0"); - if(!empty($dbarray)) { - foreach($dbarray as $defoasis) { - if($defoasis['hero']>0) { - $this->query("UPDATE ".TB_PREFIX."enforcement SET hero=0 WHERE `from` = ".$defoasis['from']); - for ($i=0;$i<50;$i++) { - if($dbarray['u'.$i]>0) { - $delDef=false; - break; - } - } - if ($delDef) $this->deleteReinf($defoasis['from']); - unset($dbarray); - return true; - } - } - } - return 0; - } - - // no need to cache this method - function FindHeroInMovement($wid) { - list($wid) = $this->escape_input($wid); - - $outgoingarray = $this->getMovement(3, $wid, 0); - if(!empty($outgoingarray)) { - foreach($outgoingarray as $out) { - if ($out['t11']>0) { - $dbarray = $this->query("UPDATE ".TB_PREFIX."attacks SET t11=0 WHERE `id` = ".$out['ref']); - return true; - break; - } - } - } - $returningarray = $this->getMovement(4, $wid, 1); - if(!empty($returningarray)) { - foreach($returningarray as $ret) { - if($ret['attack_type'] != 1 && $ret['t11']>0) { - $dbarray = $this->query("UPDATE ".TB_PREFIX."attacks SET t11=0 WHERE `id` = ".$ret['ref']); - return true; - break; - } - } - } - return false; - } - - /** - * Register the hero to the capital village and kills it - * - * @param int $wref The village ID where the hero is registered - * @return bool Return true if the query was successful, false otherwise - */ - - function reassignHero($wref){ - list($wref) = $this->escape_input($wref); - - $q = "UPDATE - ".TB_PREFIX."hero AS hero - INNER JOIN ".TB_PREFIX."vdata AS vdata - ON vdata.owner = hero.uid AND vdata.capital = 1 - SET - hero.dead = 1, hero.health = 0, hero.wref = vdata.wref - WHERE - hero.wref = $wref"; - return mysqli_query($this->dblink, $q); - } - - public function getMaintenance() { - $q = "SELECT * FROM ".TB_PREFIX."maintenance WHERE id=1 LIMIT 1"; - $res = $this->query_return($q); - return $res[0]?? ['active'=>0]; - } - public function setMaintenance($active, $uid=0) { - $time = time(); - $active = (int)$active; - $uid = (int)$uid; - // REPLACE creează rândul dacă nu există - return $this->query("REPLACE INTO ".TB_PREFIX."maintenance - (id, active, started_by, started_at) - VALUES (1, $active, $uid, $time)"); - } - - /** - * Debug error-log mode (admin-controlled, transparent to players). - * Returns the single config row, falling back to safe defaults when the - * table does not exist yet (so deploying the code before creating the table - * never produces a blank page). - */ - public function getDebugMode() { - $default = [ - 'active' => 0, - 'lvl_warning' => 1, - 'lvl_notice' => 1, - 'lvl_deprecated' => 1, - 'lvl_fatal' => 1, - 'max_size_mb' => 5, - 'auto_off_hours' => 6, - 'started_by' => null, - 'started_at' => null, - ]; - try { - $res = @mysqli_query($this->dblink, "SELECT * FROM ".TB_PREFIX."debug_log WHERE id=1 LIMIT 1"); - if (!$res) { - return $default; - } - $row = mysqli_fetch_assoc($res); - return $row ?: $default; - } catch (\Throwable $e) { - return $default; - } -} - - /** - * Toggle the debug mode on/off (stamps who/when on activation). - */ - public function setDebugMode($active, $uid = 0) { - $active = (int)$active; - $uid = (int)$uid; - $time = time(); - return $this->query("UPDATE ".TB_PREFIX."debug_log - SET active = $active, started_by = $uid, started_at = $time - WHERE id = 1"); - } - - /** - * Persist the debug capture parameters (levels, size cap, auto-off window). - */ - public function setDebugSettings($warning, $notice, $deprecated, $fatal, $maxSizeMb, $autoOffHours) { - $warning = $warning ? 1 : 0; - $notice = $notice ? 1 : 0; - $deprecated = $deprecated ? 1 : 0; - $fatal = $fatal ? 1 : 0; - $maxSizeMb = max(1, (int)$maxSizeMb); - $autoOffHours = max(0, (int)$autoOffHours); - return $this->query("UPDATE ".TB_PREFIX."debug_log - SET lvl_warning = $warning, lvl_notice = $notice, lvl_deprecated = $deprecated, - lvl_fatal = $fatal, max_size_mb = $maxSizeMb, auto_off_hours = $autoOffHours - WHERE id = 1"); -} - - - /** - * Changed the actual capital with a new one - * - * @param int $wref The village ID that will became the new capital - * @return bool Return true if the query was successful, false otherwise - */ - - function changeCapital($wref, $mode = 1){ - list($wref, $mode) = $this->escape_input($wref, $mode); - - if ($mode == 1) { - // Bug fix: this function only ever did `SET capital = $mode WHERE - // wref = $wref` — it set the NEW capital's flag but never cleared - // any OTHER village belonging to the same owner that was already - // flagged capital = 1. Nothing enforces uniqueness at the schema - // level (no UNIQUE key on owner+capital), so after any capital - // change that doesn't delete the old village, the owner was left - // with two (or more) rows with capital = 1. This was harmless for - // years because nothing queried "owner=X AND capital=1" expecting - // a single row — until getVillage(..., 3) (added for the Brewery - // Mead-Festival empire-wide bonus lookup) started relying on that - // invariant, at which point a stale duplicate could be the row - // returned (no ORDER BY / LIMIT 1 on an ambiguous match), silently - // pointing Brewery's bonus/penalty checks at the wrong village. - $owner = $this->getVillageField($wref, 'owner'); - if ($owner !== false && $owner !== null) { - $owner = (int) $owner; - $q = "UPDATE " . TB_PREFIX . "vdata SET capital = 0 WHERE owner = $owner AND wref != $wref"; - mysqli_query($this->dblink, $q); - } - } - - $q = "UPDATE ".TB_PREFIX."vdata SET capital = ".$mode." WHERE wref = $wref"; - return mysqli_query($this->dblink, $q); - } }; diff --git a/GameEngine/Database/DatabaseAllianceQueries.php b/GameEngine/Database/DatabaseAllianceQueries.php new file mode 100644 index 00000000..846491c2 --- /dev/null +++ b/GameEngine/Database/DatabaseAllianceQueries.php @@ -0,0 +1,857 @@ +getAlliance($id, $use_cache); + if (!is_array($alliance) || !isset($alliance['tag'])) { + return null; + } + return $alliance['tag']; + } + + // no need to cache this method + + function getAlliancePermission($uid, $field, $alliance) { + $uid = (int)$uid; + $alliance = (int)$alliance; + + // whitelist câmpuri permise + $allowed_fields = ['ap1','ap2','ap3','ap4','ap5','ap6','ap7','ap8','ap9','ap10','owner','admin','rank']; + + if (!in_array($field, $allowed_fields)) { + error_log("Invalid field in getAlliancePermission: $field"); + return false; + } + + $q = "SELECT `$field` FROM " . TB_PREFIX . "ali_permission WHERE uid = $uid AND alliance = $alliance LIMIT 1"; + + $result = mysqli_query($this->dblink, $q); + + if (!$result) { + error_log("SQL Error in getAlliancePermission: " . mysqli_error($this->dblink) . " | Query: $q"); + return false; + } + + if (mysqli_num_rows($result) == 0) { + return false; + } + + $row = mysqli_fetch_assoc($result); + + return $row[$field]; + } + + function getAlliance($id, $use_cache = true) { + $id = (int) $id; + + // 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::$allianceDataCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * from " . TB_PREFIX . "alidata where id = $id"; + $result = mysqli_query($this->dblink,$q); + + self::$allianceDataCache[$id] = mysqli_fetch_assoc($result); + return self::$allianceDataCache[$id]; + } + + function setAlliName($aid, $name, $tag) { + list($aid, $name, $tag) = $this->escape_input((int) $aid, $name, $tag); + $name = $this->RemoveXSS($name); + $tag = $this->RemoveXSS($tag); + + $q = "UPDATE " . TB_PREFIX . "alidata set name = '$name', tag = '$tag' where id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function isAllianceOwner($id, $use_cache = true) { + $id = (int) $id; + + // 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::$allianceOwnerCheckCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT id from " . TB_PREFIX . "alidata where leader = ". $id; + $result = mysqli_query($this->dblink,$q); + if(mysqli_num_rows($result)) { + $result = mysqli_fetch_assoc($result); + $result = $result['id']; + } + else $result = false; + + self::$allianceOwnerCheckCache[$id] = $result; + return self::$allianceOwnerCheckCache[$id]; + } + + function countAllianceMembers($aid, $use_cache = true) { + $aid = (int) $aid; + + // 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::$allianceMembersCountCache, $aid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT Count(*) as Total from ".TB_PREFIX."users WHERE alliance = ".$aid; + $membersCount = $this->query_return($q); + + self::$allianceMembersCountCache[$aid] = $membersCount[0]['Total']; + return self::$allianceMembersCountCache[$aid]; + } + + // no need to cache this method + function aExist($ref, $type) { + list($ref, $type) = $this->escape_input($ref, $type); + + $q = "SELECT $type FROM " . TB_PREFIX . "alidata where $type = '$ref'"; + $result = mysqli_query($this->dblink,$q); + return mysqli_num_rows($result); + } + + /***************************************** + Function to create an alliance + References: + *****************************************/ + function createAlliance($tag, $name, $uid, $max) { + list($tag, $name, $uid, $max) = $this->escape_input($tag, $name, (int) $uid, (int) $max); + $tag = $this->RemoveXSS($tag); + $name = $this->RemoveXSS($name); + + $q = "INSERT into " . TB_PREFIX . "alidata values (0,'$name','$tag',$uid,0,0,0,'','',$max,0,0,0,0,0,0,0,0,0)"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + function procAllyPop($aid) { + list($aid) = $this->escape_input($aid); + + $ally = $this->getAlliance($aid); + $memberlist = $this->getAllMember($ally['id']); + $oldrank = 0; + $memberIDs = []; + + foreach($memberlist as $member) { + $memberIDs[] = $member['id']; + } + + $data = $this->getVSumField($memberIDs,"pop"); + + if (count($data)) { + foreach ($data as $row) { + $oldrank += $row['Total']; + } + } + + if($ally['oldrank'] != $oldrank){ + if($ally['oldrank'] < $oldrank) { + $totalpoints = $oldrank - $ally['oldrank']; + $this->addclimberrankpopAlly($ally['id'], $totalpoints); + $this->updateoldrankAlly($ally['id'], $oldrank); + } else + if($ally['oldrank'] > $oldrank) { + $totalpoints = $ally['oldrank'] - $oldrank; + $this->removeclimberrankpopAlly($ally['id'], $totalpoints); + $this->updateoldrankAlly($ally['id'], $oldrank); + } + } + } + + /***************************************** + Function to insert an alliance new + References: + *****************************************/ + function insertAlliNotice($aid, $notice) { + list($aid, $notice) = $this->escape_input($aid, $notice); + + $time = time(); + $q = "INSERT into " . TB_PREFIX . "ali_log values (0,'$aid','$notice',$time)"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + /***************************************** + Function to delete alliance if empty + References: + *****************************************/ + function deleteAlliance($aid) { + list($aid) = $this->escape_input((int) $aid); + + $result = mysqli_fetch_array(mysqli_query($this->dblink,"SELECT Count(*) as Total FROM " . TB_PREFIX . "users where alliance = $aid"), MYSQLI_ASSOC); + if ($result['Total'] == 0) { + // remove the alliance + $q = "DELETE FROM " . TB_PREFIX . "alidata WHERE id = $aid"; + mysqli_query($this->dblink,$q); + + // remove all permissions for that alliance + $q = "DELETE FROM " . TB_PREFIX . "ali_permission WHERE alliance = $aid"; + mysqli_query($this->dblink,$q); + + // remove all logs for that alliance + $q = "DELETE FROM " . TB_PREFIX . "ali_log WHERE aid = $aid"; + mysqli_query($this->dblink,$q); + + // remove all medals for that alliance + $q = "DELETE FROM " . TB_PREFIX . "allimedal WHERE allyid = $aid"; + mysqli_query($this->dblink,$q); + + // remove all invitations for that alliance + $q = "DELETE FROM " . TB_PREFIX . "ali_invite WHERE alliance = $aid"; + mysqli_query($this->dblink,$q); + } + } + + /***************************************** + Function to read all alliance news + References: + *****************************************/ + function readAlliNotice($aid) { + list($aid) = $this->escape_input((int) $aid); + + $q = "SELECT * from " . TB_PREFIX . "ali_log where aid = $aid ORDER BY date DESC"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + /***************************************** + Function to create alliance permissions + References: ID, notice, description + *****************************************/ + function createAlliPermissions($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8) { + list($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8) = $this->escape_input($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7, $opt8); + + + $q = "INSERT into " . TB_PREFIX . "ali_permission values(0,'$uid','$aid','$rank','$opt1','$opt2','$opt3','$opt4','$opt5','$opt6','$opt7','$opt8')"; + mysqli_query($this->dblink,$q); + + // update cache + $insertID = mysqli_insert_id($this->dblink); + self::$alliancePermissionsCache[$uid.$aid] = [ + 'id' => $insertID, + 'uid' => $uid, + 'alliance' => $aid, + 'rank' => $rank, + 'opt1' => $opt1, + 'opt2' => $opt2, + 'opt3' => $opt3, + 'opt4' => $opt4, + 'opt5' => $opt5, + 'opt6' => $opt6, + 'opt7' => $opt7, + 'opt8' => $opt8 + ]; + + return $insertID; + } + + /***************************************** + Function to update alliance permissions + References: + *****************************************/ + function deleteAlliPermissions($uid) { + list($uid) = $this->escape_input($uid); + + $q = "DELETE from " . TB_PREFIX . "ali_permission where uid = '$uid'"; + return mysqli_query($this->dblink,$q); + } + /***************************************** + Function to update alliance permissions + References: + *****************************************/ + + function updateAlliPermissions($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7){ + list($uid, $aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7) = $this->escape_input((int)$uid, (int)$aid, $rank, $opt1, $opt2, $opt3, $opt4, $opt5, $opt6, $opt7); + // update cache + if (isset(self::$alliancePermissionsCache[$uid.$aid])) { + self::$alliancePermissionsCache[ $uid . $aid ]['rank'] = $rank; + self::$alliancePermissionsCache[ $uid . $aid ]['opt1'] = $opt1; + self::$alliancePermissionsCache[ $uid . $aid ]['opt2'] = $opt2; + self::$alliancePermissionsCache[ $uid . $aid ]['opt3'] = $opt3; + self::$alliancePermissionsCache[ $uid . $aid ]['opt4'] = $opt4; + self::$alliancePermissionsCache[ $uid . $aid ]['opt5'] = $opt5; + self::$alliancePermissionsCache[ $uid . $aid ]['opt6'] = $opt6; + self::$alliancePermissionsCache[ $uid . $aid ]['opt7'] = $opt7; + self::$alliancePermissionsCache[ $uid . $aid ]['opt8'] = $opt8; + } + $q = "UPDATE " . TB_PREFIX . "ali_permission SET `rank` = '$rank',opt1 = '$opt1', opt2 = '$opt2', opt3 = '$opt3', opt4 = '$opt4', opt5 = '$opt5', opt6 = '$opt6', opt7 = '$opt7' WHERE uid = $uid AND alliance = $aid LIMIT 1"; + $result = mysqli_query($this->dblink, $q); + if(!$result) { + die("SQL ERROR: " . mysqli_error($this->dblink) . "

" . $q); + } + return true; + } + + /***************************************** + Function to read alliance permissions + References: ID, notice, description + *****************************************/ + function getAlliPermissions($uid, $aid, $use_cache = true) { + list($uid, $aid) = $this->escape_input((int) $uid, (int) $aid); + + // 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::$alliancePermissionsCache, $uid.$aid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "ali_permission where uid = $uid && alliance = $aid"; + $result = mysqli_query($this->dblink,$q); + + self::$alliancePermissionsCache[$uid.$aid] = mysqli_fetch_assoc($result); + return self::$alliancePermissionsCache[$uid.$aid]; + } + + /***************************************** + Function to update an alliance description and notice + References: ID, notice, description + *****************************************/ + function submitAlliProfile($aid, $notice, $desc) { + list($aid, $notice, $desc) = $this->escape_input((int) $aid, $notice, $desc); + + + $q = "UPDATE " . TB_PREFIX . "alidata SET `notice` = '$notice', `desc` = '$desc' where id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function diplomacyInviteAdd($alli1, $alli2, $type) { + list($alli1, $alli2, $type) = $this->escape_input((int) $alli1, (int) $alli2, $type); + + $q = "INSERT INTO " . TB_PREFIX . "diplomacy (alli1,alli2,type,accepted) VALUES ($alli1,$alli2," . (int)intval($type) . ",0)"; + return mysqli_query($this->dblink,$q); + } + + function diplomacyOwnOffers($sessionAlliance) { + list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); + + $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE alli1 = $sessionAlliance AND accepted = 0"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getAllianceID($name) { + list($name) = $this->escape_input($name); + + $q = "SELECT id FROM " . TB_PREFIX . "alidata WHERE tag ='" . $this->RemoveXSS($name) . "' LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['id']; + } + + function diplomacyCancelOffer($id, $sessionAlliance) { + list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); + + $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE id = $id AND alli1 = $sessionAlliance"; + return mysqli_query($this->dblink,$q); + } + + function diplomacyInviteAccept($id, $sessionAlliance) { + list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); + + $q = "UPDATE " . TB_PREFIX . "diplomacy SET accepted = 1 WHERE id = $id AND alli2 = $sessionAlliance"; + return mysqli_query($this->dblink,$q); + } + + function diplomacyInviteDenied($id, $sessionAlliance) { + list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); + + $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE id = $id AND alli2 = $sessionAlliance"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function diplomacyInviteCheck($sessionAlliance) { + list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); + + $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE alli2 = $sessionAlliance AND accepted = 0"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function diplomacyInviteCheck2($ally1, $ally2) { + list($ally1, $ally2) = $this->escape_input((int) $ally1, (int) $ally2); + + $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $ally1 OR alli2 = $ally1) AND (alli1 = $ally2 OR alli2 = $ally2)"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getAllianceDipProfile($aid, $type) { + list($aid, $type) = $this->escape_input($aid, $type); + $q = "SELECT alli1, alli2 FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '$type' AND accepted = '1' OR alli2 = '$aid' AND type = '$type' AND accepted = '1'"; + $array = $this->query_return($q); + $text = ""; + + if($array){ + foreach($array as $row){ + if($row['alli1'] == $aid) $alliance = $this->getAlliance($row['alli2']); + elseif($row['alli2'] == $aid) $alliance = $this->getAlliance($row['alli1']); + $text .= ""; + $text .= "" . $alliance['tag'] . "
"; + } + } + if(strlen($text) == 0){ + $text = "-
"; + } + return $text; + } + + // no need to cache this method + function getAllianceWar($aid) { + list($aid) = $this->escape_input($aid); + $q = "SELECT alli1, alli2 FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '3' OR alli2 = '$aid' AND type = '3' AND accepted = '1'"; + $array = $this->query_return($q); + $text = ""; + + if ($array) { + foreach($array as $row){ + if($row['alli1'] == $aid){ + $alliance = $this->getAlliance($row['alli2']); + }elseif($row['alli2'] == $aid){ + $alliance = $this->getAlliance($row['alli1']); + } + $text .= ""; + $text .= "".$alliance['tag']."
"; + } + } + if(strlen($text) == 0){ + $text = "-
"; + } + return $text; + } + + function getAllianceAlly($aid, $type, $use_cache = true) { + list($aid, $type) = $this->escape_input($aid, $type); + + // 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::$allianceAlliesCache, $aid.$type)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM ".TB_PREFIX."diplomacy WHERE (alli1 = '$aid' or alli2 = '$aid') AND (type = '$type' AND accepted = '1')"; + $result = mysqli_query($this->dblink,$q); + + self::$allianceAlliesCache[$aid.$type] = $this->mysqli_fetch_all($result); + return self::$allianceAlliesCache[$aid.$type]; + } + + // no need to cache this method + function getAllianceWar2($aid) { + list($aid) = $this->escape_input($aid); + $q = "SELECT * FROM ".TB_PREFIX."diplomacy WHERE alli1 = '$aid' AND type = '3' OR alli2 = '$aid' AND type = '3' AND accepted = '1'"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function diplomacyExistingRelationships($sessionAlliance) { + list($sessionAlliance) = $this->escape_input((int) $sessionAlliance); + + $q = "SELECT * FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $sessionAlliance OR alli2 = $sessionAlliance) AND accepted = 1"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function diplomacyCancelExistingRelationship($id, $sessionAlliance) { + list($id, $sessionAlliance) = $this->escape_input((int) $id, (int) $sessionAlliance); + + $q = "DELETE FROM " . TB_PREFIX . "diplomacy WHERE (alli1 = $sessionAlliance OR alli2 = $sessionAlliance) AND id = $id "; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function diplomacyCheckLimits($aid, $type) { + list($aid, $type) = $this->escape_input((int) $aid, (int) $type); + if($type == 3) return true; + + $q = "SELECT Count(case when alli1 = $aid then 1 end) as Total1, Count(case when alli2 = $aid then 1 end) as Total2 FROM " . TB_PREFIX . "diplomacy WHERE type = $type"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + return $result['Total1'] < 3 && $result['Total2'] < 3; + } + + function getUserAlliance($id, $use_cache = true) { + list($id) = $this->escape_input((int) $id); + + // 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::$userAllianceCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT " . TB_PREFIX . "alidata.tag from " . TB_PREFIX . "users join " . TB_PREFIX . "alidata where " . TB_PREFIX . "users.alliance = " . TB_PREFIX . "alidata.id and " . TB_PREFIX . "users.id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + if($dbarray['tag'] == "") { + self::$userAllianceCache[$id] = "-"; + } else { + self::$userAllianceCache[$id] = $dbarray['tag']; + } + + return self::$userAllianceCache[$id]; + } + + // no need to cache this method + function getInvitation($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where uid = $uid"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getInvitation2($uid, $aid) { + list($uid, $aid) = $this->escape_input((int) $uid, (int) $aid); + + $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where uid = $uid and alliance = $aid"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getAliInvitations($aid) { + list($aid) = $this->escape_input((int) $aid); + + $q = "SELECT * FROM " . TB_PREFIX . "ali_invite where alliance = $aid && accept = 0"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function sendInvitation($uid, $alli, $sender) { + list($uid, $alli, $sender) = $this->escape_input((int) $uid, (int) $alli, (int) $sender); + + $time = time(); + $q = "INSERT INTO " . TB_PREFIX . "ali_invite values (0,$uid,$alli,$sender,$time,0)"; + return mysqli_query($this->dblink,$q); + } + + function removeInvitation($id) { + list($id) = $this->escape_input((int) $id); + + $q = "DELETE FROM " . TB_PREFIX . "ali_invite where id = $id"; + return mysqli_query($this->dblink,$q); + } + + /** + * Returns a minimum level for an Embassy in order to accomodate + * the given number of members. + * + * @param int $membersCount Number of members for an alliance to accomodate. + * Maximum = 60 + * + * @return number Returns the level of Embassy required to accomodate + * the given number of members. + */ + public function getMinEmbassyLevel($membersCount) { + $membersCount = (int) $membersCount; + + if ($membersCount > 60) { + $membersCount = 60; + } + + if ($membersCount < 0) { + $membersCount = 0; + } + + return ceil((20 / 60) * $membersCount); + } + + /*** + * Returns the number of members an alliance can hold + * with the current level of leader's Embassy. + * + * @param int $embassyLevel Level of leader's Embassy building. + * + * @return number Returns the number of members an alliance + * can hold with the current level of leader's Embassy. + */ + public function getAllianceCapacity($embassyLevel) { + $embassyLevel = (int) $embassyLevel; + + if ($embassyLevel > 20) { + $embassyLevel = 20; + } + + if ($embassyLevel < 0) { + $embassyLevel = 0; + } + + // ceil is not really necessary but to make sure + // decimals won't crack this up, it's here + return ceil((60 / 20) * $embassyLevel); + } + + /** + * Checks and potentially updates the status of a player-alliance + * relationship given the user input. + * + * @param array $userData Data of the user for which we want to check + * the player-alliance relationship. + * @param boolean $demolition Determines whether the request came from + * a buiding demolition (true) or from a battle + * report (false). + * + * @return boolean Returns TRUE if there was no change + * to the player-alliance relationship + * FALSE if the player was an alliance + * leader and the alliance was destroyed + * and 0 when the player was evicted from + * the alliance due to Embassy damage. + */ + 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; + } + + $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); + } + } + + // === METODE NOI EXTRAS DIN checkAllianceEmbassiesStatus === + + 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ță + } + + // nu mai are ambasadă - evict + $this->evictUserFromAlliance($userData['id']); + $this->deleteAlliPermissions($userData['id']); + + $msgTitle = $demolition ? rc_tok('MSG_LEFT_ALLIANCE_TITLE') : rc_tok('MSG_FORCED_LEAVE_TITLE'); + $msgBody = $demolition + ? rc_tok('MSG_LEFT_DEMOLITION_BODY', $userData['username']) + : rc_tok('MSG_LEFT_ATTACK_BODY', $userData['username']); + + $this->sendMessage($userData['id'], 4, $msgTitle, $this->escape($msgBody), 0,0,0,0,0,true); + + return $demolition ? true : 0; + } + + 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 + + $hasSufficientEmbassy = $this->getSingleFieldTypeCount($userData['id'], 18, '>=', $minLevel, false, $use_cache) >= 1; + $needsAction = ($userData['lvl'] <= $minLevel) && !$hasSufficientEmbassy; + + if (!$needsAction) { + return true; + } + + $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); + } + } + + 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ță + } + + private function disbandAllianceOnDemolition(array $ownerData, array $members): bool { + $evicts = []; + foreach ($members as $member) { + $evicts[] = $member['id']; + $isOwner = ($member['id'] == $ownerData['id']); + $title = rc_tok('MSG_DISBAND_TITLE'); + $body = $isOwner + ? rc_tok('MSG_DISBAND_OWNER_BODY', $ownerData['username']) + : rc_tok('MSG_DISBAND_MEMBER_BODY', $member['username']); + + $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; + } + + 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; + } + } + } + + 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 = rc_tok('MSG_PROMOTE_BODY', $newLeaderName, $oldLeaderId, $oldLeaderData['username']); + $title = rc_tok('MSG_NOW_ALLIANCE_LEADER_TITLE'); + } else { + $msg = $customMessage; + $title = rc_tok('MSG_NOW_LEADER_TITLE'); + } + $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 = rc_tok('MSG_DISPERSE_TITLE'); + if ($isOwner) { + $body = ($membersCount > 1) + ? rc_tok('MSG_DISPERSE_OWNER_BODY_MANY', $ownerData['username'], $membersCount) + : rc_tok('MSG_DISPERSE_OWNER_BODY_FEW', $ownerData['username']); + } else { + $body = rc_tok('MSG_DISPERSE_MEMBER_BODY', $m['username'], $membersCount); + } + $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 = rc_tok('MSG_NEW_LEADER_TITLE'); + $body = $isOwner + ? rc_tok('MSG_NEWLEADER_OWNER_BODY', $ownerData['username'], count($members), $newLeaderId) + : rc_tok('MSG_NEWLEADER_MEMBER_BODY', $m['username'], $newLeaderId); + $this->sendMessage($m['id'], 4, $title, $this->escape($body), 0,0,0,0,0,true); + } + + if (!$keepOwner) { + $this->evictUserFromAlliance($ownerData['id']); + $msg = rc_tok('MSG_FORCED_LEAVE_BODY', $ownerData['username'], $newLeaderId); + $this->sendMessage($ownerData['id'], 4, rc_tok('MSG_FORCED_LEAVE_TITLE'), $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); + + Automation::updateMax($this->getVillageField($vid,"owner")); + $allianceStatus = $this->checkAllianceEmbassiesStatus([ + 'id' => $userData['id'], + 'alliance' => $userData["alliance"], + 'username' => $userData["username"], + 'lvl' => $current_level + ], false, $use_cache); + + if ($allianceStatus === false) return ' ' . rc_tok('MSG_ALLIANCE_DISPERSED_STATUS'); + else if ($allianceStatus === 0) return ' ' . rc_tok('MSG_FORCED_LEAVE_STATUS'); + else return ''; // all is good, no need to append additional alliance-related text + } + + function getAllMember($aid, $limit = 0, $use_cache = true) { + list($aid) = $this->escape_input((int) $aid); + + // 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::$allianceMembersCache, $aid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "users where alliance = $aid order by (SELECT sum(pop) FROM " . TB_PREFIX . "vdata WHERE owner = " . TB_PREFIX . "users.id) desc, " . TB_PREFIX . "users.id desc".($limit > 0 ? ' LIMIT '.(int) $limit : ''); + $result = mysqli_query($this->dblink,$q); + + self::$allianceMembersCache[$aid] = $this->mysqli_fetch_all($result); + return self::$allianceMembersCache[$aid]; + } + + function getAllMember2($aid) { + return $this->getAllMember($aid, 1); + } + + ################# -START- ################## + ## WORLD WONDER STATISTICS FUNCTIONS! ## + ############################################ + + /*************************** + Function to get user alliance name! + Made by: Dzoki + ***************************/ + + // no need to cache this method + function getUserAllianceID($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT alliance FROM " . TB_PREFIX . "users where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['alliance']; + } +} diff --git a/GameEngine/Database/DatabaseArtefactQueries.php b/GameEngine/Database/DatabaseArtefactQueries.php new file mode 100644 index 00000000..9a38ac79 --- /dev/null +++ b/GameEngine/Database/DatabaseArtefactQueries.php @@ -0,0 +1,582 @@ +escape_input((int) $vref); + + $q = "SELECT wwname FROM " . TB_PREFIX . "fdata WHERE vref = $vref LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['wwname']; + } + + /*************************** + Function to change WW name + Made by: Dzoki + ***************************/ + + function submitWWname($vref, $name) { + list($vref, $name) = $this->escape_input((int) $vref, $name); + + $q = "UPDATE " . TB_PREFIX . "fdata SET `wwname` = '$name' WHERE " . TB_PREFIX . "fdata.`vref` = $vref"; + return mysqli_query($this->dblink,$q); + } + + + /** + * Calculates how much artifacts affect troops speed, cranny efficency, etc. + * + * @param int $uid The User ID + * @param int $vid The village ID + * @param int $kind The kind of the artifact + * @param float $multiplicand The value which needs to be multiplied + * @return int Returns the new value, multiplied or divided by artifacts bonus or malus + */ + + function getArtifactsValueInfluence($uid, $vid, $kind, $multiplicand, $round = true){ + list($uid, $vid, $kind, $multiplicand, $round) = $this->escape_input((int) $uid,(int) $vid, $kind, $multiplicand, $round); + + $artefacts = $foolArefacts = []; + $multipliers = [1 => [4, 5, 3], 2 => [1/2, 1/3, 2/3], 3 => [5, 10, 3], 4 => [1/2, 1/2, 3/4], 5 => [1/2, 1/2, 3/4], 7 => [3, 6, 2]]; + + $artefacts[] = count($this->getOwnUniqueArtefactInfo2($vid, $kind, 1, 1)); //Village effect + $artefacts[] = count($this->getOwnUniqueArtefactInfo2($uid, $kind, 3, 0)); //Unique effect + $artefacts[] = count($this->getOwnUniqueArtefactInfo2($uid, $kind, 2, 0)); //Account effect + + $multiplier = 1; + for($i = 0; $i < count($artefacts); $i++) + { + if($artefacts[$i] > 0) { + $multiplier = $multipliers[$kind][$i]; + break; + } + } + + $foolArefacts[] = $this->getOwnUniqueArtefactInfo2($vid, 8, 1, 1); //Village effect + $foolArefacts[] = $this->getOwnUniqueArtefactInfo2($uid, 8, 3, 0); //Unique effect + + $foolEffect = 1; + for($i = 0; $i < count($foolArefacts); $i++) + { + if(count($foolArefacts[$i]) > 0 && $foolArefacts[$i]['kind'] == $kind) + { + $foolEffect = $foolArefacts[$i]['bad_effect'] == 1 ? 1 / $foolArefacts[$i]['effect2'] : $foolArefacts[$i]['effect2']; + break; + } + } + + if(in_array($kind, [2, 4, 5])) $foolEffect = 1 / $foolEffect; + + return !$round ? $multiplicand * $multiplier * $foolEffect : round($multiplicand * $multiplier * $foolEffect); + } + + /** + * Get the total artifacts sum, divided by small, great and unique, by kind + * + * @param int $uid The User ID + * @param int $vid The Village ID + * @param int $kind The kind of the artifact + * @return array Returns the total artifacts sum divided by size + */ + + function getArtifactsSumByKind($uid, $vid, $kind){ + list($uid, $vid, $kind) = $this->escape_input((int) $uid, (int) $vid, (int) $kind); + + $q = "SELECT SUM(IF((size = '1' AND vref = $vid) OR size > '1', 1, 0)) totals, + SUM(IF(size = '1' AND vref = $vid, 1, 0)) small, + SUM(IF(size = '2', 1, 0)) great, + SUM(IF(size = '3', 1, 0)) `unique` + FROM " . TB_PREFIX . "artefacts WHERE owner = ".$uid." AND active = 1 AND (type = $kind OR kind = $kind) AND del = 0"; + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result)[0]; + } + + function addArtefacts($wids, $artifactsArray) { + list($wids, $artifactsArray) = $this->escape_input($wids, $artifactsArray); + + if(!is_array($wids)) $wids = [$wids]; + + $time = time(); + + foreach($artifactsArray as $index => $artifact){ + $values[] = "(".$wids[$index].",".$artifact['owner'].",".$artifact['type'].",".$artifact['size'].",".$time.",'".$artifact['name']."','".$artifact['desc']."','".$artifact['effect']."','".$artifact['img']."', 0)"; + } + + $q = "INSERT INTO `" . TB_PREFIX . "artefacts` (`vref`, `owner`, `type`, `size`, `conquered`, `name`, `desc`, `effect`, `img`, `active`) VALUES ".implode(",", $values); + return mysqli_query($this->dblink, $q); + } + + function getWWConstructionPlans($uid, $alliance = 0){ + list($uid, $alliance) = $this->escape_input((int) $uid, $alliance); + + if(!$alliance){ + $q = "SELECT + Count(*) as Total + FROM + ".TB_PREFIX."artefacts + WHERE + owner = ".$uid." AND type = 11 AND active = 1 AND del = 0"; + }else{ + $q = "SELECT + Count(*) as Total + FROM + ".TB_PREFIX."artefacts AS artefacts + INNER JOIN ".TB_PREFIX."users AS users + ON users.id != ".$uid." AND users.alliance = ".$alliance." AND artefacts.owner = users.id AND artefacts.type = 11 + WHERE + users.id > 4 AND artefacts.active = 1 AND artefacts.del = 0"; + } + + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + // no need to cache this method + function getOwnArtefactInfo($vref, $use_cache = true) { + // load the data - type is irrelevant, since the method caches all data + // then returns the one for our type + $this->getOwnArtefactInfoByType($vref, 1, $use_cache); + + // return what we've cached + return (self::$artefactInfoByTypeCache[$vref]); + } + + // no need to cache this method since its called one time only + function getOwnArtefactsInfo($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE owner = $uid AND del = 0"; + $result = mysqli_query($this->dblink, $q); + + return $this->mysqli_fetch_all($result); + } + + function getOwnArtefactInfoByType2($vref, $type, $use_cache = true) { + return $this->getOwnArtefactInfoByType($vref, $type, $use_cache); + } + + function getOwnArtefactInfoByType($vref, $type, $use_cache = true) { + $vref = (int) $vref; + $type = (int) $type; + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && isset(self::$artefactInfoByTypeCache[$vref]) && is_array(self::$artefactInfoByTypeCache[$vref]) && !count(self::$artefactInfoByTypeCache[$vref])) { + return []; + } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$artefactInfoByTypeCache, $vref)) && !is_null($cachedValue)) { + return (isset($cachedValue[$type]) ? $cachedValue[$type] : []); + } + + $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE vref = $vref AND del = 0 ORDER BY size"; + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // cache all types and return the requested one + if (count($result)) { + foreach ($result as $arteInfo) { + if (!isset(self::$artefactInfoByTypeCache[$arteInfo['vref']])) { + self::$artefactInfoByTypeCache[$arteInfo['vref']] = []; + } + + // we're sorting by size, thus we only need the first one per each type + if (isset(self::$artefactInfoByTypeCache[$arteInfo['vref']]) && !isset(self::$artefactInfoByTypeCache[$arteInfo['vref']][$arteInfo['type']])) { + self::$artefactInfoByTypeCache[$arteInfo['vref']][$arteInfo['type']] = $arteInfo; + } + } + } else { + self::$artefactInfoByTypeCache[$vref] = []; + } + + return (isset(self::$artefactInfoByTypeCache[$vref][$type]) ? self::$artefactInfoByTypeCache[$vref][$type] : []); + } + + function getOwnUniqueArtefactInfo2($id, $type, $size, $mode, $use_cache = true) { + list($id, $type, $size, $mode) = $this->escape_input((int) $id, (int) $type, (int) $size, $mode); + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && isset(self::$artefactDataCache[$id.$mode]) && is_array(self::$artefactDataCache[$id.$mode]) && !count(self::$artefactDataCache[$id.$mode])) { + return []; + } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$artefactDataCache, $id.$mode)) && !is_null($cachedValue)) { + return (isset($cachedValue[$size.$type]) ? $cachedValue[$size.$type] : []); + } + + $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE ".(!$mode ? 'owner' : 'vref')." = $id AND active = 1 AND del = 0"; + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // cache all types and return the requested one + if (count($result)) { + foreach ($result as $arteInfo) { + if (!isset(self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode])) { + self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode] = []; + } + + self::$artefactDataCache[$arteInfo[(!$mode ? 'owner' : 'vref')] . $mode][$arteInfo['size'].$arteInfo['type']] = $arteInfo; + } + } else { + self::$artefactDataCache[$id.$mode] = []; + } + + return (isset(self::$artefactDataCache[$id.$mode][$size.$type]) ? self::$artefactDataCache[$id.$mode][$size.$type] : []); + } + + /** + * Get deleted artifacts + * + * @return array Returns the deleted artifacts + */ + + function getDeletedArtifacts(){ + $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE del = 1"; + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result); + } + + function villageHasArtefact($vref) { + // this is a somewhat non-ideal, externally non-changeable way of caching + // but since we're only ever going to be calling this from a single point of Automation, + // this will more than suffice + static $cachedData = []; + $vref = (int) $vref; + + if (isset($cachedData[$vref])) { + return $cachedData[$vref]; + } + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "artefacts WHERE vref = $vref AND del = 0"; + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + $cachedData[$vref] = $result['Total']; + + return $cachedData[$vref]; + } + + /** + * ===================================================================== + * SERVER MILESTONES (NEW_FUNCTIONS_MILESTONES) + * ===================================================================== + * "First player on the server to..." achievements. Each milestone_key + * is recorded AT MOST ONCE, ever — the table's UNIQUE KEY on + * milestone_key does the actual "first wins" enforcement at the DB + * level via INSERT IGNORE, so this is race-condition-safe even if two + * players' actions are processed in the same cron batch: only one + * INSERT can ever succeed for a given key, no matter how many + * processes attempt it concurrently. + */ + + /** + * Attempts to record a milestone. Only the very first call for a given + * $key across the server's lifetime actually stores anything. + * + * @param string $key Unique milestone identifier, e.g. 'second_village' + * @param int $uid The user who achieved it + * @param int $vref The village where it happened (0 if not applicable) + * @param string $extra Optional free-form context (e.g. alliance name) + * @return bool true if THIS call is the one that recorded the milestone + */ + function recordMilestoneIfFirst($key, $uid, $vref = 0, $extra = '') { + list($key, $extra) = $this->escape_input((string) $key, (string) $extra); + list($uid, $vref) = [(int) $uid, (int) $vref]; + + $time = time(); + $q = "INSERT IGNORE INTO " . TB_PREFIX . "milestones (milestone_key, uid, vref, extra, achieved_time) + VALUES ('$key', $uid, $vref, '$extra', $time)"; + mysqli_query($this->dblink, $q); + + return mysqli_affected_rows($this->dblink) > 0; + } + + /** + * @return array All recorded milestones, keyed by milestone_key, each + * row enriched with the achiever's username and (if + * applicable) the village name. + */ + function getMilestones() { + $q = "SELECT m.milestone_key, m.uid, m.vref, m.extra, m.achieved_time, + u.username, v.name AS village_name + FROM " . TB_PREFIX . "milestones m + LEFT JOIN " . TB_PREFIX . "users u ON u.id = m.uid + LEFT JOIN " . TB_PREFIX . "vdata v ON v.wref = m.vref"; + $result = mysqli_query($this->dblink, $q); + $rows = $this->mysqli_fetch_all($result); + + $byKey = []; + foreach ($rows as $row) { + $byKey[$row['milestone_key']] = $row; + } + return $byKey; + } + + function claimArtefact($vref, $ovref, $id) { + list($vref, $ovref, $id) = $this->escape_input((int) $vref, (int) $ovref, (int) $id); + + $time = time(); + $q = "UPDATE " . TB_PREFIX . "artefacts SET vref = $vref, owner = $id, conquered = $time, active = 0 WHERE vref = $ovref"; + + if(mysqli_query($this->dblink, $q)) + { + $artifactInfo = reset($this->getOwnArtefactInfo($vref, false)); + $artifactID = $artifactInfo['id']; + + // Milestones: first artifact EVER captured by any player (any + // type, including the WW Building Plan), and — separately — + // the first WW Building Plan (artefacts.type == 11) specifically. + // claimArtefact() is the single function that transfers artifact + // ownership (both the "conquer the artifact's own village" path + // and the "hero carries it home" path call this), so hooking + // here covers every capture route with no risk of missing one. + if (defined('NEW_FUNCTIONS_MILESTONES') && NEW_FUNCTIONS_MILESTONES) { + $this->recordMilestoneIfFirst('first_artifact', $id, $vref); + if ((int)($artifactInfo['type'] ?? 0) === 11) { + $this->recordMilestoneIfFirst('first_ww_plan', $id, $vref); + } + } + + return $this->addArtifactsChronology($artifactID, $id, $vref, $time); + } + else return false; + } + + /** + * Retrieves the chronology of one specific artifact + * + * @param int $artefactid The id of the artifact + * @return array Returns the chronology for the passed artifact + */ + + function getArtifactsChronology($artifactID){ + list($artifactID) = $this->escape_input((int) $artifactID); + + $q = "SELECT * FROM " . TB_PREFIX . "artefacts_chrono WHERE artefactid = $artifactID ORDER BY conqueredtime ASC"; + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result); + } + + /** + * Stores when an artifact was conquered and who had conquered it + * + * @param int $artefactid The id of the artifact + * @param int $vref The vref of the village that has conquered the artifact + * @return bool Return true if the query was successful, false otherwise + */ + + function addArtifactsChronology($artifactID, $uid, $vref, $conqueredTime){ + list($artifactID, $uid, $vref, $conqueredTime) = $this->escape_input((int) $artifactID, (int) $uid, (int) $vref, (int) $conqueredTime); + + $q = "INSERT INTO " . TB_PREFIX . "artefacts_chrono (artefactid, uid, vref, conqueredtime) VALUES ('$artifactID', '$uid', '$vref', '$conqueredTime')"; + return mysqli_query($this->dblink, $q); + } + + /** + * @param mixed $size The integer/array which contains the artifacts size(s) + * @return int Returns if there are at least one not deleted artifact + */ + + function getArtifactsBysize($size){ + list($size) = $this->escape_input($size); + + if(!is_array($size)) $size = [$size]; + + $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE size IN(".implode(',', $size).") AND del = 0 ORDER BY id ASC"; + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result); + } + + /** + * @param bool $mode true: check if WW Building plans are already out, false: check if artifacts are already out + * @return int Returns if artifacts are already out or not + */ + + function areArtifactsSpawned($mode = false){ + list($mode) = $this->escape_input($mode); + + $q = "SELECT 1 FROM ".TB_PREFIX."artefacts".($mode ? " WHERE type = 11" : ""); + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + return $result; + } + + + /** + * Check if WW villages are already out or not + * + * @return int Returns if artifacts are already out or not + */ + + function areWWVillagesSpawned(){ + $q = "SELECT 1 FROM ".TB_PREFIX."vdata WHERE natar = 1"; + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + return $result; + } + + /** + * Get all inactive artifacts which can be activated + * + * @return bool Returns all inactive artifacts + */ + + function getInactiveArtifacts($time){ + list($time) = $this->escape_input($time); + + $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE active = 0 AND owner > 5 AND conquered <= $time AND del = 0 ORDER BY conquered ASC, size ASC"; + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result); + } + + /** + * Get the sum of active artifacts by user ID, divided by: total, great, small and unique + * + * @param int $uid The User ID of the player + * @param bool $mode True if you want only active artifacts, false if you want all artifacts + * @return array Returns the artifacts sum of the account, divided by: total, great, small and unique + */ + + function getOwnArtifactsSum($uid, $mode = false){ + list($uid) = $this->escape_input((int) $uid, $mode); + + $q = "SELECT Count(size) AS totals, + SUM(IF(size = '1', 1, 0)) small, + SUM(IF(size = '2', 1, 0)) great, + SUM(IF(size = '3', 1, 0)) `unique` + FROM " . TB_PREFIX . "artefacts WHERE owner = ".(int) $uid.($mode ? " AND active = 1 AND del = 0" : ""); + $result = mysqli_query($this->dblink, $q); + return $this->mysqli_fetch_all($result)[0]; + } + + /** + * Activate an artifact by his id + * + * @param int $id The id of the artifact + * @param int $mode 1 for activating an artifact, 0 for deactivating it + * @return bool Returns true if the query was successful, false otherwise + */ + + function activateArtifact($id, $mode = 1){ + list($id) = $this->escape_input((int) $id); + + $time = time(); + $q = "UPDATE " . TB_PREFIX . "artefacts SET active = $mode WHERE id = $id"; + return mysqli_query($this->dblink, $q); + } + + /** + * Get the newest active artifact by size + * + * @param int $size The size of the artifcat (village, account, unique) + * @return array Returns the newest active artifact infomations by size + */ + + function getNewestArtifactBySize($id, $size){ + list($id, $size) = $this->escape_input((int) $id, (int) $size); + + $q = "SELECT * FROM ".TB_PREFIX."artefacts WHERE active = 1 AND owner = $id AND size = $size AND del = 0 ORDER BY conquered DESC LIMIT 1"; + $result = mysqli_query($this->dblink, $q); + return mysqli_fetch_array($result); + } + + // no need to cache this method + public function canClaimArtifact($from, $vref, $size, $type) { + list($size, $type) = $this->escape_input((int) $size, (int) $type); + + $artifact = $this->getOwnArtefactInfo($from); + if (!empty($artifact)) return "Treasury is full. Your hero could not claim the artefact"; + + $uid = $this->getVillageField($from, "owner"); + $vuid = $this->getVillageField($vref, "owner"); + + $artifact = $this->getOwnArtifactsSum($uid); + + if ($artifact['totals'] < 3 || $uid == $vuid) { + $DefenderFields = $this->getResourceLevel( $vref ); + $defcanclaim = true; + + for ($i = 19; $i <= 38; $i++) { + if ($DefenderFields['f'.$i.'t'] == 27) { + $defTresuaryLevel = $DefenderFields['f'.$i]; + if ($defTresuaryLevel > 0) { + $defcanclaim = false; + return "Treasury has not been destroyed. Your hero could not claim the artefact"; + } + else $defcanclaim = true; + } + } + + $AttackerFields = $this->getResourceLevel( $from, 2 ); + + for($i = 19; $i <= 38; $i++) { + if($AttackerFields['f'.$i.'t'] == 27) { + $attTresuaryLevel = $AttackerFields['f'.$i]; + $villageartifact = $attTresuaryLevel >= 10; + $accountartifact = $attTresuaryLevel >= 20; + } + } + + if(($artifact['great'] > 0 || $artifact['unique'] > 0) && $size > 1 && $uid != $vuid) { + return "Max num. of great/unique artefacts. Your hero could not claim the artefact"; + } + + if(($size == 1 && ($villageartifact || $accountartifact)) || (($size == 2 || $size == 3) && $accountartifact)) { + return ""; + } + else return "Your level treasury is low. Your hero could not claim the artefact"; + } + else return "Max num. of artefacts. Your hero could not claim the artefact"; + } + + /** + * Get the informations of a single artifact + * + * @param int $id The artifact id + * @param int $del If 0, it will search not-deleted artifacts, and vice versa with 1 + * @return array Returns the artefact informations + */ + + function getArtefactDetails($id, $del = 0) { + list($id, $del) = $this->escape_input((int) $id, (int) $del); + + $q = "SELECT * FROM " . TB_PREFIX . "artefacts WHERE id = ".$id." AND del = ".$del." LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + return mysqli_fetch_array($result); + } + + /** + * Update an artifact with a specified fields => values array + * + * @param int $id The artifact ID + * @param array $detailsArray Contains the fields to update and the relative values + * @return bool Returns true if the query was successful, false otherwise + */ + + function updateArtifactDetails($id, $detailsArray){ + list($id, $detailsArray) = $this->escape_input((int) $id, $detailsArray); + + $values = []; + foreach($detailsArray as $field => $value) $values[] = $field."=".$value; + + $q = "UPDATE ".TB_PREFIX."artefacts SET ".implode(",", $values)." WHERE id = $id"; + return mysqli_query($this->dblink, $q); + } +} diff --git a/GameEngine/Database/DatabaseBuildingQueries.php b/GameEngine/Database/DatabaseBuildingQueries.php new file mode 100644 index 00000000..b249c2ea --- /dev/null +++ b/GameEngine/Database/DatabaseBuildingQueries.php @@ -0,0 +1,470 @@ +escape_input((int) $wid, $field, (int) $type, (int) $loop, (int) $time, (int) $master, (int) $level); + + $x = "UPDATE " . TB_PREFIX . "fdata SET f" . $field . "t=" . $type . " WHERE vref=" . $wid; + mysqli_query($this->dblink,$x); + $q = "INSERT into " . TB_PREFIX . "bdata values (0, $wid, $field, $type, $loop, $time, $master, $level)"; + return mysqli_query($this->dblink,$q); + } + + function getBuildLock($wid) { + $wid = (int) $wid; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('build_village_$wid', 10) AS locked"); + $row = mysqli_fetch_assoc($result); + return $row['locked'] == 1; + } + + function releaseBuildLock($wid) { + $wid = (int) $wid; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('build_village_$wid')"); + } + + /** + * Get the time required to build a specified building + * + * @param int $id The ID where the building is located + * @param int $tid The type of the building + * @param int $plus The construction queue count + * @param int $wref The village ID + * @param array $buildingArray The array containing the buildings in the village + * @return int Returns the building time + */ + + function getBuildingTime($id, $tid, $plus, $wref, $buildingArray) { + list($id, $tid, $plus, $wref, $buildingArray) = $this->escape_input((int) $id, (int) $tid, (int) $plus, (int) $wref, $buildingArray); + global ${'bid'.$tid}, $bid15; + + $dataArray = ${'bid'.$tid}; + + //Check if we've the main building or not + $mainBuilding = $this->getFieldLevelInVillage($wref, 15); + if($tid == 15){ + if($mainBuilding == 0) return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] / SPEED * 5); + else return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] / SPEED); + }else{ + if($mainBuilding > 0) { + return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] * ($bid15[$mainBuilding]['attri'] / 100) / SPEED); + } + else return round($dataArray[$buildingArray['f'.$id] + $plus]['time'] * 5 / SPEED); + } + } + + /** + * Called when removing a queued building by a player or because destroyed by catapults + * + * @param int $d The ID of the building which needs to be deleted + * @param int $tribe The tribe of the player + * @param int $wid The village ID of the player + * @param array $fieldsArray Optional, the array containing the village building/resource fields + * @return bool Returns true if the building was delete successfully, false otherwise + */ + + function removeBuilding($d, $tribe, $wid, $fieldsArray = []) { + list($d, $tribe, $wid, $fieldsArray) = $this->escape_input((int) $d, (int) $tribe, (int) $wid, $fieldsArray); + + //Variables initialization + $jobToDelete = []; + $canBeRemoved = true; + $time = time(); + $newTime = $loopTime = 0; + if(empty($fieldsArray)) $fieldsArray = $this->getResourceLevel($wid); + $jobs = $this->getJobsOrderByID($wid); + + //Search the job which needs to be deleted + foreach($jobs as $job){ + //We need to modify waiting loop orders + if(!empty($jobToDelete) && $job['loopcon'] == 1 && ($tribe != 1 || ($tribe == 1 && (($jobToDelete['field'] <= 18 && $job['field'] <= 18) || ($jobToDelete['field'] >= 19 && $job['field'] >= 19))))){ + + //Does this job have the same field of the deleted one? + $sameBuilding = $jobToDelete['field'] == $job['field'] ? 1 : 0; + + //Can the building be completely removed from the village? + if($sameBuilding && $canBeRemoved) $canBeRemoved = !$sameBuilding; + + //Get the time required to upgrade the building at the given level + $newTime = $this->getBuildingTime( + $job['field'], + $job['type'], + $job['level'] - $fieldsArray['f'.$job['field']] - $sameBuilding, + $wid, + $fieldsArray); + + //Increase the looptime + $loopTime += $newTime; + + //Update the values + $q = "UPDATE + " .TB_PREFIX. "bdata + SET + ".($job['master'] ? "" : "loopcon = 0,")." + timestamp = ".($job['master'] ? $newTime : $loopTime + $time)." + ".($sameBuilding ? ", level = level - 1" : "")." + WHERE + id = ".$job['id']; + mysqli_query($this->dblink, $q); + + } + + //We found the job that needs to be deleted + if($job['id'] == $d) $jobToDelete = $job; + } + + if($canBeRemoved && $jobToDelete['field'] > 18 && $jobToDelete['field'] != 99 && $jobToDelete['level'] - 1 == 0){ + $this->setVillageLevel($wid, ["f".$jobToDelete['field']."t"], [0]); + } + + $q = "DELETE FROM " . TB_PREFIX . "bdata where id = $d"; + return mysqli_query($this->dblink, $q); + } + + function addDemolition($wid, $field) { + list($wid, $field) = $this->escape_input((int) $wid, (int) $field); + + global $building, $village, $session; + + $fLevel = $this->getFieldLevel($wid, $field); + + // check if we're not demolishing an Embassy if the player is in an alliance + if ($this->getFieldType($wid,$field) == 18 && $session->alliance) { + + // get field level, alliance members count and the minimum + // level of Embassy to be able to hold this number of people + $membersCount = $this->countAllianceMembers($session->alliance); + $minEmbassyLevel = $this->getMinEmbassyLevel($membersCount); + $isOwner = $this->isAllianceOwner($session->uid) == $session->alliance; + + // make sure minimum Embassy level is 3 of the player is alliance owner + if ($isOwner && $minEmbassyLevel < 3) { + $minEmbassyLevel = 3; + } + + // check if this user is the founder of the alliance + // and whether we're not trying to demolish under the lowest level + // which can hold current number of members + if ($fLevel == $minEmbassyLevel && $session->alliance && $isOwner) { + // check if we have any other players in this alliance left + if ($membersCount > 1) { + // check if this player has only 1 last Embassy on a sufficient level + if ($this->getSingleFieldTypeCount($session->uid, 18, '>=', $minEmbassyLevel) == 1) { + // cannot demolish Embassy further until the player quits the alliance, + // as they are founder and there are still other players in the alliance, + // thus destroying Embassy would evict this player from the alliance + // and leave a new random leader + return 18; + } + } + } + } + + $q = "DELETE FROM ".TB_PREFIX."bdata WHERE field=$field AND wid=$wid"; + mysqli_query($this->dblink,$q); + $uprequire = $building->resourceRequired($field,$village->resarray['f'.$field.'t'],0); + $newLevel = max(0, $fLevel - 1); + $q = "INSERT INTO ".TB_PREFIX."demolition VALUES (".$wid.",".$field.",".$newLevel.",".(time() + floor($uprequire['time'] / 2)).")"; + mysqli_query($this->dblink,$q); + + return true; + } + + // no need to cache this method + function getDemolition($wid = 0) { + list($wid) = $this->escape_input((int) $wid); + + if($wid) { + $q = "SELECT * FROM " . TB_PREFIX . "demolition WHERE vref=" . $wid; + } else { + $q = "SELECT * FROM " . TB_PREFIX . "demolition WHERE timetofinish<=" . time(); + } + $result = mysqli_query($this->dblink,$q); + if(!empty($result)) { + return $this->mysqli_fetch_all($result); + } else { + return NULL; + } + } + + function finishDemolition($wid) { + list($wid) = $this->escape_input((int) $wid); + + $q = "UPDATE " . TB_PREFIX . "demolition SET timetofinish=" . time() . " WHERE vref=" . $wid; + $result= mysqli_query($this->dblink,$q); + return mysqli_affected_rows($this->dblink); + } + + function delDemolition($wid, $checkEmbassy = false) { + $wid = (int) $wid; + + if ($checkEmbassy) { + // check if we've demolished an Embassy + // and select the user it belonged to as well, + // so we can potentially evict them from the alliance + // and remove it - if they don't have any more Embassies + // or if the they are founder and they have no more lvl 3+ Embassies + $q = ' + SELECT + u.id, u.username, u.alliance, d.buildnumber, d.lvl + FROM + '.TB_PREFIX.'demolition d + LEFT JOIN '.TB_PREFIX.'vdata v ON d.vref = v.wref + LEFT JOIN '.TB_PREFIX.'users u ON u.id = v.owner + WHERE d.vref = '.$wid; + + $res = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + foreach ($res as $key) { + // if this building being demolished is an Embassy or was demolished completely + // and the player is in an alliance, check and update their alliance status + if (($key['alliance'] > 0) && ($key['lvl'] == 0 || $this->getFieldType($wid, $key['buildnumber']) == 18)) { + $this->checkAllianceEmbassiesStatus($key, true); + } + } + } + + $q = "DELETE FROM " . TB_PREFIX . "demolition WHERE vref=" . $wid; + return mysqli_query($this->dblink,$q); + } + + /** + * Modify or delete a building being constructed/in queue + * + * @param int The village ID + * @param int $field The field where the building is located + * @param array $levels The new level of the building and the old one + * @param int $tribe The player's tribe + */ + + function modifyBData($wid, $field, $levels, $tribe){ + list($wid, $field, $levels, $tribe) = $this->escape_input((int) $wid, (int) $field, (int) $levels, (int) $tribe); + + if($levels[0] == 0){ + $q = "SELECT id FROM " .TB_PREFIX. "bdata WHERE wid = $wid AND field = $field"; + $orders = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q)); + foreach($orders as $order) $this->removeBuilding($order['id'], $tribe, $wid); + } + else mysqli_query($this->dblink, $q = "UPDATE " .TB_PREFIX. "bdata SET level = level - $levels[1] + $levels[0] WHERE wid = $wid AND field = $field"); + } + + private function getBData($wid, $use_cache = true, $orderByID = false) { + $wid = (int) $wid; + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && isset(self::$buildingsUnderConstructionCache[$wid]) && is_array(self::$buildingsUnderConstructionCache[$wid]) && !count(self::$buildingsUnderConstructionCache[$wid])) { + return []; + } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$buildingsUnderConstructionCache, $wid)) && !is_null($cachedValue)) { + return self::$buildingsUnderConstructionCache[$wid]; + } + + $q = "SELECT * FROM " . TB_PREFIX . "bdata where wid = $wid order by ".(!$orderByID ? "master,timestamp" : "id")." ASC"; + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + self::$buildingsUnderConstructionCache[$wid] = $result; + return $result; + } + + // do not cache output, as building jobs can change when using instant build (PLUS) etc. + function getJobs($wid) { + return $this->getBData($wid, false); + } + + function getJobsOrderByID($wid) { + return $this->getBData($wid, false, true); + } + + function FinishWoodcutter($wid) { + $bdata = $this->getBData($wid); + $time = time()-1; + + // find our woodcutter + $dbarray = []; + foreach ($bdata as $row) { + if ($row['type'] == 1) { + $dbarray = $row; + break; + } + } + + // no woodcutters? just return + if (!count($dbarray)) { + return; + } + + // make it complete + $q = "UPDATE ".TB_PREFIX."bdata SET timestamp = $time WHERE id = ".$dbarray['id']; + $this->query($q); + + $tribe = $this->getUserField($this->getVillageField($wid, "owner"), "tribe", 0); + + // find first field that's the next one in the loop after our finished woodcutter + $dbarray2 = []; + foreach ($bdata as $row) { + if ($row['loopcon'] == 1 && ($tribe == 1 ? $row['field'] >= 19 : true)) { + $dbarray2 = $row; + break; + } + } + + // if found, update it's finish time by subtracting the resulting time for our woodcutter, + // which is now finished + if (count($dbarray2)){ + $wc_time = $dbarray['timestamp']; + $q2 = "UPDATE ".TB_PREFIX."bdata SET timestamp = timestamp - $wc_time WHERE id = ".$dbarray2['id']; + $this->query($q2); + } + } + + function getMasterJobs($wid) { + // cache data + $bdata = $this->getBData($wid); + + // return all master jobs + $data = []; + foreach ($bdata as $row) { + if ($row['master'] == 1) $data[] = $row; + } + + return $data; + } + + function getMasterJobsByField($wid,$field) { + // cache data + $bdata = $this->getBData($wid); + + // return all master jobs for the requested field + $data = []; + foreach ($bdata as $row) { + if ($row['master'] == 1 && $row['field'] == $field) { + $data[] = $row; + } + } + + return $data; + } + + function getBuildingByField($wid,$field) { + // cache data + $bdata = $this->getBData($wid); + + // return all non-master jobs for the requested field + $data = []; + foreach ($bdata as $row) { + if ($row['master'] == 0 && $row['field'] == $field) { + $data[] = $row; + } + } + + return $data; + } + + // no need to cache this method + function getBuildingByField2($wid,$field) { + list($wid,$field) = $this->escape_input((int) $wid,(int) $field); + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "bdata where wid = $wid and field = $field and master = 0"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + return $result['Total']; + } + + function getBuildingByType($wid,$type) { + // cache data + $bdata = $this->getBData($wid); + $type = (strpos($type, ',') === false ? [(int) $type] : explode(',', str_replace(' ', '', $this->escape($type)))); + + // return all jobs which are of the requested type + $data = []; + foreach ($bdata as $row) { + if (in_array($row['field'], $type)) { + $data[] = $row; + } + } + + return $data; + } + + function getBuildingByType2($wid,$type) { + $wid = (int) $wid; + + if (!is_array($type)) { + $type = [$type]; + } else { + foreach ($type as $index => $typeValue) { + $type[$index] = (int) $typeValue; + } + } + + $q = "SELECT CONCAT(type, \"=\", Count(type)) FROM " . TB_PREFIX . "bdata where wid = $wid and type IN(".implode(', ', $type).") and master = 0 GROUP BY type"; + $result = mysqli_query($this->dblink, $q); + $newresult = []; + + if (mysqli_num_rows($result)) { + while ($row = mysqli_fetch_array($result, MYSQLI_NUM)) { + if ($row[0]) { + $val = explode( '=', $row[0] ); + $newresult[ $val[0] ] = $val[1]; + } + } + + $result = $newresult; + + } else { + $result = []; + } + + return $result; + } + + function getDorf1Building($wid) { + // cache data + $bdata = $this->getBData($wid); + + // return all non-master jobs for field type under 19 + $data = []; + foreach ($bdata as $row) { + if ($row['master'] == 0 && $row['field'] < 19) $data[] = $row; + } + + return $data; + } + + function getDorf2Building($wid) { + // cache data + $bdata = $this->getBData($wid); + + // return all non-master jobs for field type above 18 + $data = []; + foreach ($bdata as $row) { + if ($row['master'] == 0 && $row['field'] > 18) $data[] = $row; + } + + return $data; + } + + function updateBuildingWithMaster($id, $time, $loop) { + list($id, $time, $loop) = $this->escape_input((int) $id, (int) $time, (int) $loop); + + $q = "UPDATE " . TB_PREFIX . "bdata SET master = 0, timestamp = ".$time.", loopcon = ".$loop." WHERE id = ".$id.""; + return mysqli_query($this->dblink,$q); + } +} diff --git a/GameEngine/Database/DatabaseConnectionCore.php b/GameEngine/Database/DatabaseConnectionCore.php new file mode 100644 index 00000000..d02eb533 --- /dev/null +++ b/GameEngine/Database/DatabaseConnectionCore.php @@ -0,0 +1,355 @@ +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]); + } + + /** + * {@inheritDoc} + * @see \App\Database\IDbConnection::connect() + */ + public function connect() { + $host = (string) $this->hostname; + $port = (int) $this->port; + if ($port <= 0) $port = 3306; + + // If host is in form host:port (common in env files), split it once. + if (strpos($host, ':') !== false && substr_count($host, ':') === 1) { + $hostPort = explode(':', $host, 2); + if (isset($hostPort[0], $hostPort[1]) && is_numeric($hostPort[1])) { + $host = $hostPort[0]; + $port = (int) $hostPort[1]; + } + } + + $attempts = 15; + $waitMicros = 1000000; // 1 second + + for ($i = 0; $i < $attempts; $i++) { + try { + $this->dblink = mysqli_connect($host, $this->username, $this->password, $this->dbname, $port); + } catch (\Throwable $exception) { + $this->dblink = false; + } + + if ($this->dblink instanceof \mysqli) { + return true; + } + + // During container startup DB may still be booting. + if ($i < $attempts - 1) { + usleep($waitMicros); + } + } + + return false; + } + + /** + * {@inheritDoc} + * @see \App\Database\IDbConnection::disconnect() + */ + public function disconnect() { + if ($this->dblink) { + if (!$this->dblink->close()) { + return false; + } + + $this->dblink = null; + } + + return true; + } + + /** + * {@inheritDoc} + * @see \App\Database\IDbConnection::reconnect() + */ + public function reconnect() { + $this->disconnect(); + return $this->connect(); + } + + /** + * {@inheritDoc} + * @see \App\Database\IDbConnection::query_new() + */ + public function query_new($statement, ...$params) { + if ($prep = mysqli_prepare($this->dblink, $statement)) { + // if we're doing a multi-update/insert/delete query, + // we'll need to mark it as such + $is_multi_query = false; + + // determine the nature of this query + preg_match('/[^AZ-az]*(\()?[^AZ-az]*SELECT/i', $statement, $select_matches); + preg_match('/[^AZ-az]*(\()?[^AZ-az]*DELETE/i', $statement, $delete_matches); + preg_match('/[^AZ-az]*(\()?[^AZ-az]*INSERT/i', $statement, $insert_matches); + preg_match('/[^AZ-az]*(\()?[^AZ-az]*REPLACE/i', $statement, $replace_matches); + preg_match('/[^AZ-az]*(\()?[^AZ-az]*UPDATE/i', $statement, $update_matches); + + // a single array parameter means that we're batching multiple + // value feeds for a single prepared statement, so we just use + // the first array value to actually prepare the statement + // and determine all the binding types + if (count($params) == 1) { + $paramsArray = $params[0]; + $is_multi_query = true; + } else { + $paramsArray = $params; + // convert method parameters into an array, + // so we can reuse it in both cases - when we're executing + // just a single prepared statement and also when we're + // batching up multiple values for an insert/update/delete statement + $params = [$params]; + } + + // determine and prepare parameter types + $types = []; + foreach ($paramsArray as $param) { + // default to string, change if neccessary + $paramType = 's'; + + if (Math::isInt($param)) { + $paramType = 'i'; + } else if (Math::isFloat($param)) { + $paramType = 'd'; + } + + $types[] = $paramType; + } + + // dynamically bind each data batch using previously + // defined parameters + $implodedNames = [implode('', $types)]; + $outputValues = []; + + foreach ($params as $dataBatch) { + $bind_names = $implodedNames; + for ($i=0; $iselectQueryCount++; + $queryResult = []; + + // read metadata, so we know what fields we were actually selecting + // and can prepare our temporary variables to read them into + $resultMetaData = mysqli_stmt_result_metadata($prep); + + $stmtRow = array(); + $rowReferences = array(); + while ($field = mysqli_fetch_field($resultMetaData)) { + $rowReferences[] = &$stmtRow[$field->name]; + } + mysqli_free_result($resultMetaData); + + // now call bind_result with all our variables to recive the data prepared + call_user_func_array(array($prep, 'bind_result'), $rowReferences); + + // prepare the array-ed result + while(mysqli_stmt_fetch($prep)){ + $row = array(); + foreach($stmtRow as $key => $value){ + $row[$key] = $value; + } + $queryResult[] = $row; + } + + // free the result + mysqli_stmt_free_result($prep); + + $outputValues[] = $queryResult; + } else { + throw new Exception('Failed to execute an SQL statement!'); + } + } + } + + // free the prepared statement + mysqli_stmt_close($prep); + + // return the expected result + if (count($select_matches)) { + // if there is only a single result, return it alone + if (count($outputValues) === 1) { + return $outputValues[0]; + } else { + // otherwise, return all the data + return $outputValues; + } + } + } else { + throw new Exception('Failed to prepare an SQL statement!'); + } + + return false; + } + + /** + * {@inheritDoc} + * @see \App\Database\IDbConnection::is_connected() + */ + public function is_connected() { + return ($this->dblink ? true : false); + } + + /*************************** + Function to process MYSQLi->fetch_all (Only exist in MYSQL) + References: Result + ***************************/ + function mysqli_fetch_all($result) { + // REFACTORIZAT: garantăm array pentru PHP 7.2+, eliminăm escape pe resource + $all = []; + if($result instanceof \mysqli_result) { + while($row = mysqli_fetch_assoc($result)) { + $all[] = $row; + } + 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); + } + + /*************************** + Function to do free query + References: Query + ***************************/ + function query($query) { + // REFACTORIZAT: contorizare centralizată + $this->countQueryType($query); + return mysqli_query($this->dblink,$query); + } + + function RemoveXSS($val) { + list($val) = $this->escape_input($val); + + return htmlspecialchars($val, ENT_QUOTES); + } + + /** + * Returns a value previously cached from the database, if present. + * + * @param $arrayVariable array Reference to the static array in Database class to use for the lookup. + * @param $arrayFieldName string The actual array field name to look a cached value for. + * + * @return Returns the requested cached value or null if it's not cached yet. + */ + private static function returnCachedContent(&$arrayVariable, $arrayStructure) { + if (!isset($arrayVariable[$arrayStructure])) { + $arrayVariable[$arrayStructure] = []; + } + + if (isset($arrayVariable[$arrayStructure]) && !empty($arrayVariable[$arrayStructure])) { + return $arrayVariable[$arrayStructure]; + } + else return null; + } + + /** + * Clears cached village data, so after automation is run, we can re-load new data (like resource levels etc) + * to be displayed in the front-end. + */ + public static function clearVillageCache() { + self::$villageFieldsCache = []; + self::$villageFieldsCacheByWorldID = []; + } + + /** + * Returns a string value safely escaped to be used in mysqli_query() method. + * + * @param $value string The value to sanitize. + * + * @return string Returns a sanitized string, safe for SQL queries. + */ + function escape($value) { + if (is_string($value)) { + $value = stripslashes( $value ); + return mysqli_real_escape_string($this->dblink, $value); + } else { + return $value; + } + } + + /** + * Returns a list of safely escaped values which can be used to re-retrieve + * them in a list() method. + * + * @example list($username, $password) = $database->escape_input($username, $password); + * + * @return array Returns an array with all items sanitized and safe to be used in SQL statements. + */ + function escape_input() { + $numargs = func_num_args(); + $arg_list = func_get_args(); + $ret = []; + + for ($i = 0; $i < $numargs; $i++) { + if (is_string($arg_list[$i])) { + $arg_list[$i] = stripslashes($arg_list[$i]); + $res[] = mysqli_real_escape_string($this->dblink, $arg_list[$i]); + } else { + $res[] = $arg_list[$i]; + } + } + + return $res; + } + + function return_link() { + return $this->dblink; + } +} diff --git a/GameEngine/Database/DatabaseForumQueries.php b/GameEngine/Database/DatabaseForumQueries.php new file mode 100644 index 00000000..81f6da1f --- /dev/null +++ b/GameEngine/Database/DatabaseForumQueries.php @@ -0,0 +1,630 @@ +escape_input((int) $uid, (int) $alliance); + + $allianceForums = $confForums = $closedForums = []; + + $q = "SELECT * FROM + ".TB_PREFIX."forum_cat + WHERE + display_to_alliances + LIKE + '%,$alliance,%' + OR + display_to_alliances + LIKE + '%,$alliance%' + OR + display_to_alliances + LIKE + '%$alliance,%' + OR + display_to_alliances + = + '$alliance' + OR + display_to_users + LIKE + '%,$uid,%' + OR + display_to_users + LIKE + '%,$uid%' + OR + display_to_users + LIKE + '%$uid,%' + OR + display_to_users + = + '$uid' + "; + $result = mysqli_query($this->dblink, $q); + if(!empty($result)){ + while($row = mysqli_fetch_assoc($result)) { + switch($row['forum_area']){ + case 0: $allianceForums[] = $row; break; + case 2: $confForums[] = $row; break; + case 3: $closedForums[] = $row; break; + } + } + } + + //Get the alliance confederation forums + if($alliance > 0){ + $confederations = $this->diplomacyExistingRelationships($alliance); + if(!empty($confederations)){ + foreach($confederations as $confederation){ + if($confederation['type'] == 1){ + $confederationForums = $this->ForumCat($confederation['alli1'] == $alliance ? $confederation['alli2'] : $confederation['alli1'], 1); + if(!empty($confederationForums)){ + foreach($confederationForums as $forum){ + if($forum['forum_area'] == 2) $confForums[] = $forum; + } + } + } + } + } + } + + return ['alliance' => $allianceForums, 'confederation' => $confForums, 'closed' => $closedForums]; + } + + /** + * Get the total amount of the wanted forum, based on alliance and the forum area + * + * @param int $forumArea The forum Area 0 = alliance, 1 = public, 2 = confederation, 3 = closed + * @param int $ally The alliance ID + * @return int Returns the total amount of the wanted forum + */ + + function countForums($forumArea, $ally){ + list($forumArea, $ally) = $this->escape_input((int) $forumArea, (int) $ally); + + $q = "SELECT Count(*) as Total FROM ".TB_PREFIX."forum_cat WHERE ".($ally != -1 ? "alliance = $ally AND" : "")." forum_area = $forumArea"; + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC ); + return $result['Total']; + } + + // no need to refactor this method + function CheckForum($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "forum_cat where alliance = $id"; + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + // no need to refactor this method + function CountCat($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_topic where cat = '$id'"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + return $row[0]; + } + + // no need to refactor this method + function LastTopic($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' order by post_date"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function CheckLastTopic($id, $use_cache = true) { + list($id) = $this->escape_input($id); + + // 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::$lastTopicCheckCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_topic where cat = $id"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + if($result['Total']) { + self::$lastTopicCheckCache[$id] = true; + } else { + self::$lastTopicCheckCache[$id] = false; + } + + return self::$lastTopicCheckCache[$id]; + } + + function CheckLastPost($id, $use_cache = true) { + list($id) = $this->escape_input($id); + + // 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::$lastPostForTopicCheckCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_post where topic = $id"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + if ($result['Total']) { + self::$lastPostForTopicCheckCache[$id] = true; + } else { + self::$lastPostForTopicCheckCache[$id] = false; + } + + return self::$lastPostForTopicCheckCache[$id]; + } + + function LastPost($id, $use_cache = true) { + list($id) = $this->escape_input($id); + + // 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::$lastPostForTopicCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * from " . TB_PREFIX . "forum_post where topic = '$id'"; + $result = mysqli_query($this->dblink,$q); + + self::$lastPostForTopicCache[$id] = $this->mysqli_fetch_all($result); + return self::$lastPostForTopicCache[$id]; + } + + function CountTopic($id, $use_cache = true) { + list($id) = $this->escape_input($id); + + // 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::$topicCountCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_post where owner = '$id'"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + + $qs = "SELECT count(id) FROM " . TB_PREFIX . "forum_topic where owner = '$id'"; + $results = mysqli_query($this->dblink,$qs); + $rows = mysqli_fetch_row($results); + + self::$topicCountCache[$id] = $row[0] + $rows[0]; + return self::$topicCountCache[$id]; + } + + // no need to cache this method + function CountPost($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT count(id) FROM " . TB_PREFIX . "forum_post where topic = '$id'"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + return $row[0]; + } + + // no need to cache this method + function ForumCat($id, $mode = 0) { + list($id, $mode) = $this->escape_input($id, $mode); + + $q = "SELECT * from " . TB_PREFIX . "forum_cat where alliance = '$id' ".(!$mode ? "OR forum_area = 1" : "")." ORDER BY sorting DESC, id"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ForumCatEdit($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT * from " . TB_PREFIX . "forum_cat where id = '$id'"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ForumCatAlliance($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT alliance from " . TB_PREFIX . "forum_cat where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink, $q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['alliance']; + } + + // no need to cache this method + function ForumCatName($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT forum_name from " . TB_PREFIX . "forum_cat where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['forum_name']; + } + + // no need to cache this method + function CheckCatTopic($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_topic where cat = $id"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + // no need to cache this method + function CheckResultEdit($alli) { + list($alli) = $this->escape_input($alli); + + $q = "SELECT Count(*) as Total from " . TB_PREFIX . "forum_edit where alliance = $alli"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + // no need to cache this method + function CheckCloseTopic($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT close from " . TB_PREFIX . "forum_topic where id = '$id' LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['close']; + } + + function CheckEditRes($alli, $use_cache = true) { + list($alli) = $this->escape_input($alli); + + // 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::$editResultsCache, $alli)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT result from " . TB_PREFIX . "forum_edit where alliance = '$alli' LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + + self::$editResultsCache[$alli] = $dbarray['result']; + return self::$editResultsCache[$alli]; + } + + function CreatResultEdit($alli, $result) { + list($alli, $result) = $this->escape_input($alli, $result); + + $q = "INSERT into " . TB_PREFIX . "forum_edit values (0,'$alli','$result')"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + function UpdateResultEdit($alli, $result) { + list($alli, $result) = $this->escape_input($alli, $result); + + $date = time(); + $q = "UPDATE " . TB_PREFIX . "forum_edit set result = '$result' where alliance = '$alli'"; + return mysqli_query($this->dblink,$q); + } + + function MoveForum($id, $area, $ally, $mode){ + list($id, $area, $ally, $mode) = $this->escape_input((int) $id, (int) $area, (int) $ally, $mode); + + $q = "UPDATE + ".TB_PREFIX."forum_cat + SET + sorting = (SELECT * FROM(SELECT ".(!$mode ? "MIN" : "MAX")."(sorting) FROM ".TB_PREFIX."forum_cat WHERE forum_area = $area ".($area != 1 ? "AND alliance = $ally" : "")." AND id != $id) f) ".(!$mode ? "-" : "+")." 1 + WHERE + id = $id"; + return mysqli_query($this->dblink, $q); + } + + function UpdateEditTopic($id, $title, $cat) { + list($id, $title, $cat) = $this->escape_input((int) $id, $title, $cat); + + $q = "UPDATE " . TB_PREFIX . "forum_topic set title = '$title', cat = '$cat' where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function UpdateEditForum($id, $name, $des, $alliances, $users) { + list($id, $name, $des, $alliances, $users) = $this->escape_input((int) $id, $name, $des, $alliances, $users); + + $q = "UPDATE " . TB_PREFIX . "forum_cat SET forum_name = '$name', forum_des = '$des', display_to_alliances = '$alliances', display_to_users = '$users' WHERE id = $id"; + return mysqli_query($this->dblink,$q); + } + + function StickTopic($id, $mode) { + list($id, $mode) = $this->escape_input((int) $id, (int) $mode); + + $q = "UPDATE " . TB_PREFIX . "forum_topic SET stick = $mode WHERE id = $id"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function ForumCatTopic($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' AND stick = '' ORDER BY post_date desc"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ForumCatTopicStick($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT * from " . TB_PREFIX . "forum_topic where cat = '$id' AND stick = '1' ORDER BY post_date desc"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ShowTopic($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * from " . TB_PREFIX . "forum_topic where id = $id"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ShowPost($id) { + list($id) = $this->escape_input($id); + + $q = "SELECT * from " . TB_PREFIX . "forum_post where topic = '$id' ORDER BY id ASC"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function ShowPostEdit($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * from " . TB_PREFIX . "forum_post where id = $id"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function CreatForum($owner, $alli, $name, $des, $area, $alliances, $users) { + list($owner, $alli, $name, $des, $area, $alliances, $users) = $this->escape_input($owner, $alli, $name, $des, $area, $alliances, $users); + + $q = "INSERT into " . TB_PREFIX . "forum_cat values (0, 0,'$owner','$alli','$name','$des','$area','$alliances','$users')"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + function CreatTopic($title, $post, $cat, $owner, $alli, $ends) { + list($title, $post, $cat, $owner, $alli, $ends) = $this->escape_input($title, $post, (int) $cat, (int) $owner, (int) $alli, (int) $ends); + + $date = time(); + $q = "INSERT into " . TB_PREFIX . "forum_topic values (0,'$title','$post',$date, $date, $cat, $owner, $alli, $ends, 0, 0)"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + /************************* + FORUM SUREY + *************************/ + + function createSurvey($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends) { + list($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends) = $this->escape_input($topic, $title, $option1, $option2, $option3, $option4, $option5, $option6, $option7, $option8, $ends); + + $q = "INSERT into " . TB_PREFIX . "forum_survey (topic,title,option1,option2,option3,option4,option5,option6,option7,option8,ends) values ('$topic','$title','$option1','$option2','$option3','$option4','$option5','$option6','$option7','$option8','$ends')"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getSurvey($topic) { + list($topic) = $this->escape_input((int) $topic); + + $q = "SELECT * FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + return mysqli_fetch_array($result); + } + + // no need to cache this method + function checkSurvey($topic) { + list($topic) = $this->escape_input((int) $topic); + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "forum_survey where topic = $topic"; + $result = mysqli_fetch_array( mysqli_query( $this->dblink, $q ), MYSQLI_ASSOC ); + + if ( $result['Total'] ) { + return true; + } else { + return false; + } + } + + function Vote($topic, $num, $text) { + list($topic, $num, $text) = $this->escape_input((int) $topic, (int) $num, $text); + + $q = "UPDATE " . TB_PREFIX . "forum_survey set vote".$num." = vote".$num." + 1, voted = '$text' where topic = ".$topic.""; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function checkVote($topic, $uid) { + list( $topic, $uid ) = $this->escape_input( (int) $topic, $uid ); + + $q = "SELECT voted FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; + $result = mysqli_query( $this->dblink, $q ); + $array = mysqli_fetch_array( $result ); + $text = $array['voted']; + + if ( preg_match( '/,' . $uid . ',/', $text ) ) { + return true; + } else { + return false; + } + } + + // no need to cache this method + function getVoteSum($topic) { + list( $topic ) = $this->escape_input( (int) $topic ); + + $q = "SELECT * FROM " . TB_PREFIX . "forum_survey where topic = $topic LIMIT 1"; + $result = mysqli_query( $this->dblink, $q ); + $array = mysqli_fetch_array( $result ); + $sum = 0; + + for ( $i = 1; $i <= 8; $i ++ ) { + $sum += $array[ 'vote' . $i ]; + } + + return $sum; + } + + + /************************* + FORUM SUREY + *************************/ + + function CreatPost($post, $tids, $owner, $fid2 = 0) { + global $message, $session; + list($post, $tids, $owner, $fid2) = $this->escape_input($post, (int) $tids, $owner, (int) $fid2); + + $date = time(); + $q = "INSERT into " . TB_PREFIX . "forum_post values (0,'$post',$tids,'$owner','$date')"; + mysqli_query($this->dblink,$q); + $postID = mysqli_insert_id($this->dblink); + + // create a message notification for each person subscribed to this topic + // ... for now it's everyone who ever posted there, there is no real un/subscription yet + if(NEW_FUNCTIONS_FORUM_POST_MESSAGE){ + if ($fid2 !== 0) { + $q = "SELECT DISTINCT owner FROM ".TB_PREFIX . "forum_post WHERE topic = $tids"; + $result = mysqli_query($this->dblink, $q); + if ($result->num_rows) { + while ($row = mysqli_fetch_assoc($result)) { + if ($row['owner'] != $owner) { + $this->sendMessage( + (int) $row['owner'], + 4, + rc_tok('MSG_FORUM_NEW_TITLE'), + rc_tok( + 'MSG_FORUM_NEW_BODY', + rtrim(SERVER, '/')."/spieler.php?uid=".(int) $session->uid, + $this->escape($session->username), + rtrim(SERVER, '/')."/allianz.php?s=2&pid=2&fid2=$fid2&tid=$tids" + ), + 0, + 0, + 0, + 0, + 0, + true); + } + } + } + } + } + return $postID; + } + + function UpdatePostDate($id) { + list($id) = $this->escape_input((int) $id); + + $date = time(); + $q = "UPDATE " . TB_PREFIX . "forum_topic set post_date = '$date' where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function EditUpdateTopic($id, $post) { + list($id, $post) = $this->escape_input((int) $id, $post); + + $q = "UPDATE " . TB_PREFIX . "forum_topic set post = '$post' where id = $id"; + + return mysqli_query($this->dblink, $q); + } + + function EditUpdatePost($id, $post) { + list($id, $post) = $this->escape_input((int) $id, $post); + + $q = "UPDATE " . TB_PREFIX . "forum_post set post = '$post' where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function LockTopic($id, $mode) { + list($id, $mode) = $this->escape_input((int) $id, $mode); + + $q = "UPDATE " . TB_PREFIX . "forum_topic set close = '$mode' where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function DeleteCat($id) { + list($id) = $this->escape_input($id); + + $qs = "DELETE from " . TB_PREFIX . "forum_cat where id = '$id'"; + $q = "DELETE from " . TB_PREFIX . "forum_topic where cat = '$id'"; + $q2="SELECT id from ".TB_PREFIX."forum_topic where cat ='$id'"; + $result = mysqli_query($this->dblink,$q2); + if (!empty($result)) { + $array=$this->mysqli_fetch_all($result); + $toDelete = []; + foreach($array as $ss) { + $toDelete[] = $ss['id']; + } + $this->DeleteSurvey($toDelete); + } + mysqli_query($this->dblink,$qs); + return mysqli_query($this->dblink,$q); + } + + function DeleteSurvey($id) { + if (!is_array($id)) { + $id = [$id]; + } + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + + $qs = "DELETE from " . TB_PREFIX . "forum_survey where topic IN(".implode(', ', $id).")"; + return mysqli_query($this->dblink,$qs); + } + + function DeleteTopic($id) { + list($id) = $this->escape_input($id); + + $qs = "DELETE from " . TB_PREFIX . "forum_topic where id = '$id'"; + return mysqli_query($this->dblink,$qs); + } + + function DeletePost($id) { + list($id) = $this->escape_input($id); + + $q = "DELETE from " . TB_PREFIX . "forum_post where id = '$id'"; + return mysqli_query($this->dblink,$q); + } + + function setAlliForumdblink($aid, $dblink) { + list($aid, $dblink) = $this->escape_input((int) $aid, $dblink); + + $q = "UPDATE " . TB_PREFIX . "alidata SET `forumlink` = '$dblink' WHERE id = $aid"; + return mysqli_query($this->dblink,$q); + } +} diff --git a/GameEngine/Database/DatabaseHeroQueries.php b/GameEngine/Database/DatabaseHeroQueries.php new file mode 100644 index 00000000..d4006641 --- /dev/null +++ b/GameEngine/Database/DatabaseHeroQueries.php @@ -0,0 +1,234 @@ +escape_input((int) $uid,$all); + + // 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::$heroCache, $uid.$all.($include_dead ? 1 : 0))) && !is_null($cachedValue)) { + return $cachedValue; + } + + if ($all) { + $q = "SELECT * FROM ".TB_PREFIX."hero WHERE uid=$uid ORDER BY lastupdate DESC"; + } elseif (!$uid) { + $q = "SELECT * FROM ".TB_PREFIX."hero"; + } else { + $q = "SELECT * FROM ".TB_PREFIX."hero WHERE ".($include_dead ? '' : "dead=0 AND ")."uid=$uid LIMIT 1"; + } + + $result = mysqli_query($this->dblink,$q); + if (!empty($result)) { + self::$heroCache[$uid.$all.($include_dead ? 1 : 0)] = $this->mysqli_fetch_all($result); + } else { + self::$heroCache[$uid.$all.($include_dead ? 1 : 0)] = null; + } + + return self::$heroCache[$uid.$all.($include_dead ? 1 : 0)]; + } + + function getHeroField($uid,$field, $use_cache = true) { + list($uid,$field) = $this->escape_input((int) $uid,$field); + + // 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::$heroFieldCache, $uid.$field)) && !is_null($cachedValue)) { + return $cachedValue[$field]; + } + + $q = "SELECT * FROM ".TB_PREFIX."hero WHERE uid = $uid AND dead = 0"; + $result = mysqli_query($this->dblink,$q); + + self::$heroFieldCache[$uid.$field] = $this->mysqli_fetch_all($result)[0]; + return self::$heroFieldCache[$uid.$field][$field]; + } + + function modifyHero($column,$value,$heroid,$mode=null) { + if (!is_array($column)) { + $column = [$column]; + $value = [$value]; + $mode = [$mode]; + } + + $pairs = []; + foreach ($column as $index => $columnValue) { + if($mode[$index] === null) { + $pairs[] = "$columnValue = ".(Math::isInt($value[$index]) ? $value[$index] : '"'.$this->escape($value[$index]).'"'); + } elseif($mode[$index]=1) { + $pairs[] = "$columnValue = $columnValue + ".(int) $value[$index]; + } else { + $pairs[] = "$columnValue = $columnValue - ".(int) $value[$index]; + } + } + + $q = "UPDATE `".TB_PREFIX."hero` SET ".implode(', ', $pairs)." WHERE heroid = $heroid"; + return mysqli_query($this->dblink,$q); + } + + function modifyHeroXp($column,$value,$heroid) { + list($column,$value,$heroid) = $this->escape_input($column,(int) $value,(int) $heroid); + + $q = "UPDATE ".TB_PREFIX."hero SET $column = $column + $value WHERE heroid=$heroid"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getHeroDeadReviveOrInTraining($id) { + $id = (int) $id; + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "hero WHERE `uid` = $id AND dead = 0 AND inrevive = 0 AND intraining = 0"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + /*************************** + Function to Kill hero if not found + Made by: Shadow and brainiacX + ***************************/ + function KillMyHero($id) { + list( $id ) = $this->escape_input( (int) $id ); + + $q = "UPDATE " . TB_PREFIX . "hero set dead = 1, intraining = 0, inrevive = 0, health = 0 where uid = " . $id . " AND dead = 0"; + return mysqli_query( $this->dblink, $q ); + } + + /*************************** + Function to find Hero place + Made by: ronix + ***************************/ + // no need to cache this method + function FindHeroInVil($wid) { + list($wid) = $this->escape_input($wid); + + $result = $this->query("SELECT hero FROM ".TB_PREFIX."units WHERE hero>0 AND vref='".$wid."' LIMIT 1"); + if (!empty($result)) { + $dbarray = mysqli_fetch_array($result); + if(isset($dbarray['hero'])) { + $this->query("UPDATE ".TB_PREFIX."units SET hero=0 WHERE vref='".$wid."'"); + unset($dbarray); + return true; + } + } + return false; + } + + // no need to cache this method + function FindHeroInDef($wid) { + list($wid) = $this->escape_input($wid); + + $delDef=true; + $result = $this->query_return("SELECT * FROM ".TB_PREFIX."enforcement WHERE hero>0 AND `from` = ".$wid); + if (!empty($result)) { + $dbarray = $result; + if(isset($dbarray['hero'])) { + $this->query("UPDATE ".TB_PREFIX."enforcement SET hero=0 WHERE `from` = ".$wid); + for ($i=0;$i<50;$i++) { + if($dbarray['u'.$i]>0) { + $delDef=false; + break; + } + } + if ($delDef) $this->deleteReinf($wid); + unset($dbarray); + return true; + } + } + return false; + } + + // no need to cache this method + function FindHeroInOasis($uid) { + list($uid) = $this->escape_input($uid); + + $delDef=true; + $dbarray = $this->query_return("SELECT e.*,o.conqured,o.owner FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.owner=".$uid." AND e.hero>0"); + if(!empty($dbarray)) { + foreach($dbarray as $defoasis) { + if($defoasis['hero']>0) { + $this->query("UPDATE ".TB_PREFIX."enforcement SET hero=0 WHERE `from` = ".$defoasis['from']); + for ($i=0;$i<50;$i++) { + if($dbarray['u'.$i]>0) { + $delDef=false; + break; + } + } + if ($delDef) $this->deleteReinf($defoasis['from']); + unset($dbarray); + return true; + } + } + } + return 0; + } + + // no need to cache this method + function FindHeroInMovement($wid) { + list($wid) = $this->escape_input($wid); + + $outgoingarray = $this->getMovement(3, $wid, 0); + if(!empty($outgoingarray)) { + foreach($outgoingarray as $out) { + if ($out['t11']>0) { + $dbarray = $this->query("UPDATE ".TB_PREFIX."attacks SET t11=0 WHERE `id` = ".$out['ref']); + return true; + break; + } + } + } + $returningarray = $this->getMovement(4, $wid, 1); + if(!empty($returningarray)) { + foreach($returningarray as $ret) { + if($ret['attack_type'] != 1 && $ret['t11']>0) { + $dbarray = $this->query("UPDATE ".TB_PREFIX."attacks SET t11=0 WHERE `id` = ".$ret['ref']); + return true; + break; + } + } + } + return false; + } + + /** + * Register the hero to the capital village and kills it + * + * @param int $wref The village ID where the hero is registered + * @return bool Return true if the query was successful, false otherwise + */ + + function reassignHero($wref){ + list($wref) = $this->escape_input($wref); + + $q = "UPDATE + ".TB_PREFIX."hero AS hero + INNER JOIN ".TB_PREFIX."vdata AS vdata + ON vdata.owner = hero.uid AND vdata.capital = 1 + SET + hero.dead = 1, hero.health = 0, hero.wref = vdata.wref + WHERE + hero.wref = $wref"; + return mysqli_query($this->dblink, $q); + } +} diff --git a/GameEngine/Database/DatabaseMarketQueries.php b/GameEngine/Database/DatabaseMarketQueries.php new file mode 100644 index 00000000..39f8b4d8 --- /dev/null +++ b/GameEngine/Database/DatabaseMarketQueries.php @@ -0,0 +1,327 @@ +dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function getMarketLogVillage($village) { + list($village) = $this->escape_input((int) $village); + + $q = "SELECT wref,owner,name from " . TB_PREFIX . "vdata where wref =$village "; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + function getMarketLogUsers($id_user) { + list($id_user) = $this->escape_input((int) $id_user); + + $q = "SELECT id,username from " . TB_PREFIX . "users where id = $id_user "; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + /** + * Delete expired trade routes + * + */ + + function delTradeRoute() { + $time = time(); + $q = "DELETE from " . TB_PREFIX . "route where timeleft < $time"; + return mysqli_query($this->dblink, $q); + } + + function createTradeRoute($uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time) { + list($uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time) = $this->escape_input((int) $uid,(int) $wid,(int) $from,(int) $r1,(int) $r2,(int) $r3,(int) $r4,(int) $start,(int) $deliveries,(int) $merchant,(int) $time); + + $x = "UPDATE " . TB_PREFIX . "users SET gold = gold - 2 WHERE id = " . $uid; + mysqli_query( $this->dblink, $x ); + $timeleft = time() + 604800; + $q = "INSERT into " . TB_PREFIX . "route values (0,$uid,$wid,$from,$r1,$r2,$r3,$r4,$start,$deliveries,$merchant,$time,$timeleft)"; + + return mysqli_query( $this->dblink, $q ); + } + + // no need to cache this method + function getTradeRoute($from) { + list($from) = $this->escape_input((int) $from); + + $q = "SELECT * FROM " . TB_PREFIX . "route where `from` = $from ORDER BY timestamp ASC"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getTradeRoute2($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * FROM " . TB_PREFIX . "route where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray; + } + + // no need to cache this method + function getTradeRouteUid($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT uid FROM " . TB_PREFIX . "route where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['uid']; + } + + function editTradeRoute($id,$column,$value,$mode) { + list($id,$column,$value,$mode) = $this->escape_input((int) $id,$column,(int) $value,$mode); + + if ( ! $mode ) { + $q = "UPDATE " . TB_PREFIX . "route set $column = $value where id = $id"; + } else { + $q = "UPDATE " . TB_PREFIX . "route set $column = $column + $value where id = $id"; + } + + return mysqli_query( $this->dblink, $q ); + } + + function deleteTradeRoute($id) { + list($id) = $this->escape_input((int) $id); + + $q = "DELETE FROM " . TB_PREFIX . "route where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function deleteTradeRoutesByVillage($id) { + list($id) = $this->escape_input((int) $id); + + $q = "DELETE FROM " . TB_PREFIX . "route where `from` = $id"; + return mysqli_query($this->dblink,$q); + } + + /*************************** + Function to set accept flag on market + References: id + ***************************/ + function setMarketAcc($id) { + if (!is_array($id)) { + $id = [$id]; + } + + foreach ($id as $index => $value) { + $id[$index] = (int) $value; + } + + $q = "UPDATE " . TB_PREFIX . "market set accept = 1 where id IN(".implode(', ', $id ).")"; + return mysqli_query($this->dblink,$q); + } + + /*************************** + Function to send resource to other village + Mode 0: Send + Mode 1: Cancel + References: Wood/ID, Clay, Iron, Crop, Mode + ***************************/ + function sendResource($ref, $clay, $iron, $crop, $merchant, $mode) { + // always prepare for multiple inserts at once + if (!is_array($ref)) { + $ref = [$ref]; + $clay = [$clay]; + $iron = [$iron]; + $crop = [$crop]; + $merchant = [$merchant]; + } + + $pairs = []; + foreach ($ref as $index => $refValue) { + if(!$mode) { + $pairs[] = '(0, ' . (int) $refValue . ', ' . (int) $clay[$index] . ', ' . (int) $iron[$index] . ', ' . (int) $crop[$index] . ', ' . (int) $merchant[$index] . ')'; + } else { + $pairs[] = (int) $refValue; + } + } + + if(!$mode) { + $q = "INSERT INTO " . TB_PREFIX . "send VALUES ".implode(', ', $pairs); + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } else { + $q = "DELETE FROM " . TB_PREFIX . "send WHERE id IN(".implode(', ', $pairs).")"; + return mysqli_query($this->dblink,$q); + } + } + + /*************************** + Function to get resources back if you delete offer + References: VillageRef (vref) + Made by: Dzoki + ***************************/ + + function getResourcesBack($vref, $gtype, $gamt) { + list($vref, $gtype, $gamt) = $this->escape_input((int) $vref, (int) $gtype, (int) $gamt); + + //Xtype (1) = wood, (2) = clay, (3) = iron, (4) = crop + if($gtype == 1) { + $q = "UPDATE " . TB_PREFIX . "vdata SET `wood` = `wood` + $gamt WHERE wref = $vref"; + return mysqli_query($this->dblink,$q); + } else + if($gtype == 2) { + $q = "UPDATE " . TB_PREFIX . "vdata SET `clay` = `clay` + $gamt WHERE wref = $vref"; + return mysqli_query($this->dblink,$q); + } else + if($gtype == 3) { + $q = "UPDATE " . TB_PREFIX . "vdata SET `iron` = `iron` + $gamt WHERE wref = $vref"; + return mysqli_query($this->dblink,$q); + } else + if($gtype == 4) { + $q = "UPDATE " . TB_PREFIX . "vdata SET `crop` = `crop` + $gamt WHERE wref = $vref"; + return mysqli_query($this->dblink,$q); + } + } + + /*************************** + Function to get info about offered resources + References: VillageRef (vref) + Made by: Dzoki + ***************************/ + + function getMarketField($vref, $id, $field, $use_cache = true) { + list($vref, $id, $field) = $this->escape_input($vref, $id, $field); + + // 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::$marketFieldCache, $vref.$field)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "market WHERE id = $id AND vref = $vref"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + + self::$marketFieldCache[$vref.$field] = $dbarray[$field]; + return self::$marketFieldCache[$vref.$field]; + } + + function removeAcceptedOffer($id) { + list($id) = $this->escape_input((int) $id); + + $q = "DELETE FROM " . TB_PREFIX . "market where id = $id"; + $result = mysqli_query($this->dblink,$q); + return mysqli_fetch_assoc($result); + } + + /** + * Function to add market offer + * + * Mode 0: Add + * Mode 1: Cancel + * References: Village, Give, Amt, Want, Amt, Time, Alliance, Mode + */ + + function addMarket($vid, $gtype, $gamt, $wtype, $wamt, $time, $alliance, $merchant, $mode) { + list($vid, $gtype, $gamt, $wtype, $wamt, $time, $alliance, $merchant, $mode) = $this->escape_input((int) $vid, (int) $gtype, (int) $gamt, (int) $wtype, (int) $wamt, (int) $time, (int) $alliance, (int) $merchant, $mode); + + if(!$mode) { + $q = "INSERT INTO " . TB_PREFIX . "market values (0,$vid,$gtype,$gamt,$wtype,$wamt,0,$time,$alliance,$merchant)"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } else { + $q = "DELETE FROM " . TB_PREFIX . "market where id = $gtype and vref = $vid"; + return mysqli_query($this->dblink,$q); + } + } + + /*************************** + Function to get market offer + References: Village, Mode + ***************************/ + // no need to cache this method + function getMarket($vid, $mode) { + list($vid, $mode) = $this->escape_input((int) $vid, $mode); + + $alliance = (int) $this->getUserField($this->getVillageField($vid, "owner"), "alliance", 0); + if(!$mode) { + $q = "SELECT * FROM " . TB_PREFIX . "market where vref = $vid and accept = 0"; + } else { + $q = "SELECT * FROM " . TB_PREFIX . "market where vref != $vid and alliance = $alliance or vref != $vid and alliance = 0 and accept = 0"; + } + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + /*************************** + Function to get market offer + References: ID + ***************************/ + // no need to cache this method + function getMarketInfo($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * FROM " . TB_PREFIX . "market where id = $id"; + $result = mysqli_query($this->dblink,$q); + return mysqli_fetch_assoc($result); + } + + /*************************** + Function to retrieve used merchant + References: Village + ***************************/ + function totalMerchantUsed($vid, $use_cache = true) { + list($vid) = $this->escape_input((int) $vid); + + // 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::$merchantsUseCountCache, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + self::$merchantsUseCountCache[$vid] = mysqli_fetch_array(mysqli_query($this->dblink, ' + SELECT + IFNULL((SELECT sum('.TB_PREFIX.'send.merchant) FROM '.TB_PREFIX.'send, '.TB_PREFIX.'movement WHERE '.TB_PREFIX.'movement.`from` = '.$vid.' AND '.TB_PREFIX.'send.id = '.TB_PREFIX.'movement.ref AND '.TB_PREFIX.'movement.proc = 0 AND sort_type = 0), 0) + + IFNULL((SELECT sum(ref) FROM '.TB_PREFIX.'movement WHERE sort_type = 2 AND '.TB_PREFIX.'movement.`to` = '.$vid.' AND proc = 0), 0) + + IFNULL((SELECT sum(merchant) FROM '.TB_PREFIX.'market WHERE vref = '.$vid.' AND accept = 0), 0) + as merchants_used' + ), MYSQLI_ASSOC)['merchants_used']; + + return self::$merchantsUseCountCache[$vid]; + } + + /*************************** + Function to acquire/release MySQL advisory lock for merchant operations + Prevents race conditions when sending merchants concurrently + ***************************/ + function getMerchantLock($vid, $timeout = 10) + { + $lockName = TB_PREFIX . 'merchant_' . (int)$vid; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('$lockName', $timeout) AS lock_acquired"); + $row = mysqli_fetch_assoc($result); + return $row['lock_acquired'] == 1; + } + + function releaseMerchantLock($vid) + { + $lockName = TB_PREFIX . 'merchant_' . (int)$vid; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('$lockName')"); + } +} diff --git a/GameEngine/Database/DatabaseMessageQueries.php b/GameEngine/Database/DatabaseMessageQueries.php new file mode 100644 index 00000000..74a4a475 --- /dev/null +++ b/GameEngine/Database/DatabaseMessageQueries.php @@ -0,0 +1,291 @@ +getUserField($uid, 'access', 0) == ADMIN) && ADMIN_RECEIVE_SUPPORT_MESSAGES) { + $ids[] = 1; + } + + if ($this->getUserField($uid, 'access', 0) == MULTIHUNTER) { + $ids[] = 5; + } + + $q = 'SELECT Count(*) as numUnread FROM '.TB_PREFIX.'mdata WHERE target IN('.implode(', ', $ids).') AND viewed = 0'; + return mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC)['numUnread']; + } + + // no need to cache this method + function getUnreadNoticesCount($uid) { + $uid = (int) $uid; + + return mysqli_fetch_array(mysqli_query($this->dblink, ' + SELECT Count(*) as numUnread FROM '.TB_PREFIX.'ndata WHERE uid = '.$uid.' AND viewed = 0' + ), MYSQLI_ASSOC)['numUnread']; + } + + function sendMessage($client, $owner, $topic, $message, $send, $alliance, $player, $coor, $report, $skip_escaping = false) { + if (!$skip_escaping) { + list($client, $owner, $topic, $message, $send, $alliance, $player, $coor, $report) = $this->escape_input((int) $client, (int) $owner, $topic, $message, (int) $send, (int) $alliance, (int) $player, (int) $coor, (int) $report); + } + + $time = time(); + + // add this message to the query cache, so we save some queries + // if we need to send multiple messages at once + self::$sendMessageQueryCache[] = "(0,$client,$owner,'$topic','$message',0,0,$send,$time,0,0,$alliance,$player,$coor,$report)"; + + // check if we don't have too many messages to be sent out cached, + // in which case we'll flush the cache and start again + $retValue = true; + if (count(self::$sendMessageQueryCache) >= self::$sendMessageQueryCacheMaxRecords) { + $retValue = mysqli_query($this->dblink, "INSERT INTO " . TB_PREFIX . "mdata VALUES " . implode(', ', self::$sendMessageQueryCache)); + self::$sendMessageQueryCache = []; + } + + return $retValue; + } + + public function sendPendingMessages() { + if (count(self::$sendMessageQueryCache)) { + mysqli_query($this->dblink, "INSERT INTO " . TB_PREFIX . "mdata VALUES " . implode(', ', self::$sendMessageQueryCache)); + } + } + + function setArchived($id) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + $q = "UPDATE " . TB_PREFIX . "mdata set archived = 1 where id IN(".implode(', ', $id).")"; + return mysqli_query($this->dblink,$q); + } + + function setNorm($id) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + $q = "UPDATE " . TB_PREFIX . "mdata set archived = 0 where id IN(".implode(',', $id).")"; + return mysqli_query($this->dblink,$q); + } + +/*************************** +Function to get messages +Mode 1: Get inbox +Mode 2: Get sent +Mode 3: Get message +Mode 4: Set viewed +Mode 5: Remove message +Mode 6: Retrieve archive +References: User ID/Message ID, Mode +***************************/ + // no need to cache this method + function getMessage($id, $mode) { + global $session; + + $mode = (int) $mode; + $mode_updated = false; + // update $id if we should show Support messages for Admins and we are an admin + if ( + $session->access == ADMIN + && ADMIN_RECEIVE_SUPPORT_MESSAGES + && in_array($mode, [1,2,6,9,10,11]) + ) { + $id = $id . ', 1'; + $mode_updated = true; + } + + // update $id if we should show Multihunter messages for the current player + if ( + $session->access == MULTIHUNTER + && in_array($mode, [1,2,6,9,10,11]) + ) { + $id = $id . ', 5'; + $mode_updated = true; + } + + if (in_array($mode, [5,7,8])) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + } else { + if (!$mode_updated) { + $id = (int) $id; + } + } + + switch($mode) { + case 1: + $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE target IN($id) and send = 0 and archived = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + case 2: + $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE owner IN($id) ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + case 3: + $q = "SELECT * FROM " . TB_PREFIX . "mdata where id = $id"; + break; + case 4: + $show_target = $session->uid; + if ($session->access == ADMIN && ADMIN_RECEIVE_SUPPORT_MESSAGES) $show_target .= ',1'; + if ($session->access == MULTIHUNTER) $show_target .= ',5'; + + $q = "UPDATE " . TB_PREFIX . "mdata set viewed = 1 where id = $id AND target IN(".$show_target.")"; + break; + case 5: + $q = "UPDATE " . TB_PREFIX . "mdata set deltarget = 1, viewed = 1 where id IN(".implode(', ', $id).")"; + break; + case 6: + $q = "SELECT * FROM " . TB_PREFIX . "mdata where target IN($id) and send = 0 and archived = 1 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + case 7: + $q = "UPDATE " . TB_PREFIX . "mdata set delowner = 1 where id IN(".implode(', ', $id).")"; + break; + case 8: + $q = "UPDATE " . TB_PREFIX . "mdata set deltarget = 1, delowner = 1, viewed = 1 where id IN(".implode(', ', $id).")"; + break; + case 9: + $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE target IN($id) and send = 0 and archived = 0 and deltarget = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + case 10: + $q = "SELECT * FROM " . TB_PREFIX . "mdata WHERE owner IN($id) and delowner = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + case 11: + $q = "SELECT * FROM " . TB_PREFIX . "mdata where target IN($id) and send = 0 and archived = 1 and deltarget = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + break; + } + + if($mode <= 3 || $mode == 6 || $mode > 8) { + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + else return mysqli_query($this->dblink,$q); + } + + function unarchiveNotice($id) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + $q = "UPDATE " . TB_PREFIX . "ndata set ntype = archive, archive = 0 where id IN(".implode(',', $id).")"; + return mysqli_query($this->dblink,$q); + } + + function archiveNotice($id) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + $q = "update " . TB_PREFIX . "ndata set archive = ntype, ntype = 9 where id IN(".implode(',', $id).")"; + return mysqli_query($this->dblink,$q); + } + + function removeNotice($id) { + if (!is_array($id)) { + $id = [$id]; + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + $q = "UPDATE " . TB_PREFIX . "ndata set del = 1,viewed = 1 where id IN(".implode(',', $id).")"; + return mysqli_query($this->dblink,$q); + } + + function noticeViewed($id) { + list($id) = $this->escape_input((int) $id); + + $q = "UPDATE " . TB_PREFIX . "ndata set viewed = 1 where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function addNotice($uid, $toWref, $ally, $type, $topic, $data, $time = 0) { + list($uid, $toWref, $ally, $type, $topic, $data, $time) = $this->escape_input((int) $uid, (int) $toWref, (int) $ally, (int) $type, $topic, $data, (int) $time); + + //We don't need to send reports to Nature or Natars + if($uid == 2 || $uid == 3) return; + if($time == 0) $time = time(); + + $q = "INSERT INTO " . TB_PREFIX . "ndata (id, uid, toWref, ally, topic, ntype, data, time, viewed) values (0,'$uid','$toWref','$ally','$topic',$type,'$data',$time,0)"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getNotice($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT * FROM " . TB_PREFIX . "ndata where uid = $uid and del = 0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function getNotice2($id, $field = null, $use_cache = true) { + list($id, $field) = $this->escape_input((int) $id, $field); + + // 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::$noticesCacheById, $id)) && !is_null($cachedValue)) { + return $cachedValue[$field]; + } + + $q = "SELECT * FROM " . TB_PREFIX . "ndata where `id` = $id ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC')." LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + + self::$noticesCacheById[$id] = $dbarray; + return is_null($field) ? self::$noticesCacheById[$id] : self::$noticesCacheById[$id][$field]; + } + + function getUnViewNotice($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT * FROM " . TB_PREFIX . "ndata where uid = $uid AND viewed=0 ORDER BY time ".(isset($_GET['o']) && $_GET['o'] == 1 ? 'ASC' : 'DESC'); + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } +} diff --git a/GameEngine/Database/DatabaseMovementQueries.php b/GameEngine/Database/DatabaseMovementQueries.php new file mode 100644 index 00000000..ec57271d --- /dev/null +++ b/GameEngine/Database/DatabaseMovementQueries.php @@ -0,0 +1,710 @@ +escape_input($moveid); + } + + if(empty($moveid)) return; + + // rather than re-selecting data and updating cache here, let's just + // flush the cache and let it re-cach itself as neccessary + self::$marketMovementCache = []; + + $q = "UPDATE " . TB_PREFIX . "movement set proc = 1 where moveid IN($moveid)"; + return mysqli_query($this->dblink,$q); + } + + function getMovement($type, $village, $mode, $use_cache = true) { + $array_passed = is_array($village); + + if (!$array_passed) { + $village = [(int) $village]; + } else { + foreach ($village as $index => $villageValue) { + $village[$index] = (int) $villageValue; + } + } + + if (!count($village)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$marketMovementCache[$type.$village[0].$mode]) && is_array(self::$marketMovementCache[$type.$village[0].$mode]) && !count(self::$marketMovementCache[$type.$village[0].$mode])) { + return self::$marketMovementCache[$type.$village[0].$mode]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newIDs = []; + foreach ($village as $key) { + if (!isset(self::$marketMovementCache[$type.$key.$mode])) { + $newIDs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newIDs)) { + return self::$marketMovementCache; + } else { + // update remaining IDs to select and cache + $village = $newIDs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$marketMovementCache, $type.$village[0].$mode)) && !is_null($cachedValue)) { + // special case when we have empty arrays cached for this cache only + return ($array_passed ? self::$marketMovementCache: $cachedValue); + } + + $time = time(); + if(!$mode) { + $where = "from"; + } else { + $where = "to"; + } + switch($type) { + case 0: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "send where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "send.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 0 ORDER BY endtime ASC"; + break; + case 1: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "send where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "send.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 6 ORDER BY endtime ASC"; + break; + case 2: + $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 2 ORDER BY endtime ASC"; + break; + case 3: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; + break; + case 4: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 4 ORDER BY endtime ASC"; + break; + case 5: + $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 5 and proc = 0 ORDER BY endtime ASC"; + break; + case 6: + $q = "SELECT * FROM " . TB_PREFIX . "movement," . TB_PREFIX . "odata, " . TB_PREFIX . "attacks where " . TB_PREFIX . "odata.wref IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.to IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "attacks.attack_type != 1 and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; + //$q = "SELECT * FROM " . TB_PREFIX . "movement," . TB_PREFIX . "odata, " . TB_PREFIX . "attacks where " . TB_PREFIX . "odata.conqured IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.to = " . TB_PREFIX . "odata.wref and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 ORDER BY endtime ASC"; + break; + case 7: + $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 4 and ref = 0 and proc = 0 ORDER BY endtime ASC"; + break; + case 8: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 and " . TB_PREFIX . "attacks.attack_type = 1 ORDER BY endtime ASC"; + break; + // T4 hero port: hero travelling to an adventure (plain select, + // ref points to hero_adventure - NOT to attacks, so no join). + case 20: + $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 20 and proc = 0 ORDER BY endtime ASC"; + break; + // T4 hero port: hero returning from an adventure. + case 21: + $q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 21 and proc = 0 ORDER BY endtime ASC"; + break; + case 34: + $q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 or " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 4 ORDER BY endtime ASC"; + break; + default: + return []; + } + + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$marketMovementCache[$type.$village[0].$mode] = $result; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + self::$marketMovementCache[ $type . $record[ $where ] . $mode ][] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no movements were found for these villages + foreach ($village as $key) { + if (!isset(self::$marketMovementCache[$type.$key.$mode])) { + self::$marketMovementCache[$type.$key.$mode] = []; + } + } + } + + return ($array_passed ? self::$marketMovementCache : self::$marketMovementCache[$type.$village[0].$mode]); + } + + function addA2b($ckey, $timestamp, $to, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type) { + list($ckey, $timestamp, $to, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type) = $this->escape_input($ckey, (int) $timestamp, (int) $to, (int) $t1, (int) $t2, (int) $t3, (int) $t4, (int) $t5, (int) $t6, (int) $t7, (int) $t8, (int) $t9, (int) $t10, (int) $t11, (int) $type); + + $q = "INSERT INTO " . TB_PREFIX . "a2b (ckey,time_check,to_vid,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,type) VALUES ('$ckey', '$timestamp', '$to', '$t1', '$t2', '$t3', '$t4', '$t5', '$t6', '$t7', '$t8', '$t9', '$t10', '$t11', '$type')"; + mysqli_query($this->dblink,$q); + return mysqli_insert_id($this->dblink); + } + + function remA2b($id) { + $id = (int) $id; + + $q = "DELETE FROM " . TB_PREFIX . "a2b WHERE id = $id"; + return mysqli_query($this->dblink,$q); + } + + function claimA2b($id, $ckey) { + $id = (int)$id; + list($ckey) = $this->escape_input($ckey); + + $q = "DELETE FROM " . TB_PREFIX . "a2b WHERE id = $id AND ckey = '".$ckey."' LIMIT 1"; + mysqli_query($this->dblink, $q); + + return (mysqli_affected_rows($this->dblink) === 1); + } + + // no need to cache this method + function getA2b($ckey) { + list($ckey) = $this->escape_input($ckey); + + $q = "SELECT * from " . TB_PREFIX . "a2b where ckey = '" . $ckey . "'"; + $result = mysqli_query($this->dblink,$q); + if($result) return mysqli_fetch_assoc($result); + else return false; + } + + function addMovement($type, $from, $to, $ref, $time, $endtime, $send = 1, $wood = 0, $clay = 0, $iron = 0, $crop = 0, $ref2 = 0) { + // always prepare for multiple inserts at once + if (!is_array($type)) { + $type = [$type]; + $from = [$from]; + $to = [$to]; + $ref = [$ref]; + $time = [$time]; + $endtime = [$endtime]; + $send = [$send]; + $wood = [$wood]; + $clay = [$clay]; + $iron = [$iron]; + $crop = [$crop]; + $ref2 = [$ref2]; + } + + $counter = 0; + $pairs = []; + + foreach ($type as $index => $typeValue) { + $pairs[] = '(0, '.(int) $typeValue.', '.(int) $from[$index].', '.(int) $to[$index].', '.(int) $ref[$index].', '.(int) $ref2[$index].', '.(int) $time[$index].', '.(int) $endtime[$index].', 0, '.(int) $send[$index].', '.(int) $wood[$index].', '.(int) $clay[$index].', '.(int) $iron[$index].', '.(int) $crop[$index].')'; + + if ($counter++ > 25) { + $q = "INSERT INTO " . TB_PREFIX . "movement (moveid, sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop) VALUES ".implode(', ', $pairs); + mysqli_query($this->dblink,$q); + + $pairs = []; + $counter = 0; + } + } + + if ($counter > 0) { + $q = "INSERT INTO " . TB_PREFIX . "movement (moveid, sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop) VALUES " . implode( ', ', $pairs ); + return mysqli_query( $this->dblink, $q ); + } else { + return true; + } + } + + function addAttack($vid, $t1, $t2, $t3, $t4, $t5, $t6, $t7, $t8, $t9, $t10, $t11, $type, $ctar1, $ctar2, $spy,$b1=0,$b2=0,$b3=0,$b4=0,$b5=0,$b6=0,$b7=0,$b8=0) { + if (!is_array($vid)) { + $vid = [$vid]; + $t1 = [$t1]; + $t2 = [$t2]; + $t3 = [$t3]; + $t4 = [$t4]; + $t5 = [$t5]; + $t6 = [$t6]; + $t7 = [$t7]; + $t8 = [$t8]; + $t9 = [$t9]; + $t10 = [$t10]; + $t11 = [$t11]; + $type = [$type]; + $ctar1 = [$ctar1]; + $ctar2 = [$ctar2]; + $spy = [$spy]; + $b1 = [$b1]; + $b2 = [$b2]; + $b3 = [$b3]; + $b4 = [$b4]; + $b5 = [$b5]; + $b6 = [$b6]; + $b7 = [$b7]; + $b8 = [$b8]; + } + + $values = []; + foreach ($vid as $index => $vidValue) { + $values[] = '(0, '.(int) $vidValue.', '.(int) $t1[$index].', '.(int) $t2[$index].', '.(int) $t3[$index].', '. + (int) $t4[$index].', '.(int) $t5[$index].', '.(int) $t6[$index].', '.(int) $t7[$index].', '. + (int) $t8[$index].', '.(int) $t9[$index].', '.(int) $t10[$index].', '.(int) $t11[$index]. + ', '.(int) $type[$index].', '.(int) $ctar1[$index].', '.(int) $ctar2[$index].', '. + (int) $spy[$index].', '.(int) $b1[$index].', '.(int) $b2[$index].', '.(int) $b3[$index]. + ', '.(int) $b4[$index].', '.(int) $b5[$index].', '.(int) $b6[$index].', '.(int) $b7[$index]. + ', '.(int) $b8[$index].')'; + } + + $q = "INSERT INTO " . TB_PREFIX . "attacks VALUES ".implode(', ', $values); + mysqli_query($this->dblink,$q); + + return (count($vid) == 1 ? mysqli_insert_id($this->dblink) : true); + } + + function modifyAttack($aid, $unit, $amt) { + list($aid, $unit, $amt) = $this->escape_input((int) $aid, $unit, (int) $amt); + + $unit = 't' . $unit; + $q = "UPDATE " . TB_PREFIX . "attacks set $unit = $unit - $amt where id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function modifyAttack2($aid, $unit, $amt, $mode = 1) { + list($aid, $unit, $amt) = $this->escape_input((int) $aid, $unit, $amt); + + if (!is_array($unit)) { + $unit = [$unit]; + $amt = [$amt]; + } + + $pairs = []; + foreach ($unit as $index => $unitValue) { + $unitValue = 't' . $this->escape($unitValue); + $pairs[] = $unitValue . ' = ' . $unitValue . (($mode) ? ' + ' : ' - ') . (int) $amt[$index]; + } + + $q = "UPDATE " . TB_PREFIX . "attacks SET ".implode(', ', $pairs)." WHERE id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function modifyAttack3($aid, $units) { + list($aid, $units) = $this->escape_input((int) $aid, $units); + + $q = "UPDATE ".TB_PREFIX."attacks set $units WHERE id = $aid"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getVillageMovement($id) { + list($id) = $this->escape_input($id); + + $vinfo = $this->getVillage($id); + $vtribe = $this->getUserField($vinfo['owner'], "tribe", 0); + $movingunits = []; + + $outgoingarray = $this->getMovement(3, $id, 0); + if(!empty($outgoingarray) && count($outgoingarray)) { + foreach($outgoingarray as $out) { + for($i = 1; $i <= 10; $i++) { + if (!isset($movingunits['u'.(($vtribe - 1) * 10 + $i)])) { + $movingunits['u'.(($vtribe - 1) * 10 + $i)] = 0; + } + + if (!isset($out['t'.$i])) $out['t'.$i] = 0; + $movingunits['u'.(($vtribe - 1) * 10 + $i)] += $out['t'.$i]; + } + + if (!isset($movingunits['hero'])) $movingunits['hero'] = 0; + if (!isset($out['t11'])) $out['t11'] = 0; + + $movingunits['hero'] += $out['t11']; + } + } + + $returningarray = $this->getMovement(4, $id, 1); + if(!empty($returningarray) && count($returningarray)) { + foreach($returningarray as $ret) { + for($i = 1; $i <= 10; $i++) { + if (!isset($movingunits['u'.(($vtribe - 1) * 10 + $i)])) { + $movingunits['u'.(($vtribe - 1) * 10 + $i)] = 0; + } + $movingunits['u'.(($vtribe - 1) * 10 + $i)] += $ret['t' . $i]; + } + + if (!isset($movingunits['hero'])) $movingunits['hero'] = 0; + $movingunits['hero'] += $ret['t11']; + } + } + + $settlerarray = $this->getMovement(5, $id, 0); + if(!empty($settlerarray)) { + if (!isset($movingunits['u'.($vtribe * 10)])) { + $movingunits['u'.($vtribe * 10)] = 0; + } + $movingunits['u'.($vtribe * 10)] += 3 * count($settlerarray); + } + return $movingunits; + } + + // no need to cache this method + function getMovementById($id) { + list($id) = $this->escape_input((int) $id); + $q = "SELECT * FROM ".TB_PREFIX."movement WHERE moveid = ".$id; + $result = mysqli_query($this->dblink,$q); + $array = $this->mysqli_fetch_all($result); + return $array; + } + + // Rally-point attack marker (issue #245): a defender can tag an incoming + // attack green/yellow/red. The WHERE clause restricts the update to a + // movement whose target village (`to`) belongs to $uid, so a player can + // only mark attacks incoming on their own villages. + function setMovementMarker($moveid, $marker, $uid) { + $moveid = (int) $moveid; + $marker = (int) $marker; + $uid = (int) $uid; + if ($marker < 0 || $marker > 3 || $moveid <= 0 || $uid <= 0) { + return false; + } + $q = "UPDATE ".TB_PREFIX."movement SET marker = ".$marker. + " WHERE moveid = ".$moveid. + " AND `to` IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner = ".$uid.")"; + return mysqli_query($this->dblink, $q) && mysqli_affected_rows($this->dblink) > 0; + } + + // no need to cache this method + function getVilFarmlist($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = 'SELECT * FROM ' . TB_PREFIX . 'farmlist WHERE owner = '.$uid.' ORDER BY wref ASC LIMIT 1'; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return ($dbarray['id'] ?? 0) > 0; + } + + // no need to cache this method + function getRaidList($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * FROM " . TB_PREFIX . "raidlist WHERE id = ".$id." LIMIT 1"; + $result = mysqli_query($this->dblink, $q); + return mysqli_fetch_array($result); + } + + /** + * Get all informations about a farm list + * + * @param int $id The farmlist ID + * @return array Returns the seleted farm list informations + */ + + function getFLData($id) { + list($id) = $this->escape_input((int) $id); + + $q = "SELECT * FROM " . TB_PREFIX . "farmlist where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + return mysqli_fetch_array($result); + } + + + function delFarmList($id, $owner) { + list($id, $owner) = $this->escape_input((int) $id, (int) $owner); + + $q = "DELETE FROM " . TB_PREFIX . "farmlist where id = $id and owner = $owner"; + if(mysqli_query($this->dblink, $q) && mysqli_affected_rows($this->dblink) > 0){ + $q = "DELETE FROM " . TB_PREFIX . "raidlist where lid = $id"; + return mysqli_query($this->dblink, $q); + } + return false; + } + + function delSlotFarm($id, $owner, $lid) { + list($id, $owner, $lid) = $this->escape_input((int) $id, (int) $owner, (int) $lid); + + $q = "DELETE FROM " . TB_PREFIX . "raidlist WHERE id = $id AND lid = $lid AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $lid AND owner = $owner)"; + return mysqli_query($this->dblink,$q); + } + + function createFarmList($wref, $owner, $name) { + list($wref, $owner, $name) = $this->escape_input($wref, $owner, $name); + + $q = "INSERT INTO " . TB_PREFIX . "farmlist (`wref`, `owner`, `name`) VALUES ('$wref', '$owner', '$name')"; + return mysqli_query($this->dblink,$q); + } + + function addSlotFarm($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6) { + list($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6) = $this->escape_input($lid, $towref, $x, $y, $distance, $t1, $t2, $t3, $t4, $t5, $t6); + + for($i = 1; $i <= 6; $i++) { + if (${'t'.$i} == '') { + ${'t'.$i} = 0; + } + } + $q = "INSERT INTO " . TB_PREFIX . "raidlist (`lid`, `towref`, `x`, `y`, `distance`, `t1`, `t2`, `t3`, `t4`, `t5`, `t6`) VALUES ('$lid', '$towref', '$x', '$y', '$distance', '$t1', '$t2', '$t3', '$t4', '$t5', '$t6')"; + return mysqli_query($this->dblink,$q); + } + + function editSlotFarm($eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6) { + list($eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6) = $this->escape_input((int) $eid, $lid, $oldLid, $owner, $wref, $x, $y, $dist, $t1, $t2, $t3, $t4, $t5, $t6); + + for($i = 1; $i <= 6; $i++) { + if (${'t'.$i} == '') { + ${'t'.$i} = 0; + } + } + $q = "UPDATE " . TB_PREFIX . "raidlist SET lid = '$lid', towref = '$wref', x = '$x', y = '$y', distance = '$dist', t1 = '$t1', t2 = '$t2', t3 = '$t3', t4 = '$t4', t5 = '$t5', t6 = '$t6' WHERE id = $eid AND lid = $oldLid AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $lid AND owner = $owner) AND EXISTS(SELECT 1 FROM " . TB_PREFIX . "farmlist WHERE id = $oldLid AND owner = $owner)"; + return mysqli_query($this->dblink,$q); + } + + //general statistics + + function addGeneralAttack($casualties) { + list($casualties) = $this->escape_input($casualties); + + $time = time(); + $q = "INSERT INTO " . TB_PREFIX . "general values (0,'$casualties','$time',1)"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getAttackByDate($time) { + list($time) = $this->escape_input($time); + + $q = "SELECT time FROM " . TB_PREFIX . "general where shown = 1"; + $result = $this->query_return($q); + $attack = 0; + foreach($result as $general) { + if(date("j. M",$time) == date("j. M",$general['time'])){ + $attack += 1; + } + } + return $attack; + } + + // no need to cache this method + function getAttackCasualties($time) { + list($time) = $this->escape_input($time); + + $q = "SELECT time, casualties FROM " . TB_PREFIX . "general where shown = 1"; + $result = $this->query_return($q); + $casualties = 0; + foreach($result as $general){ + if(date("j. M",$time) == date("j. M",$general['time'])){ + $casualties += $general['casualties']; + } + } + return $casualties; + } + + function setVillageEvasion($vid) { + list($vid) = $this->escape_input($vid); + + $village = $this->getVillage((int) $vid); + if($village['evasion'] == 0){ + $q = "UPDATE " . TB_PREFIX . "vdata SET evasion = 1 WHERE wref = $vid"; + }else{ + $q = "UPDATE " . TB_PREFIX . "vdata SET evasion = 0 WHERE wref = $vid"; + } + return mysqli_query($this->dblink,$q); + } + + function addPrisoners($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) { + list($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) = $this->escape_input((int) $wid,(int) $from,(int) $t1,(int) $t2,(int) $t3,(int) $t4,(int) $t5,(int) $t6,(int) $t7,(int) $t8,(int) $t9,(int) $t10,(int) $t11); + + $q = "INSERT INTO " . TB_PREFIX . "prisoners values (0,$wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11)"; + mysqli_query($this->dblink,$q); + self::$prisonersCache = []; + return mysqli_insert_id($this->dblink); + } + + function updatePrisoners($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) { + list($wid,$from,$t1,$t2,$t3,$t4,$t5,$t6,$t7,$t8,$t9,$t10,$t11) = $this->escape_input((int) $wid,(int) $from,(int) $t1,(int) $t2,(int) $t3,(int) $t4,(int) $t5,(int) $t6,(int) $t7,(int) $t8,(int) $t9,(int) $t10,(int) $t11); + + $q = "UPDATE " . TB_PREFIX . "prisoners set t1 = t1 + $t1, t2 = t2 + $t2, t3 = t3 + $t3, t4 = t4 + $t4, t5 = t5 + $t5, t6 = t6 + $t6, t7 = t7 + $t7, t8 = t8 + $t8, t9 = t9 + $t9, t10 = t10 + $t10, t11 = t11 + $t11 where wref = $wid and ".TB_PREFIX."prisoners.from = $from"; + $res = mysqli_query($this->dblink,$q); + self::$prisonersCache = []; + return $res; + } + + /** + * Used to modify prisoners through the inserted id + * + * @param int $id The prisoner id where prisoners are in the database + * @param int $unit The type of the unit + * @param int $amount The amount of the unit you want to sum/subtract + * @param int $mode 0 for subtracting the inserted amount, 1 for adding it + * @return bool Returns false on failure and true on success + */ + + function modifyPrisoners($id, $units, $amount, $mode) { + list($id, $units, $amount, $mode) = $this->escape_input((int) $id, $units, $amount,(int) $mode); + + if (!is_array($units)) + { + $units = [$units]; + $amount = [$amount]; + } + + $prisoners = []; + foreach($units as $index => $unit) + { + $unit = 't'.$this->escape($unit); + $prisoners[] = $unit." = ".$unit.(!$mode ? " - " : " + ").(int)$amount[$index]; + } + + $q = "UPDATE " . TB_PREFIX . "prisoners set ".implode(', ', $prisoners)." WHERE id = $id"; + return mysqli_query($this->dblink,$q); + } + + function getPrisoners($wid, $mode = 0, $use_cache = true) { + $array_passed = is_array($wid); + $mode = (int) $mode; + + if (!$array_passed) { + $wid = [(int) $wid]; + } else { + foreach ($wid as $index => $widValue) { + $wid[$index] = (int) $widValue; + } + } + + if (!count($wid)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$prisonersCache[$wid[0].$mode]) && is_array(self::$prisonersCache[$wid[0].$mode]) && !count(self::$prisonersCache[$wid[0].$mode])) { + return self::$prisonersCache[$wid[0].$mode]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newWIDs = []; + foreach ($wid as $key) { + if (!isset(self::$prisonersCache[$key.$mode])) { + $newWIDs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newWIDs)) { + return self::$prisonersCache; + } else { + // update remaining IDs to select and cache + $wid = $newWIDs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$prisonersCache, $wid[0].$mode)) && !is_null($cachedValue)) { + // special case when we have empty arrays cached for this cache only + return $cachedValue; + } + + if(!$mode) { + $q = "SELECT * FROM " . TB_PREFIX . "prisoners where wref IN(".implode(', ', $wid).")"; + }else { + $q = "SELECT * FROM " . TB_PREFIX . "prisoners where `from` IN(".implode(', ', $wid).")"; + } + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + if (count($result) == 1) { + $result = $result[0]; + } + self::$prisonersCache[$wid[0].$mode] = (count($result) ? [$result] : []); + } else { + if ($result && count($result)) { + foreach ($result as $record) { + $key = $record[($mode ? 'from' : 'wref')] . $mode; + if (!isset(self::$prisonersCache[$key])) { + self::$prisonersCache[$key] = []; + } + self::$prisonersCache[$key][] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no prisoners were found for these villages + foreach ($wid as $key) { + if (!isset(self::$prisonersCache[$key.$mode])) { + self::$prisonersCache[$key.$mode] = []; + } + } +} + + return ($array_passed ? self::$prisonersCache : self::$prisonersCache[$wid[0].$mode]); + } + + function getPrisoners2($wid,$from, $use_cache = true) { + list($wid,$from) = $this->escape_input((int) $wid,(int) $from); + + // 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::$prisonersCacheByVillageAndFromIDs, $wid.$from)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "prisoners where wref = $wid and " . TB_PREFIX . "prisoners.from = $from"; + $result = mysqli_query($this->dblink,$q); + + self::$prisonersCacheByVillageAndFromIDs[$wid.$from] = $this->mysqli_fetch_all($result); + return self::$prisonersCacheByVillageAndFromIDs[$wid.$from]; + } + + function getPrisonersByID($id, $use_cache = true) { + list($id) = $this->escape_input((int) $id); + + // 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::$prisonersCacheByID, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "prisoners where id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + + self::$prisonersCacheByID[$id] = mysqli_fetch_array($result); + return self::$prisonersCacheByID[$id]; + } + + function getPrisoners3($from, $use_cache = true) { + list($from) = $this->escape_input((int) $from); + + // 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::$prisonersCacheByVillageAndFromIDs, $from)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "prisoners where " . TB_PREFIX . "prisoners.from = $from"; + $result = mysqli_query($this->dblink,$q); + + self::$prisonersCacheByVillageAndFromIDs[$from] = $this->mysqli_fetch_all($result); // FIX: scos $wid + return self::$prisonersCacheByVillageAndFromIDs[$from]; + } + + function deletePrisoners($id) { + if (!is_array($id)) { + $id = [$id]; + } + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + + $q = "DELETE FROM " . TB_PREFIX . "prisoners WHERE id IN(".implode(', ', $id).")"; + mysqli_query($this->dblink,$q); + + self::$prisonersCache = []; + } +} diff --git a/GameEngine/Database/DatabaseStatisticsQueries.php b/GameEngine/Database/DatabaseStatisticsQueries.php new file mode 100644 index 00000000..f7ae31dc --- /dev/null +++ b/GameEngine/Database/DatabaseStatisticsQueries.php @@ -0,0 +1,226 @@ +escape_input((int) $uid); + + $q = "SELECT id,categorie,plaats,week,img,points from " . TB_PREFIX . "medal where userid = $uid and del = 0 order by id desc"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + + } + + // no need to refactor this method + function getProfileMedalAlly($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT id,categorie,plaats,week,img,points from " . TB_PREFIX . "allimedal where allyid = $uid and del = 0 order by id desc"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + + } + + function modifyPoints($aid, $points, $amt) { + $aid = (int) $aid; + + if (!is_array($points)) { + $points = [$points]; + $amt = [$amt]; + } + + $updates = []; + foreach ($points as $index => $value) { + $value = $this->escape($value); + $updates[] = $value.' = ' . $value . ' + ' . (int) $amt[$index]; + } + + $q = "UPDATE " . TB_PREFIX . "users SET ".implode(', ', $updates)." WHERE id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function modifyPointsAlly($aid, $points, $amt) { + $aid = (int) $aid; + + if (!is_array($points)) { + $points = [$points]; + $amt = [$amt]; + } + + $updates = []; + foreach ($points as $index => $value) { + $value = $this->escape($value); + $updates[] = $value.' = ' . $value . ' + ' . (int) $amt[$index]; + } + + $q = "UPDATE " . TB_PREFIX . "alidata SET ".implode(', ', $updates)." WHERE id = $aid"; + return mysqli_query($this->dblink,$q); + } + + function isThereAWinner(){ + $q = "SELECT Count(*) as Total FROM ".TB_PREFIX."fdata WHERE f99 = 100 and f99t = 40"; + $result = mysqli_fetch_array(mysqli_query($this->dblink, $q), MYSQLI_ASSOC); + return $result['Total'] > 0; + } + + // no need to cache this method + function getVRanking() { + $q = "SELECT v.wref,v.name,v.owner,v.pop FROM " . TB_PREFIX . "vdata AS v," . TB_PREFIX . "users AS u WHERE v.owner=u.id AND u.tribe IN(1,2,3".(SHOW_NATARS ? ',5' : '').") AND v.wref != '' AND u.access<" . (INCLUDE_ADMIN ? "10" : "8"); + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function getARanking($use_cache = true) { + // 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::$allianceRankingCache, 0)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT id,name,tag,oldrank,Aap,Adp FROM " . TB_PREFIX . "alidata where id != '' ORDER BY id DESC"; + $result = mysqli_query($this->dblink,$q); + + self::$allianceRankingCache[0] = $this->mysqli_fetch_all($result); + return self::$allianceRankingCache[0]; + } + + // no need to cache this method + function getUserByTribe($tribe) { + list($tribe) = $this->escape_input((int) $tribe); + $q = "SELECT * FROM " . TB_PREFIX . "users where tribe = $tribe"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getUserByAlliance($aid) { + list($aid) = $this->escape_input((int) $aid); + $q = "SELECT * FROM " . TB_PREFIX . "users where alliance = $aid"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getHeroRanking() { + $q = "SELECT * FROM " . TB_PREFIX . "hero WHERE dead = 0"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + //medal functions + function addclimberrankpop($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "users set clp = clp + $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function removeclimberrankpop($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "users set clp = clp - $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function setclimberrankpop($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "users set clp = $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function updateoldrank($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "users set oldrank = $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + // ALLIANCE MEDAL FUNCTIONS + function addclimberrankpopAlly($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "alidata set clp = clp + $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function removeclimberrankpopAlly($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "alidata set clp = clp - $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function updateoldrankAlly($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "alidata set oldrank = $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + function countUser($use_cache = true) { + // 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::$usersCountCache, 0)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT count(id) FROM " . TB_PREFIX . "users where id > 5"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + + self::$usersCountCache[0] = $row[0]; + return self::$usersCountCache[0]; + } + + function countAlli($use_cache = true) { + // 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::$allianceCountCache, 0)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT count(id) FROM " . TB_PREFIX . "alidata where id != 0"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + + self::$allianceCountCache[0] = $row[0]; + return self::$allianceCountCache[0]; + } + + /** + * @param int $uid + * @return int How many villages this user currently owns. Deliberately + * uncached (always a fresh COUNT) since it's used right + * after a village INSERT to decide a one-time milestone. + */ + function countVillages($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT COUNT(*) AS total FROM " . TB_PREFIX . "vdata WHERE owner = $uid"; + $result = mysqli_query($this->dblink, $q); + $row = mysqli_fetch_assoc($result); + + return $row ? (int) $row['total'] : 0; + } +} diff --git a/GameEngine/Database/DatabaseSystemQueries.php b/GameEngine/Database/DatabaseSystemQueries.php new file mode 100644 index 00000000..55e35d8b --- /dev/null +++ b/GameEngine/Database/DatabaseSystemQueries.php @@ -0,0 +1,466 @@ +dblink,$q); + return $this->mysqli_fetch_all($result); + } + + /** + * Creates a database structure for the game. + * Used during installation. + * + * @return boolean|number Returns TRUE, FALSE or -1. True is for successful data import + * (from prepared SQL file), false is in case of an SQL error. + * -1 will be returned in case of any unexpected behavior + * and unhandled exceptions. + */ + + public function createDbStructure() { + global $autoprefix; + + try { + // check that we don't have the structure in place already + // (we'd have at least 1 user present, since 4 are being created by default - Support, Nature, Multihunter & Taskmaster) + try { + $data_exist = $this->query_return("SELECT * FROM " . TB_PREFIX . "users LIMIT 1"); + if ($data_exist && count($data_exist)) { + return false; + } + } catch (\Exception $e) { + + } + + // load the DB structure SQL file + $str = file_get_contents($autoprefix."var/db/struct.sql"); + $str = preg_replace("'%PREFIX%'", TB_PREFIX, $str); + $result = $this->dblink->multi_query($str); + + // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work + while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} + + if (!$result) { + return false; + } + } catch (\Exception $e) { + echo($e); + return -1; + } + + return true; + } + + /** + * Populates the game database with Map World Data (i.e. creates the whole + * world with X,Y coordinate squares and their types). + * + * Also populates oasis' table data for the squares where there are oasis. + * + * @return boolean|number Returns TRUE, FALSE or -1. True is for successful data import + * (from prepared SQL file), false is in case of an SQL error. + * -1 will be returned in case of any unexpected behavior + * and unhandled exceptions. + */ + public function populateWorldData() { + global $autoprefix; + + $wdataTable = TB_PREFIX . "wdata"; + $droppedIndexes = []; + + try { + // check if we don't already have world data + $data_exist = $this->query_return("SELECT * FROM " . $wdataTable . " LIMIT 1"); + if ($data_exist && count($data_exist)) { + return false; + } + + // Best-effort session tuning for faster bulk inserts. + $sessionTuningStatements = [ + "SET SESSION innodb_flush_log_at_trx_commit=2", + "SET SESSION sync_binlog=0", + "SET SESSION unique_checks=0", + "SET SESSION foreign_key_checks=0", + ]; + foreach ($sessionTuningStatements as $stmt) { + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore tuning failures, they are not required for correctness. + } + } + + // Temporarily drop secondary indexes before heavy INSERT .. SELECT. + // Recreated at the end to speed up map generation on larger worlds. + $indexesToDrop = ['occupied', 'fieldtype', 'x-y']; + foreach ($indexesToDrop as $indexName) { + try { + $escapedIndexName = mysqli_real_escape_string($this->dblink, $indexName); + $res = mysqli_query($this->dblink, "SHOW INDEX FROM `{$wdataTable}` WHERE Key_name = '{$escapedIndexName}'"); + if ($res && mysqli_num_rows($res) > 0) { + mysqli_query($this->dblink, "ALTER TABLE `{$wdataTable}` DROP INDEX `{$indexName}`"); + $droppedIndexes[$indexName] = true; + } + } catch (Throwable $e) { + // If we can't drop an index, continue with generation anyway. + } + } + + // load the data generation SQL file + $str = file_get_contents($autoprefix."var/db/datagen-world-data.sql"); + $str = preg_replace(["'%PREFIX%'", "'%WORLDSIZE%'"], [TB_PREFIX, WORLD_MAX], $str); + $result = $this->dblink->multi_query($str); + + // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work + while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} + + if (!$result) { + return -1; + } + + $result = $this->regenerateOasisUnits(-1); + if (!$result) { + return -1; + } + } catch (\Throwable $e) { + return -1; + } finally { + // Recreate dropped indexes to keep runtime query performance unchanged. + if (!empty($droppedIndexes)) { + try { + if (isset($droppedIndexes['occupied'])) { + mysqli_query($this->dblink, "CREATE INDEX `occupied` ON `{$wdataTable}` (`occupied`)"); + } + if (isset($droppedIndexes['fieldtype'])) { + mysqli_query($this->dblink, "CREATE INDEX `fieldtype` ON `{$wdataTable}` (`fieldtype`)"); + } + if (isset($droppedIndexes['x-y'])) { + mysqli_query($this->dblink, "CREATE INDEX `x-y` ON `{$wdataTable}` (`x`, `y`)"); + } + } catch (Throwable $e) { + // Best effort only; installation can proceed and indexes may be rebuilt manually if needed. + } + } + + // Restore conservative defaults for this session (best effort). + try { mysqli_query($this->dblink, "SET SESSION unique_checks=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION foreign_key_checks=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION innodb_flush_log_at_trx_commit=1"); } catch (Throwable $e) {} + try { mysqli_query($this->dblink, "SET SESSION sync_binlog=1"); } catch (Throwable $e) {} + } + + return true; + } + + + /*** Build/rebuild the croppers precompute table from wdata. */ + + public function TotalCroppers(): int { + $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; + $WDATA = $TBP . 'wdata'; + + $res = mysqli_query($this->dblink, "SELECT COUNT(*) AS cnt FROM `$WDATA` WHERE fieldtype IN (1,6)"); + if (!$res) { + throw new Exception('Count query failed: ' . mysqli_error($this->dblink)); + } + + $row = mysqli_fetch_assoc($res); + return (int)($row['cnt'] ?? 0); + } + + public function populateCroppers(int $countTotal = 0, bool $truncateFirst = false, int $batch = 20000, ?callable $reporter = null ): array { + @set_time_limit(0); + @ini_set('memory_limit', '1G'); + + $TBP = defined('TB_PREFIX') ? TB_PREFIX : 's1_'; + $CROP_TABLE = $TBP . 'croppers'; + $WDATA = $TBP . 'wdata'; + + $total = 0; + $inTransaction = false; + + try { + if ($countTotal <= 0) { + $row = mysqli_fetch_assoc(mysqli_query($this->dblink, "SELECT COUNT(*) cnt FROM `$WDATA` WHERE fieldtype IN (1,6)")); + $countTotal = (int)($row['cnt'] ?? 0); + } + + if ($truncateFirst && !mysqli_query($this->dblink, "TRUNCATE TABLE `$CROP_TABLE`")) { + return ['ok'=>false,'msg'=>'TRUNCATE failed: '.mysqli_error($this->dblink)]; + } + + $sessionTuningStatements = [ + "SET SESSION innodb_flush_log_at_trx_commit=2", + "SET SESSION sync_binlog=0", + "SET SESSION unique_checks=0", + "SET SESSION foreign_key_checks=0", + ]; + foreach($sessionTuningStatements as $stmt){ + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore tuning failures, they are not required for correctness. + } + } + + if ($batch < 1000) $batch = 1000; + if ($batch > 100000) $batch = 100000; + if($countTotal < 1000) $sliceSize = 200; + elseif($countTotal < 5000) $sliceSize = 500; + else $sliceSize = 1000; + + $lastId = 0; + while (true) { + $res = mysqli_query( + $this->dblink, + "SELECT id AS wref, x, y, fieldtype + FROM `$WDATA` + WHERE fieldtype IN (1,6) AND id > $lastId + ORDER BY id ASC + LIMIT $batch" + ); + if (!$res) { + return ['ok'=>false,'msg'=>'SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } + + $rows = []; + while ($r = mysqli_fetch_assoc($res)) { $rows[] = $r; } + if (!$rows) break; + + mysqli_begin_transaction($this->dblink); + $inTransaction = true; + + $n = count($rows); + for ($i = 0; $i < $n; $i += $sliceSize) { + $chunk = array_slice($rows, $i, $sliceSize); + $values = []; + + // Compute oasis crop bonus in batch for the whole chunk. + $bonusByWref = []; + $wrefs = []; + foreach ($chunk as $r) { + $wrefs[] = (int)$r['wref']; + } + + if (!empty($wrefs)) { + $bonusSql = "SELECT + c.id AS wref, + LEAST( + 150, + (50 * LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3)) + + (25 * LEAST( + GREATEST(3 - LEAST(SUM(CASE WHEN od.type = 12 THEN 1 ELSE 0 END), 3), 0), + SUM(CASE WHEN od.type IN (4,9,10,11) THEN 1 ELSE 0 END) + )) + ) AS bonus + FROM `$WDATA` c + LEFT JOIN `$WDATA` o + ON o.fieldtype = 0 + AND o.x BETWEEN (c.x - 3) AND (c.x + 3) + AND o.y BETWEEN (c.y - 3) AND (c.y + 3) + LEFT JOIN `{$TBP}odata` od + ON od.wref = o.id + AND od.type IN (12,4,9,10,11) + WHERE c.id IN (".implode(',', $wrefs).") + GROUP BY c.id"; + + $bonusRes = mysqli_query($this->dblink, $bonusSql); + if (!$bonusRes) { + mysqli_rollback($this->dblink); + $inTransaction = false; + return ['ok'=>false,'msg'=>'BONUS SELECT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } + + while ($bonusRow = mysqli_fetch_assoc($bonusRes)) { + $bonusByWref[(int)$bonusRow['wref']] = (int)($bonusRow['bonus'] ?? 0); + } + } + + foreach ($chunk as $r) { + $x = (int)$r['x']; + $y = (int)$r['y']; + $bonus = (int)($bonusByWref[(int)$r['wref']] ?? 0); + if ($bonus < 0) $bonus = 0; + if ($bonus > 150) $bonus = 150; + $values[] = sprintf("(%d,%d,%d,%d,%d)", (int)$r['wref'], $x, $y, (int)$r['fieldtype'], $bonus); + } + + if ($values) { + $sql = "INSERT INTO `$CROP_TABLE` + (`wref`,`x`,`y`,`fieldtype`,`best_oasis_bonus`) + VALUES ".implode(',', $values)." + ON DUPLICATE KEY UPDATE + `x`=VALUES(`x`), + `y`=VALUES(`y`), + `fieldtype`=VALUES(`fieldtype`), + `best_oasis_bonus`=VALUES(`best_oasis_bonus`)"; + if (!mysqli_query($this->dblink, $sql)) { + mysqli_rollback($this->dblink); + $inTransaction = false; + return ['ok'=>false,'msg'=>'INSERT failed: '.mysqli_error($this->dblink),'processed'=>$total,'target'=>$countTotal]; + } + } + + $total += count($chunk); + if ($reporter) { + $pct = $countTotal ? min(100, (int)floor(($total / $countTotal) * 100)) : 0; + $reporter($total, $countTotal, $pct); + } + } + + mysqli_commit($this->dblink); + $inTransaction = false; + $lastId = (int)$rows[$n - 1]['wref']; + } + + foreach(["SET SESSION unique_checks=1", "SET SESSION foreign_key_checks=1"] as $stmt){ + try { + mysqli_query($this->dblink, $stmt); + } catch (Throwable $e) { + // Ignore restore failures if the engine does not support the setting. + } + } + + @mysqli_query($this->dblink, "ANALYZE TABLE `$CROP_TABLE`"); + if ($reporter) { $reporter($total, $countTotal, 100); } + return ['ok'=>true,'msg'=>'Croppers populated','processed'=>$total,'target'=>$countTotal]; + } catch (Throwable $e) { + if ($inTransaction) { + try { + mysqli_rollback($this->dblink); + } catch (Throwable $rollbackException) { + // Ignore rollback errors after fatal DB exceptions. + } + } + return ['ok'=>false,'msg'=>$e->getMessage(),'processed'=>$total,'target'=>$countTotal]; + } + } + + /** + * Display a system message to all players + * + * @param string $message The text of the system message that will be written and displayed to all players + */ + + function displaySystemMessage($message){ + list($message) = $this->escape_input($message); + global $autoprefix; + + $myFile = $autoprefix."Templates/text.tpl"; + $fh = fopen($myFile, 'w'); + $text = file_get_contents($autoprefix."Templates/text_format.tpl"); + $text = preg_replace("'%TEKST%'", $message, $text); + fwrite($fh, $text); + + //Set "OK" to 1 to all players, so they can visualize the message + $this->setUsersOk(); + } + + /** + * Called when a system message is sent or Natars/Artifacts have been spawned + * + * @param int $value 1 to make a system message visible to all users, 0 to hide it + * @return bool Returns true if the query was successful, false otherwise + */ + + function setUsersOk($value = 1){ + list($value) = $this->escape_input((int) $value); + + $q = "UPDATE " . TB_PREFIX . "users SET ok = $value"; + return mysqli_query($this->dblink, $q); + } + + public function getMaintenance() { + $q = "SELECT * FROM ".TB_PREFIX."maintenance WHERE id=1 LIMIT 1"; + $res = $this->query_return($q); + return $res[0]?? ['active'=>0]; + } + public function setMaintenance($active, $uid=0) { + $time = time(); + $active = (int)$active; + $uid = (int)$uid; + // REPLACE creează rândul dacă nu există + return $this->query("REPLACE INTO ".TB_PREFIX."maintenance + (id, active, started_by, started_at) + VALUES (1, $active, $uid, $time)"); + } + + /** + * Debug error-log mode (admin-controlled, transparent to players). + * Returns the single config row, falling back to safe defaults when the + * table does not exist yet (so deploying the code before creating the table + * never produces a blank page). + */ + public function getDebugMode() { + $default = [ + 'active' => 0, + 'lvl_warning' => 1, + 'lvl_notice' => 1, + 'lvl_deprecated' => 1, + 'lvl_fatal' => 1, + 'max_size_mb' => 5, + 'auto_off_hours' => 6, + 'started_by' => null, + 'started_at' => null, + ]; + try { + $res = @mysqli_query($this->dblink, "SELECT * FROM ".TB_PREFIX."debug_log WHERE id=1 LIMIT 1"); + if (!$res) { + return $default; + } + $row = mysqli_fetch_assoc($res); + return $row ?: $default; + } catch (\Throwable $e) { + return $default; + } +} + + /** + * Toggle the debug mode on/off (stamps who/when on activation). + */ + public function setDebugMode($active, $uid = 0) { + $active = (int)$active; + $uid = (int)$uid; + $time = time(); + return $this->query("UPDATE ".TB_PREFIX."debug_log + SET active = $active, started_by = $uid, started_at = $time + WHERE id = 1"); + } + + /** + * Persist the debug capture parameters (levels, size cap, auto-off window). + */ + public function setDebugSettings($warning, $notice, $deprecated, $fatal, $maxSizeMb, $autoOffHours) { + $warning = $warning ? 1 : 0; + $notice = $notice ? 1 : 0; + $deprecated = $deprecated ? 1 : 0; + $fatal = $fatal ? 1 : 0; + $maxSizeMb = max(1, (int)$maxSizeMb); + $autoOffHours = max(0, (int)$autoOffHours); + return $this->query("UPDATE ".TB_PREFIX."debug_log + SET lvl_warning = $warning, lvl_notice = $notice, lvl_deprecated = $deprecated, + lvl_fatal = $fatal, max_size_mb = $maxSizeMb, auto_off_hours = $autoOffHours + WHERE id = 1"); +} +} diff --git a/GameEngine/Database/DatabaseTroopQueries.php b/GameEngine/Database/DatabaseTroopQueries.php new file mode 100644 index 00000000..973c9a48 --- /dev/null +++ b/GameEngine/Database/DatabaseTroopQueries.php @@ -0,0 +1,928 @@ +escape_input((int) $id); + + $q = "DELETE from " . TB_PREFIX . "enforcement where id = '$id'"; + mysqli_query($this->dblink,$q); + self::clearReinforcementsCache(); + } + + public function countOasisTroops($vref, $use_cache = true) { + list($vref) = $this->escape_input((int) $vref); + + // 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::$oasisTroopsCountCache, $vref)) && !is_null($cachedValue)) { + return $cachedValue; + } + + //count oasis troops: $troops_o + $troops_o = 0; + $o_unit = $this->getUnit($vref, $use_cache); + + for ( $i = 1; $i < 51; $i ++ ) { + $troops_o += $o_unit[ 'u'.$i ]; + } + + $troops_o += $o_unit['hero']; + $o_unit2 = $this->getEnforceVillage($vref, 0, $use_cache); + + foreach ($o_unit2 as $o_unit) { + for ( $i = 1; $i < 51; $i ++ ) { + $troops_o += $o_unit[ 'u'.$i ]; + } + + $troops_o += $o_unit['hero']; + } + + self::$oasisTroopsCountCache[$vref] = $troops_o; + return self::$oasisTroopsCountCache[$vref]; + } + + function getResearchLock($wid) { + $wid = (int) $wid; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('research_village_$wid', 10) AS locked"); + $row = mysqli_fetch_assoc($result); + return $row['locked'] == 1; + } + + function releaseResearchLock($wid) { + $wid = (int) $wid; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('research_village_$wid')"); + } + + function getTrainingLock($wid) { + $wid = (int) $wid; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('train_village_$wid', 10) AS locked"); + $row = mysqli_fetch_assoc($result); + return $row['locked'] == 1; + } + + function releaseTrainingLock($wid) { + $wid = (int) $wid; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('train_village_$wid')"); + } + + function getEnforceLock($id) { + $id = (int) $id; + $result = mysqli_query($this->dblink, "SELECT GET_LOCK('enforce_$id', 10) AS locked"); + $row = mysqli_fetch_assoc($result); + return $row['locked'] == 1; + } + + function releaseEnforceLock($id) { + $id = (int) $id; + mysqli_query($this->dblink, "SELECT RELEASE_LOCK('enforce_$id')"); + } + + /** + * Add the unit table(s) and troops if presents + * + * @param mixed $vid The villaged ID(s) + * @param array $troopsArray divided in two portion, which contains the types (unidimensional array) and the values of the + * troops that need to be added (bidimensional array) + * @return bool Returns true if the query was successful, false otherwise + */ + + function addUnits($vid, $troopsArray = null) { + list($vid) = $this->escape_input($vid); + + if(empty($vid)) return; + if (!is_array($vid)) $vid = [$vid]; + $types = ""; + $typeKeys = []; + $values = []; + + if($troopsArray != null){ + $typeKeys = $troopsArray[0]; + $values = $troopsArray[1]; + + $types = ",u".implode(",u", $typeKeys); + } + + foreach ($vid as $index => $vidValue) $vid[$index] = (int) $vidValue.($troopsArray != null ? ",".implode(",", $values[$index]) : ""); + + $duplicateUpdate = ""; + if(!empty($typeKeys)){ + $duplicateColumns = []; + foreach($typeKeys as $typeKey){ + $typeKey = (int) $typeKey; + $duplicateColumns[] = "u".$typeKey."=VALUES(u".$typeKey.")"; + } + $duplicateUpdate = " ON DUPLICATE KEY UPDATE ".implode(', ', $duplicateColumns); + }else{ + $duplicateUpdate = " ON DUPLICATE KEY UPDATE vref=vref"; + } + + $q = "INSERT into " . TB_PREFIX . "units (vref$types) values (".implode('),(', $vid).")".$duplicateUpdate; + return mysqli_query($this->dblink,$q); + } + + function getUnit($vid, $use_cache = true) { + $array_passed = is_array($vid); + + if (!$array_passed) { + $singleVillage = true; + $vid = [$vid]; + } else { + foreach ($vid as $index => $vidValue) { + $vid[$index] = (int) $vidValue; + } + } + + $returnArray = []; + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$unitsCache, (int) $vid[0])) && !is_null($cachedValue)) { + return $cachedValue; + } else if ($use_cache && $array_passed) { + $newIDs = []; + foreach ($vid as $villageID) { + // don't cache what we don't need to cache + if (isset(self::$unitsCache[$villageID])) { + $returnArray[$villageID] = self::$unitsCache[$villageID]; + } else { + // add the uncached ID, so we can select and cache it + $newIDs[] = $villageID; + } + } + $vid = $newIDs; + + // nothing to cache? return what we have + if (!count($vid)) { + return $returnArray; + } + } + + $q = "SELECT * from " . TB_PREFIX . "units where vref IN(".implode(', ', $vid).")"; + $result = mysqli_query($this->dblink,$q); + $resCount = 0; + $vidCount = count($vid); + + if (!empty($result) && ($resCount = mysqli_num_rows($result)) && $resCount) { + while ($row = mysqli_fetch_assoc($result)) { + self::$unitsCache[$row['vref']] = $row; + $returnArray[$row['vref']] = $row; + } + } else { + // fill everything with nulls + foreach ($vid as $id) { + self::$unitsCache[$id] = null; + $returnArray[$id] = null; + } + } + + // check if we're not missing any return values + if ($vidCount != $resCount) { + // fill-in the gaps, as it would mean some of the IDs we got were not found + // (which is super-strange, but it's still a mathematical possibility) + foreach ($vid as $id) { + if (!isset($returnArray[$id])) { + $returnArray[$id] = null; + } + } + } + + return (!isset($singleVillage) ? $returnArray : reset($returnArray)); + } + + // no need to cache this method + function getUnitsNumber($vid, $mode = 1, $use_cache = false) { + list( $vid ) = $this->escape_input( (int) $vid ); + + $dbarray = $this->getUnit( $vid ); + $totalunits = 0; + for ( $i = 1; $i <= 90; $i ++ ) { + $totalunits += $dbarray[ 'u' . $i ]; + } + + $totalunits += $dbarray['hero']; + if(!$mode) return $totalunits; + + $movingunits = $this->getVillageMovement( $vid ); + $reinforcingunits = $this->getEnforceArray( $vid, 1 ); + $owner = $this->getVillageField( $vid, "owner" ); + $ownertribe = $this->getUserField( $owner, "tribe", 0 ); + $start = ( $ownertribe - 1 ) * 10 + 1; + $end = ( $ownertribe * 10 ); + + for ( $i = $start; $i <= $end; $i ++ ) { + $totalunits += $movingunits[ 'u' . $i ] ?? 0; + $totalunits += $reinforcingunits[ 'u' . $i ] ?? 0; + } + + $totalunits += $movingunits['hero'] ?? 0; + $totalunits += $reinforcingunits['hero'] ?? 0; + + return $totalunits; + } + + function addTech($vid) { + if(empty($vid)) return; + if (!is_array($vid)) { + $vid = [$vid]; + } + + foreach ($vid as $index => $vidValue) { + $vid[$index] = (int) $vidValue; + } + + $q = "INSERT INTO " . TB_PREFIX . "tdata (vref) VALUES (".implode('),(', $vid).")"; + return mysqli_query($this->dblink,$q); + } + + function addABTech($vid) { + if(empty($vid)) return; + if (!is_array($vid)) { + $vid = [$vid]; + } + + foreach ($vid as $index => $vidValue) { + $vid[$index] = (int) $vidValue; + } + + self::$abTechCache = []; + $q = "INSERT INTO " . TB_PREFIX . "abdata (vref) VALUES (".implode('),(', $vid).")"; + return mysqli_query($this->dblink,$q); + } + + function getABTech($vid, $use_cache = true) { + $array_passed = is_array($vid); + + if (!$array_passed) { + $vid = [(int) $vid]; + } else { + foreach ($vid as $index => $ivdValue) { + $vid[$index] = (int) $ivdValue; + } + } + + if (!count($vid)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$abTechCache[$vid[0]]) && is_array(self::$abTechCache[$vid[0]]) && !count(self::$abTechCache[$vid[0]])) { + return self::$abTechCache[$vid[0]]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newVIDs = []; + foreach ($vid as $key) { + if (!isset(self::$abTechCache[$key])) { + $newVIDs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newVIDs)) { + return self::$abTechCache; + } else { + // update remaining IDs to select and cache + $vid = $newVIDs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$abTechCache, $vid[0])) && !is_null($cachedValue)) { + // special case when we have empty arrays cached for this cache only + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "abdata where vref IN(".implode(', ', $vid).")"; + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$abTechCache[$vid[0]] = $result[0]; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + self::$abTechCache[ $record['vref']] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no reinforcements were found for these villages + foreach ($vid as $key) { + if (!isset(self::$abTechCache[$key])) { + self::$abTechCache[$key] = []; + } + } + } + + return ($array_passed ? self::$abTechCache : self::$abTechCache[$vid[0]]); + } + + function addResearch($vid, $tech, $time) { + list($vid, $tech, $time) = $this->escape_input((int) $vid, $tech, (int) $time); + + self::$researchingCache = []; + $q = "INSERT into " . TB_PREFIX . "research values (0,$vid,'$tech',$time)"; + return mysqli_query($this->dblink,$q); + } + + function getResearching($vid, $use_cache = true) { + $vid = (int) $vid; + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && isset(self::$researchingCache[$vid]) && is_array(self::$researchingCache[$vid]) && !count(self::$researchingCache[$vid])) { + return self::$researchingCache[$vid]; + } else if ($use_cache && ($cachedValue = self::returnCachedContent(self::$researchingCache, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "research where vref = $vid ORDER BY timestamp ASC"; + $result = mysqli_query($this->dblink,$q); + $researchingCache[$vid] = $this->mysqli_fetch_all($result); + return $researchingCache[$vid]; + } + + function checkIfResearched($vref, $unit, $use_cache = true) { + list($vref, $unit) = $this->escape_input((int) $vref, $unit); + + // 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::$isResearchedCache, $vref)) && !is_null($cachedValue)) { + return $cachedValue[$unit]; + } + + $q = "SELECT * FROM " . TB_PREFIX . "tdata WHERE vref = $vref LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result, MYSQLI_ASSOC); + + self::$isResearchedCache[$vref] = $dbarray; + return self::$isResearchedCache[$vref][$unit]; + } + + function getTech($vid) { + // this is a somewhat non-ideal, externally non-changeable way of caching + // but since we're only ever going to be calling this from Village constructor + // for our current village, this will more than suffice + static $cachedData = []; + $vid = (int) $vid; + + if (isset($cachedData[$vid])) { + return $cachedData[$vid]; + } + + $q = "SELECT * from " . TB_PREFIX . "tdata where vref = $vid"; + $result = mysqli_query($this->dblink,$q); + $cachedData[$vid] = mysqli_fetch_assoc($result); + + return $cachedData[$vid]; + } + + // no need to cache this method + + // ==================== HOSPITAL (raniti + vindecare) ==================== + + function getWounded($vid) { + list($vid) = $this->escape_input((int)$vid); + $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "hospital WHERE vref = $vid"); + return $result ? mysqli_fetch_array($result, MYSQLI_ASSOC) : null; + } + + function addWounded($vid, array $units, array $amounts) { + list($vid) = $this->escape_input((int)$vid); + $cols = []; $vals = []; $upd = []; + foreach($units as $k => $u) { + $u = (int)$u; $a = (int)$amounts[$k]; + if($a <= 0 || $u < 1 || $u > 90) continue; + $cols[] = "u$u"; $vals[] = $a; $upd[] = "u$u = u$u + $a"; + } + if(empty($cols)) return true; + $q = "INSERT INTO " . TB_PREFIX . "hospital (vref, " . implode(',', $cols) . ") VALUES ($vid, " . implode(',', $vals) . ") ON DUPLICATE KEY UPDATE " . implode(',', $upd); + return mysqli_query($this->dblink, $q); + } + + function deductWounded($vid, $unit, $amt) { + list($vid, $unit, $amt) = $this->escape_input((int)$vid, (int)$unit, (int)$amt); + return mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "hospital SET u$unit = GREATEST(u$unit - $amt, 0) WHERE vref = $vid"); + } + + function clearHospital($vid) { + list($vid) = $this->escape_input((int)$vid); + mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "hospital WHERE vref = $vid"); + mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "healing WHERE vref = $vid"); + } + + function getHealing($vid) { + list($vid) = $this->escape_input((int)$vid); + $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "healing WHERE vref = $vid ORDER BY id"); + $rows = []; + if($result) while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) $rows[] = $row; + return $rows; + } + + function getHealingDue($time) { + list($time) = $this->escape_input((int)$time); + $result = mysqli_query($this->dblink, "SELECT * FROM " . TB_PREFIX . "healing WHERE timestamp2 <= $time AND amt > 0"); + $rows = []; + if($result) while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) $rows[] = $row; + return $rows; + } + + function healUnit($vid, $unit, $amt, $each) { + list($vid, $unit, $amt, $each) = $this->escape_input((int)$vid, (int)$unit, (int)$amt, (int)$each); + $now = time(); + $q = "INSERT INTO " . TB_PREFIX . "healing (vref, unit, amt, timestamp, eachtime, timestamp2) VALUES ($vid, $unit, $amt, $now, $each, " . ($now + $each) . ")"; + return mysqli_query($this->dblink, $q); + } + + function updateHealing($id, $amt, $timestamp2) { + list($id, $amt, $timestamp2) = $this->escape_input((int)$id, (int)$amt, (int)$timestamp2); + return mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "healing SET amt = $amt, timestamp2 = $timestamp2 WHERE id = $id"); + } + + function deleteHealing($id) { + list($id) = $this->escape_input((int)$id); + return mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "healing WHERE id = $id"); + } + + function getTraining($vid) { + list($vid) = $this->escape_input((int) $vid); + + $q = "SELECT * FROM " . TB_PREFIX . "training where vref = $vid ORDER BY id"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function trainUnit($vid, $unit, $amt, $pop, $each, $mode) { + list($vid, $unit, $amt, $pop, $each, $mode) = $this->escape_input((int) $vid, (int) $unit, (int) $amt, (int) $pop, (int) $each, $mode); + + global $technology; + + if(!$mode) { + // Rutare generica pe tipul unitatii (u1-u90 + offsetul +1000 pentru cladirile mari) + global $unitsbytype; + $isGreat = $unit > 1000; + $baseUnit = $isGreat ? $unit - 1000 : $unit; + + if($baseUnit == 99) $queued = $technology->getTrainingList(8); + elseif(in_array($baseUnit, $unitsbytype['expansion'])) $queued = $technology->getTrainingList(4); + elseif(in_array($baseUnit, $unitsbytype['siege'])) $queued = $technology->getTrainingList($isGreat ? 7 : 3); + elseif(in_array($baseUnit, $unitsbytype['cavalry'])) $queued = $technology->getTrainingList($isGreat ? 6 : 2); + else $queued = $technology->getTrainingList($isGreat ? 5 : 1); + + $now = time(); + $uid = $this->getVillageField($vid, "owner"); + $each = $this->getArtifactsValueInfluence($uid, $vid, 5, $each); + + $time2 = $now + $each; + $time = $now + ($each * $amt); + if(count($queued) > 0){ + $time += $queued[count($queued) - 1]['timestamp'] - $now; + $time2 += $queued[count($queued) - 1]['timestamp'] - $now; + } + + $q = "INSERT INTO " . TB_PREFIX . "training values (0, $vid, $unit, $amt, $pop, $time, $each, $time2)"; + } + else $q = "DELETE FROM " . TB_PREFIX . "training where id = $vid"; + + return mysqli_query($this->dblink,$q); + } + + function updateTraining($id, $trained, $each) { + list($id, $trained, $each) = $this->escape_input((int) $id, (int) $trained, (int) $each); + + $q = "UPDATE " . TB_PREFIX . "training set amt = amt - $trained, timestamp2 = timestamp2 + $each where id = $id"; + return mysqli_query($this->dblink,$q); + } + + function modifyUnit($vref, $array_unit, $array_amt, $array_mode) { + list($vref, $array_unit, $array_amt, $array_mode) = $this->escape_input((int) $vref, $array_unit, $array_amt, $array_mode); + $i = -1; + $units=''; + $number = count($array_unit); + foreach($array_unit as $unit){ + if($unit == 230) $unit = 30; + if($unit == 231) $unit = 31; + if($unit == 120) $unit = 20; + if($unit == 121) $unit = 21; + if($unit =="hero") $unit = 'hero'; + else $unit = 'u' . $unit; + + ++$i; + //Fixed part of negative troops (double troops) - by InCube + $array_amt[$i] = (int) $array_amt[$i] < 0 ? 0 : $array_amt[$i]; + //Fixed part of negative troops (double troops) - by InCube + $units .= $unit.' = '.$unit.' '.(($array_mode[$i] == 1)? '+':'-').' '.($array_amt[$i] ? $array_amt[$i] : 0).(($number > $i+1) ? ', ' : ''); + } + $q = "UPDATE ".TB_PREFIX."units set $units WHERE vref = $vref"; + return mysqli_query($this->dblink, $q); + } + + function getEnforce($vid, $from, $use_cache = true) { + $array_passed = is_array($vid); + if (!$array_passed) { + $vid = [$vid]; + $from = [$from]; + } else { + foreach ($vid as $index => $vidValue) { + $vid[$index] = (int) $vidValue; + $from[$index] = (int) $from[$index]; + } + } + + if (!count($vid)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$villageFromReinforcementsCache[$vid[0].$from[0]]) && is_array(self::$villageFromReinforcementsCache[$vid[0].$from[0]]) && !count(self::$villageFromReinforcementsCache[$vid[0].$from[0]])) { + return self::$villageFromReinforcementsCache[$vid[0].$from[0]]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newVIDs = []; + $newFROMs = []; + foreach ($vid as $index => $vidValue) { + if (!isset(self::$villageFromReinforcementsCache[$vidValue.$from[$index]])) { + $newVIDs[] = $vidValue; + $newFROMs[] = $from[$index]; + } + } + + // everything's cached, just return the cache + if (!count($newVIDs)) { + return self::$villageFromReinforcementsCache; + } else { + // update remaining IDs to select and cache + $vid = $newVIDs; + $from = $newFROMs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageFromReinforcementsCache, $vid[0].$from[0])) && !is_null($cachedValue)) { + return $cachedValue; + } + + // build SELECT pairs + $pairs = []; + foreach ($vid as $index => $vidValue) { + $pairs[] = '(`from` = '.(int) $from[$index].' AND vref = '.(int) $vidValue.')'; + } + + $q = "SELECT * FROM " . TB_PREFIX . "enforcement WHERE ".implode(' OR ', $pairs); + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$villageFromReinforcementsCache[$vid[0].$from[0]] = $result[0]; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + self::$villageFromReinforcementsCache[$record['vref'].$record['from']] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no reinforcements were found for these villages + foreach ($vid as $index => $vidValue) { + if (!isset(self::$villageFromReinforcementsCache[$vidValue.$from[$index]])) { + self::$villageFromReinforcementsCache[$vidValue.$from[$index]] = []; + } + } + } + + return ($array_passed ? self::$villageFromReinforcementsCache : self::$villageFromReinforcementsCache[$vid[0].$from[0]]); + } + + function getOasisEnforce($ref, $mode=0, $use_cache = true) { + $array_passed = is_array($ref); + $mode = (int) $mode; + + if (!$array_passed) { + $ref = [(int) $ref]; + } else { + foreach ($ref as $index => $refValue) { + $ref[$index] = (int) $refValue; + } + } + + if (!count($ref)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$oasisReinforcementsCache[$ref[0].$mode]) && is_array(self::$oasisReinforcementsCache[$ref[0].$mode]) && !count(self::$oasisReinforcementsCache[$ref[0].$mode])) { + return self::$oasisReinforcementsCache[$ref[0].$mode]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newREFs = []; + foreach ($ref as $key) { + if (!isset(self::$oasisReinforcementsCache[$key.$mode])) { + $newREFs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newREFs)) { + return self::$oasisReinforcementsCache; + } else { + // update remaining IDs to select and cache + $ref = $newREFs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$oasisReinforcementsCache, $ref[0].$mode)) && !is_null($cachedValue)) { + // special case when we have empty arrays cached for this cache only + return $cachedValue; + } + + if (!$mode) { + $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.conqured IN(".implode(', ', $ref).") AND e.from NOT IN(".implode(', ', $ref).")"; + }else if ($mode == 1) { + $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where o.conqured IN(".implode(', ', $ref).")"; + } else if ($mode == 2) { + $q = "SELECT e.*,o.conqured,o.wref,o.high, o.owner as ownero, v.owner as ownerv FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref where o.conqured IN(".implode(', ', $ref).") AND o.owner<>v.owner"; + } else if ($mode == 3) { + $q = "SELECT e.*,o.conqured,o.wref,o.high, o.owner as ownero, v.owner as ownerv FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref where o.conqured IN(".implode(', ', $ref).") AND o.owner=v.owner"; + } + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$oasisReinforcementsCache[$ref[0].$mode] = $result; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + if ( ! isset( self::$oasisReinforcementsCache[ $record['conqured'] . $mode ] ) ) { + self::$oasisReinforcementsCache[ $record['conqured'] . $mode ] = []; + } + + self::$oasisReinforcementsCache[ $record['conqured'] . $mode ][] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no reinforcements were found for these villages + foreach ($ref as $key) { + if (!isset(self::$oasisReinforcementsCache[$key.$mode])) { + self::$oasisReinforcementsCache[$key.$mode] = []; + } + } + } + + return ($array_passed ? self::$oasisReinforcementsCache : self::$oasisReinforcementsCache[$ref[0].$mode]); + } + + function getOasisEnforceArray($id, $mode=0, $use_cache = true) { + list($id, $mode) = $this->escape_input((int) $id, $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::$oasisArrayReinforcementsCache, $id.$mode)) && !is_null($cachedValue)) { + return $cachedValue; + } + + if (!$mode) { + $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.vref=o.wref where e.id = $id"; + }else{ + $q = "SELECT e.*,o.conqured FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."odata as o ON e.from=o.wref where e.id =$id"; + } + $result = mysqli_query($this->dblink,$q); + + self::$oasisArrayReinforcementsCache[$id.$mode] = mysqli_fetch_assoc($result); + return self::$oasisArrayReinforcementsCache[$id.$mode]; + } + + function addEnforce($data) { + list($data) = $this->escape_input($data); + + $q = "INSERT into " . TB_PREFIX . "enforcement (vref,`from`) values (" . (int) $data['to'] . "," . (int) $data['from'] . ")"; + mysqli_query($this->dblink,$q); + $id = mysqli_insert_id($this->dblink); + $owntribe = $this->getUserField($this->getVillageField($data['from'], "owner"), "tribe", 0); + $start = ($owntribe - 1) * 10 + 1; + $end = ($owntribe * 10); + //add unit + $j = 1; + $units = []; + $amounts = []; + $modes = []; + + for($i = $start; $i <= $end; $i++) { + $units[] = ($i < 0 ? 0 : $i); + $amounts[] = $data['t' . $j . '']; + $modes[] = 1; + $j++; + } + + // add hero + $units[] = 'hero'; + $amounts[] = $data['t11']; + $modes[] = 1; + + $this->modifyEnforce($id,$units, $amounts, $modes); + } + + function addEnforce2($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11) { + list($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11) = $this->escape_input($data,$tribe,$dead1,$dead2,$dead3,$dead4,$dead5,$dead6,$dead7,$dead8,$dead9,$dead10,$dead11); + + $q = "INSERT into " . TB_PREFIX . "enforcement (vref,`from`) values (" . (int) $data['to'] . "," . (int) $data['from'] . ")"; + mysqli_query($this->dblink,$q); + $id = mysqli_insert_id($this->dblink); + $owntribe = $this->getUserField($this->getVillageField($data['from'], "owner"), "tribe", 0); + $start = ($owntribe - 1) * 10 + 1; + $end = ($owntribe * 10); + $start2 = ($tribe - 1) * 10 + 1; + $start3 = ($tribe - 1) * 10; + if($start3 == 0){ + $start3 = ""; + } + $end2 = ($tribe * 10); + //add unit + $j = 1; + + $units = []; + $amounts = []; + $modes = []; + + for($i = $start; $i <= $end; $i++) { + $units[] = ($i < 0 ? 0 : $i); + $amounts[] = $data['t' . $j . '']; + $modes[] = 1; + + $units[] = ($i < 0 ? 0 : $i); + $amounts[] = ${'dead'.$j}; + $modes[] = 0; + + $j++; + } + + // process heroes + $units[] = 'hero'; + $amounts[] = $data['t11']; + $modes[] = 1; + + $units[] = 'hero'; + $amounts[] = $dead11; + $modes[] = 0; + + $this->modifyEnforce($id,$units, $amounts, $modes); + } + + function modifyEnforce($id, $unit, $amt, $mode) { + $id = (int) $id; + + // prepare pairing array, even if we're not passing arrays, so we can use the same logic + $pairs = []; + if (!is_array($unit)) { + $unit = [$unit]; + $amt = [(int) $amt]; + $mode = [(int) $mode]; + } + + foreach ($unit as $index => $unitType) { + $unitType = ($unitType != 'hero' ? 'u' . $this->escape($unitType) : $unitType); + $pairs[] = $unitType . ' = ' . $unitType . (!(int) $mode[$index] ? ' - ' : ' + ') . (int) $amt[$index]; + } + + $q = "UPDATE " . TB_PREFIX . "enforcement SET ".implode(', ', $pairs)." WHERE id = $id"; + mysqli_query($this->dblink,$q); + + // clear enforce cache + self::$villageReinforcementsCache = []; + self::$villageFromReinforcementsCache = []; + self::$reinforcementsCache = []; + } + + function getEnforceArray($id, $mode, $use_cache = true) { + list($id, $mode) = $this->escape_input((int) $id, $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::$reinforcementsCache, $id.$mode)) && !is_null($cachedValue)) { + return $cachedValue; + } + + if(!$mode) { + $q = "SELECT * from " . TB_PREFIX . "enforcement where id = $id"; + } else { + $q = "SELECT * from " . TB_PREFIX . "enforcement where `from` = $id"; + } + $result = mysqli_query($this->dblink,$q); + + self::$reinforcementsCache[$id.$mode] = mysqli_fetch_assoc($result); + return self::$reinforcementsCache[$id.$mode]; + } + + function getEnforceVillage($id, $mode, $use_cache = true) { + $array_passed = is_array($id); + $mode = (int) $mode; + + if (!$array_passed) { + $id = [(int) $id]; + } else { + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + if (!count($id)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$villageReinforcementsCache[$id[0].$mode]) && is_array(self::$villageReinforcementsCache[$id[0].$mode]) && !count(self::$villageReinforcementsCache[$id[0].$mode])) { + return self::$villageReinforcementsCache[$id[0].$mode]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newIDs = []; + foreach ($id as $key) { + if (!isset(self::$villageReinforcementsCache[$key.$mode])) { + $newIDs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newIDs)) { + return self::$villageReinforcementsCache; + } else { + // update remaining IDs to select and cache + $id = $newIDs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageReinforcementsCache, $id[0].$mode)) && !is_null($cachedValue)) { + // special case when we have empty arrays cached for this cache only + return $cachedValue; + } + + if(!$mode) { + $q = "SELECT * from " . TB_PREFIX . "enforcement where vref IN(".implode(', ', $id).")"; + } else if ($mode == 1) { + $q = "SELECT * from " . TB_PREFIX . "enforcement where `from` IN(".implode(', ', $id).")"; + } else if ($mode == 2) { + $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner<>v1.owner"; + } else if ($mode == 3) { + $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner=v1.owner"; + } else if ($mode == 4) { + $q = "SELECT e.*, v.owner as ownerv, v1.owner as owner1 FROM ".TB_PREFIX."enforcement as e LEFT JOIN ".TB_PREFIX."vdata as v ON e.from=v.wref LEFT JOIN ".TB_PREFIX."vdata as v1 ON e.vref=v1.wref where e.vref IN(".implode(', ', $id).") AND v.owner=v1.owner"; + } + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$villageReinforcementsCache[$id[0].$mode] = $result; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + if ( ! isset( self::$villageReinforcementsCache[ $record['vref'] . $mode ] ) ) { + self::$villageReinforcementsCache[ $record['vref'] . $mode ] = []; + } + + self::$villageReinforcementsCache[ $record['vref'] . $mode ][] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no reinforcements were found for these villages + foreach ($id as $key) { + if (!isset(self::$villageReinforcementsCache[$key.$mode])) { + self::$villageReinforcementsCache[$key.$mode] = []; + } + } + } + + return ($array_passed ? self::$villageReinforcementsCache : self::$villageReinforcementsCache[$id[0].$mode]); + } + + public static function clearReinforcementsCache() { + self::$reinforcementsCache = []; + self::$villageReinforcementsCache = []; + self::$villageFromReinforcementsCache = []; + self::$oasisArrayReinforcementsCache = []; + self::$oasisReinforcementsCache = []; + self::clearUnitsCache(); + } + + public static function clearUnitsCache() { + self::$unitsCache = []; + } + + // no need to cache this method + function getTrainingList() { + $q = "SELECT * FROM " . TB_PREFIX . "training where vref IS NOT NULL"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } +} diff --git a/GameEngine/Database/DatabaseUserQueries.php b/GameEngine/Database/DatabaseUserQueries.php new file mode 100644 index 00000000..3a54cf97 --- /dev/null +++ b/GameEngine/Database/DatabaseUserQueries.php @@ -0,0 +1,768 @@ + $time) ? $startTime : $time) + PROTECTION : 0; + + // încercăm varianta cu is_bcrypt (PHP 8.3) + $stmt = $this->dblink->prepare( + "INSERT INTO `".TB_PREFIX."users` + (id, username, password, access, email, timestamp, tribe, act, protect, lastupdate, regtime, desc2, is_bcrypt) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)" + ); + $is_bcrypt = 1; + $stmt->bind_param("issisiiiiiisi", $uid, $username, $password, $access, $email, $time, $tribe, $act, $protectionTime, $time, $time, $desc, $is_bcrypt); + + if($stmt->execute()){ + $id = $stmt->insert_id ?: $uid; + $stmt->close(); + $this->grantRegistrationGold($id); + return $id; + } + $stmt->close(); + + // fallback pentru DB vechi fără coloana is_bcrypt + $stmt2 = $this->dblink->prepare( + "INSERT INTO `".TB_PREFIX."users` + (id, username, password, access, email, timestamp, tribe, act, protect, lastupdate, regtime, desc2) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" + ); + $stmt2->bind_param("issisiiiiiis", $uid, $username, $password, $access, $email, $time, $tribe, $act, $protectionTime, $time, $time, $desc); + + if($stmt2->execute()){ + $id = $stmt2->insert_id ?: $uid; + $stmt2->close(); + $this->grantRegistrationGold($id); + return $id; + } + $stmt2->close(); + return false; +} + /** + * Registration bonus gold (NEW_FUNCTION_REGISTRATION_GOLD). + * Grants a one-time gold bonus to a freshly created player account. Called + * from register() right after the users row is inserted, so it covers every + * registration path (email activation, instant registration, admin-created + * users). No-ops when disabled, amount <= 0, or system account (id <= 3). + */ + private function grantRegistrationGold($id) { + $id = (int) $id; + if ($id <= 3) { + return; // system accounts: admin / nature / natars + } + if (!defined('NEW_FUNCTION_REGISTRATION_GOLD') || !NEW_FUNCTION_REGISTRATION_GOLD) { + return; + } + $amount = defined('NEW_FUNCTION_REGISTRATION_GOLD_VALUE') ? (int) NEW_FUNCTION_REGISTRATION_GOLD_VALUE : 0; + if ($amount <= 0) { + return; + } + $this->modifyGold($id, $amount, 1); // mode 1 = add + + // Best-effort audit trail. The village does not exist yet, so wid = 0. + if (defined('LOG_GOLD_FIN') && LOG_GOLD_FIN) { + try { + $now = time(); + $details = 'Registration bonus'; + $stmt = $this->dblink->prepare( + "INSERT INTO `".TB_PREFIX."gold_fin_log` (wid, uid, action, gold, time, details) + VALUES (0, ?, 'Registration bonus Gold', ?, ?, ?)" + ); + if ($stmt) { + $stmt->bind_param("iiis", $id, $amount, $now, $details); + $stmt->execute(); + $stmt->close(); + } + } catch (\Throwable $e) { + // swallow: logging must never block account creation + } + } + } + + function activate($username, $password, $email, $tribe, $locate, $act, $act2) { + $username = trim($username); + $email = filter_var($email, FILTER_VALIDATE_EMAIL) ?: ''; + $tribe = (int)$tribe; + $locate = (int)$locate; + $time = time(); + $access = USER; + + $stmt = $this->dblink->prepare( + "INSERT INTO `".TB_PREFIX."activate` + (username,password,access,email,tribe,timestamp,location,act,act2) + VALUES (?,?,?,?,?,?,?,?,?)" + ); + $stmt->bind_param("ssisiiiss", $username, $password, $access, $email, $tribe, $time, $locate, $act, $act2); + + if($stmt->execute()){ + $id = $stmt->insert_id; + $stmt->close(); + return $id; + } + $stmt->close(); + return false; +} + + function unreg($username) { + list($username) = $this->escape_input($username); + + $q = "DELETE from " . TB_PREFIX . "activate where username = '$username'"; + return mysqli_query($this->dblink,$q); + } + + /** + * IP ban lookup (issue #185). + * Returns the active ban row for the given packed binary IP, or false. + * Uses a prepared statement and tolerates the table not existing yet + * (older installs): in that case it simply reports "not banned". + * + * @param string $ipBinary Packed IP (inet_pton output), 4 or 16 bytes. + * @return array|false + */ + function ipBanActive($ipBinary) { + if (!is_string($ipBinary) || $ipBinary === '') { + return false; + } + try { + $now = time(); + $stmt = $this->dblink->prepare( + "SELECT id, ip_text, reason, end FROM `".TB_PREFIX."banlist_ip` + WHERE ip = ? AND active = 1 AND (end IS NULL OR end = 0 OR end > ?) LIMIT 1" + ); + if (!$stmt) { + return false; // table missing / prepare failed (non-throwing mysqli) + } + $stmt->bind_param("si", $ipBinary, $now); + $stmt->execute(); + $res = $stmt->get_result(); + $row = $res ? $res->fetch_assoc() : null; + $stmt->close(); + return $row ?: false; + } catch (\Throwable $e) { + return false; // table missing (throwing mysqli) → treat as not banned + } + } + + // no need to cache this method + public function hasBeginnerProtection($vid) { + list($vid) = $this->escape_input($vid); + $q = "SELECT u.protect FROM ".TB_PREFIX."users u,".TB_PREFIX."vdata v WHERE u.id=v.owner AND v.wref=".(int) $vid." LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + if(!empty($dbarray)) { + if(time()<$dbarray[0]) { + return true; + } else { + return false; + } + } else { + return false; + } +} + + function updateUserField($ref, $field, $value, $switch) { + list($ref) = $this->escape_input($ref); + + if (!is_array($field)) { + $field = [$field]; + $value = [$value]; + } + + $pairs = []; + foreach ($field as $i => $f) { + $pairs[] = $this->escape($f) . ' = ' . (is_numeric($value[$i]) ? $value[$i] : "'".$this->escape($value[$i])."'"); + } + + $q = !$switch + ? "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE username = '$ref'" + : "UPDATE ".TB_PREFIX."users SET ".implode(', ', $pairs)." WHERE id = ".(int)$ref; + + $ret = mysqli_query($this->dblink, $q); + + // === 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) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT id from " . TB_PREFIX . "users where sit1 = $uid or sit2 = $uid"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function removeMeSit($uid, $uid2) { + list($uid, $uid2) = $this->escape_input((int) $uid, (int) $uid2); + + $q = "UPDATE " . TB_PREFIX . "users set sit1 = 0 where id = $uid and sit1 = $uid2"; + mysqli_query($this->dblink,$q); + $q2 = "UPDATE " . TB_PREFIX . "users set sit2 = 0 where id = $uid and sit2 = $uid2"; + mysqli_query($this->dblink,$q2); + } + + function getUserField($ref, $field, $mode, $use_cache = true) { + // update for Multihunter's username and ID + if (($mode && $ref == '') || (!$mode && $ref == 0)) { + $ref = 'Multihunter'; + $mode = 1; + } + + // return all data, don't waste time by selecting fields one by one + $userArray = $this->getUserArray($ref, ($mode ? 0 : 1), $use_cache); + $result = (isset($userArray[$field]) ? $userArray[$field] : null); + + if ($result) { + // will return the result + } elseif($field=="username") { + $result = "[?]"; + } else { + $result = 0; + } + + return $result; + } + + function getUserFields($ref, $fields, $mode, $use_cache = true) { + // update for Multihunter's username and ID + if (($mode && $ref == '') || (!$mode && $ref == 0)) { + $ref = 'Multihunter'; + $mode = 1; + } + + // return all data, don't waste time by selecting fields one by one + return $this->getUserArray($ref, ($mode ? 0 : 1), $use_cache); + } + + // no need to cache this method + function getInvitedUser($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT * FROM " . TB_PREFIX . "users where invited = $uid order by regtime desc"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getActivateField($ref, $field, $mode) { + list($ref, $field, $mode) = $this->escape_input($ref, $field, $mode); + + if(!$mode) { + $q = "SELECT $field FROM " . TB_PREFIX . "activate where id = " . (int) $ref . " LIMIT 1"; + } else { + $q = "SELECT $field FROM " . TB_PREFIX . "activate where username = '$ref' LIMIT 1"; + } + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray[$field]; + } + + function login($username, $password) { + static $cachedResult = null; + + if ($cachedResult !== null) { + return $cachedResult; + } + + list($username, $password) = $this->escape_input($username, $password); + $q = "SELECT id,password,sessid,is_bcrypt FROM " . TB_PREFIX . "users where username = '$username'"; + $result = mysqli_query($this->dblink,$q); + + // if we didn't update the database for bcrypt hashes yet... + if (mysqli_error($this->dblink) != '') { + $q = "SELECT id, password,sessid,0 as is_bcrypt FROM " . TB_PREFIX . "users where username = '$username' LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $bcrypt_update_done = false; + } else { + $bcrypt_update_done = true; + } + + $dbarray = mysqli_fetch_array($result); + + // even if we didn't do a DB conversion for bcrypt passwords, + // we still need to check if this password wasn't encrypted via password_hash, + // since all methods were updated to use that instead of md5 and therefore + // new passwords in DB will be bcrypt already even without the is_bcrypt field present + $bcrypted = true; + $pwOk = password_verify($password, $dbarray['password']); + + if (!$pwOk && !$dbarray['is_bcrypt']) { + $pwOk = ($dbarray['password'] == md5($password)); + $bcrypted = false; + } + + if($pwOk) { + // update password to bcrypt, if correct + if (!$dbarray['is_bcrypt'] && !$bcrypted) { + mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "users SET password = '".password_hash($password, PASSWORD_BCRYPT,['cost' => 12])."'".($bcrypt_update_done ? ', is_bcrypt = 1' : '')." where id = ".(int) $dbarray['id']); + } + $cachedResult = true; + } else { + $cachedResult = false; + } + + return $cachedResult; + } + + function sitterLogin($username, $password) { + list($username, $password) = $this->escape_input($username, $password); + + $q = "SELECT sit1,sit2 FROM " . TB_PREFIX . "users where username = '$username' and access != " . BANNED ." LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + if($dbarray['sit1'] != 0) { + $q2 = "SELECT password FROM " . TB_PREFIX . "users where id = " . (int) $dbarray['sit1'] . " and access != " . BANNED . " LIMIT 1"; + $result2 = mysqli_query($this->dblink,$q2); + $dbarray2 = mysqli_fetch_array($result2); + } + if($dbarray['sit2'] != 0) { + $q3 = "SELECT password FROM " . TB_PREFIX . "users where id = " . (int) $dbarray['sit2'] . " and access != " . BANNED . " LIMIT 1"; + $result3 = mysqli_query($this->dblink,$q3); + $dbarray3 = mysqli_fetch_array($result3); + } + if($dbarray['sit1'] != 0 || $dbarray['sit2'] != 0) { + if(password_verify($password, $dbarray2['password']) || password_verify($password, $dbarray3['password'])) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + function setDeleting($uid, $mode) { + list($uid, $mode) = $this->escape_input((int) $uid, $mode); + + $time = time() + 72 * 3600; + if(!$mode) { + $q = "INSERT into " . TB_PREFIX . "deleting values ($uid,$time)"; + } else { + $q = "DELETE FROM " . TB_PREFIX . "deleting where uid = $uid"; + } + mysqli_query($this->dblink,$q); + } + + function isDeleting($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT timestamp from " . TB_PREFIX . "deleting where uid = $uid LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return isset($dbarray['timestamp']) ? (int)$dbarray['timestamp'] : 0; + } + + function modifyGold($userid, $amt, $mode) { + list($userid, $amt, $mode) = $this->escape_input((int) $userid, (int) $amt, $mode); + + if(!$mode) $q = "UPDATE " . TB_PREFIX . "users set gold = gold - $amt where id = $userid"; + else $q = "UPDATE " . TB_PREFIX . "users set gold = gold + $amt where id = $userid"; + + return mysqli_query($this->dblink,$q); + } + + /** + * Retrieves the user array via Username or ID + * + * @param int $ref The user ID or the username + * @param int $mode 0 --> Search by username, 1 --> Search by user ID + * @param bool $use_cache Will use the cache if true + * @return array Returns the user array + */ + + function getUserArray($ref, $mode, $use_cache = true) { + list($ref, $mode) = $this->escape_input($ref, $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)) { + return $cachedValue; + } + + 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); + return self::$fieldsCache[$ref.$mode]; + } + + function activeModify($username, $mode) { + list($username, $mode) = $this->escape_input($username, $mode); + + $time = time(); + if(!$mode) { + $q = "INSERT into " . TB_PREFIX . "active VALUES ('$username',$time)"; + } else { + $q = "DELETE FROM " . TB_PREFIX . "active where username = '$username'"; + } + return mysqli_query($this->dblink,$q); + } + + function addActiveUser($username, $time) { + list($username, $time) = $this->escape_input($username, $time); + + $q = "REPLACE into " . TB_PREFIX . "active values ('$username',$time)"; + if(mysqli_query($this->dblink,$q)) { + return true; + } else { + return false; + } + } + + function updateActiveUser($username, $time) { + static $updated = false; + + if ($updated) { + return; + } + + list($username, $time) = $this->escape_input($username, $time); + + $res1 = $this->addActiveUser($username, $time); + $q = "UPDATE " . TB_PREFIX . "users set timestamp = $time where username = '$username'"; + $res2 = mysqli_query($this->dblink,$q); + if($res1 && $res2) { + $updated = true; + return true; + } else { + return false; + } + } + + function submitProfile($uid, $gender, $location, $birthday, $desc1, $desc2) { + $uid = (int)$uid; + $gender = (int)$gender; + $location = mb_substr(trim($location), 0, 30, 'UTF-8'); + $birthday = trim($birthday); + // Issue #250: cap profile descriptions (BBCode is rendered as HTML on + // display) so a single field cannot store an oversized payload. + $desc1 = mb_substr(trim($desc1), 0, 3000, 'UTF-8'); + $desc2 = mb_substr(trim($desc2), 0, 3000, 'UTF-8'); + + $stmt = $this->dblink->prepare( + "UPDATE `".TB_PREFIX."users` + SET gender = ?, location = ?, birthday = ?, desc1 = ?, desc2 = ? + WHERE id = ? LIMIT 1" + ); + $stmt->bind_param("issssi", $gender, $location, $birthday, $desc1, $desc2, $uid); + $stmt->execute(); + $stmt->close(); + return true; + } + + function gpack($uid, $gpack) { + list($uid, $gpack) = $this->escape_input((int) $uid, $gpack); + + $q = "UPDATE " . TB_PREFIX . "users set gpack = '$gpack' where id = $uid"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function GetOnline($uid) { + list($uid) = $this->escape_input((int) $uid); + + $q = "SELECT sit FROM " . TB_PREFIX . "online WHERE uid = $uid LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['sit']; + } + + function UpdateOnline($mode, $name = "", $time = "", $uid = 0) { + list($mode, $name, $time, $uid) = $this->escape_input($mode, $name, $time, (int) $uid); + + global $session; + if($mode == "login") { + $q = "INSERT IGNORE INTO " . TB_PREFIX . "online (name, uid, time, sit) VALUES ('$name', $uid, '" . time() . "', 0)"; + return mysqli_query($this->dblink,$q); + } else if($mode == "sitter") { + $q = "INSERT IGNORE INTO " . TB_PREFIX . "online (name, uid, time, sit) VALUES ('$name', $uid, '" . time() . "', 1)"; + return mysqli_query($this->dblink,$q); + } else { + $q = "DELETE FROM " . TB_PREFIX . "online WHERE name ='" . $this->escape($session->username) . "'"; + return mysqli_query($this->dblink,$q); + } + } + + // no need to cache this method + function getNeedDelete() { + $time = time(); + $q = "SELECT uid FROM " . TB_PREFIX . "deleting where timestamp < $time"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + // no need to cache this method + function getLinks($id) { + list($id) = $this->escape_input((int) $id); + $q = 'SELECT * FROM `' . TB_PREFIX . 'links` WHERE `userid` = ' . $id . ' ORDER BY `pos` ASC'; + return mysqli_query($this->dblink,$q); + } + + function removeLinks($id,$uid) { + list($id,$uid) = $this->escape_input((int) $id,(int) $uid); + $q = "DELETE FROM " . TB_PREFIX . "links WHERE `id` = ".$id." and `userid` = ".$uid; + return mysqli_query($this->dblink,$q); + } + + function addPassword($uid, $npw, $cpw) { + list($uid, $npw, $cpw) = $this->escape_input((int) $uid, $npw, $cpw); + $q = "REPLACE INTO `" . TB_PREFIX . "password`(uid, npw, cpw) VALUES ($uid, '$npw', '$cpw')"; + mysqli_query($this->dblink,$q); + } + + function resetPassword($uid, $cpw) { + list($uid, $cpw) = $this->escape_input((int) $uid, $cpw); + $q = "SELECT npw FROM `" . TB_PREFIX . "password` WHERE uid = $uid AND cpw = '$cpw' AND used = 0 LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + + if(!empty($dbarray)) { + if(!$this->updateUserField($uid, 'password', password_hash($dbarray['npw'], PASSWORD_BCRYPT,['cost' => 12]), 1)) return false; + $q = "UPDATE `" . TB_PREFIX . "password` SET used = 1 WHERE uid = $uid AND cpw = '$cpw' AND used = 0"; + mysqli_query($this->dblink,$q); + return true; + } + + return false; + } + + //end general statistics + + function addFriend($uid, $column, $friend) { + list($uid, $column, $friend) = $this->escape_input((int) $uid, $column, (int) $friend); + + $q = "UPDATE " . TB_PREFIX . "users SET $column = $friend WHERE id = $uid"; + return mysqli_query($this->dblink,$q); + } + + function deleteFriend($uid, $column) { + list($uid, $column) = $this->escape_input((int) $uid, $column); + + $q = "UPDATE " . TB_PREFIX . "users SET $column = 0 WHERE id = $uid"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function checkFriends($uid) { + list($uid) = $this->escape_input($uid); + global $session; + + $user = $this->getUserArray($uid, 1); + for($i = 0; $i <= 19; $i++){ + if($user['friend'.$i] == 0 && $user['friend'.$i.'wait'] == 0){ + for($j = $i + 1; $j <= 19; $j++){ + $k = $j - 1; + if($user['friend'.$j] != 0){ + $friend = $this->getUserField($uid, "friend".$j, 0); + $this->addFriend($uid, "friend".$k, $friend); + $this->deleteFriend($uid, "friend".$j); + } + + if($user['friend'.$j.'wait'] == 0){ + $friendwait = $this->getUserField($uid, "friend".$j."wait", 0); + $this->addFriend($session->uid, "friend".$k."wait", $friendwait); + $this->deleteFriend($uid, "friend".$j."wait"); + } + } + } + } + } + + /***************************************** + Function to vacation mode - by advocaite + References: + *****************************************/ + + function setvacmode($uid, $days) { + // TODO: refactor vacation mode + list ($uid, $days) = $this->escape_input((int) $uid, (int) $days); + $days1 = 60 * 60 * 24 * $days; + $time = time() + $days1; + $q = "UPDATE " . TB_PREFIX . "users SET vac_mode = '1' , vac_time=" . $time . " WHERE id=" . $uid . ""; + $result = mysqli_query($this->dblink, $q); + return; + } + + function removevacationmode($uid){ + // TODO: refactor vacation mode + list ($uid) = $this->escape_input((int) $uid); + $q = "UPDATE " . TB_PREFIX . "users SET vac_mode = '0' , vac_time='0' WHERE id=" . $uid . ""; + $result = mysqli_query($this->dblink, $q); + return; + } + + function getvacmodexy($wref){ + // TODO: refactor vacation mode + list ($wref) = $this->escape_input((int) $wref); + $q = "SELECT id,oasistype,occupied FROM " . TB_PREFIX . "wdata where id = $wref"; + $result = mysqli_query($this->dblink, $q); + $dbarray = mysqli_fetch_array($result); + if ($dbarray['occupied'] != 0 && $dbarray['oasistype'] == 0) { + $q1 = "SELECT owner FROM " . TB_PREFIX . "vdata where wref = " . (int) $dbarray['id'] . ""; + $result1 = mysqli_query($this->dblink, $q1); + $dbarray1 = mysqli_fetch_array($result1); + if ($dbarray1['owner'] != 0) { + $q2 = "SELECT vac_mode,vac_time FROM " . TB_PREFIX . "users where id = " . (int) $dbarray1['owner'] . ""; + $result2 = mysqli_query($this->dblink, $q2); + $dbarray2 = mysqli_fetch_array($result2); + return $dbarray2['vac_mode'] == 1; + } + } + else + return + false; + } + + /***************************************** + Function to vacation mode + Remake & Refactor: Shadow + *****************************************/ + + function checkVacationRequirements(int $uid): array|bool{ + $uid = (int)$uid; + $now = time(); + $errors = []; + + // HELPERS : returns true if it finds rows + $exists = function(string $sql, array $params = []) { + $stmt = mysqli_prepare($this->dblink, $sql); + if ($params) { + $types = str_repeat('i', count($params)); + mysqli_stmt_bind_param($stmt, $types, ...$params); + } + mysqli_stmt_execute($stmt); + mysqli_stmt_store_result($stmt); + $found = mysqli_stmt_num_rows($stmt) > 0; + mysqli_stmt_close($stmt); + return $found; + }; + + // HELPERS : returns a single field + $fetchOne = function(string $sql, array $params = []) { + $stmt = mysqli_prepare($this->dblink, $sql); + if ($params) { + $types = str_repeat('i', count($params)); + mysqli_stmt_bind_param($stmt, $types, ...$params); + } + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + $row = mysqli_fetch_assoc($res); + mysqli_stmt_close($stmt); + return $row; + }; + + // 1. TROOPS MOVING + $sql = "SELECT 1 FROM ".TB_PREFIX."movement m + JOIN ".TB_PREFIX."vdata v ON v.wref = m.from + WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type=3 LIMIT 1"; + if ($exists($sql, [$uid, $now])) $errors[] = "TROOPS_MOVING"; + + // 2. INCOMING TROOPS + $sql = "SELECT 1 FROM ".TB_PREFIX."movement m + JOIN ".TB_PREFIX."vdata v ON v.wref = m.to + WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type IN (3,4) LIMIT 1"; + if ($exists($sql, [$uid, $now])) $errors[] = "INCOMING_TROOPS"; + + // 3. REINFORCEMENTS + $sql = "SELECT 1 FROM ".TB_PREFIX."enforcement e + WHERE (e.from IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner=?) + OR e.vref IN (SELECT wref FROM ".TB_PREFIX."vdata WHERE owner=?)) + AND (e.u1+e.u2+e.u3+e.u4+e.u5+e.u6+e.u7+e.u8+e.u9+e.u10+ + e.u11+e.u12+e.u13+e.u14+e.u15+e.u16+e.u17+e.u18+e.u19+e.u20+ + e.u21+e.u22+e.u23+e.u24+e.u25+e.u26+e.u27+e.u28+e.u29+e.u30+ + e.u41+e.u42+e.u43+e.u44+e.u45+e.u46+e.u47+e.u48+e.u49+e.u50) > 0 + LIMIT 1"; + if ($exists($sql, [$uid, $uid])) $errors[] = "REINFORCEMENTS"; + + // 4. WONDER WORLD + $sql = "SELECT 1 FROM ".TB_PREFIX."fdata f + JOIN ".TB_PREFIX."vdata v ON v.wref=f.vref + WHERE v.owner=? AND f.f99t=40 LIMIT 1"; + if ($exists($sql, [$uid])) $errors[] = "WW"; + + // 5. ARTEFACTS + $sql = "SELECT 1 FROM ".TB_PREFIX."artefacts WHERE owner=? LIMIT 1"; + if ($exists($sql, [$uid])) $errors[] = "ARTEFACTS"; + + // 6 + 10. PROTECTION + ADMIN or MULTIHUNTER + $user = $fetchOne("SELECT protect, access FROM ".TB_PREFIX."users WHERE id=?", [$uid]); + if ($user) { + if ((int)$user['protect'] > $now) $errors[] = "PROTECTION"; + if (in_array((int)$user['access'], [8,9])) $errors[] = "NO_VACATION_ACCESS"; + } + + // 7. PRISONERS IN & OUT (Enemy troops trapped in your villages & Your troops trapped in enemy villages) + $sqlIn = "SELECT 1 FROM ".TB_PREFIX."prisoners p + JOIN ".TB_PREFIX."vdata v ON v.wref=p.wref + WHERE v.owner=? LIMIT 1"; + if ($exists($sqlIn, [$uid])) $errors[] = "PRISONERS_IN"; + + $sqlOut = "SELECT 1 FROM ".TB_PREFIX."prisoners p + JOIN ".TB_PREFIX."vdata v ON v.wref=p.from + WHERE v.owner=? LIMIT 1"; + if ($exists($sqlOut, [$uid])) $errors[] = "PRISONERS_OUT"; + + // 8. MARKETPLACE MOVING + $sql = "SELECT 1 FROM ".TB_PREFIX."movement m + JOIN ".TB_PREFIX."vdata v ON v.wref=m.from + WHERE v.owner=? AND m.proc=0 AND m.endtime>? AND m.sort_type=0 LIMIT 1"; + if ($exists($sql, [$uid, $now])) $errors[] = "MARKET"; + + // 9. ACCOUNT DELETION + $del = $fetchOne("SELECT timestamp FROM ".TB_PREFIX."deleting WHERE uid=?", [$uid]); + if (!empty($del['timestamp']) && $del['timestamp'] > $now) { + $errors[] = "ACCOUNT_DELETION"; + } + return empty($errors) ? true : $errors; + } +} diff --git a/GameEngine/Database/DatabaseVillageQueries.php b/GameEngine/Database/DatabaseVillageQueries.php new file mode 100644 index 00000000..b2f82b0c --- /dev/null +++ b/GameEngine/Database/DatabaseVillageQueries.php @@ -0,0 +1,2252 @@ +dblink, $sql); + $row = mysqli_fetch_assoc($q); + $total = (int)($row['total'] ?? 0); + if ($total > 150) $total = 150; // safety cap + return $total; +} + + function updateResource($vid, $what, $number) { + $vid = (int) $vid; + + if (!is_array($what)) { + $what = [$what]; + $number = [$number]; + } + + $pairs = []; + foreach ($what as $index => $whatValue) { + $pairs[] = $this->escape($whatValue) . ' = ' . (Math::isInt($number[$index]) ? $number[$index] : '"'.$this->escape($number[$index]).'"'); + } + + $q = "UPDATE " . TB_PREFIX . "vdata SET ".implode(', ', $pairs)." WHERE wref = $vid"; + $result = mysqli_query($this->dblink,$q); + return mysqli_query($this->dblink,$q); + } + + //TODO: Remove this function to use the more general one + // no need to cache this method + function getVilWref($x, $y) { + list($x, $y) = $this->escape_input((int) $x, (int) $y); + + $q = "SELECT id FROM " . TB_PREFIX . "wdata where x = $x AND y = $y LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $dbarray = mysqli_fetch_array($result); + return $dbarray['id']; + } + + /** + * Converts from coordinates to village IDs + * + * @param array $coordinatesArray The coordinates array, containing the coordinates which need to be converted + * @return array Returns the converted coordinates + */ + + function getVilWrefs($coordinatesArray) { + list($coordinatesArray) = $this->escape_input($coordinatesArray); + + if(!is_array($coordinatesArray[0])) $coordinatesArray = [$coordinatesArray]; + + $conditions = []; + foreach($coordinatesArray as $coordinate){ + $conditions[] = "(x = ".round($coordinate[0])." AND y = ".round($coordinate[1]).")"; + } + + $q = "SELECT id FROM " . TB_PREFIX . "wdata WHERE ".implode(" OR ", $conditions); + $result = mysqli_query($this->dblink, $q); + + while($row = mysqli_fetch_assoc($result)) $wids[] = $row['id']; + return $wids; + } + + function getVrefField($ref, $field, $use_cache = true) { + return $this->getVillage($ref, 0, $use_cache)[$field]; + } + + // no need to cache this method + function getVrefCapital($ref) { + $vdata = $this->getProfileVillages($ref); + + foreach($vdata as $village){ + if($village['capital']) return $village; + } + return false; + } + + // no need to cache this method + function getStarvation() { + return $this->getProfileVillages(0, 2); + } + + /** + * Generates a list of "free to take" villages + * + * @param int $sector The map sector, + | -, - | + , + | +, - | - (0 and > 3, 1, 2, 3) + * @param int $mode 0 if villages need be generated under certain filters, 1 if not + * @param bool $respect_gametime If is false, we generate user base really anywhere + * and that means we can generate farms closer to the middle of the map as well. + * Otherwise we'd only generate farms at corner edges in late game, which + * sucks for people in the middle who registered too soon + * @param int $numberOfVillages Number of villages which need to be generated + * @return array Return the generated villages + */ + + function generateBase($sector, $mode = 0, $numberOfVillages = 1) { + list($sector, $mode, $numberOfVillages) = $this->escape_input((int) $sector, (int) $mode, (int)$numberOfVillages); + + // don't let SQL time out when 30-500 seconds (depending on php.ini) is not enough + @set_time_limit(0); + $num_rows = $count = 0; + $villages = []; + $time = time(); + + while ($numberOfVillages > 0) { + switch($mode){ + case 0: + $daysPassedFromStart = ($time - strtotime(START_DATE) - strtotime(date('d.m.Y')) + strtotime(START_TIME)) / 86400; + + $radiusMin = min(round(pow(2 * ($daysPassedFromStart / 5 * SPEED), 2)), round(pow(WORLD_MAX * 0.8, 2)) + round(pow(WORLD_MAX * 0.8, 2))); + $radiusMax = min(round(pow(4 * ($daysPassedFromStart / 5 * SPEED), 2)) + pow($count, 2), pow(WORLD_MAX, 2) + pow(WORLD_MAX, 2)); + break; + + case 1: + default: + $radiusMin = 1; + $radiusMax = pow(WORLD_MAX, 2); + break; + + case 2: //Small artifacts & WW building plans + $radiusMin = round(pow(WORLD_MAX * 0.50, 2)); + $radiusMax = round(pow(WORLD_MAX * 0.75, 2)); + break; + + case 3: //Large artifacts + $radiusMin = round(pow(WORLD_MAX * 0.35, 2)); + $radiusMax = round(pow(WORLD_MAX * 0.55, 2)); + break; + + case 4: //Unique artifacts + $radiusMin = round(pow(WORLD_MAX * 0.05, 2)); + $radiusMax = round(pow(WORLD_MAX * 0.25, 2)); + break; + + case 5: //WW villages + $radiusMin = round(pow(WORLD_MAX * 0.8, 2)); + $radiusMax = round(pow(WORLD_MAX, 2)); + break; + } + + // The four sectors must be mutually exclusive: any tile sitting on an + // axis (x = 0 or y = 0) used to satisfy two of the predicates below + // (e.g. "x <= 0" and "x >= 0" both matched x = 0). When a batch was + // spread over several sectors (WW villages, artifact villages), + // occupied = 0 was only flipped at the very end by setFieldTaken(), + // so two sectors could each pick the SAME axis tile and assign its + // wref to two villages. The duplicate addVillage() insert then hit + // the vdata PRIMARY KEY(wref) and aborted generateVillages() before + // setFieldTaken() ran, leaving the villages in vdata (visible in the + // Natars profile) but never marked on the map (issue #301). Using + // strict bounds on one side of each axis partitions the plane so a + // tile belongs to exactly one sector. + switch($sector){ + case 1: $newSector = "x < 0 AND y >= 0"; break; // - | + + case 2: $newSector = "x >= 0 AND y > 0"; break; // + | + + case 3: $newSector = "x <= 0 AND y < 0"; break; // - | - + default: $newSector = "x > 0 AND y <= 0"; // + | - + } + + //Choose villages beetween two circumferences, by using their formula (x^2 + y^2 = r^2) + $q = "SELECT id FROM ".TB_PREFIX."wdata WHERE fieldtype = 3 AND ($newSector) AND (POWER(x, 2) + POWER(y, 2) >= $radiusMin AND POWER(x, 2) + POWER(y, 2) <= $radiusMax) AND occupied = 0 ORDER BY RAND() LIMIT $numberOfVillages"; + $result = mysqli_query($this->dblink, $q); + + //Prevent an infinite loop + $resultedRows = mysqli_num_rows($result); + if($resultedRows == 0 && $count >= WORLD_MAX * 2) break; + + //Fill the villages array + $villages = array_merge($villages, $this->mysqli_fetch_all($result)); + + $num_rows += $resultedRows; + $numberOfVillages -= $resultedRows; + $count++; + + } + + foreach($villages as $village) $wids[] = $village['id']; + + return $num_rows == 1 ? $wids[0] : $wids; + } + + function setFieldTaken($id) { + if(empty($id)) return; + if (!is_array($id)) { + $id = [$id]; + } + + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + + $q = "UPDATE " . TB_PREFIX . "wdata SET occupied = 1 WHERE id IN(". implode(', ', $id).")"; + return mysqli_query($this->dblink,$q); + } + + /** + * Creates new villages + * + * @param array $villageArrays The array of the villages which have to be created + * @param int $uid The user ID + * @param string $username The username of the future owner + * @param array $troopsArray The troops that need to be added in the village(s) + * @param array $buildingsArray The buildings that need to be created in the village(s) + * @return array Returns the created villages ID + */ + + function generateVillages($villageArrays, $uid, $username, $troopsArray = null, $buildingsArray = null){ + $uid = (int)$uid; + $username = trim($username); + + $wids = $takenWids = $countedWids = $generatedWids = $i = []; + + //Count each kid in its own array, to check how many villages must be created + foreach($villageArrays as $village){ + if($village['wid'] == 0) $countedWids[$village['mode']][$village['kid']]++; + } + + //Generate the number of desired village for each kid + //and merge them with the more general "wids" array + foreach($countedWids as $mode => $totalCount){ + foreach($totalCount as $sector => $count){ + $generatedWids = $this->generateBase($sector, $mode, $count); + // Bug fix: previously merged every sector's wids into one flat + // $wids[$mode] list and consumed it with a single shared counter + // per mode, regardless of which sector a wid actually came from. + // As soon as a batch needed more than one sector (e.g. WW + // villages spread across all 4 quadrants), villages were handed + // wids strictly in array order, not matching their own kid — so + // most ended up in the wrong quadrant. Keying by [mode][sector] + // keeps each sector's wids separate. + $wids[$mode][$sector] = !is_array($generatedWids) ? [$generatedWids] : $generatedWids; + if(empty($i[$mode][$sector])) $i[$mode][$sector] = 0; + } + } + + //Create the villages + foreach($villageArrays as $village){ + + //Check if the village wid isn't already set and assing one among the generated ones + if($village['wid'] == 0) $village['wid'] = $wids[$village['mode']][$village['kid']][$i[$village['mode']][$village['kid']]++]; + + //Merge the wids into an unique array + $takenWids[] = $village['wid']; + $villageTypes[] = $village['type']; + + //Add the village and its buildings + $this->addVillage($village['wid'], $uid, $username, $village['capital'], $village['pop'], $village['name'], $village['natar']); + } + + //Create tables for the just generated villages + $this->addResourceFields($takenWids, $villageTypes, $buildingsArray); + $this->setFieldTaken($takenWids); + $this->addUnits($takenWids, $troopsArray); + $this->addTech($takenWids); + $this->addABTech($takenWids); + + return count($takenWids) > 1 ? $takenWids : $takenWids[0]; + } + + /** + * + * Create a village + * + * @param int $wid The village ID + * @param int $uid The User ID, the village's owner + * @param string $username The username + * @param int $capital 1 if it's a capital village, 0 otherwise + * @param int $pop The default village population + * @param string $villageName The default village name + * @return bool Returns true if the query was successful, false otherwise + */ + + function addVillage($wid, $uid, $username, $capital, $pop = 2, $villageName = null, $isNatar = 0) { + $wid = (int)$wid; + $uid = (int)$uid; + $capital = (int)$capital; + $pop = (int)$pop; + $isNatar = (int)$isNatar; + $username = trim($username); + + $total = count($this->getVillagesID($uid)); + if (empty($villageName)) { + // fără backslash, fără htmlentities – doar text curat + $villageName = $username . "'s village" . ($total >= 1 ? " " . ($total + 1) : ""); + } + + $time = time(); + $storage = STORAGE_BASE; + + $stmt = $this->dblink->prepare( + "INSERT INTO `".TB_PREFIX."vdata` + (wref, owner, name, capital, pop, cp, celebration, wood, clay, iron, maxstore, crop, maxcrop, lastupdate, created, natar) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + ); + $cp = 1; $celebration = 0; $wood = 750; $clay = 750; $iron = 750; $crop = 750; + $stmt->bind_param("iisiiiiiiiiiiiii", + $wid, $uid, $villageName, $capital, $pop, $cp, $celebration, + $wood, $clay, $iron, $storage, $crop, $storage, $time, $time, $isNatar + ); + $result = $stmt->execute(); + $stmt->close(); + return $result; +} + + /** + * + * Add the buildings tables to a specified village(s), and its relative buildings + * + * @param mixed $vid The village ID(s) + * @param mixed $type int if there's only one village, array if there are multiple villages + * @param array $buildingsArray divided in two portion, which contains the types (unidimensional array) and the values of the + * buildings that need to be added (bidimensional array) + * @return bool Return true if the query was successful, false otherwise + */ + + function addResourceFields($vids, $types, $buildingsArray = null) { + list($vids, $types, $buildingsArray) = $this->escape_input($vids, $types, $buildingsArray); + + if(!is_array($vids)){ + $vids = [$vids]; + $types = [$types]; + } + + //Set the default villages structure (resources fields and main building) + $defaultVillage = "vref,f1t,f2t,f3t,f4t,f5t,f6t,f7t,f8t,f9t,f10t,f11t,f12t,f13t,f14t,f15t,f16t,f17t,f18t" + .($buildingsArray != null ? ",".implode(",",$buildingsArray[0]) : ",f26,f26t"); + $defaultValues = []; + + //Select the village type and assemble the building values + foreach($vids as $index => $vid){ + $stringValues = ""; + $stringValues .= "(".$vid.","; + switch($types[$index]) { + case 1: $stringValues .= "4,4,1,4,4,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 2: $stringValues .= "3,4,1,3,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 3: $stringValues .= "1,4,1,3,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 4: $stringValues .= "1,4,1,2,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 5: $stringValues .= "1,4,1,3,1,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 6: $stringValues .= "4,4,1,3,4,4,4,4,4,4,4,4,4,4,4,2,4,4"; break; + case 7: $stringValues .= "1,4,4,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 8: $stringValues .= "3,4,4,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 9: $stringValues .= "3,4,4,1,1,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 10: $stringValues .= "3,4,1,2,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + case 11: $stringValues .= "3,1,1,3,1,4,4,3,3,2,2,3,1,4,4,2,4,4"; break; + case 12: $stringValues .= "1,4,1,1,2,2,3,4,4,3,3,4,4,1,4,2,1,2"; break; + default: $stringValues .= "4,4,1,4,4,2,3,4,4,3,3,4,4,1,4,2,1,2"; + } + + $stringValues .= $buildingsArray != null ? ",".implode(",",$buildingsArray[1][$index]).")" : ",1,15)"; + $defaultValues[] = $stringValues; + } + + $q = "INSERT INTO " . TB_PREFIX . "fdata ($defaultVillage) values".implode(",",$defaultValues); + return mysqli_query($this->dblink, $q); + } + + function isVillageOases($wref, $use_cache = true) { + // retirieve form cache + return $this->getVillageByWorldID($wref, $use_cache)['oasistype']; + } + + public function VillageOasisCount($vref, $use_cache = true) { + list($vref) = $this->escape_input((int) $vref); + + // 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::$oasisCountCache, $vref)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT count(*) FROM `".TB_PREFIX."odata` WHERE conqured=". $vref; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_row($result); + + self::$oasisCountCache[$vref] = $row[0]; + return self::$oasisCountCache[$vref]; + } + + /** + * Calculates the distance from a village to another + * + * @param int $coorx1 X coordinate of the first village + * @param int $coory1 Y coordinate of the second village + * @param int $coorx2 X coordinate of the first village + * @param int $coory2 Y coordinate of the second village + * @return int Returns the calculated distance + */ + + public function getDistance($coorx1, $coory1, $coorx2, $coory2) { + $max = 2 * WORLD_MAX + 1; + $x1 = intval($coorx1); + $y1 = intval($coory1); + $x2 = intval($coorx2); + $y2 = intval($coory2); + $distanceX = min(abs($x2 - $x1), abs($max - abs($x2 - $x1))); + $distanceY = min(abs($y2 - $y1), abs($max - abs($y2 - $y1))); + return round(sqrt(pow($distanceX, 2) + pow($distanceY, 2)), 1); + } + + public function canConquerOasis($vref, $wref, $use_cache = true) { + list($vref,$wref) = $this->escape_input($vref,$wref); + + // 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::$oasisConquerableCache, $vref.$wref)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $AttackerFields = $this->getResourceLevel( $vref, $use_cache ); + for ( $i = 19; $i <= 38; $i ++ ) { + if ( $AttackerFields[ 'f' . $i . 't' ] == 37 ) { + $HeroMansionLevel = $AttackerFields[ 'f' . $i ]; + } + } + if ( $this->VillageOasisCount( $vref ) < floor( ( $HeroMansionLevel - 5 ) / 5 ) ) { + $OasisInfo = $this->getOasisInfo( $wref ); + //fix by ronix + if ( + $OasisInfo['conqured'] == 0 || + $OasisInfo['conqured'] != 0 && + intval( $OasisInfo['loyalty'] ) < ( 99 / min(3, (4 - $this->VillageOasisCount($OasisInfo['conqured'], $use_cache))) ) + ) { + $CoordsVillage = $this->getCoor( $vref ); + $CoordsOasis = $this->getCoor( $wref ); + $max = 2 * WORLD_MAX + 1; + $x1 = intval( $CoordsOasis['x'] ); + $y1 = intval( $CoordsOasis['y'] ); + $x2 = intval( $CoordsVillage['x'] ); + $y2 = intval( $CoordsVillage['y'] ); + $distanceX = min( abs( $x2 - $x1 ), abs( $max - abs( $x2 - $x1 ) ) ); + $distanceY = min( abs( $y2 - $y1 ), abs( $max - abs( $y2 - $y1 ) ) ); + + if ( $distanceX <= 3 && $distanceY <= 3 ) { + self::$oasisConquerableCache[ $vref . $wref ] = 1; //can + } else { + self::$oasisConquerableCache[ $vref . $wref ] = 2; //can but not in 7x7 field + } + + } else { + self::$oasisConquerableCache[ $vref . $wref ] = 3; //loyalty >0 + } + + } else { + self::$oasisConquerableCache[ $vref . $wref ] = 0; //req level hero mansion + } + + return self::$oasisConquerableCache[ $vref . $wref ]; + } + + public function conquerOasis($vref,$wref) { + list($wref) = $this->escape_input((int) $wref); + + $vinfo = $this->getVillage($vref); + $uid = (int) $vinfo['owner']; + $q = "UPDATE `".TB_PREFIX."odata` SET conqured=".(int) $vref. ",loyalty=100,lastupdated=".time().",owner=$uid,name='Occupied Oasis' WHERE wref=".$wref; + return mysqli_query($this->dblink,$q); + } + + public function modifyOasisLoyalty($wref) { + list($wref) = $this->escape_input((int) $wref); + + if($this->isVillageOases($wref) != 0) { + $OasisInfo = $this->getOasisInfo($wref); + if($OasisInfo['conqured'] != 0) { + $LoyaltyAmendment = floor(100 / min(3,(4-$this->VillageOasisCount($OasisInfo['conqured'])))); + $q = "UPDATE `".TB_PREFIX."odata` SET loyalty=loyalty-$LoyaltyAmendment, lastupdated=".time()." WHERE wref=".$wref; + $result=mysqli_query($this->dblink,$q); + return $OasisInfo['loyalty']-$LoyaltyAmendment; + } + } + } + + function regenerateOasisUnits($wid, $automation = false) { + global $autoprefix; + + if (is_array($wid)) $wid = '(' . implode('),(', $wid) . ')'; + else $wid = '(' . (int) $wid . ')'; + + // load the oasis regeneration (in-game) and units generation (during install) SQL file + // and replace village IDs for the given $wid + $str = file_get_contents($autoprefix."var/db/datagen-oasis-troops-regen.sql"); + $str = preg_replace(["'%PREFIX%'", "'%VILLAGEID%'", "'%NATURE_REG_TIME%'"], [TB_PREFIX, $wid, ($automation ? NATURE_REGTIME : -1)], $str); + $result = $this->dblink->multi_query($str); + + // fetch results of the multi-query in order to allow subsequent query() and multi_query() calls to work + while (mysqli_more_results($this->dblink) && mysqli_next_result($this->dblink)) {;} + + if (!$result) return false; + + return true; + } + + /** + * Remove all oasis of a specified village if the mode is 1, if it's 0, then it'll remove only the selected oasis + * + * @param mixed $wref The village ID(s) (mode = 1)/oasis ID (mode = 0) of the oasis owner + * @return bool Returns true if the query was successful, false otherwise + */ + + function removeOases($wref, $mode = 0) { + list($wref) = $this->escape_input((int) $wref); + + if(!is_array($wref)) $wref = [$wref]; + $wrefs = implode(",", $wref); + + $q = "UPDATE ".TB_PREFIX."odata SET conqured = 0, owner = 2, name = 'Unoccupied Oasis' WHERE ".(!$mode ? "wref IN($wrefs)" : "conqured IN($wrefs)"); + return mysqli_query($this->dblink,$q); + } + + /*************************** + Function to retrieve type of village via ID + References: Village ID + ***************************/ + function getVillageType($wref, $use_cache = true) { + // retrieve this value from the full village data cache + return $this->getVillageByWorldID($wref, $use_cache)['fieldtype']; + } + + /***************************************** + Function to retrieve if is occupied via ID + References: Village ID + *****************************************/ + function getVillageState($wref, $use_cache = true) { + // retrieve this value from the full village data cache + if ($use_cache && ($cachedValue = self::returnCachedContent(self::$villageFieldsCacheByWorldID, $wref)) && !is_null($cachedValue)) { + return (((int)($cachedValue['occupied'] ?? 0)) != 0 || ((int)($cachedValue['oasistype'] ?? 0)) != 0); + } else { + $vil = $this->getVillageByWorldID($wref, $use_cache); + return (((int)($vil['occupied'] ?? 0)) != 0 || ((int)($vil['oasistype'] ?? 0)) != 0); + } + } + + /** + * Get the first free village, if there's one + * + * @param array $wids The village IDs + * @return int Returns the wid of the first free village, if they're all taken, returns 0 + */ + + function getFreeVillage($wids){ + list($wids) = $this->escape_input($wids); + + if(!is_array($wids)) $wids = [$wids]; + + $q = "SELECT id FROM ".TB_PREFIX."wdata WHERE id IN(".implode(",", $wids).") AND occupied = 0 AND oasistype = 0 LIMIT 1"; + $result = mysqli_query($this->dblink, $q); + return mysqli_num_rows($result) > 0 ? mysqli_fetch_array($result)[0] : 0; + } + + function getVillageID($uid, $use_cache = true) { + // load cached value + return $this->getVillagesID($uid, $use_cache)[0]; + } + + function getVillagesID($uid, $use_cache = true) { + list($uid) = $this->escape_input((int) $uid); + + // 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::$villageIDsCache, $uid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $array = $this->getProfileVillages($uid, 0, $use_cache); + $newarray = array(); + + for($i = 0; $i < count($array); $i++) { + array_push($newarray, $array[$i]['wref']); + } + + self::$villageIDsCache[$uid] = $newarray; + return self::$villageIDsCache[$uid]; + } + + function getVillagesID2($uid, $use_cache = true) { + list($uid) = $this->escape_input((int) $uid); + + // 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::$villageIDsCacheSimple, $uid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $array = $this->getProfileVillages($uid, 0, $use_cache); + self::$villageIDsCacheSimple[$uid] = $array; + + return self::$villageIDsCacheSimple[$uid]; + } + + function findAlreadyCachedVillageData($vid, $mode) { + // check if we don't actually have this data cached already in one of the other modes + for ($i = 0; $i <= 4; $i++) { + if ($mode !== $i && isset(self::$villageFieldsCache[$vid.$i])) { + // loop through cached values + foreach (self::$villageFieldsCache[$vid.$i] as $index => $value) { + // check for existing record with our requested ID/name/owner... + switch ($mode) { + case 0: if ($value['wref'] == $vid) { + return $value; + } + break; + + case 1: if ($value['name'] == $vid) { + return $value; + } + break; + + case 2: if ($value['owner'] == $vid) { + return $value; + } + break; + + case 3: if ((isset($value['owner']) && isset($value['capital'])) && $value['owner'] == $vid && $value['capital'] == 1) { + return $value; + } + break; + + case 4: if ($value['owner'] == 4) { + return $value; + } + break; + } + } + } + } + + return false; + } + + function getVillage($vid, $mode = 0, $use_cache = true) { + // 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::$villageFieldsCache, ((int) $vid).$mode)) && !is_null($cachedValue)) { + return $cachedValue; + } + + if ($use_cache && ($altCachedContentSearch = $this->findAlreadyCachedVillageData($vid, $mode))) { + return $altCachedContentSearch; + } + + switch ($mode) { + // by WREF + case 0: $vid = (int) $vid; + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE wref = $vid LIMIT 1"; + break; + + // by name + case 1: $name = $this->escape($vid); + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE `name` = '$name' LIMIT 1"; + break; + + // by owner ID + case 2: $vid = (int) $vid; + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = $vid LIMIT 1"; + break; + + // by owner ID and capital = 1 + case 3: $vid = (int) $vid; + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = $vid AND capital = 1 LIMIT 1"; + break; + + // by owner = Taskmaster + case 4: $vid = (int) $vid; + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner = 4 LIMIT 1"; + break; + } + + $result = mysqli_query($this->dblink,$q); + + self::$villageFieldsCache[$vid.$mode] = mysqli_fetch_array($result, MYSQLI_ASSOC); + return self::$villageFieldsCache[$vid.$mode]; + } + + function getProfileVillages($uid, $mode = 0, $use_cache = true) { + $arrayPassed = is_array($uid); + + if (!$arrayPassed) { + $uid = [(int) $uid]; + } else { + foreach ($uid as $index => $uidValue) { + $uid[$index] = (int) $uidValue; + } + } + + if (!count($uid)) { + return []; + } + + // first of all, check if we should be using cache + if ($use_cache && !$arrayPassed && ($cachedValue = self::returnCachedContent(self::$userVillagesCache, $uid[0].$mode)) && !is_null($cachedValue)) { + return $cachedValue; + } + + // if we've given a number of villages to preload, remove those that already are + if ($use_cache && $arrayPassed) { + $newIDs = []; + foreach ($uid as $id) { + if (!isset(self::$userVillagesCache[$id.$mode])) { + $newIDs[] = $id; + } + } + $uid = $newIDs; + } + + // nothing left to cache, return the full cache + if (!count($uid)) { + return self::$userVillagesCache; + } + + switch ($mode) { + // by owner ID + case 0: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner IN(".implode(', ', $uid).") ORDER BY pop DESC"; + break; + + // capital villages where owner is a real player (i.e. not Natars etc.) + case 1: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE capital = 1 and owner > 5"; + break; + + // villages with starvation data + case 2: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE starv != 0 and owner != 3"; + break; + + // field distance calculator query + case 3: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE owner > 4 and wref != ".$uid[0]; + break; + + // villages in need of celebration data update + case 4: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE celebration < ".$uid[0]." AND celebration != 0"; + break; + + // by vref ID + case 5: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE wref IN(".implode(', ', $uid).")"; + break; + + // by loyalty updates required + case 6: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE loyalty < 100"; + break; + + // villages without starvation data, Support, Nature, Natars, Taskmaster, Multihunter are all excluded + case 7: $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE starv = 0 and owner > 5"; + break; + } + + $result = mysqli_query($this->dblink,$q); + + if (!$arrayPassed) { + $result = $this->mysqli_fetch_all($result); + self::$userVillagesCache[ $uid[0].$mode ] = $result; + + // cache each village individually into the fields cache as well + foreach ($result as $v) { + $amode = 0; + self::$villageFieldsCache[((int) $v['wref']).$amode] = $v; + } + } else { + // we're preloading, cache all the data individually + if (mysqli_num_rows($result)) { + $amode = 0; + while ( $row = mysqli_fetch_array( $result, MYSQLI_ASSOC ) ) { + if ( ! isset( self::$userVillagesCache[ $row['owner'].$mode ] ) ) { + self::$userVillagesCache[ $row['owner'].$mode ] = []; + } + + self::$userVillagesCache[ $row['owner'].$mode ][] = $row; + self::$villageFieldsCache[((int) $row['wref']).$amode] = $row; + } + + // just return the full cache if we've given an array of IDs to load villages for + $result = self::$userVillagesCache; + } + } + + return $result; + } + + function cacheVillageByWorldIDs($uid, $mode = 0) { + if (!is_array($uid)) { + $uid = [(int) $uid]; + } else { + foreach ($uid as $index => $uidValue) { + $uid[$index] = (int) $uidValue; + } + } + + $result = mysqli_query($this->dblink, " + SELECT + * + FROM + " . TB_PREFIX . "wdata as wdata + LEFT JOIN " . TB_PREFIX . "vdata as vdata ON wdata.id = vdata.wref + WHERE vdata.owner IN(".implode('', $uid).")" + ); + + if (mysqli_num_rows($result)) { + $result = $this->mysqli_fetch_all($result); + + $amode = 0; + foreach ($result as $row) { + self::$villageFieldsCacheByWorldID[$row['id']] = $row; + + // cache village fields by wref as well, for future use + if (!isset(self::$villageFieldsCache[((int) $row['wref']).$amode])) { + self::$villageFieldsCache[ ( (int) $row['wref'] ) . $amode ] = $row; + } + } + } + } + + function getVillageByWorldID($vid, $use_cache = true) { + $array_passed = is_array($vid); + + if (!$array_passed) { + $vid = [(int) $vid]; + } else { + foreach ($vid as $index => $ivdValue) { + $vid[$index] = (int) $ivdValue; + } + } + + if (!count($vid)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && isset(self::$villageFieldsCacheByWorldID[$vid[0]]) && is_array(self::$villageFieldsCacheByWorldID[$vid[0]]) && !count(self::$villageFieldsCacheByWorldID[$vid[0]])) { + return self::$villageFieldsCacheByWorldID[$vid[0]]; + } else if ($use_cache && $array_passed) { + // check what we can return from cache + $newVIDs = []; + foreach ($vid as $key) { + if (!isset(self::$villageFieldsCacheByWorldID[$key])) { + $newVIDs [] = $key; + } + } + + // everything's cached, just return the cache + if (!count($newVIDs)) { + return self::$villageFieldsCacheByWorldID; + } else { + // update remaining IDs to select and cache + $vid = $newVIDs; + } + } else if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$villageFieldsCacheByWorldID, $vid[0])) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "wdata where id IN(".implode(', ', $vid).")"; + $result = $this->mysqli_fetch_all(mysqli_query($this->dblink,$q)); + + // return a single value + if (!$array_passed) { + self::$villageFieldsCacheByWorldID[$vid[0]] = (isset($result[0]) && is_array($result[0])) ? $result[0] : []; + } else { + if ($result && count($result)) { + foreach ( $result as $record ) { + self::$villageFieldsCacheByWorldID[$record['id']] = $record; + } + } + + // check for any missing IDs and fill them in with blanks, + // since no reinforcements were found for these villages + foreach ($vid as $key) { + if (!isset(self::$villageFieldsCacheByWorldID[$key])) { + self::$villageFieldsCacheByWorldID[$key] = []; + } + } + } + + return ($array_passed ? self::$villageFieldsCacheByWorldID : self::$villageFieldsCacheByWorldID[$vid[0]]); + } + + public function getVillageBattleData($vid, $use_cache = true) { + list($vid) = $this->escape_input((int) $vid); + + // 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::$villageBattleDataCache, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT u.id,u.tribe,v.capital,f.f40 AS wall FROM ".TB_PREFIX."users u,".TB_PREFIX."fdata f,".TB_PREFIX."vdata v WHERE u.id=v.owner AND f.vref=v.wref AND v.wref=".$vid." LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + + self::$villageBattleDataCache[$vid] = mysqli_fetch_array($result, MYSQLI_ASSOC); + return self::$villageBattleDataCache[$vid]; + } + + function getOasisV($vid, $use_cache = true) { + list($vid) = $this->escape_input((int) $vid); + + // 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::$oasisFieldsCache, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "odata where wref = $vid LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + + self::$oasisFieldsCache[$vid] = mysqli_fetch_array($result, MYSQLI_ASSOC); + return self::$oasisFieldsCache[$vid]; + } + + function getMInfo($id, $use_cache = true) { + $array_passed = is_array($id); + + if (!$array_passed) { + $id = [(int) $id]; + } else { + foreach ($id as $index => $idValue) { + $id[$index] = (int) $idValue; + } + } + + if (!count($id)) { + return []; + } + + // first of all, check if we should be using cache and whether the data + // required is already cached + if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$worldAndVillageDataCache, $id[0])) && !is_null($cachedValue)) { + return $cachedValue; + } else if ($use_cache && $array_passed) { + // only select the world IDs we haven't cached yet + $newIDs = []; + foreach ($id as $key) { + if (!isset(self::$worldAndVillageDataCache[$key])) { + $newIDs[] = $key; + } + } + + // everything's cached, return the whole cache + if (!count($newIDs)) { + return self::$worldAndVillageDataCache; + } + $id = $newIDs; + } + + $q = "SELECT * FROM " . TB_PREFIX . "wdata left JOIN " . TB_PREFIX . "vdata ON " . TB_PREFIX . "vdata.wref = " . TB_PREFIX . "wdata.id where " . TB_PREFIX . "wdata.id IN(".implode(', ', $id).")"; + $result = mysqli_query($this->dblink,$q); + + // preserve the original MYSQLI_BOTH semantics (numeric + associative keys) + $rows = []; + if ($result) { + while ($row = mysqli_fetch_array($result, MYSQLI_BOTH)) { + $rows[] = $row; + } + } + + // return a single value + if (!$array_passed) { + self::$worldAndVillageDataCache[$id[0]] = isset($rows[0]) ? $rows[0] : null; + return self::$worldAndVillageDataCache[$id[0]]; + } + + // cache each returned record by its world ID (wdata.id) + foreach ($rows as $record) { + self::$worldAndVillageDataCache[$record['id']] = $record; + } + + return self::$worldAndVillageDataCache; + } + + function getOMInfo($id, $use_cache = true) { + list($id) = $this->escape_input((int) $id); + + // 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::$worldAndOasisDataCache, $id)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "wdata left JOIN " . TB_PREFIX . "odata ON " . TB_PREFIX . "odata.wref = " . TB_PREFIX . "wdata.id where " . TB_PREFIX . "wdata.id = $id LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + + self::$worldAndOasisDataCache[$id] = mysqli_fetch_array($result); + return self::$worldAndOasisDataCache[$id]; + } + + function getOasis($vid, $use_cache = true) { + list($vid) = $this->escape_input((int) $vid); + + // 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::$oasisFieldsCacheByConqueredID, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * FROM " . TB_PREFIX . "odata where conqured = $vid"; + $result = mysqli_query($this->dblink,$q); + + self::$oasisFieldsCacheByConqueredID[$vid] = $this->mysqli_fetch_all($result); + return self::$oasisFieldsCacheByConqueredID[$vid]; + } + + function getOasisInfo($wid, $use_cache = true) { + return $this->getOasisV($wid, $use_cache); + } + + function getVillageField($ref, $field, $use_cache = true) { + // return all data, don't waste time by selecting fields one by one + $villageArray = $this->getVillage($ref, 0, $use_cache); + $result = (isset($villageArray[$field]) ? $villageArray[$field] : null); + + if($result){ + // will return the result + }elseif($field=="name"){ + $result = "[?]"; + }else $result = 0; + + return $result; + } + + 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); + } + + function getOasisField($ref, $field, $use_cache = true) { + // return all data, don't waste time by selecting fields one by one + $oasisArray = $this->getOasisV($ref, $use_cache); + return (isset($oasisArray[$field]) ? $oasisArray[$field] : null); + } + + 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); + } + + function setVillageField($ref, $field, $value) { + if (!is_array($field)) { + $field = [$field]; + $value = [$value]; + } + + $pairs = []; + foreach ($field as $index => $fieldValue) { + $newValue = ((Math::isInt($value[$index]) || Math::isFloat($value[$index])) ? $value[$index] : '"'.$this->escape($value[$index]).'"'); + $pairs[] = $this->escape($fieldValue).' = '.$newValue; + + // update cache + if (isset(self::$villageFieldsCache[$ref])) { + self::$villageFieldsCache[$ref][$fieldValue] = $newValue; + } + } + + $q = "UPDATE " . TB_PREFIX . "vdata SET ".implode(', ', $pairs)." WHERE wref = ".(int) $ref; + return mysqli_query($this->dblink,$q); + } + + function setVillageFields($ref, $fields, $values) { + list($ref, $fields, $values) = $this->escape_input((int) $ref, $fields, $values); + + if (!count($fields)) { + return; + } + + // build the field-value query parts + $fieldValues = []; + foreach ($fields as $id => $fieldName) { + $fieldValues[] = $this->escape($fieldName).' = '.((Math::isInt($values[$id]) || Math::isFloat($values[$id])) ? $values[$id] : '"'.$this->escape($values[$id]).'"'); + } + + $q = "UPDATE " . TB_PREFIX . "vdata set ".implode(', ', $fieldValues)." where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + function setVillageLevel($ref, $fields, $values) { + list($ref, $fields, $values) = $this->escape_input((int) $ref, $fields, $values); + + // build the field-value query parts + $fieldValues = []; + + if (!is_array($fields)) { + $fields = [$fields]; + $values = [$values]; + } + + foreach ($fields as $id => $fieldName) { + $fieldValues[] = $this->escape($fieldName).' = '.((Math::isInt($values[$id]) || Math::isFloat($values[$id])) ? $values[$id] : '"'.$this->escape($values[$id]).'"'); + } + + $q = "UPDATE " . TB_PREFIX . "fdata set ".implode(', ', $fieldValues)." where vref = " . $ref; + return mysqli_query($this->dblink,$q); + } + + function cacheResourceLevels($vids) { + if (!is_array($vids)) { + $vids = [$vids]; + } + + $newVids = []; + foreach ($vids as $index => $vidValue) { + $vids[ $index ] = (int) $vidValue; + + // don't cache what's cached + if (!isset(self::$resourceLevelsCache[$vids[ $index ]])) { + $newVids[] = $vids[ $index ]; + } + } + $vids = $newVids; + + if (!count($vids)) { + return []; + } + + $q = "SELECT * FROM " . TB_PREFIX . "fdata WHERE vref IN(".implode(', ', $vids).")"; + $result = mysqli_query($this->dblink,$q); + + foreach ( $this->mysqli_fetch_all( $result ) as $row ) { + self::$resourceLevelsCache[ $row['vref'] ] = $row; + } + + return self::$resourceLevelsCache; + } + + function getResourceLevel($vid, $use_cache = true) { + list($vid) = $this->escape_input((int) $vid); + + // 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::$resourceLevelsCache, $vid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT * from " . TB_PREFIX . "fdata where vref = $vid"; + $result = mysqli_query($this->dblink,$q); + + self::$resourceLevelsCache[$vid] = mysqli_fetch_assoc($result); + return self::$resourceLevelsCache[$vid]; + } + + public static function clearResourseLevelsCache() { + self::$resourceLevelsCache = []; + self::$fieldLevelsInVillageSearchCache = []; + self::$fieldLevelsCache = []; + } + //end fix + + function getCoor($wref, $use_cache = true) { + // retirieve form cache + return $this->getVillageByWorldID($wref, $use_cache); + } + + function getVillageType2($wref, $use_cache = true) { + // retirieve form cache + return $this->getVillageByWorldID($wref, $use_cache)['oasistype']; + } + + // no need to cache this method + function checkVilExist($wref) { + list($wref) = $this->escape_input((int) $wref); + + // first of all, check if this exists in our cache already - and if so, we don't need an extra query + $mode = 0; + if (isset(self::$villageFieldsCache[((int) $wref).$mode])) { + return true; + } + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "vdata where wref = '$wref'"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + + return $result['Total']; + } + + // no need to cache this method + function checkOasisExist($wref) { + list($wref) = $this->escape_input((int) $wref); + + $q = "SELECT Count(*) as Total FROM " . TB_PREFIX . "odata where wref = '$wref'"; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + if($result['Total']) { + return true; + } else { + return false; + } + } + + 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 ? '-' : '+'); + + $q = " + UPDATE " . TB_PREFIX . "vdata + SET + wood = IF(wood $sign $wood < 0, 0, IF(wood $sign $wood > maxstore, maxstore, wood $sign $wood)), + clay = IF(clay $sign $clay < 0, 0, IF(clay $sign $clay > maxstore, maxstore, clay $sign $clay)), + iron = IF(iron $sign $iron < 0, 0, IF(iron $sign $iron > maxstore, maxstore, iron $sign $iron)), + crop = IF(crop $sign $crop < 0, 0, IF(crop $sign $crop > maxcrop, maxcrop, crop $sign $crop)) + WHERE + wref = " . $vid ; + + return mysqli_query( $this->dblink, $q); + } + + function setMaxStoreForVillage($vid, $maxLevel) { + $vid = (int) $vid; + $maxLevel = (int) $maxLevel; + + $this->query(" + UPDATE + ".TB_PREFIX."vdata + SET + `maxstore` = IF( `maxstore` - $maxLevel < ".STORAGE_BASE.", ".STORAGE_BASE.", `maxstore` - $maxLevel ) + WHERE + wref=$vid"); + } + + function setMaxCropForVillage($vid, $maxLevel) { + $vid = (int) $vid; + $maxLevel = (int) $maxLevel; + + $this->query(" + UPDATE + ".TB_PREFIX."vdata + SET + `maxcrop` = IF( `maxcrop` - $maxLevel < ".STORAGE_BASE.", ".STORAGE_BASE.", `maxcrop` - $maxLevel ) + WHERE + wref=$vid"); + } + + function modifyOasisResource($vid, $wood, $clay, $iron, $crop, $mode) { + list($vid, $wood, $clay, $iron, $crop, $mode) = $this->escape_input((int) $vid, (int) $wood, (int) $clay, (int) $iron, (int) $crop, $mode); + + $negativeResources = false; + $checkres = $this->getOasisV($vid); + + if (!$mode) { + $nwood = $checkres['wood'] - $wood; + $nclay = $checkres['clay'] - $clay; + $niron = $checkres['iron'] - $iron; + $ncrop = $checkres['crop'] - $crop; + + $negativeResources = $nwood < 0 || $nclay < 0 || $niron < 0 || $ncrop < 0; + + $dwood = ($nwood < 0) ? 0 : $nwood; + $dclay = ($nclay < 0) ? 0 : $nclay; + $diron = ($niron < 0) ? 0 : $niron; + $dcrop = ($ncrop < 0) ? 0 : $ncrop; + } else { + $nwood = $checkres['wood'] + $wood; + $nclay = $checkres['clay'] + $clay; + $niron = $checkres['iron'] + $iron; + $ncrop = $checkres['crop'] + $crop; + $dwood = ($nwood > $checkres['maxstore']) ? $checkres['maxstore'] : $nwood; + $dclay = ($nclay > $checkres['maxstore']) ? $checkres['maxstore'] : $nclay; + $diron = ($niron > $checkres['maxstore']) ? $checkres['maxstore'] : $niron; + $dcrop = ($ncrop > $checkres['maxcrop']) ? $checkres['maxcrop'] : $ncrop; + } + + if (!$negativeResources) { + $q = "UPDATE " . TB_PREFIX . "odata SET wood = $dwood, clay = $dclay, iron = $diron, crop = $dcrop WHERE wref = ".$vid; + return mysqli_query($this->dblink, $q); + } + else return false; + } + + function getFieldLevelInVillage($vid, $fieldType, $use_cache = true) { + $vid = (int) $vid; + + // first of all, check if we should be using cache and whether the field + // required is already cached. NB: use isset() rather than the generic + // returnCachedContent(), which treats a cached level of 0 as "empty" and + // re-queries it on every call — buildings the village does not own + // (level 0, very common) would otherwise never be cached. + if ($use_cache && isset(self::$fieldLevelsInVillageSearchCache[$vid.$fieldType])) { + return self::$fieldLevelsInVillageSearchCache[$vid.$fieldType]; + } + + // $fieldType can be both, integer and string, to be used in the IN statement, + // so we need to handle it correctly here + if (!Math::isInt($fieldType)) { + $fieldType = $this->escape($fieldType); + } + + // please don't scream... + // with the current table structure, there really IS NOT another way + // (except for stored procedures, which we can't rely on to be allowed on the server) + $result = mysqli_query($this->dblink, 'SELECT '. + 'CASE '. + 'WHEN `f1t` IN ('.$fieldType.') THEN `f1` '. + 'WHEN `f2t` IN ('.$fieldType.') THEN `f2` '. + 'WHEN `f3t` IN ('.$fieldType.') THEN `f3` '. + 'WHEN `f4t` IN ('.$fieldType.') THEN `f4` '. + 'WHEN `f5t` IN ('.$fieldType.') THEN `f5` '. + 'WHEN `f6t` IN ('.$fieldType.') THEN `f6` '. + 'WHEN `f7t` IN ('.$fieldType.') THEN `f7` '. + 'WHEN `f8t` IN ('.$fieldType.') THEN `f8` '. + 'WHEN `f9t` IN ('.$fieldType.') THEN `f9` '. + 'WHEN `f10t` IN ('.$fieldType.') THEN `f10` '. + 'WHEN `f11t` IN ('.$fieldType.') THEN `f11` '. + 'WHEN `f12t` IN ('.$fieldType.') THEN `f12` '. + 'WHEN `f13t` IN ('.$fieldType.') THEN `f13` '. + 'WHEN `f14t` IN ('.$fieldType.') THEN `f14` '. + 'WHEN `f15t` IN ('.$fieldType.') THEN `f15` '. + 'WHEN `f16t` IN ('.$fieldType.') THEN `f16` '. + 'WHEN `f17t` IN ('.$fieldType.') THEN `f17` '. + 'WHEN `f18t` IN ('.$fieldType.') THEN `f18` '. + 'WHEN `f19t` IN ('.$fieldType.') THEN `f19` '. + 'WHEN `f20t` IN ('.$fieldType.') THEN `f20` '. + 'WHEN `f21t` IN ('.$fieldType.') THEN `f21` '. + 'WHEN `f22t` IN ('.$fieldType.') THEN `f22` '. + 'WHEN `f23t` IN ('.$fieldType.') THEN `f23` '. + 'WHEN `f24t` IN ('.$fieldType.') THEN `f24` '. + 'WHEN `f25t` IN ('.$fieldType.') THEN `f25` '. + 'WHEN `f26t` IN ('.$fieldType.') THEN `f26` '. + 'WHEN `f27t` IN ('.$fieldType.') THEN `f27` '. + 'WHEN `f28t` IN ('.$fieldType.') THEN `f28` '. + 'WHEN `f29t` IN ('.$fieldType.') THEN `f29` '. + 'WHEN `f30t` IN ('.$fieldType.') THEN `f30` '. + 'WHEN `f31t` IN ('.$fieldType.') THEN `f31` '. + 'WHEN `f32t` IN ('.$fieldType.') THEN `f32` '. + 'WHEN `f33t` IN ('.$fieldType.') THEN `f33` '. + 'WHEN `f34t` IN ('.$fieldType.') THEN `f34` '. + 'WHEN `f35t` IN ('.$fieldType.') THEN `f35` '. + 'WHEN `f36t` IN ('.$fieldType.') THEN `f36` '. + 'WHEN `f37t` IN ('.$fieldType.') THEN `f37` '. + 'WHEN `f38t` IN ('.$fieldType.') THEN `f38` '. + 'WHEN `f39t` IN ('.$fieldType.') THEN `f39` '. + 'WHEN `f40t` IN ('.$fieldType.') THEN `f40` '. + 'WHEN `f99t` IN ('.$fieldType.') THEN `f99` '. + 'ELSE 0 '. + 'END AS level '. + 'FROM `'.TB_PREFIX.'fdata` '. + 'WHERE '. + '`vref` = '.$vid.' '. + 'AND ('. + '`f1t` IN ('.$fieldType.') OR '. + '`f2t` IN ('.$fieldType.') OR '. + '`f3t` IN ('.$fieldType.') OR '. + '`f4t` IN ('.$fieldType.') OR '. + '`f5t` IN ('.$fieldType.') OR '. + '`f6t` IN ('.$fieldType.') OR '. + '`f7t` IN ('.$fieldType.') OR '. + '`f8t` IN ('.$fieldType.') OR '. + '`f9t` IN ('.$fieldType.') OR '. + '`f10t` IN ('.$fieldType.') OR '. + '`f11t` IN ('.$fieldType.') OR '. + '`f12t` IN ('.$fieldType.') OR '. + '`f13t` IN ('.$fieldType.') OR '. + '`f14t` IN ('.$fieldType.') OR '. + '`f15t` IN ('.$fieldType.') OR '. + '`f16t` IN ('.$fieldType.') OR '. + '`f17t` IN ('.$fieldType.') OR '. + '`f18t` IN ('.$fieldType.') OR '. + '`f19t` IN ('.$fieldType.') OR '. + '`f20t` IN ('.$fieldType.') OR '. + '`f20t` IN ('.$fieldType.') OR '. + '`f21t` IN ('.$fieldType.') OR '. + '`f22t` IN ('.$fieldType.') OR '. + '`f23t` IN ('.$fieldType.') OR '. + '`f24t` IN ('.$fieldType.') OR '. + '`f25t` IN ('.$fieldType.') OR '. + '`f26t` IN ('.$fieldType.') OR '. + '`f27t` IN ('.$fieldType.') OR '. + '`f28t` IN ('.$fieldType.') OR '. + '`f29t` IN ('.$fieldType.') OR '. + '`f30t` IN ('.$fieldType.') OR '. + '`f30t` IN ('.$fieldType.') OR '. + '`f31t` IN ('.$fieldType.') OR '. + '`f32t` IN ('.$fieldType.') OR '. + '`f33t` IN ('.$fieldType.') OR '. + '`f34t` IN ('.$fieldType.') OR '. + '`f35t` IN ('.$fieldType.') OR '. + '`f36t` IN ('.$fieldType.') OR '. + '`f37t` IN ('.$fieldType.') OR '. + '`f38t` IN ('.$fieldType.') OR '. + '`f39t` IN ('.$fieldType.') OR '. + '`f40t` IN ('.$fieldType.') OR '. + '`f99t` IN ('.$fieldType.')) '. + 'LIMIT 1'); + $row = mysqli_fetch_array($result, MYSQLI_ASSOC); + + self::$fieldLevelsInVillageSearchCache[$vid.$fieldType] = isset($row['level']) ? (int)$row['level'] : 0; + return self::$fieldLevelsInVillageSearchCache[$vid.$fieldType]; + } + + function getFieldLevel($vid, $field, $use_cache = true) { + list($vid, $field) = $this->escape_input((int) $vid, $field); + + // 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::$resourceLevelsCache, $vid.$field)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = "SELECT f" . $field . " from " . TB_PREFIX . "fdata where vref = $vid LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $row = $result ? mysqli_fetch_array($result, MYSQLI_ASSOC) : null; + + self::$resourceLevelsCache[$vid.$field] = (is_array($row) && array_key_exists("f".$field, $row)) ? (int)$row["f" . $field] : 0; + return self::$resourceLevelsCache[$vid.$field]; + } + + function getSingleFieldTypeCount($uid, $field, $lvlComparisonSign = '=', $lvl = false, $use_cache = true) { + $uid = (int) $uid; + $field = (int) $field; + $lvl = ($lvl === false ? $lvl : (int) $lvl); + + if (!in_array($lvlComparisonSign, ['=', '<', '>', '>=', '<=', '!='])) { + $lvlComparisonSign = '='; + } + + // 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::$singleFieldTypeCountCache, $uid.$field.$lvlComparisonSign.($lvl ? 1 : 0))) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = " + SELECT + Count(*) as Total + FROM + ".TB_PREFIX."fdata f + LEFT JOIN ".TB_PREFIX."vdata v ON f.vref = v.wref + LEFT JOIN ".TB_PREFIX."users u ON v.owner = u.id + WHERE + u.id = ".$uid." + AND + ( + (f1t = ".$field.($lvl !== false ? ' AND f1 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f2t = ".$field.($lvl !== false ? ' AND f2 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f3t = ".$field.($lvl !== false ? ' AND f3 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f4t = ".$field.($lvl !== false ? ' AND f4 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f5t = ".$field.($lvl !== false ? ' AND f5 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f6t = ".$field.($lvl !== false ? ' AND f6 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f7t = ".$field.($lvl !== false ? ' AND f7 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f8t = ".$field.($lvl !== false ? ' AND f8 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f9t = ".$field.($lvl !== false ? ' AND f9 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f10t = ".$field.($lvl !== false ? ' AND f10 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f11t = ".$field.($lvl !== false ? ' AND f11 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f12t = ".$field.($lvl !== false ? ' AND f12 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f13t = ".$field.($lvl !== false ? ' AND f13 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f14t = ".$field.($lvl !== false ? ' AND f14 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f15t = ".$field.($lvl !== false ? ' AND f15 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f16t = ".$field.($lvl !== false ? ' AND f16 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f17t = ".$field.($lvl !== false ? ' AND f17 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f18t = ".$field.($lvl !== false ? ' AND f18 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f19t = ".$field.($lvl !== false ? ' AND f19 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f20t = ".$field.($lvl !== false ? ' AND f20 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f21t = ".$field.($lvl !== false ? ' AND f21 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f22t = ".$field.($lvl !== false ? ' AND f22 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f23t = ".$field.($lvl !== false ? ' AND f23 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f24t = ".$field.($lvl !== false ? ' AND f24 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f25t = ".$field.($lvl !== false ? ' AND f25 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f26t = ".$field.($lvl !== false ? ' AND f26 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f27t = ".$field.($lvl !== false ? ' AND f27 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f28t = ".$field.($lvl !== false ? ' AND f28 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f29t = ".$field.($lvl !== false ? ' AND f29 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f30t = ".$field.($lvl !== false ? ' AND f30 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f31t = ".$field.($lvl !== false ? ' AND f31 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f32t = ".$field.($lvl !== false ? ' AND f32 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f33t = ".$field.($lvl !== false ? ' AND f33 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f34t = ".$field.($lvl !== false ? ' AND f34 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f35t = ".$field.($lvl !== false ? ' AND f35 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f36t = ".$field.($lvl !== false ? ' AND f36 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f37t = ".$field.($lvl !== false ? ' AND f37 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f38t = ".$field.($lvl !== false ? ' AND f38 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f39t = ".$field.($lvl !== false ? ' AND f39 '.$lvlComparisonSign.' '.$lvl : '').") + OR (f40t = ".$field.($lvl !== false ? ' AND f40 '.$lvlComparisonSign.' '.$lvl : '').") + )"; + + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_array($result); + + self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)] = $row["Total"]; + return self::$singleFieldTypeCountCache[$uid.$field.$lvlComparisonSign.($lvl ? 1 : 0)]; + } + + function getFieldType($vid, $field, $use_cache = true) { + list($vid, $field) = $this->escape_input((int) $vid, $field); + + // 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::$fieldTypeCache, $vid.$field)) && !is_null($cachedValue)) { + return $cachedValue; + } + + if ($field && $vid) { + $q = "SELECT f" . $field . "t from " . TB_PREFIX . "fdata where vref = $vid LIMIT 1"; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_array($result); + self::$fieldTypeCache[$vid.$field] = $row["f" . $field . "t"]; + } else { + self::$fieldTypeCache[$vid.$field] = 0; + } + + return self::$fieldTypeCache[$vid.$field]; + } + + // no need to cache this method + function getFieldDistance($wid) { + list($wid) = $this->escape_input((int) $wid); + + $array = $this->getProfileVillages($wid, 3); + $coor = $this->getCoor($wid); + $x1 = intval($coor['x']); + $y1 = intval($coor['y']); + $prevdist = 0; + $array2 = $this->getVillage(0, 4); + $vill = $array2['wref']; + + if ($array && count($array)){ + foreach($array as $village){ + $coor2 = $this->getCoor($village['wref']); + $max = 2 * WORLD_MAX + 1; + $x2 = intval($coor2['x']); + $y2 = intval($coor2['y']); + $distanceX = min(abs($x2 - $x1), abs($max - abs($x2 - $x1))); + $distanceY = min(abs($y2 - $y1), abs($max - abs($y2 - $y1))); + $dist = sqrt(pow($distanceX, 2) + pow($distanceY, 2)); + if($dist < $prevdist or $prevdist == 0){ + $prevdist = $dist; + $vill = $village['wref']; + } + } + } + return $vill; + } + + function updateVSumField($field) { + list($field) = $this->escape_input($field); + + //fix by ronix + if (SPEED >10) { + $speed = 10; + } else { + $speed = SPEED; + } + + // cultural points to gain during a day + $dur_day = (86400/SPEED); + + if ($dur_day < 3600) { + $dur_day = 3600; + } + + $q = " + UPDATE " . TB_PREFIX . "users as users + SET cp = cp + ( + ( SELECT sum($field) FROM " . TB_PREFIX . "vdata as vdata WHERE vdata.owner = users.id ".($field == 'cp' ? ' AND vdata.natar = 0' : '')." ) * + (UNIX_TIMESTAMP() - lastupdate) / $dur_day + ), + lastupdate = UNIX_TIMESTAMP() + WHERE + lastupdate < (UNIX_TIMESTAMP() - 600) + "; // recount every 10 minutes + + mysqli_query($this->dblink, $q); + } + + function getVSumField($uid, $field, $use_cache = true) { + list($field) = $this->escape_input($field); + + $array_passed = is_array($uid); + if (!$array_passed) { + $uid = [(int) $uid]; + } else { + foreach ($uid as $index => $uidValue) { + $uid[$index] = (int) $uidValue; + } + } + + if (!count($uid)) { + return []; + } + + // first of all, check if we should be using cache and whether the field + // required is already cached + if ($use_cache && !$array_passed && ($cachedValue = self::returnCachedContent(self::$userSumFieldCache, $uid[0].$field)) && !is_null($cachedValue)) { + return $cachedValue; + } + + if($field != "cp"){ + $q = "SELECT owner, MIN(lastupdate), SUM(" . $field . ") as Total FROM " . TB_PREFIX . "vdata where owner IN(".implode(', ', $uid).") GROUP BY owner"; + }else{ + $q = "SELECT owner, MIN(lastupdate), SUM(" . $field . ") as Total FROM " . TB_PREFIX . "vdata where owner IN(".implode(', ', $uid).") and natar = 0 GROUP BY owner"; + } + + $result = mysqli_query($this->dblink,$q); + + // return a single value + if (!$array_passed) { + $row = mysqli_fetch_row( $result ); + self::$userSumFieldCache[$row[0].$field] = $row[2]; + } else { + $result = $this->mysqli_fetch_all($result); + if ($result && count($result)) { + foreach ( $result as $record ) { + self::$userSumFieldCache[ $record['owner'] . $field ] = $record['Total']; + } + } + } + + return ($array_passed ? $result : self::$userSumFieldCache[$uid[0].$field]); + } + + function updateVillage($vid) { + list($vid) = $this->escape_input((int) $vid); + + $time = time(); + $q = "UPDATE " . TB_PREFIX . "vdata set lastupdate = $time where wref = $vid"; + return mysqli_query($this->dblink,$q); + } + + + function updateOasis($vid) { + list($vid) = $this->escape_input((int) $vid); + + $time = time(); + $q = "UPDATE " . TB_PREFIX . "odata set lastupdated = $time where wref = $vid"; + return mysqli_query($this->dblink,$q); + } + + function setVillageName($vid, $name) { + $vid = (int)$vid; + $name = trim($name); + if ($name === '') return false; + $name = mb_substr($name, 0, 30, 'UTF-8'); + + $stmt = $this->dblink->prepare( + "UPDATE `".TB_PREFIX."vdata` SET name = ? WHERE wref = ? LIMIT 1" + ); + $stmt->bind_param("si", $name, $vid); + $result = $stmt->execute(); + $stmt->close(); + return $result; + } + + function modifyPop($vid, $pop, $mode) { + list($vid, $pop, $mode) = $this->escape_input((int) $vid, (int) $pop, $mode); + + if(!$mode) { + $q = "UPDATE " . TB_PREFIX . "vdata set pop = pop + $pop where wref = $vid"; + } else { + $q = "UPDATE " . TB_PREFIX . "vdata set pop = pop - $pop where wref = $vid"; + } + return mysqli_query($this->dblink,$q); + } + + function addCP($ref, $cp) { + list($ref, $cp) = $this->escape_input((int) $ref, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "vdata set cp = cp + $cp where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + function addCel($ref, $cel, $type) { + list($ref, $cel, $type) = $this->escape_input((int) $ref, (int) $cel, (int) $type); + + $q = "UPDATE " . TB_PREFIX . "vdata set celebration = $cel, type= $type where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getCel() { + return $this->getProfileVillages(time(), 4); + } + + function clearCel($ref) { + list($ref) = $this->escape_input((int) $ref); + + $q = "UPDATE " . TB_PREFIX . "vdata set celebration = 0, type = 0 where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + function setCelCp($user, $cp) { + list($user, $cp) = $this->escape_input((int) $user, (int) $cp); + + $q = "UPDATE " . TB_PREFIX . "users set cp = cp + $cp where id = $user"; + return mysqli_query($this->dblink,$q); + } + + /** + * Mead-Festival (Brewery, building 35 — Teutons only). + * Mirrors addCel()/getCel()/clearCel() above, but the festival grants no + * culture points: it only gates the temporary combat bonus, the chief + * persuasion penalty and the catapult randomization while it is active. + */ + function addFestival($ref, $end) { + list($ref, $end) = $this->escape_input((int) $ref, (int) $end); + + $q = "UPDATE " . TB_PREFIX . "vdata set festival = $end where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + // no need to cache this method + function getFestivals() { + $q = "SELECT * FROM " . TB_PREFIX . "vdata WHERE festival < " . time() . " AND festival != 0"; + $result = mysqli_query($this->dblink,$q); + return $this->mysqli_fetch_all($result); + } + + function clearFestival($ref) { + list($ref) = $this->escape_input((int) $ref); + + $q = "UPDATE " . TB_PREFIX . "vdata set festival = 0 where wref = $ref"; + return mysqli_query($this->dblink,$q); + } + + /** + * Delete a single village or multiple ones + * + * @param mixed $wref The Village ID(s) + */ + + function DelVillage($wref){ + list($wref) = $this->escape_input($wref); + global $units; + + //Check if we've to delete a single village or multiple ones + if(!is_array($wref)) $wref = [$wref]; + + //Create the list of village IDs + $wrefs = implode(", ", $wref); + + $this->clearExpansionSlot($wref); + $q = "DELETE FROM ".TB_PREFIX."abdata where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."bdata where wid IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."market where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."research where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."tdata where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."fdata where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."training where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."units where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."farmlist where wref IN($wrefs)"; + $this->query($q); + $q = "UPDATE ".TB_PREFIX."artefacts SET del = 1 where vref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."raidlist where towref IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."route where wid IN($wrefs) OR `from` IN($wrefs)"; + $this->query($q); + $q = "DELETE FROM ".TB_PREFIX."movement where proc = 0 AND ((`to` IN($wrefs) AND sort_type = 4) OR (`from` IN($wrefs) AND sort_type = 3))"; + $this->query($q); + $this->removeOases($wref, 1); + + // In-flight attacks still heading for the deleted village(s) must be + // bounced straight home. Query them directly instead of using + // getMovement(3, $wref, 1): when passed an array, getMovement() returns + // the WHOLE movement cache (a map of "type.village.mode" => record list), + // not the flat record list this loop expects — so $movedata['moveid'] / + // ['starttime'] were NULL and the follow-up waves were never bounced, + // leaving them stuck at sort_type=3/proc=0 and looping forever once the + // village is gone. Pre-dates the sendunitsComplete() split (issue #298). + $q = "SELECT * FROM ".TB_PREFIX."movement, ".TB_PREFIX."attacks + WHERE ".TB_PREFIX."movement.`to` IN($wrefs) + AND ".TB_PREFIX."movement.ref = ".TB_PREFIX."attacks.id + AND ".TB_PREFIX."movement.proc = 0 + AND ".TB_PREFIX."movement.sort_type = 3 + ORDER BY endtime ASC"; + $getmovement = $this->mysqli_fetch_all(mysqli_query($this->dblink, $q)); + + $moveIDs = []; + $time = microtime(true); + $types = []; + $froms = []; + $tos = []; + $refs = []; + $times = []; + $endtimes = []; + + foreach($getmovement as $movedata) { + $time2 = $time - $movedata['starttime']; + $moveIDs[] = $movedata['moveid']; + $types[] = 4; + $froms[] = $movedata['to']; + $tos[] = $movedata['from']; + $refs[] = $movedata['ref']; + $times[] = $time; + $endtimes[] = $time+$time2; + } + + $this->setMovementProc(implode(', ', $moveIDs)); + $this->addMovement($types, $froms, $tos, $refs, $times, $endtimes); + + $q = "DELETE FROM ".TB_PREFIX."enforcement WHERE `from` IN($wrefs)"; + $this->query($q); + + //check return enforcement from del village + foreach($wref as $w) $units->returnTroops($w); + + $q = "DELETE FROM ".TB_PREFIX."vdata WHERE `wref` IN($wrefs)"; + $this->query($q); + + if (mysqli_affected_rows($this->dblink) > 0) { + $q = "UPDATE ".TB_PREFIX."wdata set occupied = 0 where id IN($wrefs)"; + $this->query($q); + + // clear expansion slots, if this village is an expansion of any other village + $this->clearExpansionSlot($wref, 1); + + $getprisoners = $this->getPrisoners($wref); + foreach($getprisoners as $pris) { + $troops = 0; + for($i = 1; $i < 12; $i++) $troops += $pris['t'.$i]; + $this->modifyUnit($pris['wref'], ["99o"], [$troops], [0]); + $this->deletePrisoners($pris['id']); + } + + $getprisoners = $this->getPrisoners($wref, 1); + foreach($getprisoners as $pris) { + $troops = 0; + for($i = 1; $i < 12; $i++) $troops += $pris['t'.$i]; + $this->modifyUnit($pris['wref'], ["99o"], [$troops], [0]); + $this->deletePrisoners($pris['id']); + } + } + } + + /** + * Clear the expansion slots of a specified village(s) + * + * @param mixed $id The village ID(s) + * @param number $mode 0 If there's the need to clear all expansion slots of a village, + * 1 if there's the need to clear a single expansion slot of a village + */ + + function clearExpansionSlot($id, $mode = 0) { + // acceptă int sau array, fără (int) pe array + if(!is_array($id)) { + $id = [$id]; + } + // curățare sigură – doar numere + $id = array_map('intval', $id); + $ids = implode(",", $id); + + if(!$ids) return; + + if(!$mode){ + // ștergem sloturile DIN satul care se distruge + $pairs = []; + for($i = 1; $i <= 3; $i++) $pairs[] = 'exp'.$i.' = 0'; + $q = "UPDATE ".TB_PREFIX."vdata SET ".implode(',', $pairs)." WHERE wref IN($ids)"; + }else{ + // ștergem referința DIN satul părinte + $q = " + UPDATE ".TB_PREFIX."vdata + SET + exp1 = IF(exp1 IN($ids), 0, exp1), + exp2 = IF(exp2 IN($ids), 0, exp2), + exp3 = IF(exp3 IN($ids), 0, exp3) + WHERE + exp1 IN($ids) OR + exp2 IN($ids) OR + exp3 IN($ids)"; + } + mysqli_query($this->dblink, $q); + } + + function getVillageByName($name, $use_cache = true) { + return $this->getVillage($name, 1, $use_cache)['wref']; + } + + /** + * Build the village-name autocomplete suggestions for the rally point and + * the marketplace, honouring the player's auto-completion preferences + * (issue #198): + * v1 -> own villages + * v2 -> villages in the surroundings of the active village + * v3 -> villages of the player's alliance members + * System accounts (Nature, Natars, Multihunter, ...) are excluded. + * + * @param int $uid Player id (own villages / self). + * @param int $alliance Player's alliance id (0 = none). + * @param int $x Active village X coordinate (vicinity center). + * @param int $y Active village Y coordinate (vicinity center). + * @param bool $v1 Include own villages. + * @param bool $v2 Include surrounding villages. + * @param bool $v3 Include alliance villages. + * @param int $radius Vicinity radius in fields (v2). + * @param int $limit Max number of names returned. + * @return string[] Distinct village names. + */ + function getAutoCompleteVillages($uid, $alliance, $x, $y, $v1, $v2, $v3, $radius = 25, $limit = 100) { + $uid = (int) $uid; + $alliance = (int) $alliance; + $x = (int) $x; + $y = (int) $y; + $radius = (int) $radius; + $limit = (int) $limit; + + $joins = "JOIN " . TB_PREFIX . "users u ON u.id = v.owner "; + $conds = []; + + if ($v1) { + $conds[] = "v.owner = $uid"; + } + if ($v3 && $alliance > 0) { + $conds[] = "u.alliance = $alliance"; + } + if ($v2) { + $joins .= "JOIN " . TB_PREFIX . "wdata w ON w.id = v.wref "; + $conds[] = "(ABS(w.x - $x) <= $radius AND ABS(w.y - $y) <= $radius)"; + } + + if (!count($conds)) { + return []; + } + + $q = "SELECT DISTINCT v.name FROM " . TB_PREFIX . "vdata v " . + $joins . + "WHERE u.id > 5 AND (" . implode(' OR ', $conds) . ") " . + "ORDER BY v.name LIMIT $limit"; + + $result = mysqli_query($this->dblink, $q); + $names = []; + if ($result) { + while ($row = mysqli_fetch_assoc($result)) { + $names[] = $row['name']; + } + } + return $names; + } + + function getVillageByOwner($uid, $use_cache = true) { + $uid = (int) $uid; + + // 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::$villageDataByOwnerCache, $uid)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $q = 'SELECT * FROM `' . TB_PREFIX . 'vdata` WHERE `owner` = ' . $uid . ' LIMIT 1'; + $result = mysqli_fetch_array(mysqli_query($this->dblink,$q), MYSQLI_ASSOC); + + self::$villageDataByOwnerCache[$uid] = $result; + return self::$villageDataByOwnerCache[$uid]; + } + + //MARKET FIXES + function getWoodAvailable($wref, $use_cache = true) { + // return from cache + return $this->getVillage($wref, 0, $use_cache)['wood']; + } + + function getClayAvailable($wref, $use_cache = true) { + // return from cache + return $this->getVillage($wref, 0, $use_cache)['clay']; + } + + function getIronAvailable($wref, $use_cache = true) { + // return from cache + return $this->getVillage($wref, 0, $use_cache)['iron']; + } + + function getCropAvailable($wref, $use_cache = true) { + // return from cache + return $this->getVillage($wref, 0, $use_cache)['crop']; + } + + function Getowner($vid) { + // return from cache + return $this->getVillage($vid, 0, $use_cache)['owner']; + } + + + // no need to cache, not used in any loops or more than once for each page load + public function getAvailableExpansionTraining() { + global $building, $session, $technology, $village; + + $vilData = $this->getVillage($village->wid); + $maxslots = (($vilData['exp1'] == 0 ? 1 : 0) + ($vilData['exp2'] == 0 ? 1 : 0) + ($vilData['exp3'] == 0 ? 1 : 0)); + $residence = $building->getTypeLevel(25); + $palace = $building->getTypeLevel(26); + + if($residence > 0) { + $maxslots -= (3 - floor($residence / 10)); + } + + if($palace > 0) { + $maxslots -= (3 - floor(($palace - 5) / 5)); + } + + // Command Center (Huni) - sloturi de expansiune ca Residence (nivel 10/20) + $commandcenter = $building->getTypeLevel(44); + if($commandcenter > 0) { + $maxslots -= (3 - floor($commandcenter / 10)); + } + + // Units at home + $q = "SELECT (u10+u20+u30+u60+u70+u80+u90) as R1, (u9+u19+u29+u59+u69+u79+u89) as R2 + FROM " . TB_PREFIX . "units + WHERE vref = " . (int)$village->wid; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_array($result, MYSQLI_ASSOC); + $settlers = (int)$row['R1']; + $chiefs = (int)$row['R2']; + + // Movements + $settlers += 3 * count($this->getMovement(5, $village->wid, 0)); + + $current_movement = $this->getMovement(3, $village->wid, 0); + if(!empty($current_movement)) { + foreach($current_movement as $build) { + $settlers += (int)$build['t10']; + $chiefs += (int)$build['t9']; + } + } + + $current_movement = $this->getMovement(4, $village->wid, 1); + if(!empty($current_movement)) { + foreach($current_movement as $build) { + $settlers += (int)$build['t10']; + $chiefs += (int)$build['t9']; + } + } + + // FIX: Count ALL reinforcements properly (SUM over ALL rows) + $q = "SELECT COALESCE(SUM(u10+u20+u30+u60+u70+u80+u90),0) AS s + FROM " . TB_PREFIX . "enforcement + WHERE `from` = " . (int)$village->wid; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_array($result, MYSQLI_ASSOC); + $settlers += (int)$row['s']; + + $q = "SELECT COALESCE(SUM(u9+u19+u29+u59+u69+u79+u89),0) AS c + FROM " . TB_PREFIX . "enforcement + WHERE `from` = " . (int)$village->wid; + $result = mysqli_query($this->dblink,$q); + $row = mysqli_fetch_array($result, MYSQLI_ASSOC); + $chiefs += (int)$row['c']; + + // Training queue (your existing logic) + $trainlist = $technology->getTrainingList(4); + if(!empty($trainlist)) { + foreach($trainlist as $train) { + if($train['unit'] % 10 == 0) { + $settlers += (int)$train['amt']; + } + if($train['unit'] % 10 == 9) { + $chiefs += (int)$train['amt']; + } + } + } + + // Trapped troops + $trappedTroops = $this->getPrisoners($village->wid, 1); + if(!empty($trappedTroops)){ + foreach($trappedTroops as $trapped){ + $settlers += (int)$trapped['t10']; + $chiefs += (int)$trapped['t9']; + } + } + + // Slot math (unchanged, but clamp to 0 to avoid negatives) + $settlerslots = ($maxslots * 3) - ($chiefs * 3) - $settlers; + $chiefslots = $maxslots - $chiefs - floor(($settlers + 2) / 3); + + if(!$technology->getTech(($session->tribe - 1) * 10 + 9)) { + $chiefslots = 0; + } + + if ($settlerslots < 0) $settlerslots = 0; + if ($chiefslots < 0) $chiefslots = 0; + + return ["chiefs" => $chiefslots, "settlers" => $settlerslots]; +} + + // no need to cache this method + function getArrayMemberVillage($uid) { + list($uid) = $this->escape_input((int) $uid); + $q = 'SELECT a.wref, a.name, b.x, b.y from '.TB_PREFIX.'vdata AS a left join '.TB_PREFIX.'wdata AS b ON b.id = a.wref where owner = '.$uid.' ORDER BY name ASC'; + $result = mysqli_query($this->dblink,$q); + $array = $this->mysqli_fetch_all($result); + return $array; + } + + function getCropProdstarv($wref, $use_cache = true) { + global $bid4, $bid8, $bid9, $technology; + + // 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::$cropProductionStarvationValueCache, $wref)) && !is_null($cachedValue)) { + return $cachedValue; + } + + $basecrop = $grainmill = $bakery = $cropo = 0; + $owner = $this->getVrefField($wref, 'owner', $use_cache); + $bonus = $this->getUserField($owner, 'b4', 0); + + $buildarray = $this->getResourceLevel($wref); + $cropholder = []; + for($i = 1; $i <= 38; $i++){ + if($buildarray['f'.$i.'t'] == 4) array_push($cropholder, 'f'.$i); + if($buildarray['f'.$i.'t'] == 8) $grainmill = $buildarray['f'.$i]; + if($buildarray['f'.$i.'t'] == 9) $bakery = $buildarray['f'.$i]; + } + + $q = "SELECT type FROM `" . TB_PREFIX . "odata` WHERE conqured = ".(int) $wref; + $oasis = $this->query_return($q); + foreach($oasis as $oa){ + switch($oa['type']) { + case 3: + case 6: + case 9: + case 10: + case 11: + $cropo++; + break; + case 12: + $cropo += 2; + break; + } + } + + for($i = 0; $i <= count($cropholder) - 1; $i++){ + $basecrop += $bid4[$buildarray[$cropholder[$i]]]['prod']; + } + + $crop = $basecrop + $basecrop * 0.25 * $cropo; + + if($grainmill >= 1 || $bakery >= 1){ + $crop += $basecrop / 100 * ((isset($bid8[$grainmill]['attri']) ? $bid8[$grainmill]['attri'] : 0) + (isset($bid9[$bakery]['attri']) ? $bid9[$bakery]['attri'] : 0)); + } + if($bonus > time()) $crop *= 1.25; + + $crop *= SPEED; + + self::$cropProductionStarvationValueCache[$wref] = $crop; + return self::$cropProductionStarvationValueCache[$wref]; + } + + /** + * Adds the starvation data in villages with a negative value of crop + * + * @param int $wref The village ID where the crop is negative + */ + + public function addStarvationData($wref){ + global $technology; + + $getVillage = $this->getVillage($wref); + + // FIX: dacă satul nu există, ieși imediat + if (!$getVillage || !is_array($getVillage)) { + return; + } + + //Exlude Support, Nature, Natars, TaskMaster and Multihunter + if (($getVillage['owner'] ?? 0) > 5){ + $crop = $this->getCropProdstarv($wref, false); + $unitArrays = $technology->getAllUnits($wref, false, 0, false); + $villageUpkeep = $getVillage['pop'] + $technology->getUpkeep($unitArrays, 0, $wref); + $starv = $getVillage['starv']; + + if ($crop < $villageUpkeep){ + //Add starvation data + $fields = ['starv']; + $values = [$villageUpkeep]; + + //Update the starvupdate if it's set to 0 + if($getVillage['starvupdate'] == 0) { + $fields[] = 'starvupdate'; + $values[] = time(); + } + + //Update the starvation datas + $this->setVillageFields($wref, $fields, $values); + } + } + } + + + /** + * Changed the actual capital with a new one + * + * @param int $wref The village ID that will became the new capital + * @return bool Return true if the query was successful, false otherwise + */ + + function changeCapital($wref, $mode = 1){ + list($wref, $mode) = $this->escape_input($wref, $mode); + + if ($mode == 1) { + // Bug fix: this function only ever did `SET capital = $mode WHERE + // wref = $wref` — it set the NEW capital's flag but never cleared + // any OTHER village belonging to the same owner that was already + // flagged capital = 1. Nothing enforces uniqueness at the schema + // level (no UNIQUE key on owner+capital), so after any capital + // change that doesn't delete the old village, the owner was left + // with two (or more) rows with capital = 1. This was harmless for + // years because nothing queried "owner=X AND capital=1" expecting + // a single row — until getVillage(..., 3) (added for the Brewery + // Mead-Festival empire-wide bonus lookup) started relying on that + // invariant, at which point a stale duplicate could be the row + // returned (no ORDER BY / LIMIT 1 on an ambiguous match), silently + // pointing Brewery's bonus/penalty checks at the wrong village. + $owner = $this->getVillageField($wref, 'owner'); + if ($owner !== false && $owner !== null) { + $owner = (int) $owner; + $q = "UPDATE " . TB_PREFIX . "vdata SET capital = 0 WHERE owner = $owner AND wref != $wref"; + mysqli_query($this->dblink, $q); + } + } + + $q = "UPDATE ".TB_PREFIX."vdata SET capital = ".$mode." WHERE wref = $wref"; + return mysqli_query($this->dblink, $q); + } +} diff --git a/GameEngine/Database/index.php b/GameEngine/Database/index.php new file mode 100644 index 00000000..d94085d2 --- /dev/null +++ b/GameEngine/Database/index.php @@ -0,0 +1,2 @@ +