diff --git a/GameEngine/HeroItems.php b/GameEngine/HeroItems.php
index f087cc40..7c9d05e5 100644
--- a/GameEngine/HeroItems.php
+++ b/GameEngine/HeroItems.php
@@ -515,6 +515,95 @@ class HeroItems
return self::$awayCache[$uid] = $reason;
}
+ /** @var array cache per request: uid => detaliile deplasarii */
+ private static $locationCache = array();
+
+ /**
+ * Acelasi motiv ca heroAwayReason(), plus DESTINATIA si TIMPUL RAMAS.
+ *
+ * Intoarce mereu un array:
+ * 'reason' => '' | 'nohero' | 'adventure' | 'attack' | 'reinforcement'
+ * 'target' => wref-ul catre care se indreapta (0 daca nu se stie)
+ * 'endtime' => timestamp-ul sosirii (0 = nu e in miscare, ex. intarire
+ * deja ajunsa, care sta pe loc pana e retrasa)
+ * 'sort' => sort_type-ul miscarii (3 = dus, 4 = intoarcere), 0 daca nu e cazul
+ *
+ * Costul: query-ul de detalii se face DOAR daca eroul chiar e plecat, deci
+ * cazul obisnuit (erou acasa) ramane exact cat era inainte - un query.
+ */
+ public function heroLocation($uid)
+ {
+ $uid = (int) $uid;
+
+ if (isset(self::$locationCache[$uid])) {
+ return self::$locationCache[$uid];
+ }
+
+ $info = array('reason' => $this->heroAwayReason($uid), 'target' => 0, 'endtime' => 0, 'sort' => 0);
+
+ if ($info['reason'] === '' || $info['reason'] === 'nohero') {
+ return self::$locationCache[$uid] = $info;
+ }
+
+ $p = TB_PREFIX;
+ $q = '';
+
+ if ($info['reason'] === 'adventure') {
+
+ // LEFT JOIN, nu INNER: daca randul de miscare a fost deja procesat,
+ // vrem macar destinatia, nu sa pierdem tot randul.
+ $q = "SELECT a.wref AS target, IFNULL(m.endtime, 0) AS endtime, IFNULL(m.sort_type, 0) AS sort_type
+ FROM {$p}hero_adventure a
+ LEFT JOIN {$p}movement m ON m.moveid = a.moveid
+ WHERE a.uid = ? AND a.status = 1
+ LIMIT 1";
+
+ } elseif ($info['reason'] === 'attack') {
+
+ // La dus (sort_type 3) satul jucatorului e `from`, la intoarcere
+ // (4) e `to`; IF() alege coloana corecta pentru verificarea
+ // proprietarului. Destinatia afisata e mereu `to`.
+ $q = "SELECT m.`to` AS target, m.endtime, m.sort_type
+ FROM {$p}movement m
+ INNER JOIN {$p}attacks a ON a.id = m.ref
+ INNER JOIN {$p}vdata v ON v.wref = IF(m.sort_type = 4, m.`to`, m.`from`)
+ WHERE v.owner = ? AND m.proc = 0 AND m.sort_type IN (3, 4) AND a.t11 > 0
+ ORDER BY m.endtime ASC
+ LIMIT 1";
+
+ } else {
+
+ // Intarirea a ajuns deja: nu exista miscare, deci nici timp ramas.
+ $q = "SELECT e.vref AS target, 0 AS endtime, 0 AS sort_type
+ FROM {$p}enforcement e
+ INNER JOIN {$p}vdata v ON v.wref = e.`from`
+ WHERE v.owner = ? AND e.hero > 0
+ LIMIT 1";
+ }
+
+ $stmt = $this->db->prepare($q);
+
+ if (!$stmt) {
+ // instalarile vechi pot sa nu aiba toate tabelele - motivul ramane
+ // valid, doar detaliile lipsesc
+ return self::$locationCache[$uid] = $info;
+ }
+
+ $stmt->bind_param('i', $uid);
+ $stmt->execute();
+ $stmt->bind_result($target, $endtime, $sortType);
+
+ if ($stmt->fetch()) {
+ $info['target'] = (int) $target;
+ $info['endtime'] = (int) $endtime;
+ $info['sort'] = (int) $sortType;
+ }
+
+ $stmt->close();
+
+ return self::$locationCache[$uid] = $info;
+ }
+
public function equipItem($uid, $rowId)
{
global $database;
diff --git a/GameEngine/Lang/en.php b/GameEngine/Lang/en.php
index 46c54b26..b56aaa50 100755
--- a/GameEngine/Lang/en.php
+++ b/GameEngine/Lang/en.php
@@ -4366,3 +4366,12 @@ tz_def('ALLYBONUS_MSG_OVERMAX', 'This is the final level: you can donate at most
//////////////////////////////////////////////////////////////////////////////////////////////////////
tz_def('ADM_PROTECTED_PLAYERS', 'Protected players');
tz_def('ADM_PROTECTED_PLAYERS_TIP', 'Comma-separated player names that cannot be attacked or raided by anyone. Reinforcements are still allowed. Leave empty to disable.');
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// COMPONENTA HERO DIN BARA DE SUS
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+tz_def('HERO_HEADER_AT_HOME', 'Hero at home');
+tz_def('HERO_HEADER_DEAD', 'Hero is dead');
+tz_def('HERO_HEADER_TRAINING', 'Hero in training');
+tz_def('HERO_HEADER_NOHERO', 'No hero yet');
+tz_def('TZ_HEALTH', 'Health');
diff --git a/GameEngine/Lang/fr.php b/GameEngine/Lang/fr.php
index c3e27470..f78331e0 100644
--- a/GameEngine/Lang/fr.php
+++ b/GameEngine/Lang/fr.php
@@ -4309,3 +4309,12 @@ tz_def('ALLYBONUS_MSG_OVERMAX', 'Ceci est le dernier niveau : vous pouvez donner
//////////////////////////////////////////////////////////////////////////////////////////////////////
tz_def('ADM_PROTECTED_PLAYERS', 'Joueurs proteges');
tz_def('ADM_PROTECTED_PLAYERS_TIP', 'Noms de joueurs separes par des virgules qui ne peuvent etre ni attaques ni pilles. Les renforts restent autorises. Laisser vide pour desactiver.');
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// COMPONENTA HERO DIN BARA DE SUS
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+tz_def('HERO_HEADER_AT_HOME', 'Le heros est chez lui');
+tz_def('HERO_HEADER_DEAD', 'Le heros est mort');
+tz_def('HERO_HEADER_TRAINING', 'Heros en formation');
+tz_def('HERO_HEADER_NOHERO', 'Pas encore de heros');
+tz_def('TZ_HEALTH', 'Sante');
diff --git a/GameEngine/Lang/ro.php b/GameEngine/Lang/ro.php
index 35e264d7..8b86074b 100644
--- a/GameEngine/Lang/ro.php
+++ b/GameEngine/Lang/ro.php
@@ -4106,3 +4106,12 @@ tz_def('ALLYBONUS_MSG_OVERMAX', 'Acesta e ultimul nivel: poti dona cel mult %s r
//////////////////////////////////////////////////////////////////////////////////////////////////////
tz_def('ADM_PROTECTED_PLAYERS', 'Jucatori protejati');
tz_def('ADM_PROTECTED_PLAYERS_TIP', 'Nume de jucatori separate prin virgula, care nu pot fi atacati sau jefuiti de nimeni. Intaririle raman permise. Lasa gol ca sa dezactivezi.');
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+// COMPONENTA HERO DIN BARA DE SUS
+//////////////////////////////////////////////////////////////////////////////////////////////////////
+tz_def('HERO_HEADER_AT_HOME', 'Eroul este acasa');
+tz_def('HERO_HEADER_DEAD', 'Eroul este mort');
+tz_def('HERO_HEADER_TRAINING', 'Eroul este in antrenament');
+tz_def('HERO_HEADER_NOHERO', 'Inca nu ai erou');
+tz_def('TZ_HEALTH', 'Sanatate');
diff --git a/Templates/Build/37_auction.tpl b/Templates/Build/37_auction.tpl
index f8e7b2c1..f4b464ec 100644
--- a/Templates/Build/37_auction.tpl
+++ b/Templates/Build/37_auction.tpl
@@ -135,7 +135,51 @@ $t4RateS2G = HeroItems::silverForOneGold();
-
+ 'roman.png', // Romani
+ 2 => 'teuton.png', // Teutoni
+ 3 => 'gaul.png', // Gali
+ 6 => 'huns.png', // Huni
+ 7 => 'egiptean.png', // Egipteni
+ 8 => 'spartan.png', // Spartani
+ 9 => 'vikings.png', // Vikingi
+ ];
+
+ $t4Tribe = 0;
+
+ if (isset($session->tribe)) {
+ $t4Tribe = (int) $session->tribe;
+ } elseif (isset($session->userinfo['tribe'])) {
+ $t4Tribe = (int) $session->userinfo['tribe'];
+ }
+
+ $t4MerchantFile = isset($t4MerchantByTribe[$t4Tribe])
+ ? $t4MerchantByTribe[$t4Tribe]
+ : 'merchant.png';
+
+ // Daca fisierul tribului inca n-a fost urcat pe server, nu
+ // lasam un patrat gol - revenim la negustorul generic.
+ if (!@file_exists('img/hero/' . $t4MerchantFile)) {
+ $t4MerchantFile = 'merchant.png';
+ }
+ ?>
+
diff --git a/Templates/header.tpl b/Templates/header.tpl
index a950317c..11b622e8 100644
--- a/Templates/header.tpl
+++ b/Templates/header.tpl
@@ -170,7 +170,31 @@ if ($hour > 1759 || $hour < 500) {
-
+
+
+
+
+
1759 || $hour < 500) {
display: inline-block;
}
- /* GOLD IN CERCUL VERDE */
+ /* GOLD IN DREPTUNGHIUL ALBASTRU (RELOCARE_GOLD_LOCATIE_EROU.png)
+ Vechea pozitie era left:370px, adica exact cercul rosu - acolo sta
+ acum componenta Hero (#tzHeroBox), asa ca aurul s-a mutat la dreapta,
+ dupa butonul Plus si iconita day/night. */
#goldHeader {
position: absolute;
- left: 370px; /* << AICI ESTE CERCUL VERDE - muta 370-400 daca e nevoie */
+ left: 630px; /* << DREPTUNGHIUL ALBASTRU - muta 620-680 daca e nevoie */
/* Centrare pe verticala in bara gri: ancoram la mijloc si compensam
inaltimea proprie. Asa ramane centrat si acum, cand sunt doua
randuri (aur + argint), nu impins spre partea de jos. */
diff --git a/Templates/hero_header.tpl b/Templates/hero_header.tpl
new file mode 100644
index 00000000..b5a04fe8
--- /dev/null
+++ b/Templates/hero_header.tpl
@@ -0,0 +1,573 @@
+ 37.tpl ##
+## (craniu, cand eroul e mort -> tot 37.tpl) ##
+## - centru : portretul eroului + inelul Health(rosu)/Experience(albastru)
+## - dreapta sus : numarul de aventuri -> 37_adventures.tpl ##
+## - dreapta jos : argintul -> 37_auction.tpl ##
+## ##
+## Toate valorile sunt reale (nimic hardcodat). Inelul e SVG desenat din ##
+## procentele efective, NU o imagine statica. ##
+## ##
+#################################################################################
+
+/**
+ * Gard 1: fara sesiune de jucator real nu afisam nimic.
+ * $isRestrictedUser vine din header.tpl (contul special cu id 1).
+ */
+if (!isset($session) || !is_object($session)) {
+ return;
+}
+
+if (isset($isRestrictedUser) && $isRestrictedUser) {
+ return;
+}
+
+if (!isset($database) || !is_object($database)) {
+ return;
+}
+
+$tzHeroUid = (int) $session->uid;
+
+if ($tzHeroUid <= 0) {
+ return;
+}
+
+/**
+ * Functiile T4 de erou (aventuri / licitatii / argint) sunt optionale.
+ * Cand steagul e stins, ramane doar cercul cu erou + casa, fiindca
+ * Adventures si Silver n-ar avea unde sa duca.
+ */
+$tzHeroT4 = defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4;
+
+/**
+ * ---------------------------------------------------------------------------
+ * 1. Randul eroului
+ * ---------------------------------------------------------------------------
+ * getHero($uid, 1) intoarce TOTI eroii, ordonati dupa lastupdate DESC.
+ * Alegem, ca in Units::Hero(), primul erou viu si care nu e in antrenament;
+ * daca nu exista, luam primul rand (cel mai recent folosit) - asa prindem
+ * si eroul mort, de care avem nevoie pentru starea DEAD.
+ */
+$tzHeroRow = false;
+$tzHeroRows = $database->getHero($tzHeroUid, 1);
+
+if (is_array($tzHeroRows) && count($tzHeroRows)) {
+
+ foreach ($tzHeroRows as $tzRow) {
+
+ if ((int) $tzRow['dead'] !== 1 && (int) $tzRow['intraining'] !== 1) {
+ $tzHeroRow = $tzRow;
+ break;
+ }
+ }
+
+ if (!$tzHeroRow) {
+ $tzHeroRow = $tzHeroRows[0];
+ }
+}
+
+$tzHeroExists = is_array($tzHeroRow);
+$tzHeroDead = $tzHeroExists ? ((int) $tzHeroRow['dead'] === 1) : false;
+$tzHeroTraining = $tzHeroExists ? ((int) $tzHeroRow['intraining'] === 1) : false;
+$tzHeroName = $tzHeroExists ? (string) $tzHeroRow['name'] : '';
+$tzHeroLevel = $tzHeroExists ? (int) $tzHeroRow['level'] : 0;
+$tzHeroUnit = $tzHeroExists ? (int) $tzHeroRow['unit'] : 0;
+$tzHeroWref = $tzHeroExists ? (int) $tzHeroRow['wref'] : 0;
+
+/**
+ * health e float(12,9) in baza de date. Il rotunjim doar la afisare.
+ */
+$tzHeroHealth = $tzHeroExists ? (float) $tzHeroRow['health'] : 0;
+$tzHeroHealth = max(0, min(100, $tzHeroHealth));
+
+if ($tzHeroDead) {
+ $tzHeroHealth = 0;
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 2. Procentul de Experience catre nivelul urmator
+ * ---------------------------------------------------------------------------
+ * Aceeasi formula ca in Templates/Build/37_hero.tpl, ca sa nu existe doua
+ * adevaruri diferite in interfata. $hero_levels vine din
+ * GameEngine/Data/hero_full.php, incarcat de Session.php pe fiecare pagina.
+ */
+$tzHeroLevels = (isset($GLOBALS['hero_levels']) && is_array($GLOBALS['hero_levels']) && count($GLOBALS['hero_levels']) > 1)
+ ? $GLOBALS['hero_levels']
+ : array(0 => 0, 1 => 100);
+
+$tzMaxLevel = max(array_keys($tzHeroLevels)) - 1;
+$tzMaxExp = isset($tzHeroLevels[$tzMaxLevel]) ? (int) $tzHeroLevels[$tzMaxLevel] : 0;
+
+$tzCurExp = $tzHeroExists ? (int) $tzHeroRow['experience'] : 0;
+$tzExpCur = isset($tzHeroLevels[$tzHeroLevel]) ? (int) $tzHeroLevels[$tzHeroLevel] : 0;
+$tzExpNext = isset($tzHeroLevels[$tzHeroLevel + 1]) ? (int) $tzHeroLevels[$tzHeroLevel + 1] : $tzMaxExp;
+
+if ($tzHeroExists && $tzCurExp < $tzMaxExp && $tzExpNext > $tzExpCur && $tzHeroLevel < $tzMaxLevel) {
+ $tzHeroExpPct = ($tzCurExp - $tzExpCur) / ($tzExpNext - $tzExpCur) * 100;
+ $tzHeroExpPct = max(0, min(100, $tzHeroExpPct));
+} else {
+ $tzHeroExpPct = $tzHeroExists ? 100 : 0;
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 3. Unde se afla eroul acum
+ * ---------------------------------------------------------------------------
+ * Refolosim HeroItems::heroAwayReason(), aceeasi sursa de adevar ca pagina de
+ * echipare: un singur query cu subinterogari separate, care intoarce
+ * '' (acasa) | 'nohero' | 'adventure' | 'attack' | 'reinforcement'.
+ * - 'adventure' : hero_adventure cu status = 1
+ * - 'attack' : movement sort_type 3 (dus) sau 4 (intors) cu attacks.t11
+ * - 'reinforcement' : enforcement.hero pornit dintr-un sat al jucatorului
+ * Metoda are cache static pe uid, deci daca o mai cheama cineva pe aceeasi
+ * pagina nu se duce a doua oara la baza de date.
+ */
+$tzHeroAway = '';
+$tzHeroTarget = 0;
+$tzHeroEndtime = 0;
+$tzHeroSort = 0;
+
+if ($tzHeroExists && !$tzHeroDead && !$tzHeroTraining && class_exists('HeroItems')) {
+
+ $tzAwayObject = new HeroItems();
+
+ if (method_exists($tzAwayObject, 'heroLocation')) {
+
+ $tzLoc = $tzAwayObject->heroLocation($tzHeroUid);
+ $tzHeroAway = (string) $tzLoc['reason'];
+ $tzHeroTarget = (int) $tzLoc['target'];
+ $tzHeroEndtime = (int) $tzLoc['endtime'];
+ $tzHeroSort = (int) $tzLoc['sort'];
+
+ } else {
+
+ // rezerva, daca ajunge pe un server cu HeroItems.php vechi
+ $tzHeroAway = (string) $tzAwayObject->heroAwayReason($tzHeroUid);
+ }
+}
+
+// 'nohero' nu ne intereseaza aici - stim deja ca eroul exista
+if ($tzHeroAway === 'nohero') {
+ $tzHeroAway = '';
+}
+
+/**
+ * Coordonatele destinatiei si secundele ramase pana la sosire.
+ * La intoarcere (sort_type 4) destinatia e chiar satul jucatorului.
+ */
+$tzHeroTargetCoor = '';
+
+if ($tzHeroTarget > 0) {
+
+ $tzCoor = $database->getCoor($tzHeroTarget);
+
+ if (is_array($tzCoor) && isset($tzCoor['x'], $tzCoor['y'])) {
+ $tzHeroTargetCoor = '(' . (int) $tzCoor['x'] . '|' . (int) $tzCoor['y'] . ')';
+ }
+}
+
+$tzHeroSecondsLeft = ($tzHeroEndtime > 0) ? max(0, $tzHeroEndtime - time()) : 0;
+
+/**
+ * Starea unica a componentei, din care ies si iconita, si tooltip-ul.
+ * Ordinea conteaza: mort > inexistent > in antrenament > plecat > acasa.
+ */
+if (!$tzHeroExists) {
+ $tzHeroState = 'nohero';
+} elseif ($tzHeroDead) {
+ $tzHeroState = 'dead';
+} elseif ($tzHeroTraining) {
+ $tzHeroState = 'training';
+} elseif ($tzHeroAway !== '') {
+ $tzHeroState = $tzHeroAway;
+} else {
+ $tzHeroState = 'home';
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 4. Satul eroului
+ * ---------------------------------------------------------------------------
+ */
+$tzHeroVillage = '';
+
+if ($tzHeroWref > 0) {
+ $tzHeroVillage = (string) $database->getVillageField($tzHeroWref, 'name');
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 5. Aventuri disponibile
+ * ---------------------------------------------------------------------------
+ * Un erou mort nu poate pleca in aventura, deci afisam 0 (cerinta 7),
+ * dar zona ramane clickabila.
+ */
+$tzHeroAdv = 0;
+
+if ($tzHeroT4 && !$tzHeroDead && class_exists('HeroAdventure')) {
+
+ $tzAdvObject = new HeroAdventure();
+ $tzAdvOffers = $tzAdvObject->getOffers($tzHeroUid);
+ $tzHeroAdv = is_array($tzAdvOffers) ? count($tzAdvOffers) : 0;
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 6. Argintul
+ * ---------------------------------------------------------------------------
+ * Refolosim exact acelasi cache ca blocul de aur din header.tpl
+ * ($GLOBALS['t4SilverValue']), ca sa nu iasa doua interogari pe pagina.
+ */
+$tzHeroSilver = 0;
+
+if ($tzHeroT4 && class_exists('HeroItems')) {
+
+ if (!isset($GLOBALS['t4SilverValue'])) {
+ $tzSilverObject = new HeroItems();
+ $GLOBALS['t4SilverValue'] = (int) $tzSilverObject->getSilver($tzHeroUid);
+ }
+
+ $tzHeroSilver = (int) $GLOBALS['t4SilverValue'];
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 7. Link-urile catre Resedinta Eroului
+ * ---------------------------------------------------------------------------
+ * 37.tpl / 37_adventures.tpl / 37_auction.tpl nu sunt URL-uri: se ajunge
+ * la ele prin build.php pe campul unde e cladirea 37, cu parametrul t4tab.
+ *
+ * getTypeField() cauta DOAR in satul curent. Daca jucatorul se uita la un
+ * sat fara Resedinta Eroului, trimitem catre satul unde sta eroul, cu
+ * newdid + gid=37 (build.php pastreaza t4tab peste redirect - vezi patch-ul
+ * din build.php livrat impreuna cu acest fisier).
+ */
+$tzHeroField = 0;
+
+if (isset($building) && is_object($building) && method_exists($building, 'getTypeField')) {
+ $tzHeroField = (int) $building->getTypeField(37);
+}
+
+if ($tzHeroField > 0) {
+ $tzHeroBase = 'build.php?id=' . $tzHeroField;
+} elseif ($tzHeroWref > 0) {
+ $tzHeroBase = 'build.php?newdid=' . $tzHeroWref . '&gid=37';
+} else {
+ $tzHeroBase = 'build.php?gid=37';
+}
+
+$tzLinkHero = $tzHeroBase;
+$tzLinkAdv = $tzHeroBase . '&t4tab=adventures';
+$tzLinkAuc = $tzHeroBase . '&t4tab=auction';
+
+/**
+ * ---------------------------------------------------------------------------
+ * 8. Portretul eroului
+ * ---------------------------------------------------------------------------
+ * img/u2/uN.gif sunt portretele mari (150x120), existente pentru toate
+ * unitatile de erou, inclusiv triburile noi 6-9 (u51..u90). Daca lipseste
+ * fisierul, cadem pe iconita mica img/u/N.gif.
+ */
+$tzHeroPortrait = '';
+
+if ($tzHeroUnit > 0) {
+
+ $tzBig = GP_LOCATE . 'img/u2/u' . $tzHeroUnit . '.gif';
+ $tzSmall = GP_LOCATE . 'img/u/' . $tzHeroUnit . '.gif';
+
+ if (@file_exists($tzBig)) {
+ $tzHeroPortrait = $tzBig;
+ } elseif (@file_exists($tzSmall)) {
+ $tzHeroPortrait = $tzSmall;
+ }
+}
+
+/**
+ * ---------------------------------------------------------------------------
+ * 9. Geometria inelului
+ * ---------------------------------------------------------------------------
+ * Doua arce concentrice, in acelasi SVG: exterior = Health (rosu),
+ * interior = Experience (albastru). Lungimea vizibila se obtine cu
+ * stroke-dasharray, iar rotate(-90) porneste arcele de sus.
+ */
+$tzRingR1 = 26.5; // raza arcului de Health
+$tzRingR2 = 21; // raza arcului de Experience
+$tzRingC1 = 2 * M_PI * $tzRingR1; // circumferinte
+$tzRingC2 = 2 * M_PI * $tzRingR2;
+
+$tzDashHealth = round($tzRingC1 * $tzHeroHealth / 100, 2);
+$tzDashExp = round($tzRingC2 * $tzHeroExpPct / 100, 2);
+
+/**
+ * ---------------------------------------------------------------------------
+ * 10. Texte
+ * ---------------------------------------------------------------------------
+ * Tiparul defined()?: e cel folosit deja in 37_hero.tpl / 37_t4nav.tpl -
+ * componenta merge si pe servere care n-au inca noile constante de limba.
+ */
+$tzTxtAtHome = defined('HERO_HEADER_AT_HOME') ? HERO_HEADER_AT_HOME : 'Hero at home';
+$tzTxtDead = defined('HERO_HEADER_DEAD') ? HERO_HEADER_DEAD : 'Hero is dead';
+$tzTxtTraining = defined('HERO_HEADER_TRAINING') ? HERO_HEADER_TRAINING : 'Hero in training';
+$tzTxtNoHero = defined('HERO_HEADER_NOHERO') ? HERO_HEADER_NOHERO : 'No hero yet';
+$tzTxtAdventure = defined('HERO_HEADER_ADVENTURE') ? HERO_HEADER_ADVENTURE : 'Hero on adventure';
+$tzTxtAttack = defined('HERO_HEADER_ATTACK') ? HERO_HEADER_ATTACK : 'Hero is with the army';
+$tzTxtReinforce = defined('HERO_HEADER_REINFORCE') ? HERO_HEADER_REINFORCE : 'Hero is reinforcing';
+$tzTxtIn = defined('HERO_HEADER_IN') ? HERO_HEADER_IN : 'in';
+$tzTxtVillage = defined('VILLAGE') ? VILLAGE : 'Village';
+$tzTxtLevel = defined('LEVEL') ? LEVEL : 'Level';
+$tzTxtHealth = defined('TZ_HEALTH') ? TZ_HEALTH : 'Health';
+$tzTxtExperience = defined('EXPERIENCE') ? EXPERIENCE : 'Experience';
+$tzTxtSilver = defined('HERO_SILVER') ? HERO_SILVER : 'Silver';
+$tzTxtAdventures = defined('HERO_T4_TAB_ADVENTURES') ? HERO_T4_TAB_ADVENTURES : 'Adventures';
+
+/**
+ * Numele afisate vin din baza de date, deci trec obligatoriu prin escape.
+ */
+$tzHeroNameSafe = htmlspecialchars($tzHeroName, ENT_QUOTES, 'UTF-8');
+$tzHeroVillageSafe = htmlspecialchars($tzHeroVillage, ENT_QUOTES, 'UTF-8');
+
+?>
+
+
+
+
+ 0) { ?>
+
+
diff --git a/build.php b/build.php
index b0e270b3..4e86c247 100644
--- a/build.php
+++ b/build.php
@@ -29,7 +29,35 @@ AccessLogger::logRequest();
if(isset($_GET['newdid'])){
$_SESSION['wid'] = $_GET['newdid'];
- header("Location: " . $_SERVER['PHP_SELF'].(isset($_GET['id']) ? '?id='.$_GET['id'] : (isset($_GET['gid']) ? '?gid='.$_GET['gid'] : '')));
+
+ /**
+ * Redirectul pastra doar id/gid, deci un link de forma
+ * "build.php?newdid=X&gid=37&t4tab=adventures" (folosit de componenta
+ * Hero din bara de sus, cand eroul e in alt sat decat cel privit)
+ * ajungea pe tab-ul implicit al Resedintei Eroului, nu pe Aventuri.
+ * Pastram acum si t4tab (lista alba) si parametrul "land" al oazelor.
+ *
+ * Bonus: id/gid trec prin acelasi filtru ca mai jos inainte sa intre in
+ * antetul Location - inainte se concatena valoarea bruta din $_GET.
+ */
+ $tzRedirect = '';
+
+ if (isset($_GET['id'])) {
+ $tzRedirect = '?id=' . preg_replace("/[^a-zA-Z0-9_-]/", "", $_GET['id']);
+ } else if (isset($_GET['gid'])) {
+ $tzRedirect = '?gid=' . preg_replace("/[^a-zA-Z0-9_-]/", "", $_GET['gid']);
+ }
+
+ if ($tzRedirect !== '') {
+
+ if (isset($_GET['t4tab']) && in_array($_GET['t4tab'], ['items', 'adventures', 'auction'], true)) {
+ $tzRedirect .= '&t4tab=' . $_GET['t4tab'];
+ } else if (isset($_GET['land'])) {
+ $tzRedirect .= '&land';
+ }
+ }
+
+ header("Location: " . $_SERVER['PHP_SELF'] . $tzRedirect);
exit;
}
if(isset($_GET['id']) && ($_GET['id'] < 1 || $_GET['id'] > 40 && ($_GET['id'] == 99 && $village->natar == 0 || $_GET['id'] != 99))){
diff --git a/css/hero_header.css b/css/hero_header.css
new file mode 100644
index 00000000..dd5507ef
--- /dev/null
+++ b/css/hero_header.css
@@ -0,0 +1,273 @@
+/*
+ * ###########################################################################
+ * hero_header.css - componenta Hero din bara de sus (TravianZ)
+ * Proiect : TravianZ - https://github.com/Shadowss/TravianZ
+ * Copyright: TravianZ (c) 2010-2026. All rights reserved.
+ * ###########################################################################
+ *
+ * Elementele sunt piese UI separate (casa, inel, badge aventuri, argint),
+ * asamblate cu pozitionare absoluta in interiorul lui #tzHeroBox - NU o
+ * imagine mare unica.
+ *
+ * REGLAJ POZITIE: singurul lucru de atins cand vrei sa muti componenta e
+ * "left" din #tzHeroBox (coordonate relative la #mtop, care e position:relative).
+ */
+
+/* =========================================================================
+ Ordinea de desenare a barii de sus
+ =========================================================================
+ #mtop are z-index:2 in graphic pack si creeaza un CONTEXT DE STIVUIRE,
+ deci z-index-ul tooltip-urilor nu poate depasi bara insasi. Randul de
+ resurse (div#res) e tot pe z-index 2/3 SI e inclus la finalul paginii
+ (dupa footer), asa ca la valoare egala castiga ordinea din DOM si trecea
+ peste tooltip-uri.
+ Ridicam bara la 4: bate #res, dar ramane mult sub popup-uri (50+),
+ harta (20+), chat (999) si ferestrele de ghid (9999). */
+
+div#mtop {
+ z-index: 4;
+}
+
+/* =========================================================================
+ Cadrul componentei
+ ========================================================================= */
+
+#tzHeroBox {
+ position: absolute;
+ left: 344px; /* << ZONA INCERCUITA CU ROSU. Muta 330-370 daca e nevoie */
+ top: 50%;
+ margin-top: -36px; /* jumatate din inaltime = centrare pe verticala */
+ width: 124px;
+ height: 72px;
+ z-index: 60;
+ font-size: 11px;
+ line-height: normal;
+}
+
+#tzHeroBox a,
+#tzHeroBox a:hover {
+ text-decoration: none;
+ border: 0;
+ outline: 0;
+}
+
+.tzHeroSlot {
+ position: absolute;
+ display: block;
+}
+
+/* =========================================================================
+ Centru: inelul Health / Experience + portretul
+ ========================================================================= */
+
+/* Diametrul inelului = 64px, cat iconitele de navigatie din stanga barii */
+.tzHeroCenter {
+ left: 30px;
+ top: 4px;
+ width: 64px;
+ height: 64px;
+}
+
+.tzHeroRing {
+ display: block;
+ width: 64px;
+ height: 64px;
+}
+
+.tzHeroRingBg {
+ fill: #f2f1ee;
+ stroke: #c6c4bf;
+ stroke-width: 1;
+}
+
+.tzHeroTrack {
+ fill: none;
+ stroke: #dedcd7;
+ stroke-width: 4;
+}
+
+/* ROSU = procentul de Health al eroului */
+.tzHeroArcHealth {
+ fill: none;
+ stroke: #c0392b;
+ stroke-width: 4;
+ stroke-linecap: butt;
+}
+
+/* ALBASTRU = progresul Experience catre nivelul urmator */
+.tzHeroArcExp {
+ fill: none;
+ stroke: #2f6fb0;
+ stroke-width: 4;
+ stroke-linecap: butt;
+}
+
+.tzHeroInner {
+ fill: #ffffff;
+ stroke: #d6d4cf;
+ stroke-width: 1;
+}
+
+/* ATENTIE: portretul e copil al lui .tzHeroCenter, care e position:absolute,
+ deci coordonatele sunt relative la INEL (64x64), nu la #tzHeroBox.
+ Centrare = (64 - 32) / 2 pe ambele axe. */
+.tzHeroPortrait {
+ position: absolute;
+ left: 16px;
+ top: 16px;
+ width: 32px;
+ height: 32px;
+ object-fit: contain;
+}
+
+.tzHeroPortraitEmpty {
+ fill: #b9b7b2;
+}
+
+/* Eroul mort: portretul se decoloreaza, dar linkul ramane activ */
+#tzHeroBox.tzHeroDeadState .tzHeroPortrait {
+ filter: grayscale(100%);
+ opacity: .45;
+}
+
+/* =========================================================================
+ Stanga sus: casa (Hero at home) / craniu (Hero is dead)
+ ========================================================================= */
+
+.tzHeroHome {
+ left: 10px;
+ top: 0;
+ width: 24px;
+ height: 24px;
+}
+
+.tzHeroIco {
+ display: block;
+ width: 24px;
+ height: 24px;
+}
+
+.tzHeroIcoBg {
+ fill: #f6f5f2;
+ stroke: #b8b6b1;
+ stroke-width: 1;
+}
+
+.tzHeroRoof { fill: #b8452f; }
+.tzHeroWall { fill: #f0e6cf; }
+.tzHeroDoor { fill: #8a6a3f; }
+
+.tzHeroSkull { fill: #e9e7e2; stroke: #7d7b76; stroke-width: .8; }
+.tzHeroSkullEye { fill: #3a3835; }
+
+/* Starile "plecat": aventura (cizma), atac (sabii), intarire (scut) */
+.tzHeroBoot { fill: #8a6a3f; stroke: #5f482a; stroke-width: .8; }
+.tzHeroBootSole { fill: #4a3a24; }
+.tzHeroBootCuff { fill: #b08a52; }
+
+.tzHeroBlade { fill: none; stroke: #9a9894; stroke-width: 2.2; stroke-linecap: round; }
+.tzHeroHilt { fill: none; stroke: #8a6a3f; stroke-width: 2.2; stroke-linecap: round; }
+
+.tzHeroShield { fill: #c9d4e2; stroke: #5d7391; stroke-width: .9; }
+.tzHeroShieldDark { fill: #9fb2c9; stroke: none; }
+
+/* Teancul de monede din iconita de argint */
+.tzHeroCoin { fill: #d3d2ce; stroke: #8f8d88; stroke-width: .8; }
+.tzHeroCoinTop { fill: #efeeeb; stroke: none; }
+
+/* =========================================================================
+ Dreapta sus: numarul de aventuri
+ ========================================================================= */
+
+.tzHeroAdv {
+ left: 90px;
+ top: 0;
+ width: 23px;
+ height: 23px;
+ border-radius: 50%;
+ text-align: center;
+ border: 1px solid #8d8b86;
+ box-sizing: border-box;
+}
+
+.tzHeroAdvOn { background: #5aa02c; border-color: #3f7a17; }
+.tzHeroAdvOff { background: #e6e4df; border-color: #b3b1ac; }
+
+.tzHeroAdvNum {
+ display: block;
+ line-height: 21px;
+ font-size: 12px;
+ font-weight: bold;
+}
+
+.tzHeroAdvOn .tzHeroAdvNum { color: #ffffff; }
+.tzHeroAdvOff .tzHeroAdvNum { color: #6b6964; }
+
+/* =========================================================================
+ Dreapta jos: argintul / casa de licitatii
+ ========================================================================= */
+
+.tzHeroSilver {
+ left: 90px;
+ top: 45px;
+ width: 24px;
+ height: 24px;
+}
+
+/* =========================================================================
+ Tooltip-uri (numai la mouseover, fara JS)
+ ========================================================================= */
+
+.tzHeroTip {
+ display: none;
+ position: absolute;
+ z-index: 500;
+ padding: 5px 9px;
+ background-color: #3f3d39;
+ background-image: none;
+ color: #ffffff;
+ border: 1px solid #23211f;
+ border-radius: 3px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, .45);
+ opacity: 1;
+ white-space: nowrap;
+ font-size: 11px;
+ font-weight: normal;
+ text-align: left;
+ line-height: 15px;
+}
+
+.tzHeroSlot:hover .tzHeroTip {
+ display: block;
+}
+
+.tzHeroTip b {
+ display: block;
+ color: #ffffff;
+ font-weight: bold;
+}
+
+.tzHeroTip i {
+ display: block;
+ color: #d9d7d2;
+ font-style: normal;
+}
+
+.tzHeroTip .tzHeroTipWarn {
+ color: #f0a49a;
+}
+
+.tzHeroTip .tzHeroTipTime {
+ color: #ffffff;
+}
+
+/* latime fixa pe cifre: ceasul nu mai tresare la fiecare secunda */
+.tzHeroTimer {
+ font-variant-numeric: tabular-nums;
+}
+
+/* Pozitiile din imaginile de referinta */
+.tzHeroTipHome { left: 28px; top: 4px; }
+.tzHeroTipCenter { left: 58px; top: 44px; }
+.tzHeroTipAdv { left: 29px; top: 4px; }
+.tzHeroTipSilver { left: 29px; top: 2px; }
diff --git a/img/hero/egiptean.png b/img/hero/egiptean.png
new file mode 100644
index 00000000..165428ff
Binary files /dev/null and b/img/hero/egiptean.png differ
diff --git a/img/hero/gaul.png b/img/hero/gaul.png
new file mode 100644
index 00000000..d231f958
Binary files /dev/null and b/img/hero/gaul.png differ
diff --git a/img/hero/huns.png b/img/hero/huns.png
new file mode 100644
index 00000000..74a667bd
Binary files /dev/null and b/img/hero/huns.png differ
diff --git a/img/hero/roman.png b/img/hero/roman.png
new file mode 100644
index 00000000..f7f815a4
Binary files /dev/null and b/img/hero/roman.png differ
diff --git a/img/hero/spartan.png b/img/hero/spartan.png
new file mode 100644
index 00000000..ceb3cfca
Binary files /dev/null and b/img/hero/spartan.png differ
diff --git a/img/hero/teuton.png b/img/hero/teuton.png
new file mode 100644
index 00000000..d7761a84
Binary files /dev/null and b/img/hero/teuton.png differ
diff --git a/img/hero/vikings.png b/img/hero/vikings.png
new file mode 100644
index 00000000..0d8188cd
Binary files /dev/null and b/img/hero/vikings.png differ