Add T4 Hero Resource and some fix

Add T4 Hero Resource and some fix
This commit is contained in:
novgorodschi catalin
2026-07-24 08:34:50 +03:00
parent 1e19f117ca
commit 2e53094b48
20 changed files with 641 additions and 43 deletions
@@ -99,6 +99,10 @@ if (!function_exists('admin_config_template_path')) {
// regenerarea de baza a eroului: acelasi tratament, sa nu fie resetata
$cleanupDefaults['%HEROBASEREGEN%'] = array('HERO_BASE_REGEN', 10);
$cleanupDefaults['%HEROSILVERPERGOLD%'] = array('HERO_SILVER_PER_GOLD', 10);
$cleanupDefaults['%HEROSILVERTOGOLD%'] = array('HERO_SILVER_TO_GOLD', 25);
$cleanupDefaults['%HERORESALL%'] = array('HERO_RES_PER_POINT_ALL', 3);
$cleanupDefaults['%HERORESONE%'] = array('HERO_RES_PER_POINT_ONE', 10);
foreach ($overrides as $ovPlaceholder => $ovValue) {
if (strpos($text, $ovPlaceholder) !== false) {
+25 -1
View File
@@ -80,7 +80,31 @@ $heroBaseRegen = isset($_POST['hero_base_regen']) ? (int) $_POST['hero_base_rege
if ($heroBaseRegen < 0) { $heroBaseRegen = 0; }
if ($heroBaseRegen > 100) { $heroBaseRegen = 100; }
$text = admin_config_template_contents(array('%HEROBASEREGEN%' => $heroBaseRegen));
// Ratele casei de schimb aur <-> argint.
$silverPerGold = isset($_POST['hero_silver_per_gold']) ? (int) $_POST['hero_silver_per_gold'] : 10;
$silverToGold = isset($_POST['hero_silver_to_gold']) ? (int) $_POST['hero_silver_to_gold'] : 25;
if ($silverPerGold < 1) { $silverPerGold = 1; }
if ($silverPerGold > 10000) { $silverPerGold = 10000; }
if ($silverToGold < 1) { $silverToGold = 1; }
if ($silverToGold > 10000) { $silverToGold = 10000; }
// Productia de resurse a eroului (atributul "Resources").
$heroResAll = isset($_POST['hero_res_all']) ? (int) $_POST['hero_res_all'] : 3;
$heroResOne = isset($_POST['hero_res_one']) ? (int) $_POST['hero_res_one'] : 10;
if ($heroResAll < 0) { $heroResAll = 0; }
if ($heroResAll > 10000) { $heroResAll = 10000; }
if ($heroResOne < 0) { $heroResOne = 0; }
if ($heroResOne > 10000) { $heroResOne = 10000; }
$text = admin_config_template_contents(array(
'%HEROBASEREGEN%' => $heroBaseRegen,
'%HEROSILVERPERGOLD%' => $silverPerGold,
'%HEROSILVERTOGOLD%' => $silverToGold,
'%HERORESALL%' => $heroResAll,
'%HERORESONE%' => $heroResOne,
));
$text = preg_replace("'%ERRORREPORT%'", $_POST['error'], $text);
$text = preg_replace("'%ERROR%'", $_POST['error'], $text);
$text = preg_replace("'%SERVERNAME%'", $_POST['servername'], $text);
+2
View File
@@ -275,6 +275,8 @@ class MYSQLi_DB implements IDbConnection {
* @var array Cache of heroes.
*/
$heroCache = [],
// cache per request pentru eroul care produce resurse intr-un sat
$heroVillageCache = [],
/**
* @var array Cache of hero field values.
@@ -23,6 +23,33 @@ use App\Utils\Math;
trait DatabaseHeroQueries {
/**
* Eroul care produce resurse in satul dat (atributul T4 "Resources").
*
* Se ia eroul VIU, terminat de antrenat, al carui sat de resedinta (wref)
* este satul cerut. Intoarce null daca nu exista.
* Rezultatul e cache-uit per request, fiindcă productia se calculeaza des.
*/
function getHeroForVillage($wid, $use_cache = true) {
$wid = (int) $wid;
if ($use_cache && isset(self::$heroVillageCache[$wid])) {
return self::$heroVillageCache[$wid];
}
$q = "SELECT heroid, uid, wref, resources, res_type
FROM " . TB_PREFIX . "hero
WHERE wref = " . $wid . "
AND dead = 0
AND COALESCE(intraining, 0) = 0
LIMIT 1";
$result = mysqli_query($this->dblink, $q);
$row = $result ? mysqli_fetch_assoc($result) : null;
return self::$heroVillageCache[$wid] = ($row ?: null);
}
function getHero($uid=0, $all=0, $include_dead = false, $use_cache = true) {
list($uid,$all) = $this->escape_input((int) $uid,$all);
+166 -4
View File
@@ -264,6 +264,162 @@ class HeroItems
* - anything already in that slot is unequipped first (atomic enough for
* a per-user action; both statements touch only this user's rows)
*/
/* =========================================================================
* SCHIMB AUR <-> ARGINT (casa de licitatii)
* ===================================================================== */
const EXCHANGE_OK = 1;
const EXCHANGE_NO_HERO = 2;
const EXCHANGE_NOT_ENOUGH = 3;
const EXCHANGE_INVALID = 4;
const EXCHANGE_FAILED = 5;
/** Cat argint primesti pentru 1 aur (implicit 10, ca in Travian). */
public static function silverPerGold()
{
return defined('HERO_SILVER_PER_GOLD') ? max(1, (int) HERO_SILVER_PER_GOLD) : 10;
}
/** Cat argint costa 1 aur la schimbul invers (implicit 25 - diferenta e marja casei). */
public static function silverForOneGold()
{
return defined('HERO_SILVER_TO_GOLD') ? max(1, (int) HERO_SILVER_TO_GOLD) : 25;
}
/**
* Aur -> argint. $gold = cat aur dai.
*
* Ambele scrieri (scaderea aurului si adaugarea argintului) sunt intr-o
* TRANZACTIE: sunt tabele diferite (users si hero), iar fara tranzactie o
* eroare intre ele ar lasa jucatorul fara aur si fara argint. Scaderea are
* conditia "gold >= ?" chiar in UPDATE, deci doua cereri trimise simultan nu
* pot cheltui acelasi aur de doua ori.
*/
public function exchangeGoldToSilver($uid, $gold)
{
$uid = (int) $uid;
$gold = (int) $gold;
if ($gold <= 0 || $gold > 100000) {
return self::EXCHANGE_INVALID;
}
$silver = $gold * self::silverPerGold();
return $this->runExchange($uid, $gold, $silver, true);
}
/**
* Argint -> aur. $gold = cat aur vrei sa primesti.
*/
public function exchangeSilverToGold($uid, $gold)
{
$uid = (int) $uid;
$gold = (int) $gold;
if ($gold <= 0 || $gold > 100000) {
return self::EXCHANGE_INVALID;
}
$silver = $gold * self::silverForOneGold();
return $this->runExchange($uid, $gold, $silver, false);
}
/**
* Executa schimbul. $goldToSilver = true inseamna "dai aur, primesti argint".
*/
private function runExchange($uid, $gold, $silver, $goldToSilver)
{
// Fara erou nu exista unde tine argintul.
if ($this->heroRowId($uid) === 0) {
return self::EXCHANGE_NO_HERO;
}
$this->db->begin_transaction();
try {
if ($goldToSilver) {
$take = $this->db->prepare(
"UPDATE " . TB_PREFIX . "users SET gold = gold - ? WHERE id = ? AND gold >= ? LIMIT 1"
);
$take->bind_param('iii', $gold, $uid, $gold);
$take->execute();
$taken = $take->affected_rows;
$take->close();
if ($taken < 1) {
$this->db->rollback();
return self::EXCHANGE_NOT_ENOUGH;
}
$give = $this->db->prepare(
"UPDATE " . TB_PREFIX . "hero SET silver = silver + ? WHERE uid = ? LIMIT 1"
);
$give->bind_param('ii', $silver, $uid);
$give->execute();
$given = $give->affected_rows;
$give->close();
if ($given < 1) {
$this->db->rollback();
return self::EXCHANGE_FAILED;
}
} else {
$take = $this->db->prepare(
"UPDATE " . TB_PREFIX . "hero SET silver = silver - ? WHERE uid = ? AND silver >= ? LIMIT 1"
);
$take->bind_param('iii', $silver, $uid, $silver);
$take->execute();
$taken = $take->affected_rows;
$take->close();
if ($taken < 1) {
$this->db->rollback();
return self::EXCHANGE_NOT_ENOUGH;
}
$give = $this->db->prepare(
"UPDATE " . TB_PREFIX . "users SET gold = gold + ? WHERE id = ? LIMIT 1"
);
$give->bind_param('ii', $gold, $uid);
$give->execute();
$given = $give->affected_rows;
$give->close();
if ($given < 1) {
$this->db->rollback();
return self::EXCHANGE_FAILED;
}
}
$this->db->commit();
return self::EXCHANGE_OK;
} catch (Throwable $e) {
$this->db->rollback();
return self::EXCHANGE_FAILED;
}
}
/** Id-ul randului de erou al jucatorului, 0 daca nu exista. */
private function heroRowId($uid)
{
$uid = (int) $uid;
$stmt = $this->db->prepare(
"SELECT heroid FROM " . TB_PREFIX . "hero WHERE uid = ? LIMIT 1"
);
$stmt->bind_param('i', $uid);
$stmt->execute();
$stmt->bind_result($heroid);
$found = $stmt->fetch();
$stmt->close();
return $found ? (int) $heroid : 0;
}
/* =========================================================================
* DISPONIBILITATEA EROULUI PENTRU SCHIMBAREA ECHIPAMENTULUI
* ===================================================================== */
@@ -547,14 +703,20 @@ class HeroItems
if (!$hero) {
return self::USE_INVALID;
}
// Refund all spent points: 5 free points per level gained (same
// convention as Automation::calculateLevelUp). Attributes return to 0.
// FIX: un erou porneste cu 5 puncte la nivelul 0 (vezi INSERT-ul din
// 37_train.tpl), iar fiecare nivel mai da 5 (Automation::calculateLevelUp).
// Formula veche, "level * 5", uita punctele initiale: cartea returna cu 5
// mai putin decat avusese jucatorul, iar la nivelul 0 le pierdea pe toate
// si lasa eroul cu 0 puncte si toate atributele pe zero.
$level = (int) $hero['level'];
$points = $level * 5;
$points = 5 + ($level * 5);
// resources intra si el in reset (punctele se redistribuie doar cu cartea);
// res_type ramane neatins - alegerea resursei se schimba oricand, gratis.
$stmt = $this->db->prepare(
"UPDATE " . TB_PREFIX . "hero
SET points = ?, attack = 0, defence = 0, attackbonus = 0,
defencebonus = 0, regeneration = 0
defencebonus = 0, regeneration = 0, resources = 0
WHERE heroid = ? LIMIT 1"
);
$heroid = (int) $hero['heroid'];
+11
View File
@@ -2818,6 +2818,17 @@ tz_def('HERO_T4_TAB_ITEMS', 'Inventory');
tz_def('HERO_T4_TAB_ADVENTURES', 'Adventures');
tz_def('HERO_T4_TAB_AUCTION', 'Auctions');
tz_def('HERO_EXCHANGE', 'Exchange office');
tz_def('HERO_EXCHANGE_G2S', 'Gold to silver');
tz_def('HERO_EXCHANGE_S2G', 'Silver to gold');
tz_def('HERO_EXCHANGE_OK', 'Exchange completed.');
tz_def('HERO_EXCHANGE_NOTENOUGH', 'You do not have enough for this exchange.');
tz_def('HERO_EXCHANGE_FAIL', 'The exchange could not be completed.');
tz_def('HERO_EXCHANGE_HINT', 'The amount you type is the gold given or received; silver is calculated at the rate shown.');
tz_def('HERO_RES_PRODUCTION', 'Resources');
tz_def('HERO_RES_TYPE', 'Produced resource');
tz_def('HERO_RES_ALL', 'All resources');
tz_def('HERO_RES_TYPE_HINT', 'Can be changed at any time, free of charge.');
tz_def('HERO_SILVER', 'Silver');
tz_def('HERO_EXPERIENCE', 'Experience');
tz_def('RESOURCES', 'Resources');
+11
View File
@@ -2803,6 +2803,17 @@ tz_def('HERO_ITEM_USE_FAIL', 'Acest obiect nu poate fi folosit acum.');
tz_def('HERO_QUANTITY', 'Cantitate');
tz_def('HERO_RELEASE_ANIMALS', 'Elibereaza');
tz_def('HERO_RELEASE_CONFIRM', 'Eliberezi aceste animale? Vor disparea definitiv.');
tz_def('HERO_EXCHANGE', 'Casa de schimb');
tz_def('HERO_EXCHANGE_G2S', 'Aur in argint');
tz_def('HERO_EXCHANGE_S2G', 'Argint in aur');
tz_def('HERO_EXCHANGE_OK', 'Schimb efectuat.');
tz_def('HERO_EXCHANGE_NOTENOUGH', 'Nu ai suficient pentru acest schimb.');
tz_def('HERO_EXCHANGE_FAIL', 'Schimbul nu a putut fi efectuat.');
tz_def('HERO_EXCHANGE_HINT', 'Valoarea introdusa e aurul dat sau primit; argintul se calculeaza la rata afisata.');
tz_def('HERO_RES_PRODUCTION', 'Resurse');
tz_def('HERO_RES_TYPE', 'Resursa produsa');
tz_def('HERO_RES_ALL', 'Toate resursele');
tz_def('HERO_RES_TYPE_HINT', 'Se poate schimba oricand, gratuit.');
tz_def('HERO_SILVER', 'Argint');
tz_def('HERO_SLOT_1', 'Coif');
tz_def('HERO_SLOT_2', 'Armura');
+66
View File
@@ -43,6 +43,9 @@ class Village {
public $enforcetome = [];
public $enforcetoyou = [];
public $enforceoasis = [];
/** cache per sat pentru productia adusa de erou (null = neincarcat) */
private $heroProd = null;
public $currentcel = 0;
public $currentfestival = 0;
public $allcrop = 0;
@@ -306,11 +309,74 @@ class Village {
$amount += $amount / 100 * $bonusPct;
}
// Productia de resurse a eroului (atributul "Resources", ca in T4).
// Se adauga DUPA bonusurile de cladiri si oaze (alea se aplica doar campurilor)
// dar INAINTE de bonusul de 25% si de inmultirea cu SPEED, fiindca in T4 si
// bonusul de 25% si viteza serverului se aplica si productiei eroului.
$amount += $this->getHeroProdFor($type);
if ($this->sess->{$cfg['sessionBonus']} == 1) $amount *= 1.25;
return round($amount * SPEED);
}
/**
* Productia orara adusa de atributul "Resources" al eroului, pentru satul asta.
*
* Regula T4: fiecare punct produce 3 din FIECARE resursa (res_type = 0) sau 10
* dintr-un singur tip (res_type = 1..4 pentru lemn/lut/fier/cereale).
* Bonusul merge in satul in care se afla eroul (hero.wref), deci mutarea
* eroului muta si productia.
*
* Randul de erou se citeste O SINGURA DATA per instanta de sat.
*/
private function getHeroProdFor(string $type): float {
if ($this->heroProd === null) {
$this->heroProd = $this->loadHeroProd();
}
return $this->heroProd[$type] ?? 0.0;
}
/** Citeste eroul acestui sat si transforma punctele in productie per resursa. */
private function loadHeroProd(): array {
$empty = ['wood' => 0.0, 'clay' => 0.0, 'iron' => 0.0, 'crop' => 0.0];
if (!defined('NEW_FUNCTIONS_HERO_T4') || !NEW_FUNCTIONS_HERO_T4) {
return $empty;
}
$row = $this->db->getHeroForVillage($this->wid);
if (!$row) {
return $empty;
}
$points = (int) $row['resources'];
if ($points <= 0) {
return $empty;
}
$resType = (int) $row['res_type'];
$map = [1 => 'wood', 2 => 'clay', 3 => 'iron', 4 => 'crop'];
// 0 = raspandit egal: 3 din fiecare. Altfel: 10 dintr-un singur tip.
if ($resType === 0 || !isset($map[$resType])) {
$each = $points * (defined('HERO_RES_PER_POINT_ALL') ? (int) HERO_RES_PER_POINT_ALL : 3);
return ['wood' => $each, 'clay' => $each, 'iron' => $each, 'crop' => $each];
}
$single = $points * (defined('HERO_RES_PER_POINT_ONE') ? (int) HERO_RES_PER_POINT_ONE : 10);
$out = $empty;
$out[$map[$resType]] = $single;
return $out;
}
/**
* W1 #4: scans the f1..f38 fields ONLY once per load (instead of 4 passes)
* and buckets them by type.