Files
TravianZ/GameEngine/Database/DatabaseBuildingQueries.php
T
novgorodschi catalin d89ffe4482 Major refactor: Extract Database and Automation into domain-specific trait files
### Core Architecture Refactor

- Split the `Database.php` class into domain-specific traits under `GameEngine/Database/`.
- Split the `Automation` class into domain-specific traits under `GameEngine/Automation/`.
- Grouped methods by functional domain for improved maintainability and navigation.
- Preserved 100% backward compatibility.
- No logic or behavioral changes.
- Pure structural refactor.
2026-07-17 11:03:27 +03:00

471 lines
17 KiB
PHP

<?php
#################################################################################
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
## --------------------------------------------------------------------------- ##
## Project: TravianZ ##
## Filename: DatabaseBuildingQueries.php ##
## Split&Refactor Shadow ##
## Purpose: Buildings (bdata), building queues, demolitions ##
## ##
## Phase S1: Trait extracted from GameEngine/Database.php ##
## (MYSQLi_DB class). ##
## Methods were moved IDENTICALLY, with no logic changes. ##
## ##
## License: TravianZ Project ##
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
## URLs: https://travianz.org ##
## https://github.com/Shadowss/TravianZ ##
#################################################################################
trait DatabaseBuildingQueries {
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')");
}
/**
* 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);
}
}