From 1e19f117caaa0b1c2078d8c8a3e20bc673f0f4a2 Mon Sep 17 00:00:00 2001 From: novgorodschi catalin Date: Thu, 23 Jul 2026 15:16:51 +0300 Subject: [PATCH] Last fix for Hero T4 & V11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /* |-------------------------------------------------------------------------- | FIX List |-------------------------------------------------------------------------- | | 1. Hero Attribute Points | - When adding attribute points to the hero, the page currently refreshes | after every click on the "+" button. | - Implement a faster allocation method (AJAX or similar) so multiple | points can be assigned without a full page refresh. | - The implementation must remain secure and not be exploitable | (validate requests server-side, prevent double submissions, | verify available points, etc.). | | 2. Hero Equipment Restrictions (Original Travian Behavior) | - Prevent equipping or unequipping hero items while the hero is: | • On an adventure. | • Attacking (alone or with troops). | • Away as reinforcements. | | 3. Oasis Navigation | - When opening an oasis (37_land.tpl), the 37_t4nav.tpl navigation | disappears. | - Include the navigation menu on the oasis page as well. | | 4. Auction House Redesign | - Redesign 37_auction.tpl to match the Merchant.png mockup. | - Keep the existing auction functionality exactly as it is. | - Only improve the UI/UX. | - You may crop and reuse graphics from Merchant.png. | - Add Gold ⇄ Silver exchange functionality. | | 5. Hero Item Bonuses (Highest Priority) | - Verify that hero equipment bonuses are applied correctly. | - Example: | • Helmet of the Ruler should reduce Barracks training time by 20%. | • Currently, training time remains unchanged whether the helmet | is equipped or not. | - Also verify: | • Stable training speed bonuses. | • Boots bonuses. | • Any other related hero equipment effects. | | 6. Adventures Page Redesign | - Redesign 37_adventures.tpl to match the Adventure.png mockup. | - Keep the "Expires in" column intact. | */ --- Admin/Templates/config.tpl | 1 + Admin/Templates/editServerSet.tpl | 7 + Admin/Templates/search2.tpl | 2 +- GameEngine/Admin/Mods/config_template.php | 15 +- GameEngine/Admin/Mods/editServerSet.php | 8 +- GameEngine/Automation/AutomationHero.php | 62 ++++++- .../Automation/AutomationVillageUpkeep.php | 76 +++++++- GameEngine/HeroItems.php | 94 ++++++++++ GameEngine/Lang/en.php | 5 + GameEngine/Lang/ro.php | 5 + GameEngine/Technology.php | 68 +++++++ Templates/Build/37.tpl | 6 + Templates/Build/37_adventures.tpl | 69 +++++-- Templates/Build/37_hero.tpl | 175 +++++++++++++++++- Templates/Build/37_items.tpl | 34 +++- Templates/Build/37_t4nav.tpl | 3 + install/data/constant_format.tpl | 10 + install/process.php | 8 + install/templates/config.tpl | 1 + 19 files changed, 620 insertions(+), 29 deletions(-) diff --git a/Admin/Templates/config.tpl b/Admin/Templates/config.tpl index d04b4293..bd8f44bc 100644 --- a/Admin/Templates/config.tpl +++ b/Admin/Templates/config.tpl @@ -92,6 +92,7 @@ $editIcon = ' ? ?Enabled" : "Disabled"; ?> ? + Hero base regeneration ?Hit points the hero recovers per day, independent of the regeneration attribute and of equipped items. 0 disables it. HP / day diff --git a/Admin/Templates/editServerSet.tpl b/Admin/Templates/editServerSet.tpl index 3546d12f..29faa89f 100644 --- a/Admin/Templates/editServerSet.tpl +++ b/Admin/Templates/editServerSet.tpl @@ -342,6 +342,13 @@ function refresh(tz) { + + Hero base regeneration ?Hit points the hero recovers per day on top of the points invested in the regeneration attribute and any equipped items. Without it, a hero with no regeneration points would never heal and would eventually die on adventures. Scales with server speed. 0 disables it. + + HP / day + + diff --git a/Admin/Templates/search2.tpl b/Admin/Templates/search2.tpl index 25cd40f3..8a6f3ca6 100644 --- a/Admin/Templates/search2.tpl +++ b/Admin/Templates/search2.tpl @@ -17,7 +17,7 @@ ## Copyright : TravianZ (c) 2010-2025. All rights reserved. ## ## --------------------------------------------------------------------------- ## ################################################################################# -$array_tribe=array('-',TRIBE1,TRIBE2,TRIBE3,TRIBE4,TRIBE5); +$array_tribe=array('-',TRIBE1,TRIBE2,TRIBE3,TRIBE4,TRIBE5,TRIBE6,TRIBE7,TRIBE8,TRIBE9); $tribename = $array_tribe[$user['tribe']]; $searchresults = $admin->search_player($user['username']); diff --git a/GameEngine/Admin/Mods/config_template.php b/GameEngine/Admin/Mods/config_template.php index 180c9115..43aa7022 100644 --- a/GameEngine/Admin/Mods/config_template.php +++ b/GameEngine/Admin/Mods/config_template.php @@ -50,7 +50,11 @@ if (!function_exists('admin_config_template_path')) { // - existing server -> keep the current CRON_KEY (survives config saves) // - key not defined -> provision a fresh random one (servers installed // before CRON_KEY existed in the template) - function admin_config_template_contents() { + // $overrides permite unui mod de editare sa IMPUNA o valoare pentru un + // placeholder pe care altfel l-am pastra din constanta curenta (ex. + // editServerSet care schimba HERO_BASE_REGEN). Celelalte module apeleaza + // fara argumente si valorile curente raman neatinse. + function admin_config_template_contents(array $overrides = array()) { $path = admin_config_template_path(); if ($path === false) { @@ -93,6 +97,15 @@ if (!function_exists('admin_config_template_path')) { '%CLEANUPMESSAGES%' => array('CLEANUP_MESSAGES_DAYS', 0), ); + // regenerarea de baza a eroului: acelasi tratament, sa nu fie resetata + $cleanupDefaults['%HEROBASEREGEN%'] = array('HERO_BASE_REGEN', 10); + + foreach ($overrides as $ovPlaceholder => $ovValue) { + if (strpos($text, $ovPlaceholder) !== false) { + $text = str_replace($ovPlaceholder, (string) $ovValue, $text); + } + } + foreach ($cleanupDefaults as $placeholder => $info) { if (strpos($text, $placeholder) === false) { continue; diff --git a/GameEngine/Admin/Mods/editServerSet.php b/GameEngine/Admin/Mods/editServerSet.php index b49e9d0c..b105c0ff 100755 --- a/GameEngine/Admin/Mods/editServerSet.php +++ b/GameEngine/Admin/Mods/editServerSet.php @@ -74,7 +74,13 @@ $fh = fopen($myFile, 'w') or die("


Can't open file: GameEngine\con $NEW_FUNCTION_TRIBE_SPARTANS = (defined('NEW_FUNCTION_TRIBE_SPARTANS') && NEW_FUNCTION_TRIBE_SPARTANS ? 'true' : 'false'); $NEW_FUNCTION_TRIBE_VIKINGS = (defined('NEW_FUNCTION_TRIBE_VIKINGS') && NEW_FUNCTION_TRIBE_VIKINGS ? 'true' : 'false'); - $text = admin_config_template_contents(); + // Regenerarea de baza a vietii eroului (HP pe zi). 0 = dezactivata. +$heroBaseRegen = isset($_POST['hero_base_regen']) ? (int) $_POST['hero_base_regen'] : 10; + +if ($heroBaseRegen < 0) { $heroBaseRegen = 0; } +if ($heroBaseRegen > 100) { $heroBaseRegen = 100; } + +$text = admin_config_template_contents(array('%HEROBASEREGEN%' => $heroBaseRegen)); $text = preg_replace("'%ERRORREPORT%'", $_POST['error'], $text); $text = preg_replace("'%ERROR%'", $_POST['error'], $text); $text = preg_replace("'%SERVERNAME%'", $_POST['servername'], $text); diff --git a/GameEngine/Automation/AutomationHero.php b/GameEngine/Automation/AutomationHero.php index e3a53619..3cb20710 100644 --- a/GameEngine/Automation/AutomationHero.php +++ b/GameEngine/Automation/AutomationHero.php @@ -121,13 +121,48 @@ trait AutomationHero { // Passive HP regeneration: returns the new health value, or -1 if unchanged. private function calculateHealthRegen($hdata) { + // Eroii morti nu se regenereaza (se ridica prin inviere, nu singuri). + if ((int) $hdata['dead'] === 1) { + return -1; + } + if((time()-$hdata['lastupdate']) >= 1){ - if($hdata['health'] < 100 and $hdata['health'] > 0){ + // FIX: conditia veche era "health > 0", deci un erou VIU ajuns exact + // la 0 ramanea blocat acolo pentru totdeauna. Acum conteaza doar sa + // fie viu si sub 100. + if($hdata['health'] < 100){ 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']); + // FIX: bonusul HB_REGEN_HP al coifurilor (Regeneration / Health / + // Healing = +10/15/20 HP pe zi) nu era aplicat nicaieri - exista + // doar in definitia itemelor. Se adauga la regenerarea proprie a + // eroului, scalat cu aceeasi viteza de server ca aceasta, altfel + // pe un server rapid ar fi complet nesemnificativ. + // ANOMALIE DE SEMNALAT: in T4 valoarea e "HP pe zi" fixa; aici o + // scalam cu $speed pentru consecventa cu regenerarea din atribute. + // Daca preferi valoarea fixa, scoate "* $speed" de mai jos. + $itemRegen = $this->heroItemRegen($hdata); + + // FIX PRINCIPAL: pana acum regenerarea depindea EXCLUSIV de + // atributul 'regeneration'. Un erou nou porneste cu acest atribut + // pe 0 (vezi INSERT-ul din 37_train.tpl), deci daca jucatorul nu + // punea puncte acolo, viata NU se refacea niciodata: scadea la + // fiecare aventura sau lupta si nu urca inapoi. Dupa destule + // aventuri ajungea la 0 si eroul murea chiar si la o aventura + // "Normal" (care ia doar 0-8 HP) - exact simptomul raportat. + // + // Acum exista o regenerare de BAZA, independenta de atribute, ca + // in Travian T4 (implicit 10 HP pe zi). Se poate regla din + // GameEngine/config.php: define('HERO_BASE_REGEN', 10); + $baseRegen = defined('HERO_BASE_REGEN') ? (float) HERO_BASE_REGEN : 10; + + // HP pe zi din toate sursele: baza + atribut (5 per punct) + iteme + $perDay = $baseRegen + ($hdata['regeneration'] * 5) + $itemRegen; + + $reg = $hdata['health'] + + $perDay * $speed / 86400 * (time() - $hdata['lastupdate']); return ($reg <= 100) ? $reg : 100; } @@ -135,6 +170,29 @@ trait AutomationHero { return -1; } + /** + * HP pe zi adaugat de itemele echipate (HB_REGEN_HP). + * + * HeroBattleBonus::bonuses() are gard de feature flag, cache per request si + * intoarce null cand sistemul T4 e oprit - deci pe serverele fara erou T4 + * comportamentul ramane exact ca inainte. + */ + private function heroItemRegen($hdata) { + if (!class_exists('HeroBattleBonus') || !HeroBattleBonus::enabled()) { + return 0; + } + + $uid = isset($hdata['uid']) ? (int) $hdata['uid'] : 0; + + if ($uid <= 0) { + return 0; + } + + $bonuses = HeroBattleBonus::bonuses($uid); + + return $bonuses ? (float) $bonuses[HB_REGEN_HP] : 0; + } + // Level-up + score points. Returns a column fragment. private function calculateLevelUp($hdata) { global $hero_levels; diff --git a/GameEngine/Automation/AutomationVillageUpkeep.php b/GameEngine/Automation/AutomationVillageUpkeep.php index a7ce1364..0938d51a 100644 --- a/GameEngine/Automation/AutomationVillageUpkeep.php +++ b/GameEngine/Automation/AutomationVillageUpkeep.php @@ -212,10 +212,84 @@ trait AutomationVillageUpkeep { private function culturePoints() { global $database; - + + // FIX: punctele de cultura date de coifuri (Gladiator/Tribune/Consul = + // 100/400/800 pe zi) nu erau adaugate nicaieri - HB_CP exista doar in + // definitia itemelor si in agregarea din HeroItems::getBonuses(). + // Se aplica INAINTE de updateVSumField, fiindca acela reseteaza + // users.lastupdate; asa ambele folosesc exact aceeasi fereastra de timp. + $this->applyHeroCulturePoints(); + $database->updateVSumField('cp'); } + /** + * Adauga in users.cp punctele de cultura ale coifurilor echipate. + * + * Acumularea imita formula din updateVSumField(): valoarea e "pe zi", iar + * ziua de joc e 86400/SPEED (minim o ora), la fel ca pentru cladiri. + * Valorile sunt luate din catalog prin scaledBonusValue(), care aplica deja + * regula de server rapid (coifurile de cultura se injumatatesc la SPEED>=3). + * + * Se numara doar eroii vii (hero.dead = 0). Cum exista un singur slot de + * coif, un jucator nu poate avea doua iteme cu CP echipate simultan, deci + * join-ul nu poate dubla castigul. + */ + private function applyHeroCulturePoints() { + global $database, $heroItemCatalog; + + if (!class_exists('HeroBattleBonus') || !HeroBattleBonus::enabled()) { + return; + } + + if (empty($heroItemCatalog) || !is_array($heroItemCatalog)) { + return; + } + + $heroItems = new HeroItems(); + $cpValues = array(); + + foreach ($heroItemCatalog as $itemid => $def) { + if (!isset($def['bonus']) || !is_array($def['bonus']) || !isset($def['bonus'][HB_CP])) { + continue; + } + + $value = (int) $heroItems->scaledBonusValue($def, (int) $def['bonus'][HB_CP]); + + if ($value > 0) { + $cpValues[(int) $itemid] = $value; + } + } + + if (empty($cpValues)) { + return; + } + + $durDay = 86400 / SPEED; + + if ($durDay < 3600) { + $durDay = 3600; + } + + $case = ''; + + foreach ($cpValues as $itemid => $value) { + $case .= ' WHEN ' . (int) $itemid . ' THEN ' . (int) $value; + } + + $ids = implode(',', array_map('intval', array_keys($cpValues))); + + $q = "UPDATE " . TB_PREFIX . "users AS u + INNER JOIN " . TB_PREFIX . "hero_items AS hi + ON hi.uid = u.id AND hi.equipped = 1 AND hi.itemid IN (" . $ids . ") + INNER JOIN " . TB_PREFIX . "hero AS h + ON h.uid = u.id AND h.dead = 0 + SET u.cp = u.cp + ((CASE hi.itemid" . $case . " END) * (UNIX_TIMESTAMP() - u.lastupdate) / " . $durDay . ") + WHERE u.lastupdate < (UNIX_TIMESTAMP() - 600)"; + + mysqli_query($database->dblink, $q); + } + /** * Recompute a warehouse/granary capacity after a build completion. * Returns [$fieldDbName, $max]: the vdata column to update and its new value. diff --git a/GameEngine/HeroItems.php b/GameEngine/HeroItems.php index cdb2cdb2..7cb2073e 100644 --- a/GameEngine/HeroItems.php +++ b/GameEngine/HeroItems.php @@ -264,11 +264,100 @@ 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) */ + /* ========================================================================= + * DISPONIBILITATEA EROULUI PENTRU SCHIMBAREA ECHIPAMENTULUI + * ===================================================================== */ + + /** @var array cache per request: uid => motiv ('' = eroul e acasa) */ + private static $awayCache = array(); + + /** + * Motivul pentru care eroul NU poate schimba echipamentul acum. + * + * Intoarce '' cand eroul e acasa si poate fi echipat, sau una dintre: + * 'adventure' - plecat intr-o aventura + * 'attack' - plecat intr-un atac (singur sau cu trupe), dus sau intors + * 'reinforcement' - trimis ca intarire in alt sat + * + * Comportament ca in Travian original: echipamentul se schimba doar cat timp + * eroul e in sat. Eroul MORT nu e blocat - e "acasa", doar fara viata, si in + * T4 iti poti pregati echipamentul cat timp astepti invierea. + */ + public function heroAwayReason($uid) + { + $uid = (int) $uid; + + if (isset(self::$awayCache[$uid])) { + return self::$awayCache[$uid]; + } + + $p = TB_PREFIX; + + // ATENTIE: aliasul pentru miscarea de intoarcere NU poate fi "returning" - + // e cuvant rezervat in MariaDB (clauza RETURNING) si strica query-ul. + // Un singur query, cu motive separate, ca interfata sa poata afisa + // exact de ce e blocat. Coloanele `from`/`to` sunt cuvinte rezervate, + // de aceea sunt in backticks. + $q = "SELECT + (SELECT COUNT(*) FROM {$p}hero + WHERE uid = ? AND COALESCE(intraining, 0) = 0) AS hero_exists, + (SELECT COUNT(*) FROM {$p}hero_adventure + WHERE uid = ? AND status = 1) AS adventure, + (SELECT IFNULL(SUM(e.hero), 0) FROM {$p}enforcement e + INNER JOIN {$p}vdata v ON v.wref = e.`from` + WHERE v.owner = ?) AS reinforcement, + (SELECT IFNULL(SUM(a.t11), 0) FROM {$p}movement m + INNER JOIN {$p}attacks a ON a.id = m.ref + INNER JOIN {$p}vdata v ON v.wref = m.`from` + WHERE v.owner = ? AND m.proc = 0 AND m.sort_type = 3) AS outgoing, + (SELECT IFNULL(SUM(a.t11), 0) FROM {$p}movement m + INNER JOIN {$p}attacks a ON a.id = m.ref + INNER JOIN {$p}vdata v ON v.wref = m.`to` + WHERE v.owner = ? AND m.proc = 0 AND m.sort_type = 4) AS atk_return"; + + $stmt = $this->db->prepare($q); + + if (!$stmt) { + // daca query-ul nu se poate pregati, nu blocam jucatorul + return self::$awayCache[$uid] = ''; + } + + $stmt->bind_param('iiiii', $uid, $uid, $uid, $uid, $uid); + $stmt->execute(); + $stmt->bind_result($heroExists, $adventure, $reinforcement, $outgoing, $atkReturn); + $stmt->fetch(); + $stmt->close(); + + $reason = ''; + + // Fara erou antrenat nu exista pe cine echipa. Se numara si eroii morti + // (randul exista, doar dead = 1): in T4 iti poti pregati echipamentul cat + // astepti invierea. Eroii aflati INCA in antrenament (intraining = 1) nu + // conteaza - inca nu exista cu adevarat. + if ((int) $heroExists === 0) { + $reason = 'nohero'; + } elseif ((int) $adventure > 0) { + $reason = 'adventure'; + } elseif (((int) $outgoing + (int) $atkReturn) > 0) { + $reason = 'attack'; + } elseif ((int) $reinforcement > 0) { + $reason = 'reinforcement'; + } + + return self::$awayCache[$uid] = $reason; + } + public function equipItem($uid, $rowId) { global $database; $uid = (int) $uid; $rowId = (int) $rowId; + // Eroul trebuie sa fie in sat. Verificarea e AICI, nu doar in interfata, + // ca un POST trimis manual sa nu poata ocoli regula. + if ($this->heroAwayReason($uid) !== '') { + return false; + } + $row = $this->findRowById($uid, $rowId); if (!$row || $row['orphan'] || (int) $row['def']['slot'] === HSLOT_BAG) { return false; @@ -314,6 +403,11 @@ class HeroItems /** Unequip a specific owned row. Returns true on success. */ public function unequipItem($uid, $rowId) { + // Acelasi gard ca la echipare (vezi heroAwayReason). + if ($this->heroAwayReason($uid) !== '') { + return false; + } + $uid = (int) $uid; $rowId = (int) $rowId; $stmt = $this->db->prepare( "UPDATE " . TB_PREFIX . "hero_items SET equipped = 0 WHERE uid = ? AND id = ? AND equipped = 1 LIMIT 1" diff --git a/GameEngine/Lang/en.php b/GameEngine/Lang/en.php index 275a307b..0185f601 100755 --- a/GameEngine/Lang/en.php +++ b/GameEngine/Lang/en.php @@ -2813,6 +2813,7 @@ if (!function_exists('tz_loc_topic')) { * T4 HERO PORT (Phase 6) - items / adventures / auction house * ========================================================================== */ tz_def('HERO_T4_TAB_HERO', 'Hero'); +tz_def('HERO_T4_TAB_OASIS', 'Oasis'); tz_def('HERO_T4_TAB_ITEMS', 'Inventory'); tz_def('HERO_T4_TAB_ADVENTURES', 'Adventures'); tz_def('HERO_T4_TAB_AUCTION', 'Auctions'); @@ -2842,6 +2843,7 @@ tz_def('HERO_ITEM_USED_OK', 'The item has been used.'); tz_def('HERO_ITEM_USE_FAIL', 'This item cannot be used right now.'); tz_def('HERO_ITEM_USE_BATTLE', 'This item is used automatically (bandages heal returning troops after battles).'); tz_def('HERO_EQUIP_OK', 'Item equipped.'); +tz_def('HERO_LOCKED_NOHERO', 'You have no hero yet. Train one in the Hero\'s Mansion before equipping items.'); tz_def('HERO_EQUIP_FAIL', 'This item cannot be equipped (wrong hero unit or item type).'); tz_def('HERO_UNEQUIP_OK', 'Item taken off.'); @@ -2853,6 +2855,9 @@ tz_def('HERO_ADV_NONE', 'No adventures available right now. New ones ap tz_def('HERO_ADV_DIFFICULTY', 'Difficulty'); tz_def('HERO_ADV_DIFF_NORMAL', 'Normal'); tz_def('HERO_ADV_DIFF_HARD', 'Hard'); +tz_def('HERO_ADV_PLACE', 'Place'); +tz_def('HERO_ADV_DANGER', 'Danger'); +tz_def('HERO_ADV_LINK', 'Link'); tz_def('HERO_ADV_DURATION', 'Travel time (one way)'); tz_def('HERO_ADV_EXPIRES', 'Expires in'); tz_def('HERO_ADV_GO', 'Start adventure'); diff --git a/GameEngine/Lang/ro.php b/GameEngine/Lang/ro.php index eb03c8a5..2c7ee2b2 100644 --- a/GameEngine/Lang/ro.php +++ b/GameEngine/Lang/ro.php @@ -2736,6 +2736,9 @@ tz_def('HERO_ADV_DIED_INFO', 'Toata prada a fost pierduta. Eroul poate fi in tz_def('HERO_ADV_DIFFICULTY', 'Dificultate'); tz_def('HERO_ADV_DIFF_HARD', 'Grea'); tz_def('HERO_ADV_DIFF_NORMAL', 'Normala'); +tz_def('HERO_ADV_PLACE', 'Loc'); +tz_def('HERO_ADV_DANGER', 'Pericol'); +tz_def('HERO_ADV_LINK', 'Actiune'); tz_def('HERO_ADV_DURATION', 'Timp de calatorie (dus)'); tz_def('HERO_ADV_EXPIRES', 'Expira in'); tz_def('HERO_ADV_GO', 'Incepe aventura'); @@ -2787,6 +2790,7 @@ tz_def('HERO_AUC_YOUR_MAX', 'Maximul tau'); // Hero - echipare / inventar tz_def('HERO_EQUIP', 'Echipeaza'); +tz_def('HERO_LOCKED_NOHERO', 'Nu ai inca un erou. Antreneaza unul in Resedinta Eroului inainte de a echipa iteme.'); tz_def('HERO_EQUIP_FAIL', 'Acest obiect nu poate fi echipat (unitate de erou gresita sau tip de obiect gresit).'); tz_def('HERO_EQUIP_OK', 'Obiect echipat.'); tz_def('HERO_EXPERIENCE', 'Experienta'); @@ -2807,6 +2811,7 @@ tz_def('HERO_SLOT_4', 'Mana stanga'); tz_def('HERO_SLOT_5', 'Incaltaminte'); tz_def('HERO_SLOT_6', 'Cal'); tz_def('HERO_SLOT_7', 'Desaga'); +tz_def('HERO_T4_TAB_OASIS', 'Oaze'); tz_def('HERO_T4_TAB_ADVENTURES', 'Aventuri'); tz_def('HERO_T4_TAB_AUCTION', 'Licitatii'); tz_def('HERO_T4_TAB_HERO', 'Erou'); diff --git a/GameEngine/Technology.php b/GameEngine/Technology.php index d2d454a7..13c55279 100755 --- a/GameEngine/Technology.php +++ b/GameEngine/Technology.php @@ -634,9 +634,77 @@ class Technology { $each = round(($bid19[$building->getTypeLevel(36)]['attri'] / 100) * ${'u'.$unit}['time'] / SPEED); } + // FIX: bonusurile de instruire ale eroului (coifuri) nu erau aplicate + // NICAIERI - HB_TRAIN_INF si HB_TRAIN_CAV existau doar in definitia + // itemelor si in agregarea din HeroItems::getBonuses(), deci "Helmet of + // the Ruler" (-20% infanterie) sau "Helmet of the Heavy Cavalry" (-20% + // cavalerie) nu schimbau timpul cu nimic. + // + // Se aplica dupa formula cladirii, pe acelasi $each, deci merge identic + // in cazarma/grajd normale si in cele mari (Great Barracks/Great Stable), + // fiindca ramura e aleasa dupa TIPUL unitatii, nu dupa cladire. + // Nu se aplica la atelier, unitati speciale sau capcane - la fel ca in T4. + $each = $this->applyHeroTrainingBonus($each, $unit, $footies, $calvary); + return $each; } + /** + * Reduce timpul de instruire cu procentul dat de coiful echipat al eroului. + * + * Proprietarul e luat din satul in care se face instruirea (nu din sesiune), + * ca sa fie corect si cand codul ruleaza in alt context decat o pagina a + * jucatorului. HeroBattleBonus::bonuses() are deja gard de feature flag si + * cache per request, si intoarce null cand sistemul T4 e oprit. + */ + private function applyHeroTrainingBonus($each, $unit, array $footies, array $calvary) { + global $village, $database; + + if (!class_exists('HeroBattleBonus') || !HeroBattleBonus::enabled()) { + return $each; + } + + // Proprietarul satului in care se instruieste, NU utilizatorul din + // sesiune: asa bonusul e corect si cand un admin lucreaza pe satul + // altui jucator (conteaza eroul proprietarului, nu al adminului). + // getVillageField e cache-uit per request, deci nu adauga query-uri. + $uid = (isset($village->wid) && $database) + ? (int) $database->getVillageField($village->wid, 'owner') + : 0; + + if ($uid <= 0) { + return $each; + } + + $bonuses = HeroBattleBonus::bonuses($uid); + + if (!$bonuses) { + return $each; + } + + $percent = 0; + + if (in_array($unit, $footies)) { + $percent = (int) $bonuses[HB_TRAIN_INF]; + } elseif (in_array($unit, $calvary)) { + $percent = (int) $bonuses[HB_TRAIN_CAV]; + } + + if ($percent <= 0) { + return $each; + } + + // plasa de siguranta: chiar daca s-ar aduna mai multe surse, timpul nu + // poate cobori sub 10% din valoarea de baza + if ($percent > 90) { + $percent = 90; + } + + $each = (int) round($each * (100 - $percent) / 100); + + return $each > 0 ? $each : 1; + } + private function getMaxTrainable($unit, $amt, $great) { global $database, $village, $bid36; diff --git a/Templates/Build/37.tpl b/Templates/Build/37.tpl index 071ce54d..dabc80ec 100644 --- a/Templates/Build/37.tpl +++ b/Templates/Build/37.tpl @@ -90,6 +90,12 @@ } if(isset($_GET['land']) && $village->resarray['f' . $id] >= 1) { + // FIX: pagina de oaze pierdea meniul T4 - ramura asta e inaintea + // celei cu tab-uri si nu includea navigatia deloc. + if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4) { + $t4tab = 'land'; + include_once("37_t4nav.tpl"); + } include_once("37_land.tpl"); } else if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4 && $village->resarray['f' . $id] >= 1 diff --git a/Templates/Build/37_adventures.tpl b/Templates/Build/37_adventures.tpl index 6811bd18..63798d42 100644 --- a/Templates/Build/37_adventures.tpl +++ b/Templates/Build/37_adventures.tpl @@ -17,6 +17,9 @@ ## License : TravianZ Project ## ## Copyright : TravianZ (c) 2010-2026. All rights reserved. ## ## --------------------------------------------------------------------------- ## +## NOTA: doar prezentarea a fost refacuta (aspect ca in Travian original). ## +## Logica - generarea ofertelor, pornirea aventurii, mesajele - e neschimbata.## +## Coloana "Expires in" a fost pastrata (nu exista in originalul T4). ## ################################################################################# $t4Adventures = new HeroAdventure(); @@ -41,6 +44,32 @@ $t4Adventures->generateOffers($session->uid); $t4Offers = $t4Adventures->getOffers($session->uid); $t4Running = $t4Adventures->getRunning($session->uid); $t4Now = time(); + +/** + * Numele locului tintit de aventura, ca in Travian: "Abandoned valley" pentru + * un teren liber, "Unoccupied oasis" pentru o oaza libera. Tintele sunt mereu + * tile-uri neocupate (vezi HeroAdventure::generateOffers). + * getCoor() e cache-uit per request, deci nu adauga query-uri pe rand. + */ +$t4PlaceInfo = function ($wref) use ($database, $generator) { + $tile = $database->getCoor((int) $wref); + + if (!is_array($tile) || !isset($tile['x'])) { + return null; + } + + $isOasis = isset($tile['oasistype']) && (int) $tile['oasistype'] > 0; + + return array( + 'name' => $isOasis + ? (defined('UNOCCUPIED') && defined('OASIS') ? UNOCCUPIED . ' ' . strtolower(OASIS) : 'Unoccupied oasis') + : (defined('ABANDVALLEY') ? ABANDVALLEY : 'Abandoned valley'), + 'x' => (int) $tile['x'], + 'y' => (int) $tile['y'], + 'wref' => (int) $wref, + 'check' => $generator->getMapCheck((int) $wref), + ); +}; ?> @@ -59,10 +88,11 @@ $t4Now = time(); - + - + + @@ -70,31 +100,42 @@ $t4Now = time(); + + + + (|) + + + + + + - - +
- - hard - - - normal - - - getTimeFormat((int) $t4Offer['duration']); ?> + <?php echo $t4Hard ? HERO_ADV_DIFF_HARD : HERO_ADV_DIFF_NORMAL; ?> + getTimeFormat(max(0, $t4Offer['expire'] - $t4Now)); ?> + + -
+ - +
-
diff --git a/Templates/Build/37_hero.tpl b/Templates/Build/37_hero.tpl index aa1bcf7b..373ac341 100644 --- a/Templates/Build/37_hero.tpl +++ b/Templates/Build/37_hero.tpl @@ -46,7 +46,11 @@ $heroStatColumns = [ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { $field = $heroStatColumns[$action]; if ($hero_info['points'] > 0 && $hero_info[$field] < 100) { - return "(+)"; + // class + data-* pentru JS; linkul ramane un GET valid, deci fara JS + // pagina se comporta exact ca inainte (un punct per click, cu refresh). + return "(+)"; } return "(+)"; }; @@ -71,7 +75,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { - + @@ -80,7 +84,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { - + @@ -89,7 +93,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { - + @@ -98,7 +102,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { - + @@ -107,7 +111,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { - + @@ -130,17 +134,111 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { % <?php echo (int)$percent; ?>% - + : 100% 100% - + + 0) { ?> +
+ + $t4Col) { ?> + + + +
+ + + ".ERROR_NAME_SHORT."

"; } @@ -156,6 +254,67 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) { // NOTE: the actions below are triggered by GET (?add=...) and modify // data in the DB. This was the original (without CSRF), I did not change this aspect - // it is an existing behavior in all build.php, not specific to this file. +// Distribuire IN BLOC (butonul "Save" din interfata cu JS). +// Siguranta: totul se valideaza pe server intr-un SINGUR UPDATE atomic, cu +// garzi in WHERE. Daca cineva trimite un POST modificat (mai multe puncte decat +// are, sau peste 100 la o statistica), conditiile nu se potrivesc, UPDATE-ul nu +// afecteaza niciun rand si nu se schimba nimic. Fiind o singura instructiune, +// nici doua cereri trimise simultan nu pot cheltui aceleasi puncte de doua ori. +if (isset($_POST['t4points'])) { + + $t4Alloc = array(); + $t4Total = 0; + + foreach ($heroStatColumns as $t4Key => $t4Col) { + $t4Value = isset($_POST['p_' . $t4Key]) ? (int) $_POST['p_' . $t4Key] : 0; + + if ($t4Value < 0) { + $t4Value = 0; + } + + $t4Alloc[$t4Col] = $t4Value; + $t4Total += $t4Value; + } + + if ($t4Total > 0) { + $t4Stmt = $database->dblink->prepare( + "UPDATE " . TB_PREFIX . "hero SET + `attack` = `attack` + ?, + `defence` = `defence` + ?, + `attackbonus` = `attackbonus` + ?, + `defencebonus` = `defencebonus` + ?, + `regeneration` = `regeneration` + ?, + `points` = `points` - ? + WHERE `heroid` = ? + AND `points` >= ? + AND `attack` + ? <= 100 + AND `defence` + ? <= 100 + AND `attackbonus` + ? <= 100 + AND `defencebonus` + ? <= 100 + AND `regeneration` + ? <= 100" + ); + + if ($t4Stmt) { + $t4HeroId = (int) $hero_info['heroid']; + + $t4Stmt->bind_param( + 'iiiiiiiiiiiii', + $t4Alloc['attack'], $t4Alloc['defence'], $t4Alloc['attackbonus'], + $t4Alloc['defencebonus'], $t4Alloc['regeneration'], + $t4Total, $t4HeroId, $t4Total, + $t4Alloc['attack'], $t4Alloc['defence'], $t4Alloc['attackbonus'], + $t4Alloc['defencebonus'], $t4Alloc['regeneration'] + ); + + $t4Stmt->execute(); + $t4Stmt->close(); + } + } + + header("Location: build.php?id=" . $id); + exit; +} + if (isset($_GET['add'])) { $action = $_GET['add']; diff --git a/Templates/Build/37_items.tpl b/Templates/Build/37_items.tpl index 3d1c701d..caca4a8a 100644 --- a/Templates/Build/37_items.tpl +++ b/Templates/Build/37_items.tpl @@ -21,6 +21,27 @@ $t4Msg = ''; +// Eroul poate schimba echipamentul doar cat timp e in sat (ca in Travian +// original). Motivul e calculat o singura data si folosit atat pentru mesaj, +// cat si pentru ascunderea butoanelor. Regula e impusa si in HeroItems, deci +// un POST trimis manual nu o poate ocoli. +$t4AwayReason = $t4HeroItems->heroAwayReason($session->uid); + +$t4AwayText = ''; +if ($t4AwayReason === 'nohero') { + $t4AwayText = defined('HERO_LOCKED_NOHERO') ? HERO_LOCKED_NOHERO + : 'You have no hero yet. Train one in the Hero\'s Mansion before equipping items.'; +} elseif ($t4AwayReason === 'adventure') { + $t4AwayText = defined('HERO_LOCKED_ADVENTURE') ? HERO_LOCKED_ADVENTURE + : 'Your hero is on an adventure. Equipment can only be changed while the hero is in a village.'; +} elseif ($t4AwayReason === 'attack') { + $t4AwayText = defined('HERO_LOCKED_ATTACK') ? HERO_LOCKED_ATTACK + : 'Your hero is on the move with the army. Equipment can only be changed while the hero is in a village.'; +} elseif ($t4AwayReason === 'reinforcement') { + $t4AwayText = defined('HERO_LOCKED_REINFORCEMENT') ? HERO_LOCKED_REINFORCEMENT + : 'Your hero is reinforcing another village. Equipment can only be changed while the hero is in a village.'; +} + if (isset($_POST['t4action'], $_POST['rowid'])) { $t4RowId = (int) $_POST['rowid']; @@ -53,6 +74,11 @@ $t4Inventory = $t4HeroItems->getInventory($session->uid); $t4Equipped = $t4HeroItems->getEquipped($session->uid); ?> + +

+ +

+

@@ -69,11 +95,15 @@ $t4Equipped = $t4HeroItems->getEquipped($session->uid); +
+ + + - @@ -110,9 +140,11 @@ $t4Equipped = $t4HeroItems->getEquipped($session->uid); - + + + diff --git a/Templates/Build/37_t4nav.tpl b/Templates/Build/37_t4nav.tpl index 4666a800..9baa2db4 100644 --- a/Templates/Build/37_t4nav.tpl +++ b/Templates/Build/37_t4nav.tpl @@ -24,6 +24,9 @@ $t4HeroItems = new HeroItems(); $t4Silver = $t4HeroItems->getSilver($session->uid); $t4Tabs = [ 'hero' => ['label' => HERO_T4_TAB_HERO, 'url' => 'build.php?id=' . $id], + // Oaze: pagina foloseste parametrul "land", nu "t4tab" (flux mai vechi). + 'land' => ['label' => defined('HERO_T4_TAB_OASIS') ? HERO_T4_TAB_OASIS : 'Oasis', + 'url' => 'build.php?id=' . $id . '&land'], 'items' => ['label' => HERO_T4_TAB_ITEMS, 'url' => 'build.php?id=' . $id . '&t4tab=items'], 'adventures' => ['label' => HERO_T4_TAB_ADVENTURES, 'url' => 'build.php?id=' . $id . '&t4tab=adventures'], 'auction' => ['label' => HERO_T4_TAB_AUCTION, 'url' => 'build.php?id=' . $id . '&t4tab=auction'], diff --git a/install/data/constant_format.tpl b/install/data/constant_format.tpl index 8603927a..d236c4ef 100644 --- a/install/data/constant_format.tpl +++ b/install/data/constant_format.tpl @@ -54,6 +54,16 @@ define('CLEANUP_MESSAGES_DAYS', %CLEANUPMESSAGES%); define('CLEANUP_INTERVAL', 3600); define('CLEANUP_BATCH', 5000); +////////////////////////////////// +// ***** EROU *****// +////////////////////////////////// +// Regenerarea de BAZA a vietii eroului, in HP pe zi, independenta de punctele +// puse in atributul de regenerare (ca in Travian T4). Fara ea, un erou cu 0 +// puncte in regenerare nu si-ar reface niciodata viata si ar muri inevitabil +// dupa destule aventuri. Se scaleaza cu viteza serverului, ca si regenerarea +// din atribute. 0 = dezactivata (comportamentul vechi). +define('HERO_BASE_REGEN', %HEROBASEREGEN%); + ////////////////////////////////// // ***** SERVER SETTINGS *****// ////////////////////////////////// diff --git a/install/process.php b/install/process.php index 9e87c8e6..8215dc5c 100644 --- a/install/process.php +++ b/install/process.php @@ -147,6 +147,14 @@ class Process { $findReplace[$placeholder] = $value; } + + // Regenerarea de baza a vietii eroului (HP pe zi). Vezi HERO_BASE_REGEN. + $heroRegen = isset($_POST['hero_base_regen']) ? (int) $_POST['hero_base_regen'] : 10; + + if ($heroRegen < 0) { $heroRegen = 0; } + if ($heroRegen > 100) { $heroRegen = 100; } + + $findReplace["%HEROBASEREGEN%"] = $heroRegen; $findReplace["%DOMAIN%"] = $_POST['domain']; $findReplace["%HOMEPAGE%"] = $_POST['homepage']; $findReplace["%SERVER%"] = $_POST['server']; diff --git a/install/templates/config.tpl b/install/templates/config.tpl index b1f20bf0..1a5ccbee 100644 --- a/install/templates/config.tpl +++ b/install/templates/config.tpl @@ -309,6 +309,7 @@ foreach($mechs as $k => $l){
+
Automation runs from cron.php instead of players' page loads. One cron invocation keeps working for