Files
TravianZ/GameEngine/Market.php
T
Catalin Novgorodschi 3804498ad9 Incremental Refactor Automation & Database (cache)
Incremental Refactor Automation (starvation split in more methods) & Database (cache & checkAllianceEmbassiesStatus refactor), Fix a bug in Alliance.php (now you cannot kick alliance leader)
2026-05-14 15:05:32 +03:00

871 lines
24 KiB
PHP
Executable File

<?php
#################################################################################
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
## --------------------------------------------------------------------------- ##
## Filename Market.php ##
## Developed by: Dzoki ##
## Some fixes: aggenkeech ##
## Refactored by: Shadow (cata7007@gmail.com) ##
## License: TravianZ Project ##
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
## URLs: https://travianz.org ##
## https://github.com/Shadowss/TravianZ ##
## ##
## Notes: ##
## - Incremental refactor only ##
## - Full legacy compatibility preserved ##
## - PHP 7+ compatible ##
## - Logic / formulas untouched ##
## - Readability and structure improved ##
#################################################################################
class Market
{
public $onsale = [];
public $onmarket = [];
public $sending = [];
public $recieving = [];
public $return = [];
public $maxcarry;
public $merchant;
public $used;
/**
* Internal request cache.
* Safe legacy-compatible cache layer.
*/
private $cache = [];
/**
* Main market processor.
*/
public function procMarket($post)
{
$this->loadMarket();
if (isset($_SESSION['loadMarket'])) {
$this->loadOnsale();
unset($_SESSION['loadMarket']);
}
if (!isset($post['ft'])) {
return;
}
switch ($post['ft']) {
case 'mk1':
$this->sendResource($post);
break;
case 'mk2':
$this->addOffer($post);
break;
case 'mk3':
$this->tradeResource($post);
break;
}
}
/**
* Remove / accept market actions.
*/
public function procRemove($get)
{
global $database, $village, $session;
if (isset($get['t']) && $get['t'] == 1) {
$this->filterNeed($get);
} elseif (
isset($get['t'], $get['a'], $get['del']) &&
$get['t'] == 2 &&
$get['a'] == 5
) {
// Get all fields from market
$type = $database->getMarketField($village->wid, $get['del'], 'gtype');
$amt = $database->getMarketField($village->wid, $get['del'], 'gamt');
$database->getResourcesBack($village->wid, $type, $amt);
$database->addMarket($village->wid, $get['del'], 0, 0, 0, 0, 0, 0, 1);
header('Location: build.php?id=' . $get['id'] . '&t=2');
exit;
}
if (
isset($get['t'], $get['a']) &&
$get['t'] == 1 &&
$get['a'] == $session->mchecker &&
!isset($get['del'])
) {
$session->changeChecker();
$this->acceptOffer($get);
}
}
/**
* Available merchants.
*/
public function merchantAvail()
{
return $this->merchant - $this->used;
}
/**
* Remember cached value for current request.
*/
private function remember($key, callable $callback)
{
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
$this->cache[$key] = $callback();
return $this->cache[$key];
}
/**
* Forget cache entry or entire cache.
*/
private function forget($key = null)
{
if ($key === null) {
$this->cache = [];
return;
}
unset($this->cache[$key]);
}
/**
* Load market data.
*/
private function loadMarket()
{
global $session, $building, $bid28, $bid17, $database, $village;
$this->recieving = $database->getMovement(0, $village->wid, 1);
$this->sending = $database->getMovement(0, $village->wid, 0);
$this->return = $database->getMovement(2, $village->wid, 1);
$this->merchant = ($building->getTypeLevel(17) > 0)
? $bid17[$building->getTypeLevel(17)]['attri']
: 0;
$this->used = $this->remember(
'merchant_used_' . $village->wid,
function () use ($database, $village) {
return $database->totalMerchantUsed($village->wid);
}
);
$this->onmarket = $this->remember(
'market_' . $village->wid,
function () use ($database, $village) {
return $database->getMarket($village->wid, 0);
}
);
// Merchant carry capacity
$this->maxcarry = ($session->tribe == 1)
? 500
: (($session->tribe == 2) ? 1000 : 750);
$this->maxcarry *= TRADER_CAPACITY;
// Trade office bonus
if ($building->getTypeLevel(28) != 0) {
$this->maxcarry *= $bid28[$building->getTypeLevel(28)]['attri'] / 100;
}
}
/**
* Send resources.
*/
private function sendResource($post)
{
global $database, $village, $session, $generator, $logging, $form;
$wtrans = (isset($post['r1']) && !empty($post['r1'])) ? $post['r1'] : 0;
$ctrans = (isset($post['r2']) && !empty($post['r2'])) ? $post['r2'] : 0;
$itrans = (isset($post['r3']) && !empty($post['r3'])) ? $post['r3'] : 0;
$crtrans = (isset($post['r4']) && !empty($post['r4'])) ? $post['r4'] : 0;
// Prevent negative values
$wtrans = str_replace('-', '', $wtrans);
$ctrans = str_replace('-', '', $ctrans);
$itrans = str_replace('-', '', $itrans);
$crtrans = str_replace('-', '', $crtrans);
// Preload village data
$database->getVillage($village->wid);
$resourcesAvailable = $this->remember(
'resources_' . $village->wid,
function () use ($database, $village) {
return [
'wood' => $database->getWoodAvailable($village->wid),
'clay' => $database->getClayAvailable($village->wid),
'iron' => $database->getIronAvailable($village->wid),
'crop' => $database->getCropAvailable($village->wid),
];
}
);
$availableWood = $resourcesAvailable['wood'];
$availableClay = $resourcesAvailable['clay'];
$availableIron = $resourcesAvailable['iron'];
$availableCrop = $resourcesAvailable['crop'];
// Resource array
$resource = [
$wtrans,
$ctrans,
$itrans,
$crtrans
];
// NOTE:
// Original code referenced $id before definition.
// Keeping logic safe while preserving compatibility.
$id = isset($post['getwref']) ? (int)$post['getwref'] : 0;
// Check vacation mode
if ($database->getvacmodexy($id)) {
$form->addError('error', USER_ON_VACATION);
}
if (!$database->checkVilExist($post['getwref'])) {
$form->addError('error', NO_COORDINATES_SELECTED);
} elseif ($post['getwref'] == $village->wid) {
$form->addError('error', CANNOT_SEND_RESOURCES);
} elseif (
$post['send3'] < 1 ||
$post['send3'] > 3 ||
($post['send3'] > 1 && !$session->goldclub)
) {
$form->addError('error', INVALID_MERCHANTS_REPETITION);
} elseif (
$availableWood >= $post['r1'] &&
$availableClay >= $post['r2'] &&
$availableIron >= $post['r3'] &&
$availableCrop >= $post['r4']
) {
$reqMerc = ceil((array_sum($resource) - 0.1) / $this->maxcarry);
// Acquire merchant lock to prevent race conditions
$database->getMerchantLock($village->wid);
$this->forget('merchant_used_' . $village->wid);
$this->used = $this->remember(
'merchant_used_' . $village->wid,
function () use ($database, $village) {
return $database->totalMerchantUsed($village->wid, false);
}
);
if ($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail()) {
$coor = $database->getCoor($id);
if ($database->getVillageState($id)) {
$timetaken = $generator->procDistanceTime(
$coor,
$village->coor,
$session->tribe,
0
);
$res = array_sum($resource);
if ($res != 0) {
$reference = $database->sendResource(
$resource[0],
$resource[1],
$resource[2],
$resource[3],
$reqMerc,
0
);
$database->modifyResource(
$village->wid,
$resource[0],
$resource[1],
$resource[2],
$resource[3],
0
);
$database->addMovement(
0,
$village->wid,
$id,
$reference,
time(),
time() + $timetaken,
$post['send3']
);
$this->forget();
$logging->addMarketLog(
$village->wid,
1,
[
$resource[0],
$resource[1],
$resource[2],
$resource[3],
$id
]
);
}
}
$database->releaseMerchantLock($village->wid);
header('Location: build.php?id=' . $post['id']);
exit;
} else {
$database->releaseMerchantLock($village->wid);
$form->addError('error', TOO_FEW_MERCHANTS);
}
} else {
$form->addError('error', TOO_FEW_RESOURCES);
}
}
/**
* Add market offer.
*/
private function addOffer($post)
{
global $database, $village, $session;
// Invalid: same resource type
if ($post['rid1'] == $post['rid2']) {
header('Location: build.php?id=' . $post['id'] . '&t=2&e2');
exit;
}
// Invalid values
if (
!isset($post['m1'], $post['m2']) ||
$post['m1'] <= 0 ||
$post['m2'] <= 0
) {
header('Location: build.php?id=' . $post['id'] . '&t=2&e2');
exit;
}
// Max 2:1 trade ratio
if ($post['m1'] > (2 * $post['m2'])) {
header('Location: build.php?id=' . $post['id'] . '&t=2&e2');
exit;
}
// Min 1:2 trade ratio
if ($post['m2'] > (2 * $post['m1'])) {
header('Location: build.php?id=' . $post['id'] . '&t=2&e2');
exit;
}
// Invalid resource ids
if (
$post['rid1'] < 1 ||
$post['rid1'] > 4 ||
$post['rid2'] < 1 ||
$post['rid2'] > 4
) {
header('Location: build.php?id=' . $post['id'] . '&t=2&e2');
exit;
}
// Offered resources
$wood = ($post['rid1'] == 1) ? $post['m1'] : 0;
$clay = ($post['rid1'] == 2) ? $post['m1'] : 0;
$iron = ($post['rid1'] == 3) ? $post['m1'] : 0;
$crop = ($post['rid1'] == 4) ? $post['m1'] : 0;
// Preload village data
$database->getVillage($village->wid);
$resourcesAvailable = $this->remember(
'resources_' . $village->wid,
function () use ($database, $village) {
return [
'wood' => $database->getWoodAvailable($village->wid),
'clay' => $database->getClayAvailable($village->wid),
'iron' => $database->getIronAvailable($village->wid),
'crop' => $database->getCropAvailable($village->wid),
];
}
);
$availableWood = $resourcesAvailable['wood'];
$availableClay = $resourcesAvailable['clay'];
$availableIron = $resourcesAvailable['iron'];
$availableCrop = $resourcesAvailable['crop'];
if (
$availableWood >= $wood &&
$availableClay >= $clay &&
$availableIron >= $iron &&
$availableCrop >= $crop
) {
$totalRes = $wood + $clay + $iron + $crop;
$reqMerc = 1;
if ($totalRes > $this->maxcarry) {
$reqMerc = round($totalRes / $this->maxcarry);
if ($totalRes > ($this->maxcarry * $reqMerc)) {
$reqMerc += 1;
}
}
// Acquire merchant lock
$database->getMerchantLock($village->wid);
$this->forget('merchant_used_' . $village->wid);
$this->used = $this->remember(
'merchant_used_' . $village->wid,
function () use ($database, $village) {
return $database->totalMerchantUsed($village->wid, false);
}
);
if ($this->merchantAvail() > 0 && $reqMerc <= $this->merchantAvail()) {
if (
$database->modifyResource(
$village->wid,
$wood,
$clay,
$iron,
$crop,
0
)
) {
$time = 0;
if (isset($_POST['d1'])) {
$time = $_POST['d2'] * 3600;
}
$alliance = (
isset($post['ally']) &&
$post['ally'] == 1
)
? $session->userinfo['alliance']
: 0;
$database->addMarket(
$village->wid,
$post['rid1'],
$post['m1'],
$post['rid2'],
$post['m2'],
$time,
$alliance,
$reqMerc,
0
);
}
$this->forget();
$database->releaseMerchantLock($village->wid);
header('Location: build.php?id=' . $post['id'] . '&t=2');
exit;
}
// Not enough merchants
$database->releaseMerchantLock($village->wid);
header('Location: build.php?id=' . $post['id'] . '&t=2&e3');
exit;
}
// Not enough resources
header('Location: build.php?id=' . $post['id'] . '&t=2&e1');
exit;
}
/**
* Accept market offer.
*/
private function acceptOffer($get)
{
global $database, $village, $session, $logging, $generator;
$infoarray = $database->getMarketInfo($get['g']);
$reqMerc = 1;
if ($infoarray['wamt'] > $this->maxcarry) {
$reqMerc = round($infoarray['wamt'] / $this->maxcarry);
if ($infoarray['wamt'] > ($this->maxcarry * $reqMerc)) {
$reqMerc += 1;
}
}
$villageResources = [
$village->awood,
$village->aclay,
$village->airon,
$village->acrop
];
// Not enough resources
if (
$infoarray['wamt'] >
$villageResources[$infoarray['wtype'] - 1]
) {
header('Location: build.php?id=' . $get['id'] . '&t=1&e1');
exit;
}
// Invalid offer
if (
($infoarray['vref'] == $village->wid) ||
(
$infoarray['alliance'] > 0 &&
$infoarray['alliance'] != $session->alliance
) ||
(
$infoarray['maxtime'] > 0 &&
($infoarray['maxtime'] * 3600) <
$generator->procDistanceTime(
$database->getCoor($infoarray['vref']),
$village->coor,
$session->tribe,
0
)
)
) {
header('Location: build.php?id=' . $get['id'] . '&t=1&e2');
exit;
}
// Not enough merchants
if ($reqMerc > $this->merchantAvail()) {
header('Location: build.php?id=' . $get['id'] . '&t=1&e3');
exit;
}
// Acquire merchant lock
$database->getMerchantLock($village->wid);
$this->forget('merchant_used_' . $village->wid);
$this->used = $this->remember(
'merchant_used_' . $village->wid,
function () use ($database, $village) {
return $database->totalMerchantUsed($village->wid, false);
}
);
// Double-check after lock
if ($reqMerc > $this->merchantAvail()) {
$this->forget();
$database->releaseMerchantLock($village->wid);
header('Location: build.php?id=' . $get['id'] . '&t=1&e3');
exit;
}
// Prepare resources
$myresource = [1 => 0, 0, 0, 0];
$hisresource = [1 => 0, 0, 0, 0];
$myresource[$infoarray['wtype']] = $infoarray['wamt'];
$mysendid = $database->sendResource(
$myresource[1],
$myresource[2],
$myresource[3],
$myresource[4],
$reqMerc,
0
);
$hisresource[$infoarray['gtype']] = $infoarray['gamt'];
$hissendid = $database->sendResource(
$hisresource[1],
$hisresource[2],
$hisresource[3],
$hisresource[4],
$infoarray['merchant'],
0
);
$hiscoor = $database->getCoor($infoarray['vref']);
$mytime = $generator->procDistanceTime(
$hiscoor,
$village->coor,
$session->tribe,
0
);
$targettribe = $database->getUserField(
$database->getVillageField($infoarray['vref'], 'owner'),
'tribe',
0
);
$histime = $generator->procDistanceTime(
$village->coor,
$hiscoor,
$targettribe,
0
);
$timestamp = time();
$database->addMovement(
[0, 0],
[$village->wid, $infoarray['vref']],
[$infoarray['vref'], $village->wid],
[$mysendid, $hissendid],
[$timestamp, $timestamp],
[$mytime + $timestamp, $histime + $timestamp]
);
$resource = [1 => 0, 0, 0, 0];
$resource[$infoarray['wtype']] = $infoarray['wamt'];
$database->modifyResource(
$village->wid,
$resource[1],
$resource[2],
$resource[3],
$resource[4],
0
);
$database->setMarketAcc($get['g']);
$database->removeAcceptedOffer($get['g']);
$logging->addMarketLog(
$village->wid,
2,
[$infoarray['vref'], $get['g']]
);
$database->releaseMerchantLock($village->wid);
header('Location: build.php?id=' . $get['id']);
exit;
}
/**
* Load onsale offers.
*/
private function loadOnsale()
{
global $database, $village, $session, $multisort, $generator;
$displayarray = $database->getMarket($village->wid, 1);
$holderarray = [];
foreach ($displayarray as $value) {
$targetcoor = $database->getCoor($value['vref']);
$duration = $generator->procDistanceTime(
$targetcoor,
$village->coor,
$session->tribe,
0
);
if (
$duration <= ($value['maxtime'] * 3600) ||
$value['maxtime'] == 0
) {
$value['duration'] = $duration;
$holderarray[] = $value;
}
}
$this->onsale = $multisort->sorte(
$holderarray,
'duration',
true,
2
);
}
/**
* Filter market offers.
*/
private function filterNeed($get)
{
if (
!isset($get['v']) &&
!isset($get['s']) &&
!isset($get['b'])
) {
$this->loadOnsale();
return;
}
$holder = [];
$holder2 = [];
// 1:1 trades
if (isset($get['v']) && $get['v'] == '1:1') {
foreach ($this->onsale as $equal) {
if ($equal['wamt'] <= $equal['gamt']) {
$holder[] = $equal;
}
}
} else {
$holder = $this->onsale;
}
foreach ($holder as $sale) {
if (isset($get['s']) && isset($get['b'])) {
if (
$sale['gtype'] == $get['s'] &&
$sale['wtype'] == $get['b']
) {
$holder2[] = $sale;
}
} elseif (isset($get['s']) && !isset($get['b'])) {
if ($sale['gtype'] == $get['s']) {
$holder2[] = $sale;
}
} elseif (isset($get['b']) && !isset($get['s'])) {
if ($sale['wtype'] == $get['b']) {
$holder2[] = $sale;
}
} else {
$holder2 = $holder;
}
}
$this->onsale = $holder2;
}
/**
* NPC merchant trade.
*/
private function tradeResource($post)
{
global $session, $database, $village;
$wwvillage = $database->getResourceLevel($village->wid);
// Prevent WW villages
if ($wwvillage['f99t'] == 40) {
return;
}
// Not enough gold
if ($session->userinfo['gold'] < 3) {
header('Location: build.php?id=' . $post['id'] . '&t=3');
exit;
}
// Prevent selling more resources than available
if (
(
(int)$post['m2'][0] < 0 &&
round($village->awood) + (int)$post['m2'][0] < 0
) ||
(
(int)$post['m2'][1] < 0 &&
round($village->aclay) + (int)$post['m2'][1] < 0
) ||
(
(int)$post['m2'][2] < 0 &&
round($village->airon) + (int)$post['m2'][2] < 0
) ||
(
(int)$post['m2'][3] < 0 &&
round($village->acrop) + (int)$post['m2'][3] < 0
)
) {
header('Location: build.php?id=' . $post['id'] . '&t=3');
exit;
}
$newTotal =
(int)$post['m2'][0] +
(int)$post['m2'][1] +
(int)$post['m2'][2] +
(int)$post['m2'][3];
$currentTotal =
round($village->awood) +
round($village->aclay) +
round($village->airon) +
round($village->acrop);
// Too many resources requested
if ($newTotal > $currentTotal) {
header('Location: build.php?id=' . $post['id'] . '&t=3');
exit;
}
$database->setVillageField(
$village->wid,
['wood', 'clay', 'iron', 'crop'],
[
$post['m2'][0],
$post['m2'][1],
$post['m2'][2],
$post['m2'][3]
]
);
$this->forget();
$database->modifyGold($session->uid, 3, 0);
header('Location: build.php?id=' . $post['id'] . '&t=3&c');
exit;
}
}
$market = new Market;
?>