Last fix for Hero T4 & V11

/*
|--------------------------------------------------------------------------
| 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.
|
*/
This commit is contained in:
novgorodschi catalin
2026-07-23 15:16:51 +03:00
parent 34e3d88e39
commit 1e19f117ca
19 changed files with 620 additions and 29 deletions
+167 -8
View File
@@ -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 "<a href=\"build.php?id=" . $id . "&add=" . $action . "\">(<b>+</b>)</a>";
// 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 "<a href=\"build.php?id=" . $id . "&add=" . $action . "\""
. " class=\"t4AddPoint\" data-stat=\"" . $action . "\""
. " data-base=\"" . (int) $hero_info[$field] . "\">(<b>+</b>)</a>";
}
return "<span class=\"none\">(+)</span>";
};
@@ -71,7 +75,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="up"><span class="none">
<?php echo $renderAddLink('off'); ?>
</td>
<td class="po"><?php echo $hero_info['attack']; ?></td>
<td class="po" id="t4po_off"><?php echo $hero_info['attack']; ?></td>
</tr>
<tr>
<th><?php echo DEFENCE; ?></th>
@@ -80,7 +84,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="up"><span class="none">
<?php echo $renderAddLink('deff'); ?>
</td>
<td class="po"><?php echo $hero_info['defence']; ?></td>
<td class="po" id="t4po_deff"><?php echo $hero_info['defence']; ?></td>
</tr>
<tr>
<th><?php echo OFF_BONUS; ?></th>
@@ -89,7 +93,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="up"><span class="none">
<?php echo $renderAddLink('obonus'); ?>
</td>
<td class="po"><?php echo $hero_info['attackbonus']; ?></td>
<td class="po" id="t4po_obonus"><?php echo $hero_info['attackbonus']; ?></td>
</tr>
<tr>
<th><?php echo DEF_BONUS; ?></th>
@@ -98,7 +102,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="up"><span class="none">
<?php echo $renderAddLink('dbonus'); ?>
</td>
<td class="po"><?php echo $hero_info['defencebonus']; ?></td>
<td class="po" id="t4po_dbonus"><?php echo $hero_info['defencebonus']; ?></td>
</tr>
<tr>
<th><?php echo REGENERATION; ?></th>
@@ -107,7 +111,7 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="up"><span class="none">
<?php echo $renderAddLink('reg'); ?>
</td>
<td class="po"><?php echo $hero_info['regeneration']; ?></td>
<td class="po" id="t4po_reg"><?php echo $hero_info['regeneration']; ?></td>
</tr>
<tr>
<td colspan="5" class="empty"></td>
@@ -130,17 +134,111 @@ $renderAddLink = function ($action) use ($hero_info, $id, $heroStatColumns) {
<td class="val"><?php echo (int)$percent; ?>%</td>
<td class="xp"><img class="bar" src="img/x.gif" style="width:<?php echo $percent*2; ?>px;" alt="<?php echo (int)$percent; ?>%" title="<?php echo (int)$percent; ?>%" /></td>
<td class="up"></td>
<td class="rem"><?php echo $hero_info['points']; ?></td>
<td class="rem" id="t4rem"><?php echo $hero_info['points']; ?></td>
<?php }else{ ?>
<th title="<?php echo TZ_UNTIL_THE_NEXT_LEVEL; ?>"><?php echo EXPERIENCE; ?>:</th>
<td class="val">100%</td>
<td class="xp"><img class="bar" src="img/x.gif" style="width:200px;" alt="100%" title="100%" /></td>
<td class="up"></td>
<td class="rem"><?php echo $hero_info['points']; ?></td>
<td class="rem" id="t4rem"><?php echo $hero_info['points']; ?></td>
<?php } ?>
</tr>
</tbody>
</table>
<?php if ((int) $hero_info['points'] > 0) { ?>
<form id="t4PointsForm" action="" method="POST" style="margin:6px 0;">
<input type="hidden" name="t4points" value="1">
<?php foreach ($heroStatColumns as $t4Key => $t4Col) { ?>
<input type="hidden" name="p_<?php echo $t4Key; ?>" id="t4in_<?php echo $t4Key; ?>" value="0">
<?php } ?>
<div id="t4PointsBar" style="display:none;">
<button type="submit" id="t4PointsSave"><b>&#10003;</b> <?php echo defined('HERO_POINTS_SAVE') ? HERO_POINTS_SAVE : 'Save points'; ?> (<span id="t4PointsCount">0</span>)</button>
<a href="#" id="t4PointsCancel" style="margin-left:8px;"><?php echo defined('HERO_POINTS_CANCEL') ? HERO_POINTS_CANCEL : 'Cancel'; ?></a>
</div>
</form>
<script type="text/javascript">
/* Distribuirea punctelor fara reincarcarea paginii la fiecare click.
Fara JS, linkurile (+) raman GET-uri normale si merg ca inainte.
JS-ul doar ADUNA local; adevarul ramane la server, care valideaza totul
intr-un singur UPDATE atomic (vezi handlerul t4points din acest fisier). */
(function () {
var available = <?php echo (int) $hero_info['points']; ?>;
var links = document.getElementsByClassName('t4AddPoint');
if (!links.length) { return; }
var pending = {}, base = {};
var bar = document.getElementById('t4PointsBar');
var counter = document.getElementById('t4PointsCount');
var remCell = document.getElementById('t4rem');
function render() {
var used = 0, stat;
for (stat in pending) {
if (pending.hasOwnProperty(stat)) {
used += pending[stat];
var cell = document.getElementById('t4po_' + stat);
if (cell) {
cell.innerHTML = pending[stat] > 0
? base[stat] + ' <span style="color:#0a0;">(+' + pending[stat] + ')</span>'
: String(base[stat]);
}
var input = document.getElementById('t4in_' + stat);
if (input) { input.value = pending[stat]; }
}
}
if (remCell) { remCell.innerHTML = String(available - used); }
if (counter) { counter.innerHTML = String(used); }
if (bar) { bar.style.display = used > 0 ? '' : 'none'; }
}
for (var i = 0; i < links.length; i++) {
(function (link) {
var stat = link.getAttribute('data-stat');
base[stat] = parseInt(link.getAttribute('data-base'), 10) || 0;
pending[stat] = 0;
link.onclick = function (e) {
e.preventDefault();
var used = 0;
for (var k in pending) { if (pending.hasOwnProperty(k)) { used += pending[k]; } }
// nu poti aloca mai mult decat ai, nici trece de 100 pe o statistica
if (used >= available) { return false; }
if (base[stat] + pending[stat] >= 100) { return false; }
pending[stat]++;
render();
return false;
};
// click dreapta pe (+) scade alocarea, ca sa poti corecta fara reload
link.oncontextmenu = function (e) {
e.preventDefault();
if (pending[stat] > 0) { pending[stat]--; render(); }
return false;
};
})(links[i]);
}
var cancel = document.getElementById('t4PointsCancel');
if (cancel) {
cancel.onclick = function (e) {
e.preventDefault();
for (var k in pending) { if (pending.hasOwnProperty(k)) { pending[k] = 0; } }
render();
return false;
};
}
})();
</script>
<?php } ?>
<?php if(isset($_GET['e'])){
echo "<p><font size=\"1\" color=\"red\"><b>".ERROR_NAME_SHORT."</b></font></p>";
}
@@ -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'];