<?php

#################################################################################
##              -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =-                 ##
## --------------------------------------------------------------------------- ##
##  Filename       : Market.php                      	                       ##
##  Type           : Market System Backend                                     ##
## --------------------------------------------------------------------------- ##
##  Developed by   : Dzoki           			                               ##
##  Refactored by  : Shadow & Ferywir									       ##
##  Thanks to      : ronix, InCube, Akakori, Elmar & Kirilloid                 ##
## --------------------------------------------------------------------------- ##
##  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.              ##
## --------------------------------------------------------------------------- ##
#################################################################################

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 &&
            hash_equals((string) $session->mchecker, (string) ($get['a'] ?? '')) &&
            !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
        // Capacitate negustor per trib: 1=500, 2=1000, 3=750, 6=500, 7=750, 8=500, 9=750
        $carrymap = array(1 => 500, 2 => 1000, 3 => 750, 6 => 500, 7 => 750, 8 => 500, 9 => 750);
        $this->maxcarry = isset($carrymap[$session->tribe]) ? $carrymap[$session->tribe] : 750;

        $this->maxcarry *= TRADER_CAPACITY;

        // Trade office bonus
        if ($building->getTypeLevel(28) != 0) {
            $this->maxcarry *= $bid28[$building->getTypeLevel(28)]['attri'] / 100;
        }

        // Bonusul de alianta "Commerce". Se inmulteste cu biroul comercial, ca
        // in T4: 750 de baza x 2.2 (Commerce 4) x 5 (birou 20) = 8250.
        // Se ia dupa PROPRIETARUL satului, nu dupa sesiune, ca sa dea acelasi
        // rezultat si cand codul ruleaza in alt context (sitter, automatizare).
        // Calea de repetare din AutomationMarket foloseste aceeasi regula.
        if (class_exists('AllianceBonus') && AllianceBonus::enabled()) {
            $carryOwner = isset($village->wid)
                ? (int) $database->getVillageField($village->wid, 'owner')
                : (int) $session->uid;

            if ($carryOwner > 0) {
                $this->maxcarry *= AllianceBonus::multiplier($carryOwner, AllianceBonus::COMMERCE);
            }
        }

        $this->maxcarry = floor($this->maxcarry);
    }

    /**
     * Send resources.
     */
    /**
     * Push-protection enforcement (Travian-Legends style).
     * Returns true when this send must be BLOCKED because the recipient has
     * reached the amount of resources it may receive from other players in the
     * rolling window (see PushProtection::remainingAllowance).
     *
     * No-ops (returns false) for: own-village transfers, system accounts, when
     * the PushProtection engine is absent, or when hard enforcement is globally
     * disabled via  define("PUSH_PROTECTION_ENFORCE", false);  in config.php
     * (the dashboard and logging keep working either way — default is ON).
     *
     * WW villages and manual "Exempt" overrides return an unlimited allowance
     * inside remainingAllowance(), so they are never blocked here.
     */
    private function exceedsPushLimit($database, $fromWid, $toWid, array $resource)
    {
        if (defined('PUSH_PROTECTION_ENFORCE') && !PUSH_PROTECTION_ENFORCE) {
            return false;
        }
        if (!class_exists('PushProtection')) {
            return false; // engine not deployed -> no enforcement
        }

        $fromOwner = (int) $database->getVillageField($fromWid, 'owner');
        $toOwner   = (int) $database->getVillageField($toWid, 'owner');

        // Own transfer or system account -> never limited.
        if ($fromOwner === $toOwner || $toOwner <= 3) {
            return false;
        }

        $sendTotal = (int) $resource[0] + (int) $resource[1] + (int) $resource[2] + (int) $resource[3];
        if ($sendTotal <= 0) {
            return false;
        }

        return $sendTotal > PushProtection::remainingAllowance($toOwner);
    }

    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;

        /**
         * PERMISIUNI SITTER: "trimite resurse altor jucatori".
         *
         * Restrictia se aplica DOAR catre alte conturi. Mutarea resurselor
         * intre satele proprii ramane permisa - altfel un sitter n-ar mai putea
         * administra contul deloc, ceea ce nu e scopul.
         *
         * Verificam proprietarul satului tinta, nu numele lui: un jucator poate
         * avea oricate sate, iar comparatia pe uid e singura de incredere.
         */
        if ($id > 0 && isset($session) && is_object($session) && method_exists($session, 'sitterCan')) {

            $targetOwner = (int) $database->getVillageField($id, 'owner');

            if ($targetOwner > 0
                && $targetOwner !== (int) $session->uid
                && !$session->sitterCan(SITTER_PERM_RES)) {

                $form->addError('error', defined('SITTER_P_DENIED')
                    ? SITTER_P_DENIED
                    : 'Your sitter permissions do not allow this action.');

                /**
                 * "return", nu doar addError: mai jos urmeaza un lant
                 * if/elseif care ajunge la ramura de trimitere indiferent de
                 * erorile adaugate inainte. Fara iesire explicita, resursele
                 * ar fi plecat oricum, iar mesajul de eroare ar fi fost doar
                 * decorativ.
                 */
                return;
            }
        }

        /**
         * BUG PREEXISTENT: modul vacanta era doar semnalat, nu si blocat.
         *
         * Eroarea se adauga aici, dar mai jos urmeaza un lant if/elseif care
         * ajunge la ramura de trimitere indiferent de erorile de dinainte.
         * Rezultatul: mesajul "utilizatorul e in vacanta" aparea pe ecran, dar
         * resursele plecau oricum catre contul protejat. Iesim explicit.
         */
        if ($database->getvacmodexy($id)) {
            $form->addError('error', USER_ON_VACATION);
            return;
        }

        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 (
            $this->exceedsPushLimit($database, $village->wid, $id, $resource)
        ) {
            // Push protection: recipient has reached its transfer limit for the
            // current window. Uses a language constant when available, else a
            // clear English fallback (add PUSH_PROTECTION_LIMIT to your Lang
            // files to localise).
            $form->addError('error', defined('PUSH_PROTECTION_LIMIT')
                ? PUSH_PROTECTION_LIMIT
                : 'This account has reached its resource-transfer limit for now and cannot receive that many resources. Send less, or wait for the limit to recover.');
        } elseif (
            $availableWood >= $wtrans &&
            $availableClay >= $ctrans &&
            $availableIron >= $itrans &&
            $availableCrop >= $crtrans
        ) {
            // UM-D1: comparatia foloseste valorile curatate de semnul minus
            // ($wtrans/... in loc de $post['r1']/...). Inainte, comparatia se
            // facea pe POST-ul brut (potential negativ), dar deducerea se facea
            // pe valoarea pozitiva - un "r1" negativ trecea verificarea si scadea
            // resurse pozitive.
            $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;
        }

        /**
         * PERMISIUNI SITTER: schimbul NPC costa 3 aur.
         *
         * Gardul TREBUIE sa fie aici, nu langa plata de la finalul functiei:
         * redistribuirea resurselor (setVillageField) se executa INAINTE de
         * scaderea aurului, deci un garda pus doar pe plata ar fi oferit
         * sitterului NPC gratuit.
         */
        if (isset($session) && method_exists($session, 'sitterCan')
            && !$session->sitterCan(SITTER_PERM_GOLD)) {

            header('Location: build.php?id=' . $post['id'] . '&t=3');
            exit;
        }

        // Sanitize the requested distribution: never negative, never above the
        // warehouse / granary capacity. Guards against a forged or NaN-corrupted
        // POST (issue #211: NPC distribution).
        $maxstore = (int) $village->maxstore;
        $maxcrop  = (int) $village->maxcrop;

        $m2 = [
            max(0, min($maxstore, (int)($post['m2'][0] ?? 0))),
            max(0, min($maxstore, (int)($post['m2'][1] ?? 0))),
            max(0, min($maxstore, (int)($post['m2'][2] ?? 0))),
            max(0, min($maxcrop,  (int)($post['m2'][3] ?? 0))),
        ];

        $newTotal = $m2[0] + $m2[1] + $m2[2] + $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'],
            [
                $m2[0],
                $m2[1],
                $m2[2],
                $m2[3]
            ]
        );
		$this->forget();

        // permisiunea a fost verificata la intrarea in functie
        $database->spendGold($session->uid, 3, 'NPC merchant');

        header('Location: build.php?id=' . $post['id'] . '&t=3&c');
        exit;
    }
}

$market = new Market;
?>