mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-07-10 06:36:07 +00:00
Add T4 Hero System (configurable on install)
Add T4 Hero System (configurable on install with TRUE/FALS) part of #285 -Adventures -Merchants -Equipment
This commit is contained in:
@@ -118,6 +118,7 @@ $editIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke=
|
||||
<tr><td>Special Medals<em class="tooltip">?<span class="classic">Special Medals</span></em></td><td><?php echo NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM ? "<span class='badge green'>Enabled</span>" : "<span class='badge red'>Disabled</span>"; ?></td></tr>
|
||||
<tr><td>Server Milestones<em class="tooltip">?<span class="classic">Server Milestones</span></em></td><td><?php echo NEW_FUNCTIONS_MILESTONES ? "<span class='badge green'>Enabled</span>" : "<span class='badge red'>Disabled</span>"; ?></td></tr>
|
||||
<tr><td>Server Medal Reset Timer<em class="tooltip">?<span class="classic">Server Medal Reset Timer</span></em></td><td><?php echo NEW_FUNCTIONS_MEDAL_RESET ? "<span class='badge green'>Enabled</span>" : "<span class='badge red'>Disabled</span>"; ?></td></tr>
|
||||
<tr><td>T4 Hero (items, adventures, auction)<em class="tooltip">?<span class="classic">T4 Hero (items, adventures, auction)</span></em></td><td><?php echo NEW_FUNCTIONS_HERO_T4 ? "<span class='badge green'>Enabled</span>" : "<span class='badge red'>Disabled</span>"; ?></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : editHeroT4.tpl ##
|
||||
## Type : Admin Panel Frontend - T4 hero port controls ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
if (!isset($_GET['uid'])) { echo '<p>Missing uid.</p>'; return; }
|
||||
$id = (int) $_GET['uid'];
|
||||
|
||||
include_once("../GameEngine/Data/hero_items.php");
|
||||
include_once("../GameEngine/HeroItems.php");
|
||||
include_once("../GameEngine/HeroAuction.php");
|
||||
include_once("../GameEngine/HeroAdventure.php");
|
||||
|
||||
$t4HeroItems = new HeroItems();
|
||||
$t4Auction = new HeroAuction();
|
||||
$t4Adv = new HeroAdventure();
|
||||
|
||||
$t4Silver = $t4HeroItems->getSilver($id);
|
||||
$t4Inventory = $t4HeroItems->getInventory($id);
|
||||
$t4Sales = $t4Auction->getMySales($id);
|
||||
$t4Bids = $t4Auction->getMyBids($id);
|
||||
$t4Offers = $t4Adv->getOffers($id);
|
||||
$t4Running = $t4Adv->getRunning($id);
|
||||
$t4SlotNames = [1=>'Helmet',2=>'Body',3=>'Right hand',4=>'Left hand',5=>'Shoes',6=>'Horse',7=>'Bag'];
|
||||
?>
|
||||
<style>
|
||||
.t4a-card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.04);margin-bottom:12px;}
|
||||
.t4a-card h3{margin:0;padding:9px 14px;background:#f8fafc;border-bottom:1px solid #e5e7eb;font-size:12px;text-transform:uppercase;color:#475569;letter-spacing:.4px;font-weight:600;}
|
||||
.t4a-card .body{padding:14px;}
|
||||
.t4a-table{width:100%;border-collapse:collapse;font-size:13px;}
|
||||
.t4a-table th{background:#f8fafc;padding:8px 10px;font-size:11px;text-transform:uppercase;color:#64748b;text-align:left;border-bottom:1px solid #e5e7eb;}
|
||||
.t4a-table td{padding:8px 10px;border-bottom:1px solid #f1f5f9;vertical-align:middle;}
|
||||
.t4a-table tr:last-child td{border-bottom:0;}
|
||||
.t4a-btn{padding:5px 12px;border-radius:6px;border:1px solid #cbd5e1;background:#fff;cursor:pointer;font-size:12px;font-weight:600;color:#475569;}
|
||||
.t4a-btn:hover{background:#66CCCC;color:#fff;border-color:#66CCCC;}
|
||||
.t4a-btn.danger:hover{background:#dc2626;border-color:#dc2626;}
|
||||
.t4a-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
|
||||
.t4a-inline input[type=text],.t4a-inline select{padding:6px 8px;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;}
|
||||
.t4a-msg{padding:8px 12px;border-radius:8px;margin-bottom:12px;font-size:13px;font-weight:600;}
|
||||
.t4a-msg.ok{background:#ecfdf5;color:#047857;border:1px solid #a7f3d0;}
|
||||
.t4a-msg.err{background:#fef2f2;color:#b91c1c;border:1px solid #fecaca;}
|
||||
</style>
|
||||
|
||||
<h2>T4 Hero controls — user #<?php echo $id; ?>
|
||||
<small><a href="admin.php?p=editHero&uid=<?php echo $id; ?>">« classic hero editor</a></small>
|
||||
</h2>
|
||||
|
||||
<?php if (isset($_GET['ok'])) { ?><div class="t4a-msg ok">Action completed.</div><?php } ?>
|
||||
<?php if (isset($_GET['e'])) { ?><div class="t4a-msg err">Action failed (check the values and try again).</div><?php } ?>
|
||||
|
||||
<div class="t4a-card">
|
||||
<h3>Silver</h3>
|
||||
<div class="body">
|
||||
<form action="../GameEngine/Admin/Mods/editHeroT4.php" method="POST" class="t4a-inline">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="t4admin" value="setsilver">
|
||||
<input type="hidden" name="uid" value="<?php echo $id; ?>">
|
||||
<input type="text" name="silver" value="<?php echo $t4Silver; ?>" size="10" style="text-align:right;">
|
||||
<button type="submit" class="t4a-btn">Set balance</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="t4a-card">
|
||||
<h3>Grant item</h3>
|
||||
<div class="body">
|
||||
<form action="../GameEngine/Admin/Mods/editHeroT4.php" method="POST" class="t4a-inline">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="t4admin" value="giveitem">
|
||||
<input type="hidden" name="uid" value="<?php echo $id; ?>">
|
||||
<select name="itemid">
|
||||
<?php foreach ($heroItemCatalog as $t4Iid => $t4Def) { ?>
|
||||
<option value="<?php echo $t4Iid; ?>">#<?php echo $t4Iid; ?> — <?php echo $t4Def['name']; ?> (T<?php echo $t4Def['tier']; ?>, <?php echo $t4SlotNames[$t4Def['slot']]; ?>)</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
Qty: <input type="text" name="qty" value="1" size="3" style="text-align:center;">
|
||||
<button type="submit" class="t4a-btn">Grant</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="t4a-card">
|
||||
<h3>Inventory (<?php echo count($t4Inventory); ?>)</h3>
|
||||
<div class="body">
|
||||
<?php if (count($t4Inventory)) { ?>
|
||||
<table class="t4a-table">
|
||||
<tr><th>Row</th><th>Item</th><th>Slot</th><th>Qty</th><th>Equipped</th><th></th></tr>
|
||||
<?php foreach ($t4Inventory as $t4Row) { ?>
|
||||
<tr>
|
||||
<td>#<?php echo (int) $t4Row['id']; ?></td>
|
||||
<td><?php echo $t4Row['name']; ?><?php if ($t4Row['orphan']) echo ' <b style="color:#b91c1c;">(orphan itemid ' . (int) $t4Row['itemid'] . ')</b>'; ?></td>
|
||||
<td><?php echo $t4SlotNames[(int) $t4Row['slot']] ?? (int) $t4Row['slot']; ?></td>
|
||||
<td><?php echo (int) $t4Row['quantity']; ?></td>
|
||||
<td><?php echo $t4Row['equipped'] ? 'yes' : '-'; ?></td>
|
||||
<td>
|
||||
<form action="../GameEngine/Admin/Mods/editHeroT4.php" method="POST" style="margin:0;"
|
||||
onsubmit="return confirm('Delete this item row permanently?');">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="t4admin" value="delitem">
|
||||
<input type="hidden" name="uid" value="<?php echo $id; ?>">
|
||||
<input type="hidden" name="rowid" value="<?php echo (int) $t4Row['id']; ?>">
|
||||
<button type="submit" class="t4a-btn danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
<?php } else { ?>No items.<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="t4a-card">
|
||||
<h3>Open auctions by this player (<?php echo count($t4Sales); ?>) / bids (<?php echo count($t4Bids); ?>)</h3>
|
||||
<div class="body">
|
||||
<?php if (count($t4Sales) || count($t4Bids)) { ?>
|
||||
<table class="t4a-table">
|
||||
<tr><th>Auction</th><th>Role</th><th>Item</th><th>Price</th><th>Ends</th><th></th></tr>
|
||||
<?php foreach ([['rows' => $t4Sales, 'role' => 'seller'], ['rows' => $t4Bids, 'role' => 'top bidder']] as $t4Group) { ?>
|
||||
<?php foreach ($t4Group['rows'] as $t4A) { ?>
|
||||
<tr>
|
||||
<td>#<?php echo (int) $t4A['id']; ?></td>
|
||||
<td><?php echo $t4Group['role']; ?></td>
|
||||
<td><?php echo $t4A['name']; ?> (<?php echo (int) $t4A['quantity']; ?>x)</td>
|
||||
<td><?php echo number_format((int) $t4A['silver_current']); ?></td>
|
||||
<td><?php echo date('d.m H:i', (int) $t4A['time_end']); ?></td>
|
||||
<td>
|
||||
<form action="../GameEngine/Admin/Mods/editHeroT4.php" method="POST" style="margin:0;"
|
||||
onsubmit="return confirm('Cancel this auction? Bidder is refunded, item returns to the seller.');">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="t4admin" value="cancelauction">
|
||||
<input type="hidden" name="uid" value="<?php echo $id; ?>">
|
||||
<input type="hidden" name="aucid" value="<?php echo (int) $t4A['id']; ?>">
|
||||
<button type="submit" class="t4a-btn danger">Cancel</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</table>
|
||||
<?php } else { ?>No open auction involvement.<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="t4a-card">
|
||||
<h3>Adventures</h3>
|
||||
<div class="body">
|
||||
<?php if ($t4Running) { ?>
|
||||
<p><b>Running:</b> adventure #<?php echo (int) $t4Running['id']; ?>,
|
||||
difficulty <?php echo (int) $t4Running['difficulty'] === 1 ? 'hard' : 'normal'; ?>,
|
||||
arrives <?php echo date('d.m H:i:s', (int) $t4Running['endtime']); ?> (do not delete running adventures).</p>
|
||||
<?php } ?>
|
||||
<?php if (count($t4Offers)) { ?>
|
||||
<table class="t4a-table">
|
||||
<tr><th>Offer</th><th>Difficulty</th><th>Duration</th><th>Expires</th><th></th></tr>
|
||||
<?php foreach ($t4Offers as $t4O) { ?>
|
||||
<tr>
|
||||
<td>#<?php echo (int) $t4O['id']; ?></td>
|
||||
<td><?php echo (int) $t4O['difficulty'] === 1 ? 'hard' : 'normal'; ?></td>
|
||||
<td><?php echo gmdate('H:i:s', (int) $t4O['duration']); ?></td>
|
||||
<td><?php echo date('d.m H:i', (int) $t4O['expire']); ?></td>
|
||||
<td>
|
||||
<form action="../GameEngine/Admin/Mods/editHeroT4.php" method="POST" style="margin:0;">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="t4admin" value="deladventure">
|
||||
<input type="hidden" name="uid" value="<?php echo $id; ?>">
|
||||
<input type="hidden" name="advid" value="<?php echo (int) $t4O['id']; ?>">
|
||||
<button type="submit" class="t4a-btn danger">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
<?php } else { ?>No available offers.<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,6 +232,17 @@ if($_SESSION['access'] < 9) die(ACCESS_DENIED_ADMIN);
|
||||
<option value="False" <?php if(!defined('NEW_FUNCTIONS_MEDAL_RESET') || NEW_FUNCTIONS_MEDAL_RESET == false) echo "selected";?>>False</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="b">T4 Hero (items, adventures, auction) <em class="tooltip">?<span class="classic">Enable (Disable) the "T4 Hero (items, adventures, auction)" System</span></em>
|
||||
<?php if (!defined('NEW_FUNCTIONS_HERO_T4')): ?><br><span style="color:#c0392b;font-size:11px;font-weight:normal;text-transform:none;">Not present in config.php yet — saving this form once will add it (defaults to False until then).</span><?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<select name="new_functions_hero_t4">
|
||||
<option value="True" <?php if(defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4 == true) echo "selected";?>>True</option>
|
||||
<option value="False" <?php if(!defined('NEW_FUNCTIONS_HERO_T4') || NEW_FUNCTIONS_HERO_T4 == false) echo "selected";?>>False</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -173,7 +173,7 @@ body{margin:0;background:#f1f5f9;font-family:system-ui,-apple-system,Segoe UI,Ro
|
||||
<?php
|
||||
if ($criteria!= "") {
|
||||
$artifactsEffect = ['-', VILLAGE_EFFECT, ACCOUNT_EFFECT, UNIQUE_EFFECT];
|
||||
$array_tribe = ['-', TRIBE1, TRIBE2, TRIBE3, TRIBE4, TRIBE5, TRIBE6];
|
||||
$array_tribe = ['-', TRIBE1, TRIBE2, TRIBE3, TRIBE4, TRIBE5];
|
||||
$q = "SELECT v.wref, v.owner, v.name, v.capital, v.pop, u.username, u.tribe, u.access, w.x, w.y"
|
||||
. ($includeSize? ", a.size" : "")
|
||||
. " FROM ".TB_PREFIX."vdata AS v"
|
||||
|
||||
@@ -109,6 +109,11 @@ if ($hero !== false) {
|
||||
<a class="hero-icon edit" href='admin.php?p=editHero&uid=<?php echo $id; ?>&hid=<?php echo $h['heroid'] ?>' title="Edit">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>
|
||||
</a>
|
||||
<?php if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4) { ?>
|
||||
<a class="hero-icon edit" href='admin.php?p=editHeroT4&uid=<?php echo $id; ?>' title="T4 items / silver / auctions">
|
||||
<svg viewBox="0 0 24 24"><path d="M6 2l1.5 4h9L18 2"/><path d="M4 6h16l-2 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L4 6z"/></svg>
|
||||
</a>
|
||||
<?php } ?>
|
||||
<a class="hero-icon kill" href='?action=killHero&uid=<?php echo $id; ?>' title="Kill">
|
||||
<svg viewBox="0 0 24 24"><path d="M3 6h18M8 6V4h8v2m-1 0v14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V6h10z"/></svg>
|
||||
</a>
|
||||
|
||||
+11
-1
@@ -91,7 +91,7 @@ function admin_validated_page(string $raw): string
|
||||
'editExtraSet', 'editAdminInfo', 'resetServer', 'player', 'editUser',
|
||||
'deletion', 'Newmessage', 'editPlus', 'editSitter', 'editPassword',
|
||||
'editProtection', 'editOverall',
|
||||
'editWeek', 'userlogin', 'userillegallog', 'editHero', 'editAdditional',
|
||||
'editWeek', 'userlogin', 'userillegallog', 'editHero', 'editHeroT4', 'editAdditional',
|
||||
'village', 'editResources', 'addTroops', 'addABTroops', 'editVillage',
|
||||
'villagelog', 'techlog', 'msg',
|
||||
'alliance', 'editAli', 'delAli','editNewFunctions',
|
||||
@@ -433,6 +433,16 @@ if ($page !== '') {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'editHeroT4':
|
||||
$uid = admin_input_id($_GET, 'uid');
|
||||
if ($uid !== null) {
|
||||
$user = $database->getUserArray($uid, 1);
|
||||
$subpage = 'T4 Hero Controls (' . e($user['username']) . ')';
|
||||
} else {
|
||||
$subpage = 'T4 Hero Controls';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'editAdditional':
|
||||
$uid = admin_input_id($_GET, 'uid');
|
||||
if ($uid !== null) {
|
||||
|
||||
@@ -73,6 +73,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM = (NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MILESTONES = (NEW_FUNCTIONS_MILESTONES == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MEDAL_RESET = (NEW_FUNCTIONS_MEDAL_RESET == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_HERO_T4 = (NEW_FUNCTIONS_HERO_T4 == false ? 'false' : 'true');
|
||||
|
||||
$text = admin_config_template_contents();
|
||||
$text = preg_replace("'%ERRORREPORT%'", ERROR_REPORT, $text);
|
||||
@@ -164,6 +165,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $NEW_FUNCTIONS_MILESTONES, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $NEW_FUNCTIONS_MEDAL_RESET, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $NEW_FUNCTIONS_HERO_T4, $text);
|
||||
|
||||
// PLUS settings need to be kept intact
|
||||
$text = preg_replace("'%PLUS_TIME%'", PLUS_TIME, $text);
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : editHeroT4.php ##
|
||||
## Type : BACKEND (Admin Mod) - T4 hero port admin controls ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## POST actions (all CSRF-verified, admin access >= 9, same conventions as ##
|
||||
## editHero.php): ##
|
||||
## t4admin=setsilver uid, silver -> absolute silver balance ##
|
||||
## t4admin=giveitem uid, itemid, qty -> grant catalog item ##
|
||||
## t4admin=delitem uid, rowid -> delete inventory row ##
|
||||
## t4admin=cancelauction aucid -> refund + return item ##
|
||||
## t4admin=deladventure advid -> remove an offer ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
// Load CSRF helpers + admin_deny() before the access check (same as editHero.php #299).
|
||||
require_once(__DIR__ . '/../csrf.php');
|
||||
if (!isset($_SESSION)) {
|
||||
session_start();
|
||||
}
|
||||
if (empty($_SESSION['access']) || $_SESSION['access'] < 9) {
|
||||
admin_deny('You must be signed in as an administrator to view this page. Your session may have expired — please return to the admin panel and sign in again.');
|
||||
}
|
||||
|
||||
// POSTed to directly, so it verifies the CSRF token itself (#139).
|
||||
csrf_verify();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Autoloader path (same discovery loop as the other Mods)
|
||||
// ---------------------------------------------------------------------------
|
||||
$autoprefix = '';
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$autoprefix = str_repeat('../', $i);
|
||||
if (file_exists($autoprefix . 'autoloader.php')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
include_once($autoprefix . "GameEngine/config.php");
|
||||
include_once($autoprefix . "GameEngine/Database.php");
|
||||
include_once($autoprefix . "GameEngine/Data/hero_items.php");
|
||||
include_once($autoprefix . "GameEngine/HeroItems.php");
|
||||
include_once($autoprefix . "GameEngine/HeroAuction.php");
|
||||
|
||||
$uid = (int) ($_POST['uid'] ?? 0);
|
||||
$action = (string) ($_POST['t4admin'] ?? '');
|
||||
$status = '&e=1'; // error by default; flipped to &ok=1 on success
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'setsilver':
|
||||
// Absolute set (not delta) - simplest to reason about as an admin.
|
||||
$silver = max(0, (int) ($_POST['silver'] ?? 0));
|
||||
if ($uid > 0) {
|
||||
$stmt = $database->dblink->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET silver = ? WHERE uid = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $silver, $uid);
|
||||
$stmt->execute();
|
||||
if ($stmt->affected_rows > 0 || $heroItems->getSilver($uid) === $silver) {
|
||||
$status = '&ok=1';
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'giveitem':
|
||||
$itemid = (int) ($_POST['itemid'] ?? 0);
|
||||
$qty = max(1, (int) ($_POST['qty'] ?? 1));
|
||||
if ($uid > 0 && $heroItems->addItem($uid, $itemid, $qty) > 0) {
|
||||
$status = '&ok=1';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delitem':
|
||||
// Hard delete of an inventory row (equipped or not) - admin override.
|
||||
$rowid = (int) ($_POST['rowid'] ?? 0);
|
||||
if ($uid > 0 && $rowid > 0) {
|
||||
$stmt = $database->dblink->prepare(
|
||||
"DELETE FROM " . TB_PREFIX . "hero_items WHERE id = ? AND uid = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $rowid, $uid);
|
||||
$stmt->execute();
|
||||
if ($stmt->affected_rows > 0) {
|
||||
$status = '&ok=1';
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cancelauction':
|
||||
$aucid = (int) ($_POST['aucid'] ?? 0);
|
||||
$auction = new HeroAuction();
|
||||
if ($aucid > 0 && $auction->adminCancel($aucid)) {
|
||||
$status = '&ok=1';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'deladventure':
|
||||
// Remove an available offer (status 0 only - never a running one,
|
||||
// that would strand the hero mid-movement).
|
||||
$advid = (int) ($_POST['advid'] ?? 0);
|
||||
if ($advid > 0) {
|
||||
$stmt = $database->dblink->prepare(
|
||||
"DELETE FROM " . TB_PREFIX . "hero_adventure WHERE id = ? AND status = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $advid);
|
||||
$stmt->execute();
|
||||
if ($stmt->affected_rows > 0) {
|
||||
$status = '&ok=1';
|
||||
}
|
||||
$stmt->close();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
header("Location: ../../../Admin/admin.php?p=editHeroT4&uid=$uid$status");
|
||||
exit;
|
||||
@@ -69,6 +69,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM = (NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MILESTONES = (NEW_FUNCTIONS_MILESTONES == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MEDAL_RESET = (NEW_FUNCTIONS_MEDAL_RESET == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_HERO_T4 = (NEW_FUNCTIONS_HERO_T4 == false ? 'false' : 'true');
|
||||
|
||||
$text = admin_config_template_contents();
|
||||
$text = preg_replace("'%ERRORREPORT%'", $ERRORREPORT, $text);
|
||||
@@ -164,6 +165,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $NEW_FUNCTIONS_MILESTONES, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $NEW_FUNCTIONS_MEDAL_RESET, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $NEW_FUNCTIONS_HERO_T4, $text);
|
||||
|
||||
// PLUS settings need to be kept intact
|
||||
$text = preg_replace("'%PLUS_TIME%'", PLUS_TIME, $text);
|
||||
|
||||
@@ -131,6 +131,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $_POST['new_functions_special_medals_system'], $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $_POST['new_functions_milestones'], $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $_POST['new_functions_medal_reset'], $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $_POST['new_functions_hero_t4'], $text);
|
||||
|
||||
// PLUS settings need to be kept intact
|
||||
$text = preg_replace("'%PLUS_TIME%'", PLUS_TIME, $text);
|
||||
|
||||
@@ -75,6 +75,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM = (NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MILESTONES = (NEW_FUNCTIONS_MILESTONES == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MEDAL_RESET = (NEW_FUNCTIONS_MEDAL_RESET == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_HERO_T4 = (NEW_FUNCTIONS_HERO_T4 == false ? 'false' : 'true');
|
||||
|
||||
$text = admin_config_template_contents();
|
||||
$text = preg_replace("'%ERRORREPORT%'", $ERRORREPORT, $text);
|
||||
@@ -170,6 +171,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $NEW_FUNCTIONS_MILESTONES, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $NEW_FUNCTIONS_MEDAL_RESET, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $NEW_FUNCTIONS_HERO_T4, $text);
|
||||
|
||||
// PLUS settings need to be kept intact
|
||||
$text = preg_replace("'%PLUS_TIME%'", PLUS_TIME, $text);
|
||||
|
||||
@@ -57,6 +57,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM = (NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MILESTONES = (NEW_FUNCTIONS_MILESTONES == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MEDAL_RESET = (NEW_FUNCTIONS_MEDAL_RESET == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_HERO_T4 = (NEW_FUNCTIONS_HERO_T4 == false ? 'false' : 'true');
|
||||
|
||||
// SERVER SETTINGS - we need to keep these intact
|
||||
$text = preg_replace("'%ERRORREPORT%'", ERROR_REPORT, $text);
|
||||
@@ -148,6 +149,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $NEW_FUNCTIONS_MILESTONES, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $NEW_FUNCTIONS_MEDAL_RESET, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $NEW_FUNCTIONS_HERO_T4, $text);
|
||||
|
||||
// PLUS SETTINGS
|
||||
$text = preg_replace("'%PLUS_TIME%'", $_POST['plus_time'], $text);
|
||||
|
||||
@@ -68,6 +68,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM = (NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MILESTONES = (NEW_FUNCTIONS_MILESTONES == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_MEDAL_RESET = (NEW_FUNCTIONS_MEDAL_RESET == false ? 'false' : 'true');
|
||||
$NEW_FUNCTIONS_HERO_T4 = (NEW_FUNCTIONS_HERO_T4 == false ? 'false' : 'true');
|
||||
|
||||
$text = admin_config_template_contents();
|
||||
$text = preg_replace("'%ERRORREPORT%'", $_POST['error'], $text);
|
||||
@@ -159,6 +160,7 @@ $fh = fopen($myFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\con
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%'", $NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MILESTONES%'", $NEW_FUNCTIONS_MILESTONES, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_MEDAL_RESET%'", $NEW_FUNCTIONS_MEDAL_RESET, $text);
|
||||
$text = preg_replace("'%NEW_FUNCTIONS_HERO_T4%'", $NEW_FUNCTIONS_HERO_T4, $text);
|
||||
|
||||
// PLUS settings need to be kept intact
|
||||
$text = preg_replace("'%PLUS_TIME%'", PLUS_TIME, $text);
|
||||
|
||||
@@ -95,7 +95,8 @@ class Automation {
|
||||
"trainingComplete", "starvation", "celebrationComplete", "festivalComplete",
|
||||
"sendUnitsComplete", "loyaltyRegeneration", "sendreinfunitsComplete",
|
||||
"returnunitsComplete", "sendSettlersComplete", "spawnNatars",
|
||||
"spawnWWVillages", "spawnWWBuildingPlans", "activateArtifacts"];
|
||||
"spawnWWVillages", "spawnWWBuildingPlans", "activateArtifacts",
|
||||
"heroAdventureComplete"];
|
||||
|
||||
foreach($methodsArrays as $method){
|
||||
$file = fopen($autoprefix."GameEngine/Prevention/".$method.".txt", "w");
|
||||
@@ -2885,9 +2886,17 @@ class Automation {
|
||||
{
|
||||
$returningTroops = [];
|
||||
for($i = 1; $i <= 11; $i++) $returningTroops['t'.$i] = $data['t'.$i] - $traped[$i] - $dead[$i];
|
||||
// T4 hero port (Phase 6): bandages revive part of the
|
||||
// losses when the hero comes home alive; revived units
|
||||
// are written back to the attacks row (the source
|
||||
// returnunitsComplete reads on arrival). No-op when off.
|
||||
$returningTroops = HeroBattleBonus::applyBandages($from['owner'], $returningTroops, $dead, (int) $data['ref']);
|
||||
$troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $returningTroops, 1, 't');
|
||||
$endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime);
|
||||
$endtime += $AttackArrivalTime;
|
||||
// T4 hero port (Phase 5): the map shortens the return
|
||||
// leg when the SURVIVING hero travels home. No-op when off.
|
||||
$endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $returningTroops['t11'], $AttackArrivalTime, $endtime);
|
||||
if($type == 1){
|
||||
if($from['owner'] == 3){ // fix natar report by ronix
|
||||
$database->addNotice($to['owner'], $to['wref'], $targetally, 20, '' . addslashes($from['name']) . ' scouts ' . addslashes($to['name']) . '', $data2, $AttackArrivalTime);
|
||||
@@ -3130,6 +3139,15 @@ class Automation {
|
||||
$enforDefender = $defUnits['enforDefender'];
|
||||
$enforcementarray = $defUnits['enforcementarray'];
|
||||
|
||||
// T4 hero port (Phase 8): cages capture animals on an
|
||||
// UNOCCUPIED oasis before the fight - captured animals do
|
||||
// not defend and are stationed at home as reinforcements.
|
||||
// No-op when the flag is off, no hero rides along, the
|
||||
// oasis is owned, or the attacker has no cages.
|
||||
if ($isoasis != 0 && (int) $conqureby === 0 && (int) $data['t11'] > 0) {
|
||||
$Defender = HeroBattleBonus::applyCages($from['owner'], (int) $data['from'], (int) $data['to'], $Defender);
|
||||
}
|
||||
|
||||
// attacker army built — extracted to buildAttackerUnits() [#155]
|
||||
$atkUnits = $this->buildAttackerUnits($dataarray[$data_num], $owntribe, $isoasis);
|
||||
$Attacker = $atkUnits['Attacker'];
|
||||
@@ -3363,7 +3381,10 @@ class Automation {
|
||||
$avcrop = $resAfter['avcrop'];
|
||||
|
||||
// bounty distributed across the resources available after cranny protection (extracted to applyBounty() [#155])
|
||||
$steal = $this->applyBounty([$avwood, $avclay, $aviron, $avcrop], $battlepart['bounty']);
|
||||
// T4 hero port (Phase 5): thief sacks raise the carry
|
||||
// bounty when the attacker's living hero joined the strike.
|
||||
$heroBounty = (int) round($battlepart['bounty'] * HeroBattleBonus::raidMultiplier($from['owner'], $data['t11']));
|
||||
$steal = $this->applyBounty([$avwood, $avclay, $aviron, $avcrop], $heroBounty);
|
||||
|
||||
//chiefing village — extracted to handleConquest() [#155]
|
||||
if (!isset($village_destroyed)) $village_destroyed = 0;
|
||||
@@ -3431,6 +3452,9 @@ class Automation {
|
||||
$troopsTime = $units->getWalkingTroopsTime($from['wref'], $to['wref'], $from['owner'], $owntribe, $data, 1, 't');
|
||||
$endtime = $database->getArtifactsValueInfluence($from['owner'], $from['wref'], 2, $troopsTime);
|
||||
$endtime += $AttackArrivalTime;
|
||||
// T4 hero port (Phase 5): map return-speed bonus, same as
|
||||
// the combat return leg above. No-op when the flag is off.
|
||||
$endtime = HeroBattleBonus::adjustReturnEndtime($from['owner'], $data['t11'], $AttackArrivalTime, $endtime);
|
||||
|
||||
$database->setMovementProc($data['moveid']);
|
||||
$database->addMovement(4, $to['wref'], $from['wref'], $data['ref'], $AttackArrivalTime, $endtime);
|
||||
@@ -4338,6 +4362,28 @@ class Automation {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* T4 Hero port (Phase 3+4): process adventure arrivals (sort_type 20,
|
||||
* rewards + ntype 26 report + return movement), returns (sort_type 21,
|
||||
* hero re-enters units.hero, loot credited), top up offer lists, then
|
||||
* finalize ended auctions and restock the NPC merchant.
|
||||
* Fully feature-flagged - a no-op unless NEW_FUNCTIONS_HERO_T4 is enabled.
|
||||
*/
|
||||
private function heroAdventureComplete() {
|
||||
if (!defined('NEW_FUNCTIONS_HERO_T4') || !NEW_FUNCTIONS_HERO_T4) {
|
||||
return;
|
||||
}
|
||||
|
||||
$adventures = new HeroAdventure();
|
||||
$adventures->processArrivals();
|
||||
$adventures->processReturns();
|
||||
$adventures->generateOffersBatch();
|
||||
|
||||
$auctions = new HeroAuction();
|
||||
$auctions->processFinished();
|
||||
$auctions->seedNpcAuctions();
|
||||
}
|
||||
|
||||
private function updateHero() {
|
||||
global $database, $hero_levels;
|
||||
|
||||
|
||||
+32
-1
@@ -190,6 +190,16 @@ class Battle {
|
||||
$h_ob = 1 + (0.010 * ($attackBonus / 5));
|
||||
$h_db = 1 + (0.010 * ($defenceBonus / 5));
|
||||
|
||||
// T4 hero port (Phase 5): flat fighting strength from equipped items
|
||||
// (weapons/cuirasses/shields) counts on whichever side the hero fights.
|
||||
// No-ops to 0 when NEW_FUNCTIONS_HERO_T4 is off.
|
||||
$itemStrength = HeroBattleBonus::statBonus($uid);
|
||||
if ($itemStrength > 0) {
|
||||
$h_atk += $itemStrength;
|
||||
$h_di += $itemStrength;
|
||||
$h_dc += $itemStrength;
|
||||
}
|
||||
|
||||
return [
|
||||
'heroid' => (int)$hero['heroid'],
|
||||
'unit' => $heroUnit,
|
||||
@@ -198,6 +208,8 @@ class Battle {
|
||||
'dc' => $h_dc,
|
||||
'ob' => $h_ob,
|
||||
'db' => $h_db,
|
||||
// T4 hero port: carried along for the offense/defense/damage hooks.
|
||||
'uid' => (int)$uid,
|
||||
'health' => isset($hero['health'])
|
||||
? (int)$hero['health']
|
||||
: 0
|
||||
@@ -550,6 +562,14 @@ class Battle {
|
||||
$units['Def_unit']['hero'] = $Defender['hero'];
|
||||
$own_cdp += $defenderhero['dc'];
|
||||
$own_dp += $defenderhero['di'];
|
||||
|
||||
// T4 hero port (Phase 5): weapon adds +N defense per unit of
|
||||
// its type in the defending army, counted in both pools
|
||||
// (see HeroBattleBonus::unitDefense). No-op when flag is off.
|
||||
$itemDef = HeroBattleBonus::unitDefense($defenderhero['uid'] ?? 0, $Defender);
|
||||
$own_dp += $itemDef;
|
||||
$own_cdp += $itemDef;
|
||||
|
||||
$own_dp *= $defenderhero['db'];
|
||||
$own_cdp *= $defenderhero['db'];
|
||||
}
|
||||
@@ -762,7 +782,14 @@ class Battle {
|
||||
$ap *= $atkhero['ob'];
|
||||
$cap *= $atkhero['ob'];
|
||||
|
||||
$ap += $atkhero['atk'];
|
||||
// T4 hero port (Phase 5): hunting horn boosts the HERO's own
|
||||
// contribution vs the Natars (uid 3); weapon adds +N attack per
|
||||
// accompanying unit of its type. Neutral no-ops when the flag is off.
|
||||
$ap += $atkhero['atk'] * HeroBattleBonus::natarMultiplier($atkhero['uid'] ?? 0, $DefenderID);
|
||||
|
||||
list($itemAp, $itemCap) = HeroBattleBonus::unitOffense($atkhero['uid'] ?? 0, $Attacker, $calvaryLookup);
|
||||
$ap += $itemAp;
|
||||
$cap += $itemCap;
|
||||
}
|
||||
|
||||
if ($offhero > 0 || $hero_strenght > 0) {
|
||||
@@ -1136,6 +1163,10 @@ class Battle {
|
||||
$hero_health = (int)$fdb['health'];
|
||||
$damage_health = round(100 * $result[1]);
|
||||
|
||||
// T4 hero port (Phase 5): armors reduce the health damage the
|
||||
// hero takes in one battle (flat, floored at 0). No-op when off.
|
||||
$damage_health = HeroBattleBonus::reduceDamage($atkhero['uid'] ?? 0, $damage_health);
|
||||
|
||||
if ($hero_health <= $damage_health || $damage_health > 90) {
|
||||
|
||||
$result['casualties_attacker'][11] = 1;
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : hero_items.php ##
|
||||
## Type : Static Data / Catalog for T4 Hero port ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## SINGLE SOURCE OF TRUTH for the T4 hero port (Phase 2 - full catalog, ##
|
||||
## built from the design spreadsheet "Obiecte_Erou_Travian.xlsx"). ##
|
||||
## Pure data + guarded constants, no side effects - safe to include anywhere. ##
|
||||
## ##
|
||||
## Display names are stored as English strings in the catalog. For future ##
|
||||
## localisation, heroItemName() checks for a HERO_ITEM_<id> lang constant ##
|
||||
## first and falls back to the English catalog name. ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
/* ============================================================================
|
||||
* GAME CONSTANTS (unchanged from Phase 1; guarded against re-includes)
|
||||
* movement.sort_type in use: 0,2,3,4,5,6,10,11,12,13 -> 20/21 free
|
||||
* attacks.attack_type in use: 1,2 -> 9 free
|
||||
* ndata.ntype mapped up to 25 (settlers) -> 26 free
|
||||
* ------------------------------------------------------------------------- */
|
||||
if (!defined('MOVEMENT_ADVENTURE_OUT')) define('MOVEMENT_ADVENTURE_OUT', 20);
|
||||
if (!defined('MOVEMENT_ADVENTURE_BACK')) define('MOVEMENT_ADVENTURE_BACK', 21);
|
||||
if (!defined('ATTACK_TYPE_ADVENTURE')) define('ATTACK_TYPE_ADVENTURE', 9);
|
||||
if (!defined('NTYPE_ADVENTURE_REPORT')) define('NTYPE_ADVENTURE_REPORT', 26);
|
||||
if (!defined('NTYPE_AUCTION_REPORT')) define('NTYPE_AUCTION_REPORT', 27);
|
||||
|
||||
/* Equipment slots */
|
||||
if (!defined('HSLOT_HELMET')) define('HSLOT_HELMET', 1);
|
||||
if (!defined('HSLOT_BODY')) define('HSLOT_BODY', 2);
|
||||
if (!defined('HSLOT_RIGHT')) define('HSLOT_RIGHT', 3); // weapon
|
||||
if (!defined('HSLOT_LEFT')) define('HSLOT_LEFT', 4); // shield/horn/sack/map/pennant/standard
|
||||
if (!defined('HSLOT_SHOES')) define('HSLOT_SHOES', 5); // boots + spurs
|
||||
if (!defined('HSLOT_HORSE')) define('HSLOT_HORSE', 6);
|
||||
if (!defined('HSLOT_BAG')) define('HSLOT_BAG', 7); // stackable consumables
|
||||
|
||||
/* ============================================================================
|
||||
* BONUS TYPES (keys inside $item['bonus'])
|
||||
* Flat vs percent is documented per constant. Consumers: HeroItems::getBonuses()
|
||||
* (Phase 2), Battle/Automation/a2b integration (Phase 5).
|
||||
* ------------------------------------------------------------------------- */
|
||||
if (!defined('HB_FIGHT')) define('HB_FIGHT', 'fight_strength'); // flat hero fighting strength
|
||||
if (!defined('HB_UNIT_BONUS')) define('HB_UNIT_BONUS', 'unit_bonus'); // ['unit'=>uX,'per_unit'=>N] +N off & +N def per unit of that type in the hero's army
|
||||
if (!defined('HB_VS_NATARS')) define('HB_VS_NATARS', 'vs_natars'); // +% hero strength vs Natars
|
||||
if (!defined('HB_RAID')) define('HB_RAID', 'raid_percent'); // +% resources stolen on raids
|
||||
if (!defined('HB_RETURN_SPEED')) define('HB_RETURN_SPEED', 'return_speed'); // +% troop return speed
|
||||
if (!defined('HB_SPEED_OWN')) define('HB_SPEED_OWN', 'speed_own'); // +% speed between own villages
|
||||
if (!defined('HB_SPEED_ALLY')) define('HB_SPEED_ALLY', 'speed_ally'); // +% speed between ally villages
|
||||
if (!defined('HB_XP')) define('HB_XP', 'xp_percent'); // +% hero experience
|
||||
if (!defined('HB_REGEN_HP')) define('HB_REGEN_HP', 'regen_hp'); // flat +HP per day
|
||||
if (!defined('HB_CP')) define('HB_CP', 'culture_points'); // flat +culture points per day
|
||||
if (!defined('HB_TRAIN_CAV')) define('HB_TRAIN_CAV', 'train_cavalry'); // -% cavalry training time
|
||||
if (!defined('HB_TRAIN_INF')) define('HB_TRAIN_INF', 'train_infantry'); // -% infantry training time
|
||||
if (!defined('HB_DMG_REDUCE')) define('HB_DMG_REDUCE', 'damage_reduce'); // flat damage reduction per hit
|
||||
if (!defined('HB_TROOP_SPEED_20')) define('HB_TROOP_SPEED_20', 'troop_speed_20'); // +% troop speed beyond 20 tiles
|
||||
if (!defined('HB_MOUNT_SPEED')) define('HB_MOUNT_SPEED', 'mount_speed'); // flat +fields/hour, ONLY while a horse is equipped (spurs)
|
||||
if (!defined('HB_HORSE_SPEED')) define('HB_HORSE_SPEED', 'horse_speed'); // flat hero speed in fields/hour (horse)
|
||||
/* consumables */
|
||||
if (!defined('HB_HEAL_SELF')) define('HB_HEAL_SELF', 'heal_self'); // +1% hero HP per unit (max 99%, hero must be alive)
|
||||
if (!defined('HB_SCROLL')) define('HB_SCROLL', 'scroll_xp'); // +XP per scroll
|
||||
if (!defined('HB_BUCKET')) define('HB_BUCKET', 'bucket'); // instant free revive
|
||||
if (!defined('HB_LOYALTY')) define('HB_LOYALTY', 'loyalty'); // +1% own village loyalty per tablet (max 125%)
|
||||
if (!defined('HB_RESET')) define('HB_RESET', 'reset_points'); // reset attribute points
|
||||
if (!defined('HB_ARTWORK')) define('HB_ARTWORK', 'artwork_cp'); // CP = daily production (cap: 5000 normal / 2500 speed)
|
||||
if (!defined('HB_HEAL_TROOP')) define('HB_HEAL_TROOP', 'heal_troops'); // heals % of the hero's army (bandages)
|
||||
if (!defined('HB_CAGE')) define('HB_CAGE', 'cage'); // traps 1 oasis animal per cage
|
||||
|
||||
/* ============================================================================
|
||||
* ITEM CATALOG
|
||||
* itemid => [
|
||||
* 'name' => English display name,
|
||||
* 'slot' => HSLOT_*,
|
||||
* 'tier' => 1..3,
|
||||
* 'bonus' => [HB_* => value, ...],
|
||||
* 'unit' => uX (weapons only: hero must BE this unit to equip),
|
||||
* 'x2_on_speed' => true (value doubles on speed servers, per design sheet:
|
||||
* "+3 (1x) / +6 (3x)" - runtime multiplies by 2 when SPEED >= 3),
|
||||
* 'requires_horse' => true (spurs: bonus only counts with a horse equipped)
|
||||
* ]
|
||||
*
|
||||
* ID ranges: 1-15 helmets | 20-31 body | 40-57 left hand | 60-68 shoes
|
||||
* 70-72 horses | 101-145 weapons | 200-208 consumables
|
||||
* ------------------------------------------------------------------------- */
|
||||
$heroItemCatalog = array(
|
||||
|
||||
/* ---------------- HELMETS (sheet: Coifuri) ---------------- */
|
||||
1 => array('name' => 'Helmet of Awareness', 'slot' => HSLOT_HELMET, 'tier' => 1, 'bonus' => array(HB_XP => 15)),
|
||||
2 => array('name' => 'Helmet of Enlightenment', 'slot' => HSLOT_HELMET, 'tier' => 2, 'bonus' => array(HB_XP => 20)),
|
||||
3 => array('name' => 'Helmet of Wisdom', 'slot' => HSLOT_HELMET, 'tier' => 3, 'bonus' => array(HB_XP => 25)),
|
||||
4 => array('name' => 'Helmet of Regeneration', 'slot' => HSLOT_HELMET, 'tier' => 1, 'bonus' => array(HB_REGEN_HP => 10)),
|
||||
5 => array('name' => 'Helmet of Health', 'slot' => HSLOT_HELMET, 'tier' => 2, 'bonus' => array(HB_REGEN_HP => 15)),
|
||||
6 => array('name' => 'Helmet of Healing', 'slot' => HSLOT_HELMET, 'tier' => 3, 'bonus' => array(HB_REGEN_HP => 20)),
|
||||
7 => array('name' => 'Helmet of the Gladiator', 'slot' => HSLOT_HELMET, 'tier' => 1, 'bonus' => array(HB_CP => 100), 'x2_on_speed' => false), // sheet: 100/day (1x), 50 (3x) -> HALVED on speed; handled below
|
||||
8 => array('name' => 'Helmet of the Tribune', 'slot' => HSLOT_HELMET, 'tier' => 2, 'bonus' => array(HB_CP => 400), 'x2_on_speed' => false),
|
||||
9 => array('name' => 'Helmet of the Consul', 'slot' => HSLOT_HELMET, 'tier' => 3, 'bonus' => array(HB_CP => 800), 'x2_on_speed' => false),
|
||||
10 => array('name' => 'Helmet of the Horseman', 'slot' => HSLOT_HELMET, 'tier' => 1, 'bonus' => array(HB_TRAIN_CAV => 10)),
|
||||
11 => array('name' => 'Helmet of the Cavalry', 'slot' => HSLOT_HELMET, 'tier' => 2, 'bonus' => array(HB_TRAIN_CAV => 15)),
|
||||
12 => array('name' => 'Helmet of the Heavy Cavalry', 'slot' => HSLOT_HELMET, 'tier' => 3, 'bonus' => array(HB_TRAIN_CAV => 20)),
|
||||
13 => array('name' => 'Helmet of the Mercenary', 'slot' => HSLOT_HELMET, 'tier' => 1, 'bonus' => array(HB_TRAIN_INF => 10)),
|
||||
14 => array('name' => 'Helmet of the Warrior', 'slot' => HSLOT_HELMET, 'tier' => 2, 'bonus' => array(HB_TRAIN_INF => 15)),
|
||||
15 => array('name' => 'Helmet of the Ruler', 'slot' => HSLOT_HELMET, 'tier' => 3, 'bonus' => array(HB_TRAIN_INF => 20)),
|
||||
|
||||
/* ---------------- BODY ARMOUR (sheet: Armuri) ---------------- */
|
||||
20 => array('name' => 'Light Breastplate of Regeneration', 'slot' => HSLOT_BODY, 'tier' => 1, 'bonus' => array(HB_REGEN_HP => 20)),
|
||||
21 => array('name' => 'Breastplate of Regeneration', 'slot' => HSLOT_BODY, 'tier' => 2, 'bonus' => array(HB_REGEN_HP => 30)),
|
||||
22 => array('name' => 'Heavy Breastplate of Regeneration', 'slot' => HSLOT_BODY, 'tier' => 3, 'bonus' => array(HB_REGEN_HP => 40)),
|
||||
23 => array('name' => 'Light Armor', 'slot' => HSLOT_BODY, 'tier' => 1, 'bonus' => array(HB_DMG_REDUCE => 4, HB_REGEN_HP => 10)),
|
||||
24 => array('name' => 'Armor', 'slot' => HSLOT_BODY, 'tier' => 2, 'bonus' => array(HB_DMG_REDUCE => 6, HB_REGEN_HP => 15)),
|
||||
25 => array('name' => 'Heavy Armor', 'slot' => HSLOT_BODY, 'tier' => 3, 'bonus' => array(HB_DMG_REDUCE => 8, HB_REGEN_HP => 20)),
|
||||
26 => array('name' => 'Light Cuirass', 'slot' => HSLOT_BODY, 'tier' => 1, 'bonus' => array(HB_FIGHT => 500)),
|
||||
27 => array('name' => 'Cuirass', 'slot' => HSLOT_BODY, 'tier' => 2, 'bonus' => array(HB_FIGHT => 1000)),
|
||||
28 => array('name' => 'Heavy Cuirass', 'slot' => HSLOT_BODY, 'tier' => 3, 'bonus' => array(HB_FIGHT => 1500)),
|
||||
29 => array('name' => 'Light Segmented Armor', 'slot' => HSLOT_BODY, 'tier' => 1, 'bonus' => array(HB_DMG_REDUCE => 3, HB_FIGHT => 250)),
|
||||
30 => array('name' => 'Segmented Armor', 'slot' => HSLOT_BODY, 'tier' => 2, 'bonus' => array(HB_DMG_REDUCE => 4, HB_FIGHT => 500)),
|
||||
31 => array('name' => 'Heavy Segmented Armor', 'slot' => HSLOT_BODY, 'tier' => 3, 'bonus' => array(HB_DMG_REDUCE => 5, HB_FIGHT => 750)),
|
||||
|
||||
/* ---------------- LEFT HAND (sheets: Mana_Stanga + Diverse) ---------------- */
|
||||
40 => array('name' => 'Small Shield', 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_FIGHT => 500)),
|
||||
41 => array('name' => 'Shield', 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_FIGHT => 1000)),
|
||||
42 => array('name' => 'Great Shield', 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_FIGHT => 1500)),
|
||||
43 => array('name' => 'Small Hunting Horn', 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_VS_NATARS => 20)),
|
||||
44 => array('name' => 'Hunting Horn', 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_VS_NATARS => 25)),
|
||||
45 => array('name' => 'Great Hunting Horn', 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_VS_NATARS => 30)),
|
||||
46 => array('name' => "Thief's Satchel", 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_RAID => 10)),
|
||||
47 => array('name' => "Thief's Bag", 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_RAID => 15)),
|
||||
48 => array('name' => "Thief's Sack", 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_RAID => 20)),
|
||||
49 => array('name' => 'Small Map', 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_RETURN_SPEED => 30)),
|
||||
50 => array('name' => 'Map', 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_RETURN_SPEED => 40)),
|
||||
51 => array('name' => 'Great Map', 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_RETURN_SPEED => 50)),
|
||||
52 => array('name' => 'Small Pennant', 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_SPEED_OWN => 30)),
|
||||
53 => array('name' => 'Pennant', 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_SPEED_OWN => 40)),
|
||||
54 => array('name' => 'Great Pennant', 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_SPEED_OWN => 50)),
|
||||
55 => array('name' => 'Small Standard', 'slot' => HSLOT_LEFT, 'tier' => 1, 'bonus' => array(HB_SPEED_ALLY => 15)),
|
||||
56 => array('name' => 'Standard', 'slot' => HSLOT_LEFT, 'tier' => 2, 'bonus' => array(HB_SPEED_ALLY => 20)),
|
||||
57 => array('name' => 'Great Standard', 'slot' => HSLOT_LEFT, 'tier' => 3, 'bonus' => array(HB_SPEED_ALLY => 25)),
|
||||
|
||||
/* ---------------- SHOES (sheet: Incaltaminte_Cai - boots + spurs) ---------------- */
|
||||
60 => array('name' => 'Boots of Regeneration', 'slot' => HSLOT_SHOES, 'tier' => 1, 'bonus' => array(HB_REGEN_HP => 10)),
|
||||
61 => array('name' => 'Boots of Recovery', 'slot' => HSLOT_SHOES, 'tier' => 2, 'bonus' => array(HB_REGEN_HP => 15)),
|
||||
62 => array('name' => 'Boots of Healing', 'slot' => HSLOT_SHOES, 'tier' => 3, 'bonus' => array(HB_REGEN_HP => 20)),
|
||||
63 => array('name' => 'Boots of the Mercenary', 'slot' => HSLOT_SHOES, 'tier' => 1, 'bonus' => array(HB_TROOP_SPEED_20 => 25)),
|
||||
64 => array('name' => 'Boots of the Warrior', 'slot' => HSLOT_SHOES, 'tier' => 2, 'bonus' => array(HB_TROOP_SPEED_20 => 50)),
|
||||
65 => array('name' => 'Boots of the Ruler', 'slot' => HSLOT_SHOES, 'tier' => 3, 'bonus' => array(HB_TROOP_SPEED_20 => 75)),
|
||||
// Spurs share the shoes slot (worn on boots) and only apply while a horse is equipped.
|
||||
// Design sheet: "+3 f/h (1x) / +6 (3x)" -> base value is the 1x one, doubled on speed servers.
|
||||
66 => array('name' => 'Small Spurs', 'slot' => HSLOT_SHOES, 'tier' => 1, 'bonus' => array(HB_MOUNT_SPEED => 3), 'x2_on_speed' => true, 'requires_horse' => true),
|
||||
67 => array('name' => 'Spurs', 'slot' => HSLOT_SHOES, 'tier' => 2, 'bonus' => array(HB_MOUNT_SPEED => 4), 'x2_on_speed' => true, 'requires_horse' => true),
|
||||
68 => array('name' => 'Great Spurs', 'slot' => HSLOT_SHOES, 'tier' => 3, 'bonus' => array(HB_MOUNT_SPEED => 5), 'x2_on_speed' => true, 'requires_horse' => true),
|
||||
|
||||
/* ---------------- HORSES (sheet: Incaltaminte_Cai) ---------------- */
|
||||
70 => array('name' => 'Riding Horse', 'slot' => HSLOT_HORSE, 'tier' => 1, 'bonus' => array(HB_HORSE_SPEED => 14), 'x2_on_speed' => true),
|
||||
71 => array('name' => 'Thoroughbred', 'slot' => HSLOT_HORSE, 'tier' => 2, 'bonus' => array(HB_HORSE_SPEED => 17), 'x2_on_speed' => true),
|
||||
72 => array('name' => 'Warhorse', 'slot' => HSLOT_HORSE, 'tier' => 3, 'bonus' => array(HB_HORSE_SPEED => 20), 'x2_on_speed' => true),
|
||||
|
||||
/* ---------------- WEAPONS (sheets: Romani / Barbari / Daci) ----------------
|
||||
* Weapons are unit-bound: the hero must be trained from that unit.
|
||||
* Each gives flat fight strength (500/1000/1500 by tier) plus
|
||||
* +N off AND +N def per unit of that type accompanying the hero. */
|
||||
|
||||
// Romans - u1 Legionnaire
|
||||
101 => array('name' => "Legionnaire's Short Sword", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 1, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 1, 'per_unit' => 3))),
|
||||
102 => array('name' => "Legionnaire's Sword", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 1, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 1, 'per_unit' => 4))),
|
||||
103 => array('name' => "Legionnaire's Long Sword", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 1, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 1, 'per_unit' => 5))),
|
||||
// Romans - u2 Praetorian
|
||||
104 => array('name' => "Praetorian's Short Sword", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 2, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 2, 'per_unit' => 3))),
|
||||
105 => array('name' => "Praetorian's Sword", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 2, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 2, 'per_unit' => 4))),
|
||||
106 => array('name' => "Praetorian's Long Sword", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 2, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 2, 'per_unit' => 5))),
|
||||
// Romans - u3 Imperian
|
||||
107 => array('name' => "Imperian's Short Sword", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 3, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 3, 'per_unit' => 3))),
|
||||
108 => array('name' => "Imperian's Sword", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 3, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 3, 'per_unit' => 4))),
|
||||
109 => array('name' => "Imperian's Long Sword", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 3, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 3, 'per_unit' => 5))),
|
||||
// Romans - u5 Equites Imperatoris
|
||||
110 => array('name' => "Imperatoris' Short Saber", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 5, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 5, 'per_unit' => 9))),
|
||||
111 => array('name' => "Imperatoris' Saber", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 5, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 5, 'per_unit' => 12))),
|
||||
112 => array('name' => "Imperatoris' Long Saber", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 5, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 5, 'per_unit' => 15))),
|
||||
// Romans - u6 Equites Caesaris
|
||||
113 => array('name' => "Caesaris' Light Lance", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 6, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 6, 'per_unit' => 12))),
|
||||
114 => array('name' => "Caesaris' Lance", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 6, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 6, 'per_unit' => 16))),
|
||||
115 => array('name' => "Caesaris' Heavy Lance", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 6, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 6, 'per_unit' => 20))),
|
||||
|
||||
// Teutons - u11 Clubswinger
|
||||
116 => array('name' => "Clubswinger's Cudgel", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 11, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 11, 'per_unit' => 3))),
|
||||
117 => array('name' => "Clubswinger's Club", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 11, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 11, 'per_unit' => 4))),
|
||||
118 => array('name' => "Clubswinger's Mace", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 11, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 11, 'per_unit' => 5))),
|
||||
// Teutons - u12 Spearman
|
||||
119 => array('name' => "Spearman's Pitchfork", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 12, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 12, 'per_unit' => 3))),
|
||||
120 => array('name' => "Spearman's Pike", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 12, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 12, 'per_unit' => 4))),
|
||||
121 => array('name' => "Spearman's Spear", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 12, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 12, 'per_unit' => 5))),
|
||||
// Teutons - u13 Axeman
|
||||
122 => array('name' => "Axeman's Hatchet", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 13, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 13, 'per_unit' => 3))),
|
||||
123 => array('name' => "Axeman's Axe", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 13, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 13, 'per_unit' => 4))),
|
||||
124 => array('name' => "Axeman's Battle Axe", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 13, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 13, 'per_unit' => 5))),
|
||||
// Teutons - u15 Paladin
|
||||
125 => array('name' => "Paladin's Small Hammer", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 15, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 15, 'per_unit' => 6))),
|
||||
126 => array('name' => "Paladin's Hammer", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 15, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 15, 'per_unit' => 8))),
|
||||
127 => array('name' => "Paladin's Sledgehammer", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 15, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 15, 'per_unit' => 10))),
|
||||
// Teutons - u16 Teutonic Knight
|
||||
128 => array('name' => "Teutonic Knight's Short Sword", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 16, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 16, 'per_unit' => 9))),
|
||||
129 => array('name' => "Teutonic Knight's Sword", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 16, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 16, 'per_unit' => 12))),
|
||||
130 => array('name' => "Teutonic Knight's Long Sword", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 16, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 16, 'per_unit' => 15))),
|
||||
|
||||
// Gauls - u21 Phalanx (design sheet tribe "Daci": Scutier)
|
||||
131 => array('name' => "Phalanx's Pitchfork", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 21, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 21, 'per_unit' => 3))),
|
||||
132 => array('name' => "Phalanx's Spear", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 21, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 21, 'per_unit' => 4))),
|
||||
133 => array('name' => "Phalanx's Lance", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 21, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 21, 'per_unit' => 5))),
|
||||
// Gauls - u22 Swordsman (Pedestras)
|
||||
134 => array('name' => "Swordsman's Short Sword", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 22, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 22, 'per_unit' => 3))),
|
||||
135 => array('name' => "Swordsman's Sword", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 22, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 22, 'per_unit' => 4))),
|
||||
136 => array('name' => "Swordsman's Long Sword", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 22, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 22, 'per_unit' => 5))),
|
||||
// Gauls - u24 Theutates Thunder (Calaret Fulger)
|
||||
137 => array('name' => "Thunder's Short Bow", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 24, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 24, 'per_unit' => 6))),
|
||||
138 => array('name' => "Thunder's Bow", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 24, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 24, 'per_unit' => 8))),
|
||||
139 => array('name' => "Thunder's Long Bow", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 24, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 24, 'per_unit' => 10))),
|
||||
// Gauls - u25 Druidrider
|
||||
140 => array('name' => "Druidrider's Baton", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 25, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 25, 'per_unit' => 6))),
|
||||
141 => array('name' => "Druidrider's Staff", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 25, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 25, 'per_unit' => 8))),
|
||||
142 => array('name' => "Druidrider's Great Staff", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 25, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 25, 'per_unit' => 10))),
|
||||
// Gauls - u26 Haeduan (Tarabostes)
|
||||
143 => array('name' => "Haeduan's Light Lance", 'slot' => HSLOT_RIGHT, 'tier' => 1, 'unit' => 26, 'bonus' => array(HB_FIGHT => 500, HB_UNIT_BONUS => array('unit' => 26, 'per_unit' => 9))),
|
||||
144 => array('name' => "Haeduan's Lance", 'slot' => HSLOT_RIGHT, 'tier' => 2, 'unit' => 26, 'bonus' => array(HB_FIGHT => 1000, HB_UNIT_BONUS => array('unit' => 26, 'per_unit' => 12))),
|
||||
145 => array('name' => "Haeduan's Heavy Lance", 'slot' => HSLOT_RIGHT, 'tier' => 3, 'unit' => 26, 'bonus' => array(HB_FIGHT => 1500, HB_UNIT_BONUS => array('unit' => 26, 'per_unit' => 15))),
|
||||
|
||||
/* ---------------- CONSUMABLES (sheet: Consumabile; stackable, bag) ---------------- */
|
||||
200 => array('name' => 'Ointment', 'slot' => HSLOT_BAG, 'tier' => 1, 'bonus' => array(HB_HEAL_SELF => 1)), // +1% HP per unit, max 99%, hero must be alive
|
||||
201 => array('name' => 'Scroll', 'slot' => HSLOT_BAG, 'tier' => 1, 'bonus' => array(HB_SCROLL => 10)), // +10 XP per scroll
|
||||
202 => array('name' => 'Bucket of Water', 'slot' => HSLOT_BAG, 'tier' => 2, 'bonus' => array(HB_BUCKET => 1)), // instant revive, no resource cost
|
||||
203 => array('name' => 'Law Tablet', 'slot' => HSLOT_BAG, 'tier' => 2, 'bonus' => array(HB_LOYALTY => 1)), // +1% own village loyalty, max 125%
|
||||
204 => array('name' => 'Book of Wisdom', 'slot' => HSLOT_BAG, 'tier' => 3, 'bonus' => array(HB_RESET => 1)), // reset attribute points
|
||||
205 => array('name' => 'Artwork', 'slot' => HSLOT_BAG, 'tier' => 3, 'bonus' => array(HB_ARTWORK => 1)), // CP = daily production, cap 5000 (1x) / 2500 (speed)
|
||||
206 => array('name' => 'Small Bandage', 'slot' => HSLOT_BAG, 'tier' => 1, 'bonus' => array(HB_HEAL_TROOP => 25)), // heals 25% of hero's army
|
||||
207 => array('name' => 'Bandage', 'slot' => HSLOT_BAG, 'tier' => 2, 'bonus' => array(HB_HEAL_TROOP => 33)), // heals 33% of hero's army
|
||||
208 => array('name' => 'Cage', 'slot' => HSLOT_BAG, 'tier' => 1, 'bonus' => array(HB_CAGE => 1)), // traps 1 oasis animal per cage
|
||||
);
|
||||
|
||||
/* Helmets 7-9 (culture) are HALVED on speed servers per the design sheet
|
||||
* ("+100/day (1x) / +50 (3x)"). Runtime consumers must apply:
|
||||
* value = (SPEED >= 3) ? value / 2 : value
|
||||
* whenever 'x2_on_speed' === false is explicitly set (see HeroItems::scaledBonusValue()). */
|
||||
|
||||
/* ============================================================================
|
||||
* ADVENTURE CONFIGURATION (Phase 1, itemids updated to the new catalog;
|
||||
* equipment drops added per design feedback: adventures are the primary
|
||||
* T4 source of gear, rarer than consumables and weighted toward low tiers)
|
||||
* ------------------------------------------------------------------------- */
|
||||
$heroAdventureConfig = array(
|
||||
'offer_lifetime' => 24 * 3600,
|
||||
'max_offers' => 3,
|
||||
'refresh_interval' => 8 * 3600,
|
||||
|
||||
// Tier weights for equipment drops (percent, must sum to 100).
|
||||
'equip_tier_weights' => array(1 => 70, 2 => 25, 3 => 5),
|
||||
|
||||
0 => array( // NORMAL
|
||||
'label' => 'HERO_ADV_NORMAL',
|
||||
'exp' => array(6, 12),
|
||||
'silver' => array(0, 40),
|
||||
'hp_loss' => array(0, 8),
|
||||
'resources' => array(0, 400),
|
||||
'equip_chance' => 3, // % chance: 1 random piece of gear
|
||||
'item_chance' => 5, // % chance: consumables (rolled only if no gear dropped)
|
||||
'consumable' => array(200, 201, 208), // Ointment, Scroll, Cage
|
||||
),
|
||||
1 => array( // HARD
|
||||
'label' => 'HERO_ADV_HARD',
|
||||
'exp' => array(18, 30),
|
||||
'silver' => array(30, 120),
|
||||
'hp_loss' => array(10, 35),
|
||||
'resources' => array(200, 1200),
|
||||
'equip_chance' => 10,
|
||||
'item_chance' => 20,
|
||||
'consumable' => array(200, 201, 203, 206, 208), // + Law Tablet, Small Bandage
|
||||
),
|
||||
);
|
||||
|
||||
/* Convenience index: itemids grouped by slot. */
|
||||
$heroSlotIndex = array();
|
||||
foreach ($heroItemCatalog as $iid => $def) {
|
||||
$heroSlotIndex[$def['slot']][] = $iid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the display name of an item. Checks for a HERO_ITEM_<id> lang
|
||||
* constant first (future localisation hook), falls back to the English
|
||||
* catalog name, and finally to 'Unknown item' for ids not in the catalog.
|
||||
*/
|
||||
if (!function_exists('heroItemName')) {
|
||||
function heroItemName($itemid) {
|
||||
global $heroItemCatalog;
|
||||
$const = 'HERO_ITEM_' . (int) $itemid;
|
||||
if (defined($const)) {
|
||||
return constant($const);
|
||||
}
|
||||
return isset($heroItemCatalog[$itemid]['name']) ? $heroItemCatalog[$itemid]['name'] : 'Unknown item';
|
||||
}
|
||||
}
|
||||
@@ -5863,6 +5863,15 @@ References: User ID/Message ID, Mode
|
||||
case 8:
|
||||
$q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 and " . TB_PREFIX . "attacks.attack_type = 1 ORDER BY endtime ASC";
|
||||
break;
|
||||
// T4 hero port: hero travelling to an adventure (plain select,
|
||||
// ref points to hero_adventure - NOT to attacks, so no join).
|
||||
case 20:
|
||||
$q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 20 and proc = 0 ORDER BY endtime ASC";
|
||||
break;
|
||||
// T4 hero port: hero returning from an adventure.
|
||||
case 21:
|
||||
$q = "SELECT * FROM " . TB_PREFIX . "movement where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and sort_type = 21 and proc = 0 ORDER BY endtime ASC";
|
||||
break;
|
||||
case 34:
|
||||
$q = "SELECT * FROM " . TB_PREFIX . "movement, " . TB_PREFIX . "attacks where " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 3 or " . TB_PREFIX . "movement.`" . $where . "` IN(".implode(', ', $village).") and " . TB_PREFIX . "movement.ref = " . TB_PREFIX . "attacks.id and " . TB_PREFIX . "movement.proc = 0 and " . TB_PREFIX . "movement.sort_type = 4 ORDER BY endtime ASC";
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : HeroAdventure.php ##
|
||||
## Type : T4 Hero port - adventures backend (Phase 3) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## Adventure lifecycle: ##
|
||||
## offer (status 0) -> running (status 1, movement sort_type 20) ##
|
||||
## -> arrival processed by Automation (rewards + ntype 26 report, ##
|
||||
## status 2, return movement sort_type 21 carrying resources) ##
|
||||
## -> return processed by Automation (hero back in units.hero, ##
|
||||
## resources credited) ##
|
||||
## ##
|
||||
## Death: if HP loss would drop health to 0, the hero dies AT the site ##
|
||||
## (dead=1, health=0, loot forfeited, no return movement). units.hero was ##
|
||||
## already decremented at send - identical end state to dying in battle, ##
|
||||
## so the existing revive flow (37_revive.tpl + handleReviveCompletion) ##
|
||||
## works unchanged. ##
|
||||
## ##
|
||||
## XP: only `experience` is incremented (with the helmet HB_XP% bonus). ##
|
||||
## Level-up/points are intentionally NOT touched here - Automation's ##
|
||||
## updateHero()/calculateLevelUp() already handles that every tick. ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
class HeroAdventure
|
||||
{
|
||||
/** Hero base speed on foot, fields/hour (T4 convention). */
|
||||
const BASE_HERO_SPEED = 7;
|
||||
|
||||
/** How far from the hero's village adventure sites are picked (tiles). */
|
||||
const SITE_MIN_RADIUS = 3;
|
||||
const SITE_MAX_RADIUS = 25;
|
||||
|
||||
/** Max users to top-up with fresh offers per automation tick (bounded work). */
|
||||
const BATCH_LIMIT = 50;
|
||||
|
||||
/** startAdventure() result codes */
|
||||
const START_OK = 1;
|
||||
const START_INVALID = 0; // offer missing/expired/not yours/already running
|
||||
const START_NO_HERO = -1; // no living hero
|
||||
const START_HERO_AWAY = -2; // hero not at home village
|
||||
|
||||
/** @var mysqli */
|
||||
private $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $database;
|
||||
$this->db = $database->dblink;
|
||||
|
||||
global $heroItemCatalog, $heroAdventureConfig;
|
||||
if (!isset($heroAdventureConfig)) {
|
||||
include_once __DIR__ . '/Data/hero_items.php';
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* OFFERS
|
||||
* ===================================================================== */
|
||||
|
||||
/** Non-expired, still-available offers for a user. */
|
||||
public function getOffers($uid)
|
||||
{
|
||||
$uid = (int) $uid; $now = time();
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, uid, wref, difficulty, duration, created, expire, status, moveid
|
||||
FROM " . TB_PREFIX . "hero_adventure
|
||||
WHERE uid = ? AND status = 0 AND expire > ?
|
||||
ORDER BY difficulty ASC, duration ASC"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $now);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$out = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$out[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** The adventure the hero is currently running, or null. */
|
||||
public function getRunning($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT a.id, a.wref, a.difficulty, a.moveid, m.endtime, m.sort_type
|
||||
FROM " . TB_PREFIX . "hero_adventure a
|
||||
JOIN " . TB_PREFIX . "movement m ON m.moveid = a.moveid
|
||||
WHERE a.uid = ? AND a.status = 1 AND m.proc = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$row = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top up a single user's offer list (expires stale ones first).
|
||||
* Respects max_offers and refresh_interval from $heroAdventureConfig.
|
||||
* Returns the number of offers created.
|
||||
*/
|
||||
public function generateOffers($uid)
|
||||
{
|
||||
global $database, $heroAdventureConfig;
|
||||
$uid = (int) $uid; $now = time();
|
||||
|
||||
// Living hero needed - offers are anchored to the hero's village.
|
||||
$hero = $this->getLivingHero($uid);
|
||||
if (!$hero) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Expire stale offers (kept as rows with status untouched would clutter
|
||||
// the uid_status index; a hard delete of expired *available* offers is safe).
|
||||
$stmt = $this->db->prepare(
|
||||
"DELETE FROM " . TB_PREFIX . "hero_adventure WHERE uid = ? AND status = 0 AND expire <= ?"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $now);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Respect refresh interval: don't regenerate too often.
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT COUNT(*) AS cnt, COALESCE(MAX(created), 0) AS latest
|
||||
FROM " . TB_PREFIX . "hero_adventure WHERE uid = ? AND status = 0"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$info = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$have = (int) $info['cnt'];
|
||||
$latest = (int) $info['latest'];
|
||||
$max = (int) $heroAdventureConfig['max_offers'];
|
||||
|
||||
if ($have >= $max) {
|
||||
return 0;
|
||||
}
|
||||
if ($latest > 0 && ($now - $latest) < (int) $heroAdventureConfig['refresh_interval']) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pick random unoccupied tiles around the hero's village.
|
||||
$coor = $database->getCoor($hero['wref']);
|
||||
if (!$coor) {
|
||||
return 0;
|
||||
}
|
||||
$x = (int) $coor['x']; $y = (int) $coor['y']; $r = self::SITE_MAX_RADIUS;
|
||||
|
||||
$need = $max - $have;
|
||||
// Min radius enforced in SQL so LIMIT never undercounts (the bounding
|
||||
// box doesn't wrap around the map seam, so plain coordinate distance
|
||||
// is exact within it).
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, x, y FROM " . TB_PREFIX . "wdata
|
||||
WHERE occupied = 0 AND id <> ?
|
||||
AND x BETWEEN ? AND ? AND y BETWEEN ? AND ?
|
||||
AND (POW(x - ?, 2) + POW(y - ?, 2)) >= ?
|
||||
ORDER BY RAND() LIMIT ?"
|
||||
);
|
||||
$wref = (int) $hero['wref'];
|
||||
$x1 = $x - $r; $x2 = $x + $r; $y1 = $y - $r; $y2 = $y + $r;
|
||||
$minSq = self::SITE_MIN_RADIUS * self::SITE_MIN_RADIUS;
|
||||
$stmt->bind_param('iiiiiiiii', $wref, $x1, $x2, $y1, $y2, $x, $y, $minSq, $need);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$tiles = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$tiles[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
$created = 0;
|
||||
foreach ($tiles as $tile) {
|
||||
$dist = $database->getDistance($x, $y, $tile['x'], $tile['y']);
|
||||
// Hard adventures ~30% of offers.
|
||||
$difficulty = (mt_rand(1, 100) <= 30) ? 1 : 0;
|
||||
$duration = $this->travelSeconds($uid, $dist);
|
||||
$expire = $now + (int) $heroAdventureConfig['offer_lifetime'];
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "hero_adventure
|
||||
(uid, wref, difficulty, duration, created, expire, status, moveid)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 0, 0)"
|
||||
);
|
||||
$tileId = (int) $tile['id'];
|
||||
$stmt->bind_param('iiiiii', $uid, $tileId, $difficulty, $duration, $now, $expire);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
$created++;
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch top-up used by Automation: refresh offers for users with living,
|
||||
* at-home heroes that are below max_offers. Work bounded by BATCH_LIMIT.
|
||||
*/
|
||||
public function generateOffersBatch()
|
||||
{
|
||||
global $heroAdventureConfig;
|
||||
$now = time();
|
||||
$threshold = $now - (int) $heroAdventureConfig['refresh_interval'];
|
||||
$max = (int) $heroAdventureConfig['max_offers'];
|
||||
$limit = self::BATCH_LIMIT;
|
||||
|
||||
// Users with a living hero whose available-offer count is below max
|
||||
// and whose newest offer (if any) is older than the refresh interval.
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT h.uid
|
||||
FROM " . TB_PREFIX . "hero h
|
||||
LEFT JOIN " . TB_PREFIX . "hero_adventure a
|
||||
ON a.uid = h.uid AND a.status = 0 AND a.expire > ?
|
||||
WHERE h.dead = 0
|
||||
GROUP BY h.uid
|
||||
HAVING COUNT(a.id) < ? AND COALESCE(MAX(a.created), 0) < ?
|
||||
LIMIT ?"
|
||||
);
|
||||
$stmt->bind_param('iiii', $now, $max, $threshold, $limit);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$uids = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$uids[] = (int) $row['uid'];
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
foreach ($uids as $uid) {
|
||||
$this->generateOffers($uid);
|
||||
}
|
||||
return count($uids);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* START
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Send the hero on an available adventure offer.
|
||||
* Returns one of the START_* class constants.
|
||||
*/
|
||||
public function startAdventure($uid, $adventureId)
|
||||
{
|
||||
$uid = (int) $uid; $adventureId = (int) $adventureId; $now = time();
|
||||
|
||||
$hero = $this->getLivingHero($uid);
|
||||
if (!$hero) {
|
||||
return self::START_NO_HERO;
|
||||
}
|
||||
// Only one adventure at a time.
|
||||
if ($this->getRunning($uid)) {
|
||||
return self::START_INVALID;
|
||||
}
|
||||
|
||||
// Load the offer (must be this user's, available, not expired).
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, wref, difficulty, duration FROM " . TB_PREFIX . "hero_adventure
|
||||
WHERE id = ? AND uid = ? AND status = 0 AND expire > ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('iii', $adventureId, $uid, $now);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$offer = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
if (!$offer) {
|
||||
return self::START_INVALID;
|
||||
}
|
||||
|
||||
// Hero must be AT HOME: race-safe conditional decrement of units.hero.
|
||||
$homeWref = (int) $hero['wref'];
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "units SET hero = hero - 1 WHERE vref = ? AND hero >= 1 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $homeWref);
|
||||
$stmt->execute();
|
||||
$atHome = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
if (!$atHome) {
|
||||
return self::START_HERO_AWAY;
|
||||
}
|
||||
|
||||
// Claim the offer atomically; if someone double-clicked and a parallel
|
||||
// request claimed it first, put the hero back and bail out.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_adventure SET status = 1 WHERE id = ? AND status = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $adventureId);
|
||||
$stmt->execute();
|
||||
$claimed = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
if (!$claimed) {
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "units SET hero = hero + 1 WHERE vref = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $homeWref);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return self::START_INVALID;
|
||||
}
|
||||
|
||||
// Movement out: from = home village, to = adventure tile, ref = adventure id.
|
||||
// Recompute duration now (equipment may have changed since the offer was made).
|
||||
global $database;
|
||||
$coorHome = $database->getCoor($homeWref);
|
||||
$coorSite = $database->getCoor((int) $offer['wref']);
|
||||
$dist = $database->getDistance($coorHome['x'], $coorHome['y'], $coorSite['x'], $coorSite['y']);
|
||||
$duration = $this->travelSeconds($uid, $dist);
|
||||
$endtime = $now + $duration;
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "movement
|
||||
(sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop, marker)
|
||||
VALUES (?, ?, ?, ?, 0, ?, ?, 0, 1, 0, 0, 0, 0, 0)"
|
||||
);
|
||||
$sortType = MOVEMENT_ADVENTURE_OUT; $siteWref = (int) $offer['wref'];
|
||||
$stmt->bind_param('iiiiii', $sortType, $homeWref, $siteWref, $adventureId, $now, $endtime);
|
||||
$stmt->execute();
|
||||
$moveid = (int) $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_adventure SET moveid = ? WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $moveid, $adventureId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
return self::START_OK;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* AUTOMATION PROCESSORS
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Process heroes ARRIVING at adventure sites (sort_type 20).
|
||||
* Rolls rewards, applies HP loss/XP/silver/items, files the ntype 26
|
||||
* report, and creates the return movement carrying the resource loot.
|
||||
*/
|
||||
public function processArrivals()
|
||||
{
|
||||
global $database, $heroAdventureConfig;
|
||||
$now = time();
|
||||
|
||||
$q = "SELECT m.moveid, m.`from`, m.`to`, m.ref, m.endtime,
|
||||
a.id AS advid, a.uid, a.difficulty
|
||||
FROM " . TB_PREFIX . "movement m
|
||||
JOIN " . TB_PREFIX . "hero_adventure a ON a.id = m.ref
|
||||
WHERE m.sort_type = " . (int) MOVEMENT_ADVENTURE_OUT . "
|
||||
AND m.proc = 0 AND m.endtime < $now";
|
||||
$rows = $database->query_return($q);
|
||||
if (!$rows || !count($rows)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$processed = 0;
|
||||
foreach ($rows as $data) {
|
||||
if (!$this->claimMovement($data['moveid'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$uid = (int) $data['uid'];
|
||||
$hero = $this->getLivingHero($uid);
|
||||
$cfg = $heroAdventureConfig[(int) $data['difficulty']];
|
||||
|
||||
// Hero vanished/died meanwhile (edge case): close the adventure quietly.
|
||||
if (!$hero) {
|
||||
$this->finishAdventure($data['advid']);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- Roll rewards ---- */
|
||||
$exp = mt_rand($cfg['exp'][0], $cfg['exp'][1]);
|
||||
$silver = mt_rand($cfg['silver'][0], $cfg['silver'][1]);
|
||||
$hpLoss = mt_rand($cfg['hp_loss'][0], $cfg['hp_loss'][1]);
|
||||
$wood = mt_rand($cfg['resources'][0], $cfg['resources'][1]);
|
||||
$clay = mt_rand($cfg['resources'][0], $cfg['resources'][1]);
|
||||
$iron = mt_rand($cfg['resources'][0], $cfg['resources'][1]);
|
||||
$crop = mt_rand($cfg['resources'][0], $cfg['resources'][1]);
|
||||
|
||||
// Item drop: gear first (rarer, tier-weighted toward T1), and only
|
||||
// if no gear dropped, roll the consumable pool. One item max.
|
||||
$itemId = 0; $itemQty = 0;
|
||||
if (mt_rand(1, 100) <= (int) ($cfg['equip_chance'] ?? 0)) {
|
||||
$itemId = $this->rollEquipment();
|
||||
$itemQty = ($itemId > 0) ? 1 : 0;
|
||||
}
|
||||
if ($itemId === 0 && mt_rand(1, 100) <= (int) $cfg['item_chance']) {
|
||||
$pool = $cfg['consumable'];
|
||||
$itemId = (int) $pool[array_rand($pool)];
|
||||
$itemQty = 1;
|
||||
}
|
||||
|
||||
$health = (float) $hero['health'];
|
||||
$newHealth = $health - $hpLoss;
|
||||
|
||||
if ($newHealth <= 0) {
|
||||
/* ---- Hero dies at the site: loot forfeited, no return trip. ---- */
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET dead = 1, health = 0, lastupdate = ? WHERE heroid = ? LIMIT 1"
|
||||
);
|
||||
$heroid = (int) $hero['heroid'];
|
||||
$stmt->bind_param('ii', $now, $heroid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$payload = 'difficulty=' . (int) $data['difficulty'] . '&died=1&hp=' . $hpLoss;
|
||||
$database->addNotice($uid, (int) $data['from'], 0, NTYPE_ADVENTURE_REPORT,
|
||||
'Hero fell on an adventure', $payload, $now);
|
||||
|
||||
$this->finishAdventure($data['advid']);
|
||||
$processed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ---- Hero survives: apply rewards ---- */
|
||||
$heroItems = new HeroItems();
|
||||
|
||||
// XP with helmet bonus; level-up handled by Automation::updateHero().
|
||||
$bonuses = $heroItems->getBonuses($uid);
|
||||
$xpGain = (int) floor($exp * (100 + (int) $bonuses[HB_XP]) / 100);
|
||||
$database->modifyHeroXp('experience', $xpGain, (int) $hero['heroid']);
|
||||
|
||||
// Health loss.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET health = ? WHERE heroid = ? LIMIT 1"
|
||||
);
|
||||
$heroid = (int) $hero['heroid'];
|
||||
$stmt->bind_param('di', $newHealth, $heroid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Silver + item drop.
|
||||
if ($silver > 0) {
|
||||
$heroItems->addSilver($uid, $silver);
|
||||
}
|
||||
if ($itemId > 0) {
|
||||
$heroItems->addItem($uid, $itemId, $itemQty);
|
||||
}
|
||||
|
||||
/* ---- Return movement carries the resource loot home ---- */
|
||||
// Same travel time back as out: reuse the adventure's stored one-way duration.
|
||||
$legDuration = $this->travelLegDuration($data);
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "movement
|
||||
(sort_type, `from`, `to`, ref, ref2, starttime, endtime, proc, send, wood, clay, iron, crop, marker)
|
||||
VALUES (?, ?, ?, ?, 0, ?, ?, 0, 1, ?, ?, ?, ?, 0)"
|
||||
);
|
||||
$sortType = MOVEMENT_ADVENTURE_BACK;
|
||||
$fromSite = (int) $data['to']; $toHome = (int) $data['from'];
|
||||
$advid = (int) $data['advid'];
|
||||
$end = $now + $legDuration;
|
||||
$stmt->bind_param('iiiiiiiiii', $sortType, $fromSite, $toHome, $advid, $now, $end, $wood, $clay, $iron, $crop);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
/* ---- Report (ntype 26) ---- */
|
||||
$payload = 'difficulty=' . (int) $data['difficulty']
|
||||
. '&exp=' . $xpGain . '&silver=' . $silver . '&hp=' . $hpLoss
|
||||
. '&wood=' . $wood . '&clay=' . $clay . '&iron=' . $iron . '&crop=' . $crop;
|
||||
if ($itemId > 0) {
|
||||
$payload .= '&itemid=' . $itemId . '&itemqty=' . $itemQty;
|
||||
}
|
||||
$database->addNotice($uid, $toHome, 0, NTYPE_ADVENTURE_REPORT,
|
||||
'Hero returned from an adventure', $payload, $now);
|
||||
|
||||
$this->finishAdventure($data['advid']);
|
||||
$processed++;
|
||||
}
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process heroes RETURNING home (sort_type 21): hero re-enters units.hero,
|
||||
* resource loot is credited to the village.
|
||||
*/
|
||||
public function processReturns()
|
||||
{
|
||||
global $database;
|
||||
$now = time();
|
||||
|
||||
$q = "SELECT moveid, `from`, `to`, wood, clay, iron, crop
|
||||
FROM " . TB_PREFIX . "movement
|
||||
WHERE sort_type = " . (int) MOVEMENT_ADVENTURE_BACK . "
|
||||
AND proc = 0 AND endtime < $now";
|
||||
$rows = $database->query_return($q);
|
||||
if (!$rows || !count($rows)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$processed = 0;
|
||||
foreach ($rows as $data) {
|
||||
if (!$this->claimMovement($data['moveid'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$home = (int) $data['to'];
|
||||
|
||||
// Hero is home again.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "units SET hero = hero + 1 WHERE vref = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $home);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Credit the loot (same pattern as returnunitsComplete).
|
||||
if ($data['wood'] + $data['clay'] + $data['iron'] + $data['crop'] > 0) {
|
||||
$database->modifyResource($home, $data['wood'], $data['clay'], $data['iron'], $data['crop'], 1);
|
||||
$database->addStarvationData($home);
|
||||
}
|
||||
$processed++;
|
||||
}
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll one random piece of EQUIPMENT (any non-bag catalog item, including
|
||||
* weapons for other tribes - unusable finds feed the auction house, same
|
||||
* as T4). Tier picked by 'equip_tier_weights' (T1 common, T3 very rare),
|
||||
* then a uniform pick inside that tier. Returns an itemid, or 0 if the
|
||||
* rolled tier happens to be empty (defensive; never true with the
|
||||
* shipped catalog).
|
||||
*/
|
||||
public function rollEquipment()
|
||||
{
|
||||
global $heroItemCatalog, $heroAdventureConfig;
|
||||
|
||||
$weights = $heroAdventureConfig['equip_tier_weights'];
|
||||
$roll = mt_rand(1, 100); $acc = 0; $tier = 1;
|
||||
foreach ($weights as $t => $w) {
|
||||
$acc += (int) $w;
|
||||
if ($roll <= $acc) { $tier = (int) $t; break; }
|
||||
}
|
||||
|
||||
$pool = array();
|
||||
foreach ($heroItemCatalog as $iid => $def) {
|
||||
if ((int) $def['slot'] !== HSLOT_BAG && (int) $def['tier'] === $tier) {
|
||||
$pool[] = $iid;
|
||||
}
|
||||
}
|
||||
return count($pool) ? (int) $pool[array_rand($pool)] : 0;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* INTERNALS
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Travel time in seconds for a given distance, honoring equipment:
|
||||
* horse replaces the on-foot base speed, spurs add on top (only with a
|
||||
* horse - already enforced inside HeroItems::getBonuses()).
|
||||
*/
|
||||
public function travelSeconds($uid, $distance)
|
||||
{
|
||||
$heroItems = new HeroItems();
|
||||
$bonuses = $heroItems->getBonuses((int) $uid);
|
||||
|
||||
$speed = ((int) $bonuses[HB_HORSE_SPEED] > 0)
|
||||
? (int) $bonuses[HB_HORSE_SPEED] + (int) $bonuses[HB_MOUNT_SPEED]
|
||||
: self::BASE_HERO_SPEED;
|
||||
|
||||
$increase = defined('INCREASE_SPEED') ? max(1, (int) INCREASE_SPEED) : 1;
|
||||
return max(60, (int) round($distance / $speed * 3600 / $increase));
|
||||
}
|
||||
|
||||
/** Duration of the just-finished leg (endtime - starttime is not selected; use stored offer duration). */
|
||||
private function travelLegDuration($data)
|
||||
{
|
||||
// The return leg mirrors the outbound one. We recompute from the
|
||||
// adventure's stored one-way duration to avoid drift when Automation
|
||||
// runs late (endtime long in the past).
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT duration FROM " . TB_PREFIX . "hero_adventure WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$advid = (int) $data['advid'];
|
||||
$stmt->bind_param('i', $advid);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($duration);
|
||||
$found = $stmt->fetch();
|
||||
$stmt->close();
|
||||
return $found ? max(60, (int) $duration) : 3600;
|
||||
}
|
||||
|
||||
/** Same claim pattern as Automation::claimMovementRecord (race-safe). */
|
||||
private function claimMovement($moveid)
|
||||
{
|
||||
$moveid = (int) $moveid;
|
||||
if ($moveid <= 0) {
|
||||
return false;
|
||||
}
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "movement SET proc = 1 WHERE moveid = ? AND proc = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $moveid);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows === 1;
|
||||
$stmt->close();
|
||||
return $ok;
|
||||
}
|
||||
|
||||
private function finishAdventure($advid)
|
||||
{
|
||||
$advid = (int) $advid;
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_adventure SET status = 2 WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $advid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
private function getLivingHero($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT heroid, uid, unit, wref, level, experience, health, dead
|
||||
FROM " . TB_PREFIX . "hero WHERE uid = ? AND dead = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$row = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $row ?: null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : HeroAuction.php ##
|
||||
## Type : T4 Hero port - auction house backend (Phase 4) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## T4-style auction house with PROXY BIDDING (eBay model): ##
|
||||
## - a bid is a secret MAXIMUM (bid_max); the visible price ##
|
||||
## (silver_current) only rises to (loser's max + 1), capped at the ##
|
||||
## winner's max. ##
|
||||
## ##
|
||||
## SILVER ESCROW MODEL (documented invariant): ##
|
||||
## - when a bidder becomes top bidder, their FULL bid_max is deducted; ##
|
||||
## - when outbid, their full bid_max is refunded immediately; ##
|
||||
## - at finalization the winner is refunded (bid_max - silver_current), ##
|
||||
## so they only ever pay the visible price. ##
|
||||
## => at any moment: sum of all player silver + escrowed bid_max of top ##
|
||||
## bidders is constant (minus the seller fee at finalization). ##
|
||||
## ##
|
||||
## SELLER FEE: player sellers receive silver_current minus AUCTION_FEE_PCT ##
|
||||
## (10%, T4 convention). NPC-seller (seller = 0) proceeds vanish (silver ##
|
||||
## sink). Unsold player items are returned to the seller's inventory. ##
|
||||
## ##
|
||||
## No cancellation once listed (T4 convention) - flagged, not implemented. ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
class HeroAuction
|
||||
{
|
||||
/** Seller fee percent taken from the hammer price (player sellers only). */
|
||||
const AUCTION_FEE_PCT = 10;
|
||||
|
||||
/** Minimum bid increment over the visible price. */
|
||||
const MIN_INCREMENT = 1;
|
||||
|
||||
/** Allowed listing durations, seconds (4h / 8h / 24h like T4). */
|
||||
public static $allowedDurations = array(14400, 28800, 86400);
|
||||
|
||||
/** How many NPC auctions to keep open at once. */
|
||||
const NPC_OPEN_TARGET = 10;
|
||||
|
||||
/** NPC listing duration: 8 hours. */
|
||||
const NPC_DURATION = 28800;
|
||||
|
||||
/** status values (mirror the auction table docs from Phase 1) */
|
||||
const ST_OPEN = 0;
|
||||
const ST_SOLD = 1; // reserved intermediate state; finalization goes straight to 2
|
||||
const ST_PROCESSED = 2;
|
||||
const ST_EXPIRED = 3;
|
||||
|
||||
/** result codes for placeBid() / createAuction() */
|
||||
const BID_OK = 1;
|
||||
const BID_OUTBID = 2; // accepted, but instantly below the holder's secret max
|
||||
const BID_INVALID = 0; // bad auction/own auction/too low
|
||||
const BID_NO_SILVER = -1;
|
||||
|
||||
/** @var mysqli */
|
||||
private $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $database;
|
||||
$this->db = $database->dblink;
|
||||
|
||||
global $heroItemCatalog;
|
||||
if (!isset($heroItemCatalog)) {
|
||||
include_once __DIR__ . '/Data/hero_items.php';
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* READS
|
||||
* ===================================================================== */
|
||||
|
||||
/** All open, unexpired auctions (newest ending first optional; default: ending soonest). */
|
||||
public function getOpenAuctions($limit = 50)
|
||||
{
|
||||
$now = time(); $limit = max(1, (int) $limit);
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, seller, itemid, slot, stat_value, quantity,
|
||||
silver_start, silver_current, bidder, created, time_end, status
|
||||
FROM " . TB_PREFIX . "auction
|
||||
WHERE status = " . self::ST_OPEN . " AND time_end > ?
|
||||
ORDER BY time_end ASC LIMIT ?"
|
||||
);
|
||||
$stmt->bind_param('ii', $now, $limit);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$out = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$row['name'] = heroItemName($row['itemid']);
|
||||
// bid_max intentionally NOT selected - it's the bidder's secret.
|
||||
$out[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Auctions where $uid is the current top bidder. */
|
||||
public function getMyBids($uid)
|
||||
{
|
||||
$uid = (int) $uid; $now = time();
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, seller, itemid, quantity, silver_current, bid_max, time_end
|
||||
FROM " . TB_PREFIX . "auction
|
||||
WHERE bidder = ? AND status = " . self::ST_OPEN . " AND time_end > ?
|
||||
ORDER BY time_end ASC"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $now);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$out = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$row['name'] = heroItemName($row['itemid']);
|
||||
$out[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Open auctions listed by $uid. */
|
||||
public function getMySales($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, itemid, quantity, silver_start, silver_current, bidder, time_end, status
|
||||
FROM " . TB_PREFIX . "auction
|
||||
WHERE seller = ? AND status = " . self::ST_OPEN . "
|
||||
ORDER BY time_end ASC"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$out = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$row['name'] = heroItemName($row['itemid']);
|
||||
$out[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
return $out;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* LISTING
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* List an owned, UNEQUIPPED item (or part of a consumable stack).
|
||||
* The item leaves the inventory immediately (escrowed in the auction row).
|
||||
* Returns the new auction id, or 0 on failure.
|
||||
*/
|
||||
public function createAuction($uid, $inventoryRowId, $quantity, $startPrice, $duration)
|
||||
{
|
||||
$uid = (int) $uid; $inventoryRowId = (int) $inventoryRowId;
|
||||
$quantity = max(1, (int) $quantity);
|
||||
$startPrice = max(1, (int) $startPrice);
|
||||
$duration = (int) $duration;
|
||||
|
||||
if (!in_array($duration, self::$allowedDurations, true)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
$row = null;
|
||||
foreach ($heroItems->getInventory($uid) as $r) {
|
||||
if ((int) $r['id'] === $inventoryRowId) { $row = $r; break; }
|
||||
}
|
||||
// Must exist, be catalog-known, unequipped, and cover the quantity.
|
||||
if (!$row || $row['orphan'] || (int) $row['equipped'] === 1 || (int) $row['quantity'] < $quantity) {
|
||||
return 0;
|
||||
}
|
||||
// Gear always lists as a single piece.
|
||||
if ((int) $row['def']['slot'] !== HSLOT_BAG) {
|
||||
$quantity = 1;
|
||||
}
|
||||
|
||||
// Take the item out of the inventory first (race-safe decrement inside).
|
||||
if (!$heroItems->removeItem($uid, $inventoryRowId, $quantity)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$now = time(); $end = $now + $duration;
|
||||
$itemid = (int) $row['itemid']; $slot = (int) $row['def']['slot'];
|
||||
$statValue = (int) $row['stat_value'];
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "auction
|
||||
(seller, itemid, slot, stat_value, quantity,
|
||||
silver_start, silver_current, bidder, bid_max, created, time_end, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, " . self::ST_OPEN . ")"
|
||||
);
|
||||
$stmt->bind_param('iiiiiiiii', $uid, $itemid, $slot, $statValue, $quantity,
|
||||
$startPrice, $startPrice, $now, $end);
|
||||
$stmt->execute();
|
||||
$auctionId = (int) $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
return $auctionId;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* BIDDING (proxy model)
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Place a maximum bid. Returns a BID_* code.
|
||||
*
|
||||
* Escrow choreography (all conditional statements, race-safe):
|
||||
* 1. take $maxBid silver from the bidder (refused if balance is short);
|
||||
* 2. try to become top bidder atomically
|
||||
* (WHERE bid_max < $maxBid guards the proxy comparison);
|
||||
* 3a. success -> refund the PREVIOUS top bidder's full escrow;
|
||||
* 3b. failure -> we lost against the holder's secret max: bump the
|
||||
* visible price to min(holderMax, ourMax + 1) and refund ourselves.
|
||||
*/
|
||||
public function placeBid($uid, $auctionId, $maxBid)
|
||||
{
|
||||
$uid = (int) $uid; $auctionId = (int) $auctionId; $maxBid = (int) $maxBid;
|
||||
$now = time();
|
||||
|
||||
// Snapshot for validation (authoritative checks are in the UPDATEs).
|
||||
// bid_max is only used for the self-raise delta below - it is the
|
||||
// caller's OWN secret in that case, never exposed to rivals.
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT seller, silver_current, bidder, bid_max FROM " . TB_PREFIX . "auction
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " AND time_end > ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $auctionId, $now);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$auction = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$auction || (int) $auction['seller'] === $uid) {
|
||||
return self::BID_INVALID;
|
||||
}
|
||||
// A fresh auction (no bidder) can be won AT the start price;
|
||||
// an active one needs current + increment.
|
||||
$minBid = (int) $auction['silver_current']
|
||||
+ ((int) $auction['bidder'] > 0 ? self::MIN_INCREMENT : 0);
|
||||
if ($maxBid < $minBid) {
|
||||
return self::BID_INVALID;
|
||||
}
|
||||
// Self-raise: the old escrow (old bid_max) is already held, so only
|
||||
// the difference is taken; a raise below the current max is pointless.
|
||||
$selfRaise = ((int) $auction['bidder'] === $uid);
|
||||
if ($selfRaise && $maxBid <= (int) $auction['bid_max']) {
|
||||
return self::BID_INVALID;
|
||||
}
|
||||
$escrow = $selfRaise ? $maxBid - (int) $auction['bid_max'] : $maxBid;
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
|
||||
// 1. escrow (conditional - fails on insufficient silver).
|
||||
if (!$heroItems->spendSilver($uid, $escrow)) {
|
||||
return self::BID_NO_SILVER;
|
||||
}
|
||||
|
||||
// 2. capture the current holder as the refund target, then claim.
|
||||
// Correctness anchor is the conditional UPDATE below - this read
|
||||
// only records who to refund if we displace them.
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT bidder, bid_max FROM " . TB_PREFIX . "auction WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $auctionId);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$prev = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if ($selfRaise) {
|
||||
// Deepening our own secret max never moves the visible price and
|
||||
// never refunds anyone - the old escrow stays held, only the
|
||||
// delta was added above.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction
|
||||
SET bid_max = ?
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " AND time_end > ?
|
||||
AND bidder = ? AND bid_max < ?"
|
||||
);
|
||||
$stmt->bind_param('iiiii', $maxBid, $auctionId, $now, $uid, $maxBid);
|
||||
$stmt->execute();
|
||||
$won = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if ($won) {
|
||||
return self::BID_OK;
|
||||
}
|
||||
// Race lost: a rival displaced us between snapshot and update, so
|
||||
// their winning path already refunded our OLD escrow - the delta
|
||||
// is all we still hold. Return it.
|
||||
$heroItems->addSilver($uid, $escrow);
|
||||
return self::BID_OUTBID;
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction
|
||||
SET silver_current = LEAST(?, GREATEST(silver_current, bid_max + " . self::MIN_INCREMENT . ")),
|
||||
bidder = ?, bid_max = ?
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " AND time_end > ?
|
||||
AND bid_max < ? AND seller <> ? AND bidder <> ?"
|
||||
);
|
||||
$stmt->bind_param('iiiiiiii', $maxBid, $uid, $maxBid, $auctionId, $now, $maxBid, $uid, $uid);
|
||||
$stmt->execute();
|
||||
$won = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if ($won) {
|
||||
// 3a. refund the displaced bidder's full escrow.
|
||||
if ($prev && (int) $prev['bidder'] > 0 && (int) $prev['bidder'] !== $uid && (int) $prev['bid_max'] > 0) {
|
||||
$heroItems->addSilver((int) $prev['bidder'], (int) $prev['bid_max']);
|
||||
}
|
||||
return self::BID_OK;
|
||||
}
|
||||
|
||||
// 3b. lost against the holder's secret max (or the auction closed in
|
||||
// the meantime): bump the visible price if still open, refund us.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction
|
||||
SET silver_current = LEAST(bid_max, ? + " . self::MIN_INCREMENT . ")
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " AND time_end > ? AND bid_max >= ?"
|
||||
);
|
||||
$stmt->bind_param('iiii', $maxBid, $auctionId, $now, $maxBid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$heroItems->addSilver($uid, $escrow);
|
||||
return self::BID_OUTBID;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* AUTOMATION PROCESSORS
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Finalize ended auctions:
|
||||
* - with a bidder: award the item, refund (bid_max - silver_current) to
|
||||
* the winner, pay the player seller (minus fee), file reports;
|
||||
* - without: return the item to a player seller, mark expired.
|
||||
* NPC (seller 0) items simply vanish when unsold; NPC proceeds vanish too.
|
||||
*/
|
||||
public function processFinished()
|
||||
{
|
||||
global $database;
|
||||
$now = time();
|
||||
|
||||
$q = "SELECT id, seller, itemid, stat_value, quantity,
|
||||
silver_current, bidder, bid_max
|
||||
FROM " . TB_PREFIX . "auction
|
||||
WHERE status = " . self::ST_OPEN . " AND time_end <= $now";
|
||||
$rows = $database->query_return($q);
|
||||
if (!$rows || !count($rows)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
$processed = 0;
|
||||
|
||||
foreach ($rows as $a) {
|
||||
// Claim the row (same race-safe pattern as movement claiming).
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction SET status = " . self::ST_PROCESSED . "
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " LIMIT 1"
|
||||
);
|
||||
$aid = (int) $a['id'];
|
||||
$stmt->bind_param('i', $aid);
|
||||
$stmt->execute();
|
||||
$claimed = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
if (!$claimed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bidder = (int) $a['bidder'];
|
||||
$seller = (int) $a['seller'];
|
||||
$price = (int) $a['silver_current'];
|
||||
|
||||
if ($bidder > 0) {
|
||||
/* ---- SOLD ---- */
|
||||
$heroItems->addItem($bidder, (int) $a['itemid'], (int) $a['quantity'], (int) $a['stat_value']);
|
||||
|
||||
// Winner escrowed bid_max; they only pay the hammer price.
|
||||
$refund = (int) $a['bid_max'] - $price;
|
||||
if ($refund > 0) {
|
||||
$heroItems->addSilver($bidder, $refund);
|
||||
}
|
||||
|
||||
// Player seller gets the price minus the fee; NPC proceeds sink.
|
||||
if ($seller > 0) {
|
||||
$fee = (int) floor($price * self::AUCTION_FEE_PCT / 100);
|
||||
$heroItems->addSilver($seller, $price - $fee);
|
||||
$database->addNotice($seller, 0, 0, NTYPE_AUCTION_REPORT,
|
||||
'Auction sold',
|
||||
'role=seller&itemid=' . (int) $a['itemid'] . '&qty=' . (int) $a['quantity']
|
||||
. '&price=' . $price . '&fee=' . $fee, $now);
|
||||
}
|
||||
|
||||
$database->addNotice($bidder, 0, 0, NTYPE_AUCTION_REPORT,
|
||||
'Auction won',
|
||||
'role=winner&itemid=' . (int) $a['itemid'] . '&qty=' . (int) $a['quantity']
|
||||
. '&price=' . $price . '&refund=' . max(0, $refund), $now);
|
||||
} else {
|
||||
/* ---- UNSOLD ---- */
|
||||
if ($seller > 0) {
|
||||
$heroItems->addItem($seller, (int) $a['itemid'], (int) $a['quantity'], (int) $a['stat_value']);
|
||||
$database->addNotice($seller, 0, 0, NTYPE_AUCTION_REPORT,
|
||||
'Auction expired',
|
||||
'role=seller&expired=1&itemid=' . (int) $a['itemid'] . '&qty=' . (int) $a['quantity'], $now);
|
||||
}
|
||||
// Correct the claimed status for the unsold case.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction SET status = " . self::ST_EXPIRED . " WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $aid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
$processed++;
|
||||
}
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADMIN-ONLY: cancel an open auction outright (Phase 7 admin panel).
|
||||
* Player-facing cancellation intentionally does not exist (T4 convention);
|
||||
* this is the moderation escape hatch. Refunds the top bidder's full
|
||||
* escrow and returns the item to a player seller (NPC items vanish).
|
||||
* Returns true when an open auction was cancelled.
|
||||
*/
|
||||
public function adminCancel($auctionId)
|
||||
{
|
||||
$auctionId = (int) $auctionId;
|
||||
|
||||
// Snapshot before claiming so we know who to refund.
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT seller, itemid, stat_value, quantity, bidder, bid_max
|
||||
FROM " . TB_PREFIX . "auction
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $auctionId);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$a = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
if (!$a) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Race-safe claim straight to EXPIRED so processFinished can't touch it.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "auction SET status = " . self::ST_EXPIRED . "
|
||||
WHERE id = ? AND status = " . self::ST_OPEN . " LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $auctionId);
|
||||
$stmt->execute();
|
||||
$claimed = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
if (!$claimed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
if ((int) $a['bidder'] > 0 && (int) $a['bid_max'] > 0) {
|
||||
$heroItems->addSilver((int) $a['bidder'], (int) $a['bid_max']);
|
||||
}
|
||||
if ((int) $a['seller'] > 0) {
|
||||
$heroItems->addItem((int) $a['seller'], (int) $a['itemid'], (int) $a['quantity'], (int) $a['stat_value']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep ~NPC_OPEN_TARGET NPC-seller auctions open so the market always has
|
||||
* stock. Random catalog items; consumables list in small stacks.
|
||||
* Start price scales with tier so tier-3 gear stays expensive.
|
||||
*/
|
||||
public function seedNpcAuctions()
|
||||
{
|
||||
global $heroItemCatalog;
|
||||
$now = time();
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT COUNT(*) FROM " . TB_PREFIX . "auction
|
||||
WHERE seller = 0 AND status = " . self::ST_OPEN . " AND time_end > ?"
|
||||
);
|
||||
$stmt->bind_param('i', $now);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($open);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$need = self::NPC_OPEN_TARGET - (int) $open;
|
||||
if ($need <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$ids = array_keys($heroItemCatalog);
|
||||
$created = 0;
|
||||
for ($i = 0; $i < $need; $i++) {
|
||||
$itemid = (int) $ids[array_rand($ids)];
|
||||
$def = $heroItemCatalog[$itemid];
|
||||
$isBag = ((int) $def['slot'] === HSLOT_BAG);
|
||||
$qty = $isBag ? mt_rand(3, 15) : 1;
|
||||
// Start price: tier-scaled for gear, per-piece for consumables.
|
||||
$start = $isBag ? $qty * mt_rand(2, 5) : (int) $def['tier'] * mt_rand(40, 80);
|
||||
$end = $now + self::NPC_DURATION;
|
||||
$slot = (int) $def['slot'];
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "auction
|
||||
(seller, itemid, slot, stat_value, quantity,
|
||||
silver_start, silver_current, bidder, bid_max, created, time_end, status)
|
||||
VALUES (0, ?, ?, 0, ?, ?, ?, 0, 0, ?, ?, " . self::ST_OPEN . ")"
|
||||
);
|
||||
$stmt->bind_param('iiiiiii', $itemid, $slot, $qty, $start, $start, $now, $end);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
$created++;
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : HeroBattleBonus.php ##
|
||||
## Type : T4 Hero port - battle & speed integration (Phase 5) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## Thin, static bridge between the hero item system and the combat/speed ##
|
||||
## engine. Every entry point: ##
|
||||
## - is a NO-OP returning neutral values when NEW_FUNCTIONS_HERO_T4 is ##
|
||||
## off, so vanilla battle math is bit-for-bit unchanged; ##
|
||||
## - memoizes per uid for the duration of the request (Automation ticks ##
|
||||
## process many battles; bonuses must not re-query per battle). ##
|
||||
## ##
|
||||
## Consumers (surgical hooks): ##
|
||||
## Battle::getBattleHero() -> statBonus(), attaches uid/unit_bonus/ ##
|
||||
## vs_natars/dmg_reduce to the hero array ##
|
||||
## Battle HERO OFFENSE block -> unitOffense(), natarMultiplier() ##
|
||||
## Battle defender hero block -> unitDefense() ##
|
||||
## Battle HERO DAMAGE block -> reduceDamage() ##
|
||||
## Automation bounty callsite -> raidMultiplier() ##
|
||||
## Automation return callsites -> adjustReturnEndtime() ##
|
||||
## Units::getWalkingTroopsTime -> adjustTravelTime() ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
class HeroBattleBonus
|
||||
{
|
||||
/** @var array per-request bonus cache: uid => bonuses array */
|
||||
private static $cache = array();
|
||||
|
||||
/** @var array per-request "does uid have a living hero" cache */
|
||||
private static $livingCache = array();
|
||||
|
||||
/** Feature flag gate used by every public entry point. */
|
||||
public static function enabled()
|
||||
{
|
||||
return defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4;
|
||||
}
|
||||
|
||||
/** Cached HeroItems::getBonuses(). Returns null when the feature is off. */
|
||||
public static function bonuses($uid)
|
||||
{
|
||||
if (!self::enabled()) {
|
||||
return null;
|
||||
}
|
||||
$uid = (int) $uid;
|
||||
if (!array_key_exists($uid, self::$cache)) {
|
||||
$heroItems = new HeroItems();
|
||||
self::$cache[$uid] = $heroItems->getBonuses($uid);
|
||||
}
|
||||
return self::$cache[$uid];
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* BATTLE MATH
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Flat fighting strength from items (weapons, cuirasses, shields).
|
||||
* Added to the hero's atk AND di/dc - T4 fight strength is a single stat
|
||||
* that counts on whichever side the hero fights.
|
||||
*/
|
||||
public static function statBonus($uid)
|
||||
{
|
||||
$b = self::bonuses($uid);
|
||||
return $b ? (int) $b[HB_FIGHT] : 0;
|
||||
}
|
||||
|
||||
/** [unitId => perUnit] map from the equipped weapon (empty when none). */
|
||||
public static function unitBonusMap($uid)
|
||||
{
|
||||
$b = self::bonuses($uid);
|
||||
return ($b && !empty($b[HB_UNIT_BONUS])) ? $b[HB_UNIT_BONUS] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra offense from the weapon's per-unit bonus:
|
||||
* +N attack per accompanying unit of the weapon's type.
|
||||
* $isCavalry tells the caller whether to add it to ap or cap.
|
||||
* Returns [addedInfantryAp, addedCavalryAp].
|
||||
*/
|
||||
public static function unitOffense($uid, array $attackerUnits, array $cavalryLookup)
|
||||
{
|
||||
$map = self::unitBonusMap($uid);
|
||||
if (empty($map)) {
|
||||
return array(0, 0);
|
||||
}
|
||||
$ap = 0; $cap = 0;
|
||||
foreach ($map as $unitId => $perUnit) {
|
||||
$count = isset($attackerUnits['u' . $unitId]) ? (int) $attackerUnits['u' . $unitId] : 0;
|
||||
if ($count <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (isset($cavalryLookup[$unitId])) {
|
||||
$cap += $perUnit * $count;
|
||||
} else {
|
||||
$ap += $perUnit * $count;
|
||||
}
|
||||
}
|
||||
return array($ap, $cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra defense from the weapon's per-unit bonus:
|
||||
* +N defense per accompanying unit of the weapon's type. The design sheet
|
||||
* grants a single "+N def" figure, so it is added to BOTH the infantry
|
||||
* (dp) and cavalry (cdp) defense pools.
|
||||
* Returns the flat amount to add to each pool.
|
||||
*/
|
||||
public static function unitDefense($uid, array $defenderUnits)
|
||||
{
|
||||
$map = self::unitBonusMap($uid);
|
||||
if (empty($map)) {
|
||||
return 0;
|
||||
}
|
||||
$add = 0;
|
||||
foreach ($map as $unitId => $perUnit) {
|
||||
$count = isset($defenderUnits['u' . $unitId]) ? (int) $defenderUnits['u' . $unitId] : 0;
|
||||
if ($count > 0) {
|
||||
$add += $perUnit * $count;
|
||||
}
|
||||
}
|
||||
return $add;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hunting horn: multiplier applied to the HERO's own attack contribution
|
||||
* when the defender is the Natars (uid 3, see Artifacts::NATARS_UID).
|
||||
*/
|
||||
public static function natarMultiplier($uid, $defenderUid)
|
||||
{
|
||||
if ((int) $defenderUid !== 3) {
|
||||
return 1.0;
|
||||
}
|
||||
$b = self::bonuses($uid);
|
||||
if (!$b || (int) $b[HB_VS_NATARS] <= 0) {
|
||||
return 1.0;
|
||||
}
|
||||
return 1 + ((int) $b[HB_VS_NATARS] / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Armor damage reduction: flat points subtracted from the health damage
|
||||
* the hero takes in one battle (never below 0).
|
||||
*/
|
||||
public static function reduceDamage($uid, $damage)
|
||||
{
|
||||
$b = self::bonuses($uid);
|
||||
if (!$b || (int) $b[HB_DMG_REDUCE] <= 0) {
|
||||
return (int) $damage;
|
||||
}
|
||||
return max(0, (int) $damage - (int) $b[HB_DMG_REDUCE]);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* RAID BOUNTY (thief sacks)
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Sacks increase the resources stolen. Applies only when the attacker's
|
||||
* living hero actually took part in the strike ($heroSent > 0). The hero
|
||||
* that died in this very battle was already flagged dead by Battle.php
|
||||
* before the bounty step runs, so livingHero() naturally excludes them.
|
||||
*/
|
||||
public static function raidMultiplier($uid, $heroSent)
|
||||
{
|
||||
if ((int) $heroSent <= 0 || !self::livingHero($uid)) {
|
||||
return 1.0;
|
||||
}
|
||||
$b = self::bonuses($uid);
|
||||
if (!$b || (int) $b[HB_RAID] <= 0) {
|
||||
return 1.0;
|
||||
}
|
||||
return 1 + ((int) $b[HB_RAID] / 100);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* TRAVEL TIME (boots / pennant / standard / map)
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Adjust an OUTBOUND troop-travel time (seconds) computed by
|
||||
* Generator::procDistanceTime(). Only applies when the hero rides along.
|
||||
*
|
||||
* - Pennant (HB_SPEED_OWN): +% speed when the target village belongs
|
||||
* to the same player;
|
||||
* - Standard (HB_SPEED_ALLY): +% speed when the target belongs to an
|
||||
* alliance mate (pennant wins if both match);
|
||||
* - Boots (HB_TROOP_SPEED_20): +% speed for the part of the journey
|
||||
* beyond 20 tiles, same piecewise idea as the Tournament Square
|
||||
* (issue #304). NOTE: applied proportionally on the final time, which
|
||||
* is exact on its own; when a Tournament Square is also active the two
|
||||
* boosts compose multiplicatively on the >20 segment - a documented,
|
||||
* deliberate approximation.
|
||||
*/
|
||||
public static function adjustTravelTime($ownerUid, $heroInArmy, $fromWref, $toWref, $seconds)
|
||||
{
|
||||
global $database;
|
||||
|
||||
if (!self::enabled() || (int) $heroInArmy <= 0 || $seconds <= 0) {
|
||||
return (int) $seconds;
|
||||
}
|
||||
$b = self::bonuses($ownerUid);
|
||||
if (!$b) {
|
||||
return (int) $seconds;
|
||||
}
|
||||
|
||||
$seconds = (float) $seconds;
|
||||
|
||||
// --- pennant / standard ---
|
||||
// Pennant: BOTH endpoints must belong to the player (journeys between
|
||||
// own villages). Standard: both endpoints inside the same alliance.
|
||||
// Destination-only checks would wrongly buff every return leg home.
|
||||
$pct = 0;
|
||||
$fromOwner = (int) $database->getVillageField((int) $fromWref, 'owner');
|
||||
$targetOwner = (int) $database->getVillageField((int) $toWref, 'owner');
|
||||
if ($fromOwner > 0 && $targetOwner > 0) {
|
||||
if ($fromOwner === (int) $ownerUid && $targetOwner === (int) $ownerUid) {
|
||||
$pct = (int) $b[HB_SPEED_OWN];
|
||||
} elseif ((int) $b[HB_SPEED_ALLY] > 0) {
|
||||
$ownAlly = (int) $database->getUserField($fromOwner, 'alliance', 0);
|
||||
$targetAlly = (int) $database->getUserField($targetOwner, 'alliance', 0);
|
||||
if ($ownAlly > 0 && $ownAlly === $targetAlly) {
|
||||
$pct = (int) $b[HB_SPEED_ALLY];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($pct > 0) {
|
||||
$seconds = $seconds / (1 + $pct / 100);
|
||||
}
|
||||
|
||||
// --- boots: only the beyond-20-tiles share of the journey ---
|
||||
$bootsPct = (int) $b[HB_TROOP_SPEED_20];
|
||||
if ($bootsPct > 0) {
|
||||
$fromCoor = $database->getCoor((int) $fromWref);
|
||||
$toCoor = $database->getCoor((int) $toWref);
|
||||
if ($fromCoor && $toCoor) {
|
||||
$distance = (float) $database->getDistance($fromCoor['x'], $fromCoor['y'], $toCoor['x'], $toCoor['y']);
|
||||
if ($distance > 20) {
|
||||
$shareBeyond = ($distance - 20) / $distance; // time share past 20 tiles
|
||||
$timeBeyond = $seconds * $shareBeyond;
|
||||
$seconds = ($seconds - $timeBeyond) + $timeBeyond / (1 + $bootsPct / 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return max(1, (int) round($seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map: shorten the RETURN leg after a battle. Receives the arrival time
|
||||
* and the planned return endtime; compresses the leg duration by the
|
||||
* map's percentage when the (still living) hero is coming home.
|
||||
*/
|
||||
public static function adjustReturnEndtime($ownerUid, $heroSent, $arrivalTime, $endtime)
|
||||
{
|
||||
if (!self::enabled() || (int) $heroSent <= 0 || !self::livingHero($ownerUid)) {
|
||||
return $endtime;
|
||||
}
|
||||
$b = self::bonuses($ownerUid);
|
||||
if (!$b || (int) $b[HB_RETURN_SPEED] <= 0) {
|
||||
return $endtime;
|
||||
}
|
||||
$leg = (float) $endtime - (float) $arrivalTime;
|
||||
if ($leg <= 0) {
|
||||
return $endtime;
|
||||
}
|
||||
return $arrivalTime + $leg / (1 + (int) $b[HB_RETURN_SPEED] / 100);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* BANDAGES (post-battle troop healing)
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Heal part of the attacker's fallen troops after a battle (Phase 6):
|
||||
* up to pct% of each unit type's losses return home, consuming ONE
|
||||
* bandage per healed unit. Better bandages (33%, itemid 207) are used
|
||||
* before small ones (25%, itemid 206). Applies only when the hero came
|
||||
* home alive ($returning['t11'] > 0). The hero itself is never healable.
|
||||
*
|
||||
* NOTE: hero XP was already granted on the full casualty count before
|
||||
* this runs - healed troops still award XP (deliberate, documented).
|
||||
*
|
||||
* @param int $uid Attacking player.
|
||||
* @param array $returning ['t1'..'t11' => surviving counts] (modified copy returned).
|
||||
* @param array $dead [1..11 => losses per unit index].
|
||||
* @param int $attackRef attacks.id of this strike. returnunitsComplete
|
||||
* reads the homecoming troop counts from the
|
||||
* attacks row (movement.ref join), NOT from the
|
||||
* in-memory array - healed units MUST be written
|
||||
* back there or they would vanish on arrival.
|
||||
* @return array The adjusted returning-troops array.
|
||||
*/
|
||||
public static function applyBandages($uid, array $returning, array $dead, $attackRef = 0)
|
||||
{
|
||||
global $database;
|
||||
|
||||
if (!self::enabled() || (int) ($returning['t11'] ?? 0) <= 0) {
|
||||
return $returning;
|
||||
}
|
||||
|
||||
$heroItems = new HeroItems();
|
||||
// Bandage stacks, strongest first: [rowId, remaining, pct]
|
||||
$stacks = array();
|
||||
foreach ($heroItems->getInventory((int) $uid) as $row) {
|
||||
if ($row['orphan'] || $row['equipped'] == 1) {
|
||||
continue;
|
||||
}
|
||||
if ((int) $row['itemid'] === 207) {
|
||||
$stacks[0] = array('id' => (int) $row['id'], 'qty' => (int) $row['quantity'], 'pct' => 33);
|
||||
} elseif ((int) $row['itemid'] === 206) {
|
||||
$stacks[1] = array('id' => (int) $row['id'], 'qty' => (int) $row['quantity'], 'pct' => 25);
|
||||
}
|
||||
}
|
||||
if (empty($stacks)) {
|
||||
return $returning;
|
||||
}
|
||||
ksort($stacks);
|
||||
|
||||
$healedPerUnit = array();
|
||||
|
||||
foreach ($stacks as $stack) {
|
||||
if ($stack['qty'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
$budget = $stack['qty'];
|
||||
$used = 0;
|
||||
|
||||
for ($i = 1; $i <= 10 && $budget > 0; $i++) {
|
||||
$lost = isset($dead[$i]) ? (int) $dead[$i] : 0;
|
||||
if ($lost <= 0) {
|
||||
continue;
|
||||
}
|
||||
$healable = (int) floor($lost * $stack['pct'] / 100);
|
||||
$healed = min($healable, $budget);
|
||||
if ($healed <= 0) {
|
||||
continue;
|
||||
}
|
||||
$returning['t' . $i] = (int) ($returning['t' . $i] ?? 0) + $healed;
|
||||
$dead[$i] -= $healed; // a unit is only healed once across stacks
|
||||
$budget -= $healed;
|
||||
$used += $healed;
|
||||
$healedPerUnit[$i] = ($healedPerUnit[$i] ?? 0) + $healed;
|
||||
}
|
||||
|
||||
if ($used > 0) {
|
||||
$heroItems->removeItem((int) $uid, $stack['id'], $used);
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the revived units into the attacks row: it is the source of
|
||||
// truth returnunitsComplete hands back to the village on arrival.
|
||||
if (!empty($healedPerUnit) && (int) $attackRef > 0) {
|
||||
$sets = array();
|
||||
foreach ($healedPerUnit as $i => $healed) {
|
||||
$sets[] = 't' . (int) $i . ' = t' . (int) $i . ' + ' . (int) $healed;
|
||||
}
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"UPDATE " . TB_PREFIX . "attacks SET " . implode(', ', $sets)
|
||||
. " WHERE id = " . (int) $attackRef . " LIMIT 1"
|
||||
);
|
||||
}
|
||||
|
||||
return $returning;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* CAGES (oasis animal capture)
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Cages (itemid 208): when the hero attacks an UNOCCUPIED oasis, up to
|
||||
* one animal per cage is captured BEFORE the fight - captured animals do
|
||||
* not defend. They are stationed as defensive reinforcements in the
|
||||
* attack's home village (enforcement row vref=home, from=home; nature
|
||||
* units u31-u40 count in defense through the normal enforcement path).
|
||||
*
|
||||
* Deliberate, documented simplification vs T4: the animals appear at
|
||||
* home the moment the battle resolves instead of travelling back with
|
||||
* the hero (the attacks row can only carry the attacker's own tribe
|
||||
* units, and a schema change for this isn't worth it).
|
||||
*
|
||||
* Removal from the oasis is a CONDITIONAL decrement per unit type
|
||||
* (u31 >= N guards), so a parallel process can never drive counts
|
||||
* negative; on a lost race the capture simply shrinks.
|
||||
*
|
||||
* @param int $uid Attacking player.
|
||||
* @param int $homeWref Village the attack was sent from.
|
||||
* @param int $oasisWref The unoccupied oasis under attack.
|
||||
* @param array $Defender Defender unit array (modified copy returned).
|
||||
* @return array The Defender array minus whatever was captured.
|
||||
*/
|
||||
public static function applyCages($uid, $homeWref, $oasisWref, array $Defender)
|
||||
{
|
||||
global $database;
|
||||
|
||||
if (!self::enabled()) {
|
||||
return $Defender;
|
||||
}
|
||||
|
||||
// Animals present?
|
||||
$animals = 0;
|
||||
for ($i = 31; $i <= 40; $i++) {
|
||||
$animals += max(0, (int) ($Defender['u' . $i] ?? 0));
|
||||
}
|
||||
if ($animals <= 0) {
|
||||
return $Defender;
|
||||
}
|
||||
|
||||
// Cage stack (unequipped, itemid 208).
|
||||
$heroItems = new HeroItems();
|
||||
$cageRow = null;
|
||||
foreach ($heroItems->getInventory((int) $uid) as $row) {
|
||||
if (!$row['orphan'] && (int) $row['itemid'] === 208 && $row['equipped'] == 0) {
|
||||
$cageRow = $row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$cageRow || (int) $cageRow['quantity'] <= 0) {
|
||||
return $Defender;
|
||||
}
|
||||
|
||||
$budget = (int) $cageRow['quantity'];
|
||||
$captured = array();
|
||||
|
||||
for ($i = 31; $i <= 40 && $budget > 0; $i++) {
|
||||
$have = max(0, (int) ($Defender['u' . $i] ?? 0));
|
||||
if ($have <= 0) {
|
||||
continue;
|
||||
}
|
||||
$take = min($have, $budget);
|
||||
|
||||
// Conditional removal from the oasis - authoritative count.
|
||||
$stmt = $database->dblink->prepare(
|
||||
"UPDATE " . TB_PREFIX . "units
|
||||
SET u{$i} = u{$i} - ?
|
||||
WHERE vref = ? AND u{$i} >= ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('iii', $take, $oasisWref, $take);
|
||||
$stmt->execute();
|
||||
$removed = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
if (!$removed) {
|
||||
continue; // lost a race; leave these animals to fight
|
||||
}
|
||||
|
||||
$Defender['u' . $i] = $have - $take;
|
||||
$captured[$i] = $take;
|
||||
$budget -= $take;
|
||||
}
|
||||
|
||||
if (empty($captured)) {
|
||||
return $Defender;
|
||||
}
|
||||
|
||||
$used = array_sum($captured);
|
||||
$heroItems->removeItem((int) $uid, (int) $cageRow['id'], $used);
|
||||
|
||||
// Station the animals at home: reuse the self-enforcement row if one
|
||||
// exists (vref = from = home), otherwise create it.
|
||||
$homeWref = (int) $homeWref;
|
||||
$res = mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT id FROM " . TB_PREFIX . "enforcement
|
||||
WHERE vref = $homeWref AND `from` = $homeWref LIMIT 1"
|
||||
);
|
||||
$rowE = $res ? mysqli_fetch_assoc($res) : null;
|
||||
if (!$rowE) {
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"INSERT INTO " . TB_PREFIX . "enforcement (vref, `from`) VALUES ($homeWref, $homeWref)"
|
||||
);
|
||||
$enfId = (int) mysqli_insert_id($database->dblink);
|
||||
} else {
|
||||
$enfId = (int) $rowE['id'];
|
||||
}
|
||||
|
||||
$sets = array();
|
||||
foreach ($captured as $i => $n) {
|
||||
$sets[] = 'u' . (int) $i . ' = u' . (int) $i . ' + ' . (int) $n;
|
||||
}
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"UPDATE " . TB_PREFIX . "enforcement SET " . implode(', ', $sets)
|
||||
. " WHERE id = $enfId LIMIT 1"
|
||||
);
|
||||
|
||||
return $Defender;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* INTERNALS
|
||||
* ===================================================================== */
|
||||
|
||||
/** Cached "uid has a living hero" check. */
|
||||
private static function livingHero($uid)
|
||||
{
|
||||
global $database;
|
||||
$uid = (int) $uid;
|
||||
if (!array_key_exists($uid, self::$livingCache)) {
|
||||
$res = mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT heroid FROM " . TB_PREFIX . "hero WHERE uid = $uid AND dead = 0 LIMIT 1"
|
||||
);
|
||||
self::$livingCache[$uid] = ($res && mysqli_fetch_row($res)) ? true : false;
|
||||
}
|
||||
return self::$livingCache[$uid];
|
||||
}
|
||||
|
||||
/** Test helper: clear per-request caches (mirrors HeroItems invalidation). */
|
||||
public static function flushCache()
|
||||
{
|
||||
self::$cache = array();
|
||||
self::$livingCache = array();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,632 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : HeroItems.php ##
|
||||
## Type : T4 Hero port - items backend (Phase 2) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactored by : Shadow ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
## ##
|
||||
## Backend for hero inventory / equipment / consumables / silver. ##
|
||||
## - All statements are prepared (mysqli), all ids cast to int. ##
|
||||
## - Per-request memoization caches (invalidated on every write), matching ##
|
||||
## the Database.php cache convention. ##
|
||||
## - Consumables whose game mechanic lands in a later phase (bandages in ##
|
||||
## battle, cages in oasis attacks, law tablets in loyalty, artwork CP) ##
|
||||
## return HeroItems::USE_DEFERRED so the caller knows the item exists ##
|
||||
## but its effect is wired elsewhere. They are NOT silently consumed. ##
|
||||
## ##
|
||||
#################################################################################
|
||||
|
||||
class HeroItems
|
||||
{
|
||||
/** useItem() result codes */
|
||||
const USE_OK = 1; // consumed, effect applied here
|
||||
const USE_DEFERRED = 2; // valid item, but its effect is applied by another subsystem (Phase 3/5)
|
||||
const USE_INVALID = 0; // unknown item / not owned / not usable now
|
||||
|
||||
/** @var array per-request caches, keyed by uid */
|
||||
private static $inventoryCache = array();
|
||||
private static $bonusCache = array();
|
||||
|
||||
/** @var mysqli */
|
||||
private $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $database;
|
||||
// Reuse the game's single mysqli link.
|
||||
$this->db = $database->dblink;
|
||||
|
||||
// Catalog is idempotent to include (guarded defines).
|
||||
global $heroItemCatalog, $heroAdventureConfig, $heroSlotIndex;
|
||||
if (!isset($heroItemCatalog)) {
|
||||
include_once __DIR__ . '/Data/hero_items.php';
|
||||
}
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* READS
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Full inventory of a user (equipped + bag), catalog-joined.
|
||||
* Rows whose itemid vanished from the catalog are returned flagged with
|
||||
* 'orphan' => true instead of being hidden - anomaly surfaced, not altered.
|
||||
*/
|
||||
public function getInventory($uid)
|
||||
{
|
||||
global $heroItemCatalog;
|
||||
$uid = (int) $uid;
|
||||
|
||||
if (isset(self::$inventoryCache[$uid])) {
|
||||
return self::$inventoryCache[$uid];
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT id, uid, heroid, itemid, slot, stat_value, quantity, equipped, tstamp
|
||||
FROM " . TB_PREFIX . "hero_items
|
||||
WHERE uid = ? ORDER BY equipped DESC, slot ASC, itemid ASC"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$rows = array();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
if (isset($heroItemCatalog[$row['itemid']])) {
|
||||
$row['def'] = $heroItemCatalog[$row['itemid']];
|
||||
$row['name'] = heroItemName($row['itemid']);
|
||||
$row['orphan'] = false;
|
||||
} else {
|
||||
$row['def'] = null;
|
||||
$row['name'] = 'Unknown item #' . $row['itemid'];
|
||||
$row['orphan'] = true;
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
self::$inventoryCache[$uid] = $rows;
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** Equipped items only, indexed by slot (one item per slot). */
|
||||
public function getEquipped($uid)
|
||||
{
|
||||
$equipped = array();
|
||||
foreach ($this->getInventory($uid) as $row) {
|
||||
if ($row['equipped'] == 1 && !$row['orphan']) {
|
||||
$equipped[(int) $row['slot']] = $row;
|
||||
}
|
||||
}
|
||||
return $equipped;
|
||||
}
|
||||
|
||||
/** Silver balance (0 if the user has no hero row). */
|
||||
public function getSilver($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
$stmt = $this->db->prepare("SELECT silver FROM " . TB_PREFIX . "hero WHERE uid = ? LIMIT 1");
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($silver);
|
||||
$found = $stmt->fetch();
|
||||
$stmt->close();
|
||||
return $found ? (int) $silver : 0;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* BONUS AGGREGATION
|
||||
* Returns a normalized array all later phases read from:
|
||||
* [
|
||||
* 'fight_strength' => int, 'damage_reduce' => int,
|
||||
* 'regen_hp' => int, 'xp_percent' => int,
|
||||
* 'culture_points' => int, 'train_cavalry' => int, 'train_infantry' => int,
|
||||
* 'vs_natars' => int, 'raid_percent' => int,
|
||||
* 'return_speed' => int, 'speed_own' => int, 'speed_ally' => int,
|
||||
* 'troop_speed_20' => int, 'horse_speed' => int, 'mount_speed' => int,
|
||||
* 'unit_bonus' => [ unitId => perUnitValue, ... ]
|
||||
* ]
|
||||
* ===================================================================== */
|
||||
public function getBonuses($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
if (isset(self::$bonusCache[$uid])) {
|
||||
return self::$bonusCache[$uid];
|
||||
}
|
||||
|
||||
$bonuses = array(
|
||||
HB_FIGHT => 0, HB_DMG_REDUCE => 0, HB_REGEN_HP => 0, HB_XP => 0,
|
||||
HB_CP => 0, HB_TRAIN_CAV => 0, HB_TRAIN_INF => 0, HB_VS_NATARS => 0,
|
||||
HB_RAID => 0, HB_RETURN_SPEED => 0, HB_SPEED_OWN => 0, HB_SPEED_ALLY => 0,
|
||||
HB_TROOP_SPEED_20 => 0, HB_HORSE_SPEED => 0, HB_MOUNT_SPEED => 0,
|
||||
HB_UNIT_BONUS => array(),
|
||||
);
|
||||
|
||||
$equipped = $this->getEquipped($uid);
|
||||
$hasHorse = isset($equipped[HSLOT_HORSE]);
|
||||
|
||||
foreach ($equipped as $row) {
|
||||
$def = $row['def'];
|
||||
|
||||
// Spurs count only while a horse is equipped.
|
||||
if (!empty($def['requires_horse']) && !$hasHorse) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($def['bonus'] as $type => $value) {
|
||||
if ($type === HB_UNIT_BONUS) {
|
||||
$u = (int) $value['unit'];
|
||||
if (!isset($bonuses[HB_UNIT_BONUS][$u])) {
|
||||
$bonuses[HB_UNIT_BONUS][$u] = 0;
|
||||
}
|
||||
$bonuses[HB_UNIT_BONUS][$u] += (int) $value['per_unit'];
|
||||
continue;
|
||||
}
|
||||
if (isset($bonuses[$type])) {
|
||||
$bonuses[$type] += $this->scaledBonusValue($def, (int) $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$bonusCache[$uid] = $bonuses;
|
||||
return $bonuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the server-speed scaling rules from the design sheet:
|
||||
* 'x2_on_speed' => true -> value * 2 on speed servers (spurs, horses)
|
||||
* 'x2_on_speed' => false -> value / 2 on speed servers (culture helmets)
|
||||
* key absent -> value unchanged
|
||||
* "Speed server" = SPEED >= 3 (per the sheet's "(1x) / (3x)" notation).
|
||||
*/
|
||||
public function scaledBonusValue(array $def, $value)
|
||||
{
|
||||
if (!array_key_exists('x2_on_speed', $def)) {
|
||||
return $value;
|
||||
}
|
||||
$isSpeed = defined('SPEED') && SPEED >= 3;
|
||||
if (!$isSpeed) {
|
||||
return $value;
|
||||
}
|
||||
return $def['x2_on_speed'] ? $value * 2 : (int) floor($value / 2);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* WRITES
|
||||
* ===================================================================== */
|
||||
|
||||
/**
|
||||
* Grant an item to a user (adventure loot, auction win, admin).
|
||||
* Consumables (bag slot) stack onto an existing row; gear inserts a new row.
|
||||
* Returns the hero_items.id of the affected row, or 0 on failure.
|
||||
*/
|
||||
public function addItem($uid, $itemid, $quantity = 1, $statValue = 0)
|
||||
{
|
||||
global $heroItemCatalog;
|
||||
$uid = (int) $uid; $itemid = (int) $itemid;
|
||||
$quantity = max(1, (int) $quantity); $statValue = (int) $statValue;
|
||||
|
||||
if (!isset($heroItemCatalog[$itemid])) {
|
||||
return 0;
|
||||
}
|
||||
$def = $heroItemCatalog[$itemid];
|
||||
$slot = (int) $def['slot'];
|
||||
$now = time();
|
||||
|
||||
if ($slot === HSLOT_BAG) {
|
||||
// Stack consumables.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_items
|
||||
SET quantity = quantity + ?, tstamp = ?
|
||||
WHERE uid = ? AND itemid = ? AND equipped = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('iiii', $quantity, $now, $uid, $itemid);
|
||||
$stmt->execute();
|
||||
$affected = $stmt->affected_rows;
|
||||
$stmt->close();
|
||||
|
||||
if ($affected > 0) {
|
||||
$this->invalidateCaches($uid);
|
||||
$row = $this->findRow($uid, $itemid);
|
||||
return $row ? (int) $row['id'] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
$equipped = 0;
|
||||
$stmt = $this->db->prepare(
|
||||
"INSERT INTO " . TB_PREFIX . "hero_items
|
||||
(uid, heroid, itemid, slot, stat_value, quantity, equipped, tstamp)
|
||||
VALUES (?, 0, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->bind_param('iiiiiii', $uid, $itemid, $slot, $statValue, $quantity, $equipped, $now);
|
||||
$stmt->execute();
|
||||
$newId = (int) $stmt->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
$this->invalidateCaches($uid);
|
||||
return $newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equip an owned item into its slot. Returns true on success.
|
||||
* Rules enforced:
|
||||
* - item must belong to $uid and not be a consumable
|
||||
* - weapons ('unit' key) require the LIVING hero to be that exact unit
|
||||
* - anything already in that slot is unequipped first (atomic enough for
|
||||
* a per-user action; both statements touch only this user's rows)
|
||||
*/
|
||||
public function equipItem($uid, $rowId)
|
||||
{
|
||||
global $database;
|
||||
$uid = (int) $uid; $rowId = (int) $rowId;
|
||||
|
||||
$row = $this->findRowById($uid, $rowId);
|
||||
if (!$row || $row['orphan'] || (int) $row['def']['slot'] === HSLOT_BAG) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Weapon unit restriction.
|
||||
if (isset($row['def']['unit'])) {
|
||||
$hero = $database->getHero($uid);
|
||||
$heroUnit = 0;
|
||||
if (is_array($hero)) {
|
||||
foreach ($hero as $h) {
|
||||
if ($h['dead'] != 1) { $heroUnit = (int) $h['unit']; break; }
|
||||
}
|
||||
}
|
||||
if ($heroUnit !== (int) $row['def']['unit']) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$slot = (int) $row['def']['slot'];
|
||||
|
||||
// Vacate the slot.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_items SET equipped = 0 WHERE uid = ? AND slot = ? AND equipped = 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $slot);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Equip the requested row.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_items SET equipped = 1 WHERE uid = ? AND id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $rowId);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
$this->invalidateCaches($uid);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/** Unequip a specific owned row. Returns true on success. */
|
||||
public function unequipItem($uid, $rowId)
|
||||
{
|
||||
$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"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $rowId);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
$this->invalidateCaches($uid);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove quantity from an owned row (used by useItem, auction listing).
|
||||
* Deletes the row when the stack reaches zero. Returns true on success.
|
||||
*/
|
||||
public function removeItem($uid, $rowId, $quantity = 1)
|
||||
{
|
||||
$uid = (int) $uid; $rowId = (int) $rowId; $quantity = max(1, (int) $quantity);
|
||||
|
||||
// Conditional decrement - never goes below zero even under races.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero_items
|
||||
SET quantity = quantity - ?
|
||||
WHERE uid = ? AND id = ? AND quantity >= ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('iiii', $quantity, $uid, $rowId, $quantity);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if ($ok) {
|
||||
$stmt = $this->db->prepare(
|
||||
"DELETE FROM " . TB_PREFIX . "hero_items WHERE uid = ? AND id = ? AND quantity <= 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $uid, $rowId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
$this->invalidateCaches($uid);
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a consumable. Applies immediate effects (ointment, scroll, bucket,
|
||||
* book of wisdom, law tablets, artwork); returns USE_DEFERRED for
|
||||
* consumables whose mechanic is wired into battle processing (bandages,
|
||||
* cages - Phase 6) WITHOUT consuming them.
|
||||
* $vid: target village, required for law tablets (loyalty target).
|
||||
* Returns one of the USE_* class constants.
|
||||
*/
|
||||
public function useItem($uid, $rowId, $quantity = 1, $vid = 0)
|
||||
{
|
||||
global $database;
|
||||
$uid = (int) $uid; $rowId = (int) $rowId; $quantity = max(1, (int) $quantity);
|
||||
|
||||
$row = $this->findRowById($uid, $rowId);
|
||||
if (!$row || $row['orphan'] || (int) $row['def']['slot'] !== HSLOT_BAG) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
if ((int) $row['quantity'] < $quantity) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
|
||||
$bonus = $row['def']['bonus'];
|
||||
|
||||
/* ---- Ointment: +1% HP per unit, cap 99, hero must be alive ---- */
|
||||
if (isset($bonus[HB_HEAL_SELF])) {
|
||||
$hero = $this->getLivingHeroRow($uid);
|
||||
if (!$hero) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
$health = (float) $hero['health'];
|
||||
if ($health >= 99) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
// Only consume as many as actually heal (no waste past the 99 cap).
|
||||
$usable = min($quantity, (int) ceil(99 - $health));
|
||||
$newHealth = min(99, $health + $usable * (int) $bonus[HB_HEAL_SELF]);
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET health = ? WHERE heroid = ? LIMIT 1"
|
||||
);
|
||||
$heroid = (int) $hero['heroid'];
|
||||
$stmt->bind_param('di', $newHealth, $heroid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$this->removeItem($uid, $rowId, $usable);
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Scroll: +10 XP each ---- */
|
||||
if (isset($bonus[HB_SCROLL])) {
|
||||
$hero = $this->getLivingHeroRow($uid);
|
||||
if (!$hero) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
$xp = (int) $bonus[HB_SCROLL] * $quantity;
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET experience = experience + ? WHERE heroid = ? LIMIT 1"
|
||||
);
|
||||
$heroid = (int) $hero['heroid'];
|
||||
$stmt->bind_param('ii', $xp, $heroid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$this->removeItem($uid, $rowId, $quantity);
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Bucket of Water: instant free revive of the dead hero ---- */
|
||||
if (isset($bonus[HB_BUCKET])) {
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero
|
||||
SET dead = 0, health = 100, inrevive = 0, lastupdate = ?
|
||||
WHERE uid = ? AND dead = 1 LIMIT 1"
|
||||
);
|
||||
$now = time();
|
||||
$stmt->bind_param('ii', $now, $uid);
|
||||
$stmt->execute();
|
||||
$revived = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if (!$revived) {
|
||||
return self::USE_INVALID; // no dead hero to revive
|
||||
}
|
||||
$this->removeItem($uid, $rowId, 1); // one bucket per revive
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Book of Wisdom: reset attribute points ---- */
|
||||
if (isset($bonus[HB_RESET])) {
|
||||
$hero = $this->getLivingHeroRow($uid);
|
||||
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.
|
||||
$level = (int) $hero['level'];
|
||||
$points = $level * 5;
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero
|
||||
SET points = ?, attack = 0, defence = 0, attackbonus = 0,
|
||||
defencebonus = 0, regeneration = 0
|
||||
WHERE heroid = ? LIMIT 1"
|
||||
);
|
||||
$heroid = (int) $hero['heroid'];
|
||||
$stmt->bind_param('ii', $points, $heroid);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$this->removeItem($uid, $rowId, 1);
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Law Tablet: +1% loyalty per tablet on an OWN village, max 125% ---- */
|
||||
if (isset($bonus[HB_LOYALTY])) {
|
||||
$vid = (int) $vid;
|
||||
if ($vid <= 0) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
// Village must belong to the user; cap enforced in SQL so
|
||||
// concurrent uses can never overshoot 125.
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "vdata
|
||||
SET loyalty = LEAST(125, loyalty + ?)
|
||||
WHERE wref = ? AND owner = ? AND loyalty < 125 LIMIT 1"
|
||||
);
|
||||
$gain = (int) $bonus[HB_LOYALTY] * $quantity;
|
||||
$stmt->bind_param('iii', $gain, $vid, $uid);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if (!$ok) {
|
||||
return self::USE_INVALID; // not owner, or already at cap
|
||||
}
|
||||
$this->removeItem($uid, $rowId, $quantity);
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Artwork: culture points equal to the account's daily CP
|
||||
* production, capped at 5000 (normal) / 2500 (speed >= 3).
|
||||
* users.cp is the accumulator culturePoints() ticks into. ---- */
|
||||
if (isset($bonus[HB_ARTWORK])) {
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT COALESCE(SUM(cp), 0) FROM " . TB_PREFIX . "vdata WHERE owner = ?"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($dailyCp);
|
||||
$stmt->fetch();
|
||||
$stmt->close();
|
||||
|
||||
$cap = (defined('SPEED') && SPEED >= 3) ? 2500 : 5000;
|
||||
$gain = min($cap, (int) $dailyCp) * $quantity;
|
||||
if ($gain <= 0) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "users SET cp = cp + ? WHERE id = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $gain, $uid);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
|
||||
if (!$ok) {
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
$this->removeItem($uid, $rowId, $quantity);
|
||||
return self::USE_OK;
|
||||
}
|
||||
|
||||
/* ---- Deferred consumables (mechanic lands in Phase 6 with the UI:
|
||||
* bandages consume on post-battle healing, cages on oasis
|
||||
* attacks). NOT consumed here. ---- */
|
||||
if (isset($bonus[HB_HEAL_TROOP]) || isset($bonus[HB_CAGE])) {
|
||||
return self::USE_DEFERRED;
|
||||
}
|
||||
|
||||
return self::USE_INVALID;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* SILVER (race-safe conditional updates)
|
||||
* ===================================================================== */
|
||||
|
||||
/** Add silver to the user's hero row. Returns true if a hero row exists. */
|
||||
public function addSilver($uid, $amount)
|
||||
{
|
||||
$uid = (int) $uid; $amount = max(0, (int) $amount);
|
||||
if ($amount === 0) {
|
||||
return true;
|
||||
}
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero SET silver = silver + ? WHERE uid = ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('ii', $amount, $uid);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spend silver. The WHERE silver >= ? guard makes this race-safe:
|
||||
* two concurrent spends can never take the balance negative.
|
||||
*/
|
||||
public function spendSilver($uid, $amount)
|
||||
{
|
||||
$uid = (int) $uid; $amount = max(0, (int) $amount);
|
||||
if ($amount === 0) {
|
||||
return true;
|
||||
}
|
||||
$stmt = $this->db->prepare(
|
||||
"UPDATE " . TB_PREFIX . "hero
|
||||
SET silver = silver - ?
|
||||
WHERE uid = ? AND silver >= ? LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('iii', $amount, $uid, $amount);
|
||||
$stmt->execute();
|
||||
$ok = $stmt->affected_rows > 0;
|
||||
$stmt->close();
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* INTERNALS
|
||||
* ===================================================================== */
|
||||
|
||||
private function invalidateCaches($uid)
|
||||
{
|
||||
unset(self::$inventoryCache[(int) $uid], self::$bonusCache[(int) $uid]);
|
||||
}
|
||||
|
||||
private function findRow($uid, $itemid)
|
||||
{
|
||||
foreach ($this->getInventory($uid) as $row) {
|
||||
if ((int) $row['itemid'] === (int) $itemid && $row['equipped'] == 0) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findRowById($uid, $rowId)
|
||||
{
|
||||
foreach ($this->getInventory($uid) as $row) {
|
||||
if ((int) $row['id'] === (int) $rowId) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Fetch the raw LIVING hero row directly (health/level/points included). */
|
||||
private function getLivingHeroRow($uid)
|
||||
{
|
||||
$uid = (int) $uid;
|
||||
$stmt = $this->db->prepare(
|
||||
"SELECT heroid, uid, unit, level, points, experience, health, dead
|
||||
FROM " . TB_PREFIX . "hero WHERE uid = ? AND dead = 0 LIMIT 1"
|
||||
);
|
||||
$stmt->bind_param('i', $uid);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$row = $res->fetch_assoc();
|
||||
$stmt->close();
|
||||
return $row ?: null;
|
||||
}
|
||||
}
|
||||
@@ -2678,7 +2678,114 @@ if (!function_exists('tz_loc_topic')) {
|
||||
'Unoccupied Oasis' => TZ_RT_UNOCC_OASIS,
|
||||
'New village founded' => TZ_RT_NEW_VILLAGE,
|
||||
'Settlers returned - valley occupied' => TZ_RT_VALLEY_OCCUPIED,
|
||||
// T4 hero port (Phase 6)
|
||||
'Hero returned from an adventure' => TZ_RT_ADV_RETURNED,
|
||||
'Hero fell on an adventure' => TZ_RT_ADV_FELL,
|
||||
'Auction won' => TZ_RT_AUC_WON,
|
||||
'Auction sold' => TZ_RT_AUC_SOLD,
|
||||
'Auction expired' => TZ_RT_AUC_EXPIRED,
|
||||
);
|
||||
return strtr($s, $map);
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* T4 HERO PORT (Phase 6) - items / adventures / auction house
|
||||
* ========================================================================== */
|
||||
tz_def('HERO_T4_TAB_HERO', 'Hero');
|
||||
tz_def('HERO_T4_TAB_ITEMS', 'Inventory');
|
||||
tz_def('HERO_T4_TAB_ADVENTURES', 'Adventures');
|
||||
tz_def('HERO_T4_TAB_AUCTION', 'Auctions');
|
||||
|
||||
tz_def('HERO_SILVER', 'Silver');
|
||||
tz_def('HERO_EXPERIENCE', 'Experience');
|
||||
tz_def('RESOURCES', 'Resources');
|
||||
|
||||
/* Equipment slots */
|
||||
tz_def('HERO_SLOT_1', 'Helmet');
|
||||
tz_def('HERO_SLOT_2', 'Body');
|
||||
tz_def('HERO_SLOT_3', 'Right hand');
|
||||
tz_def('HERO_SLOT_4', 'Left hand');
|
||||
tz_def('HERO_SLOT_5', 'Shoes');
|
||||
tz_def('HERO_SLOT_6', 'Horse');
|
||||
tz_def('HERO_SLOT_7', 'Bag');
|
||||
|
||||
/* Inventory actions */
|
||||
tz_def('HERO_ITEMS_EQUIPPED', 'Equipped');
|
||||
tz_def('HERO_ITEMS_BAG', 'Inventory');
|
||||
tz_def('HERO_ITEMS_EMPTY', 'Your hero owns no items yet. Adventures and the auction house are good places to find some.');
|
||||
tz_def('HERO_EQUIP', 'Equip');
|
||||
tz_def('HERO_UNEQUIP', 'Take off');
|
||||
tz_def('HERO_USE_ITEM', 'Use');
|
||||
tz_def('HERO_QUANTITY', 'Amount');
|
||||
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_EQUIP_FAIL', 'This item cannot be equipped (wrong hero unit or item type).');
|
||||
tz_def('HERO_UNEQUIP_OK', 'Item taken off.');
|
||||
|
||||
/* Adventures */
|
||||
tz_def('HERO_ADV_NORMAL', 'a normal adventure');
|
||||
tz_def('HERO_ADV_HARD', 'a hard adventure');
|
||||
tz_def('HERO_ADV_LIST', 'Available adventures');
|
||||
tz_def('HERO_ADV_NONE', 'No adventures available right now. New ones appear over time.');
|
||||
tz_def('HERO_ADV_DIFFICULTY', 'Difficulty');
|
||||
tz_def('HERO_ADV_DIFF_NORMAL', 'Normal');
|
||||
tz_def('HERO_ADV_DIFF_HARD', 'Hard');
|
||||
tz_def('HERO_ADV_DURATION', 'Travel time (one way)');
|
||||
tz_def('HERO_ADV_EXPIRES', 'Expires in');
|
||||
tz_def('HERO_ADV_GO', 'Start adventure');
|
||||
tz_def('HERO_ADV_RUNNING', 'Your hero is on an adventure and will arrive in');
|
||||
tz_def('HERO_ADV_START_OK', 'Your hero has set off on the adventure.');
|
||||
tz_def('HERO_ADV_START_NOHERO', 'You need a living hero to start an adventure.');
|
||||
tz_def('HERO_ADV_START_AWAY', 'Your hero is not at home.');
|
||||
tz_def('HERO_ADV_START_FAIL', 'This adventure is no longer available.');
|
||||
tz_def('HERO_ADV_RETURNED', 'Your hero has returned from');
|
||||
tz_def('HERO_ADV_REWARD', 'Reward');
|
||||
tz_def('HERO_ADV_AMOUNT', 'Amount');
|
||||
tz_def('HERO_ADV_ITEM_FOUND', 'Item found');
|
||||
tz_def('HERO_ADV_HP_LOST', 'Health lost');
|
||||
tz_def('HERO_ADV_DIED', 'Your hero fell on');
|
||||
tz_def('HERO_ADV_DIED_INFO', "All loot was lost. The hero can be revived at the Hero's Mansion.");
|
||||
|
||||
/* Auction house */
|
||||
tz_def('HERO_AUC_OPEN', 'Open auctions');
|
||||
tz_def('HERO_AUC_NONE', 'There are no open auctions at the moment.');
|
||||
tz_def('HERO_AUC_ITEM', 'Item');
|
||||
tz_def('HERO_AUC_PRICE', 'Current price');
|
||||
tz_def('HERO_AUC_FINAL_PRICE', 'Final price');
|
||||
tz_def('HERO_AUC_TIME_LEFT', 'Ends in');
|
||||
tz_def('HERO_AUC_YOUR_MAX', 'Your maximum');
|
||||
tz_def('HERO_AUC_BID', 'Bid');
|
||||
tz_def('HERO_AUC_BID_OK', 'Your bid was placed. You are the highest bidder.');
|
||||
tz_def('HERO_AUC_BID_OUTBID', 'Your bid was immediately outbid by a higher maximum.');
|
||||
tz_def('HERO_AUC_BID_FAIL', 'Your bid was not accepted.');
|
||||
tz_def('HERO_AUC_BID_NOSILVER', 'You do not have enough silver for this bid.');
|
||||
tz_def('HERO_AUC_MY_BIDS', 'My bids');
|
||||
tz_def('HERO_AUC_MY_SALES', 'My sales');
|
||||
tz_def('HERO_AUC_SELL', 'Sell an item');
|
||||
tz_def('HERO_AUC_SELL_OK', 'Your item has been listed.');
|
||||
tz_def('HERO_AUC_SELL_FAIL', 'This item cannot be listed (equipped items must be taken off first).');
|
||||
tz_def('HERO_AUC_START_PRICE', 'Starting price');
|
||||
tz_def('HERO_AUC_DURATION', 'Duration');
|
||||
tz_def('HERO_AUC_LIST', 'List item');
|
||||
tz_def('HERO_AUC_SELLER_NPC', 'Merchant');
|
||||
tz_def('HERO_AUC_WON', 'You won the auction for');
|
||||
tz_def('HERO_AUC_SOLD', 'Your auction sold:');
|
||||
tz_def('HERO_AUC_REFUND', 'Refunded from your maximum bid');
|
||||
tz_def('HERO_AUC_FEE', 'Auction fee');
|
||||
tz_def('HERO_AUC_PAYOUT', 'Payout');
|
||||
tz_def('HERO_AUC_EXPIRED', 'Your auction ended without bids. The item was returned to your inventory:');
|
||||
|
||||
/* Report topics (raw English strings live in ndata.topic) */
|
||||
tz_def('TZ_RT_ADV_RETURNED', 'Hero returned from an adventure');
|
||||
tz_def('TZ_RT_ADV_FELL', 'Hero fell on an adventure');
|
||||
tz_def('TZ_RT_AUC_WON', 'Auction won');
|
||||
tz_def('TZ_RT_AUC_SOLD', 'Auction sold');
|
||||
tz_def('TZ_RT_AUC_EXPIRED', 'Auction expired');
|
||||
|
||||
/* T4 hero port - movement display (dorf1 + rally point) */
|
||||
tz_def('HERO_ADV_MOV_OUT', 'Hero on an adventure');
|
||||
tz_def('HERO_ADV_MOV_BACK', 'Hero returning from an adventure');
|
||||
tz_def('HERO_ADV_MOV_SHORT', 'Adventure');
|
||||
|
||||
@@ -452,6 +452,14 @@ class Message
|
||||
case 24:
|
||||
case 25:
|
||||
return 24;
|
||||
|
||||
// Hero adventure report (T4 hero port)
|
||||
case 26:
|
||||
return 26;
|
||||
|
||||
// Hero auction report (T4 hero port)
|
||||
case 27:
|
||||
return 27;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
@@ -796,7 +796,12 @@ class Units {
|
||||
$speeds[] = $GLOBALS['u'.$heroUnit]['speed'];
|
||||
}
|
||||
|
||||
return $generator->procDistanceTime($fromCor, $toCor, min($speeds), $mode, $from);
|
||||
$walkTime = $generator->procDistanceTime($fromCor, $toCor, min($speeds), $mode, $from);
|
||||
|
||||
// T4 hero port (Phase 5): boots (>20 tiles), pennant (own villages)
|
||||
// and standard (ally villages) shorten the trip when the hero rides
|
||||
// along. Returns the input unchanged when NEW_FUNCTIONS_HERO_T4 is off.
|
||||
return HeroBattleBonus::adjustTravelTime($owner, $unitArray[10] ?? 0, $from, $to, $walkTime);
|
||||
}
|
||||
|
||||
public function startRaidList($post){
|
||||
|
||||
@@ -207,6 +207,11 @@ $hasRally = $village->resarray['f39'] > 0;
|
||||
$set = $database->getMovement(5,$village->wid,0);
|
||||
$cnt = count($set);
|
||||
foreach($out as $u) if($u['vref']==$village->wid) $cnt++;
|
||||
// T4 hero port: adventure legs count as troops on their way too.
|
||||
if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4) {
|
||||
$cnt += count($database->getMovement(20,$village->wid,0));
|
||||
$cnt += count($database->getMovement(21,$village->wid,1));
|
||||
}
|
||||
?>
|
||||
<?php if($cnt>=1): ?>
|
||||
<h4><?= TROOPS_ON_THEIR_WAY ?></h4>
|
||||
|
||||
@@ -20,6 +20,41 @@
|
||||
#################################################################################
|
||||
|
||||
$outgoing = $database->getMovement(3, $village->wid, 0);
|
||||
|
||||
// T4 hero port: hero adventure legs (out = from this village, back = to it).
|
||||
$advWalking = [];
|
||||
if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4) {
|
||||
foreach ($database->getMovement(20, $village->wid, 0) as $m) { $m['t4adv'] = 'out'; $advWalking[] = $m; }
|
||||
foreach ($database->getMovement(21, $village->wid, 1) as $m) { $m['t4adv'] = 'back'; $advWalking[] = $m; }
|
||||
}
|
||||
?>
|
||||
|
||||
<?php foreach ($advWalking as $m):
|
||||
$session->timer++;
|
||||
$action = $m['t4adv'] === 'out'
|
||||
? (defined('HERO_ADV_MOV_OUT') ? HERO_ADV_MOV_OUT : 'Hero on an adventure')
|
||||
: (defined('HERO_ADV_MOV_BACK') ? HERO_ADV_MOV_BACK : 'Hero returning from an adventure');
|
||||
$dt = $generator->procMtime($m['endtime']);
|
||||
?>
|
||||
<table class="troop_details" cellpadding="1" cellspacing="1">
|
||||
<thead><tr>
|
||||
<td class="role"><a href="karte.php?d=<?= $village->wid?>&c=<?= $generator->getMapCheck($village->wid)?>"><?= $village->vname?></a></td>
|
||||
<td colspan="2"><?= $action?></td>
|
||||
</tr></thead>
|
||||
<tbody class="units">
|
||||
<tr><th> </th><td style="width:40px;"><img src="img/x.gif" class="unit uhero" title="<?php echo U0; ?>"></td><td> </td></tr>
|
||||
<tr><th><?= TROOPS?></th><td>1</td><td> </td></tr>
|
||||
</tbody>
|
||||
<tbody class="infos"><tr>
|
||||
<th><?= ARRIVAL?></th>
|
||||
<td colspan="2">
|
||||
<div class="in small"><span id="timer<?= $session->timer?>"><?= $generator->getTimeFormat($m['endtime']-time())?></span> h</div>
|
||||
<div class="at"><?= $dt[0]!='today'?ON.' '.$dt[0].' ':''?><?= AT?> <?= $dt[1]?></div>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
<?php endforeach;?>
|
||||
<?php
|
||||
?>
|
||||
|
||||
<?php foreach ($outgoing as $m):
|
||||
|
||||
@@ -71,7 +71,23 @@
|
||||
|
||||
if(isset($_GET['land']) && $village->resarray['f' . $id] >= 1) {
|
||||
include_once("37_land.tpl");
|
||||
} else if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4
|
||||
&& $village->resarray['f' . $id] >= 1
|
||||
&& isset($_GET['t4tab'])
|
||||
&& in_array($_GET['t4tab'], ['items', 'adventures', 'auction'], true)) {
|
||||
|
||||
// T4 hero port (Phase 6): items / adventures / auction tabs.
|
||||
// The classic hero flow below stays byte-identical when the
|
||||
// feature flag is off or no tab is selected.
|
||||
$t4tab = $_GET['t4tab'];
|
||||
include_once("37_t4nav.tpl");
|
||||
include_once("37_" . $t4tab . ".tpl");
|
||||
|
||||
} else if ($village->resarray['f' . $id] >= 1) {
|
||||
if (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4) {
|
||||
$t4tab = 'hero';
|
||||
include_once("37_t4nav.tpl");
|
||||
}
|
||||
$include_training = true;
|
||||
$include_revive = false;
|
||||
if (isset($heroes) && is_array($heroes) && count($heroes)) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# T4 hero adventures tab (37_adventures.tpl) - Phase 6 #
|
||||
# POST: t4action=startadv, advid. Countdown spans reuse the existing #
|
||||
# $session->timer JS convention (same as 37.tpl's training timer). #
|
||||
#################################################################################
|
||||
|
||||
$t4Adventures = new HeroAdventure();
|
||||
$t4Msg = '';
|
||||
|
||||
if (isset($_POST['t4action'], $_POST['advid']) && $_POST['t4action'] === 'startadv') {
|
||||
$t4Result = $t4Adventures->startAdventure($session->uid, (int) $_POST['advid']);
|
||||
if ($t4Result === HeroAdventure::START_OK) {
|
||||
$t4Msg = HERO_ADV_START_OK;
|
||||
} elseif ($t4Result === HeroAdventure::START_NO_HERO) {
|
||||
$t4Msg = HERO_ADV_START_NOHERO;
|
||||
} elseif ($t4Result === HeroAdventure::START_HERO_AWAY) {
|
||||
$t4Msg = HERO_ADV_START_AWAY;
|
||||
} else {
|
||||
$t4Msg = HERO_ADV_START_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
// Top up the list opportunistically (respects max/refresh limits internally).
|
||||
$t4Adventures->generateOffers($session->uid);
|
||||
|
||||
$t4Offers = $t4Adventures->getOffers($session->uid);
|
||||
$t4Running = $t4Adventures->getRunning($session->uid);
|
||||
$t4Now = time();
|
||||
?>
|
||||
|
||||
<?php if ($t4Msg !== '') { ?>
|
||||
<p class="message" style="font-weight:bold;"><?php echo $t4Msg; ?></p>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($t4Running) { ?>
|
||||
<table id="distribution" cellpadding="1" cellspacing="1">
|
||||
<thead>
|
||||
<tr><th><?php echo HERO_ADV_RUNNING; ?>
|
||||
<span id="timer<?php echo ++$session->timer; ?>"><?php echo $generator->getTimeFormat(max(0, $t4Running['endtime'] - $t4Now)); ?></span>
|
||||
</th></tr>
|
||||
</thead>
|
||||
</table>
|
||||
<?php } ?>
|
||||
|
||||
<table id="distribution" cellpadding="1" cellspacing="1" style="margin-top:10px;">
|
||||
<thead>
|
||||
<tr><th colspan="4"><?php echo HERO_ADV_LIST; ?></th></tr>
|
||||
<tr>
|
||||
<td><b><?php echo HERO_ADV_DIFFICULTY; ?></b></td>
|
||||
<td><b><?php echo HERO_ADV_DURATION; ?></b></td>
|
||||
<td><b><?php echo HERO_ADV_EXPIRES; ?></b></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($t4Offers)) { ?>
|
||||
<?php foreach ($t4Offers as $t4Offer) { ?>
|
||||
<tr>
|
||||
<td><?php echo ((int) $t4Offer['difficulty'] === 1) ? HERO_ADV_DIFF_HARD : HERO_ADV_DIFF_NORMAL; ?></td>
|
||||
<td><?php echo $generator->getTimeFormat((int) $t4Offer['duration']); ?></td>
|
||||
<td><span id="timer<?php echo ++$session->timer; ?>"><?php echo $generator->getTimeFormat(max(0, $t4Offer['expire'] - $t4Now)); ?></span></td>
|
||||
<td style="width:140px;text-align:center;">
|
||||
<?php if (!$t4Running) { ?>
|
||||
<form action="" method="POST" style="margin:0;">
|
||||
<input type="hidden" name="t4action" value="startadv">
|
||||
<input type="hidden" name="advid" value="<?php echo (int) $t4Offer['id']; ?>">
|
||||
<input type="submit" value="<?php echo HERO_ADV_GO; ?>">
|
||||
</form>
|
||||
<?php } else { ?>-<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } else { ?>
|
||||
<tr><td colspan="4"><?php echo HERO_ADV_NONE; ?></td></tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# T4 hero auction tab (37_auction.tpl) - Phase 6 #
|
||||
# POST actions: #
|
||||
# t4action=bid (aucid, maxbid) #
|
||||
# t4action=sell (rowid, qty, price, duration) #
|
||||
# bid_max is only ever shown for the viewer's OWN bids (getMyBids). #
|
||||
#################################################################################
|
||||
|
||||
$t4Auction = new HeroAuction();
|
||||
$t4Msg = '';
|
||||
|
||||
if (isset($_POST['t4action'])) {
|
||||
if ($_POST['t4action'] === 'bid' && isset($_POST['aucid'], $_POST['maxbid'])) {
|
||||
$t4Result = $t4Auction->placeBid($session->uid, (int) $_POST['aucid'], (int) $_POST['maxbid']);
|
||||
if ($t4Result === HeroAuction::BID_OK) {
|
||||
$t4Msg = HERO_AUC_BID_OK;
|
||||
} elseif ($t4Result === HeroAuction::BID_OUTBID) {
|
||||
$t4Msg = HERO_AUC_BID_OUTBID;
|
||||
} elseif ($t4Result === HeroAuction::BID_NO_SILVER) {
|
||||
$t4Msg = HERO_AUC_BID_NOSILVER;
|
||||
} else {
|
||||
$t4Msg = HERO_AUC_BID_FAIL;
|
||||
}
|
||||
} elseif ($_POST['t4action'] === 'sell'
|
||||
&& isset($_POST['rowid'], $_POST['qty'], $_POST['price'], $_POST['duration'])) {
|
||||
$t4Result = $t4Auction->createAuction(
|
||||
$session->uid, (int) $_POST['rowid'],
|
||||
(int) $_POST['qty'], (int) $_POST['price'], (int) $_POST['duration']
|
||||
);
|
||||
$t4Msg = ($t4Result > 0) ? HERO_AUC_SELL_OK : HERO_AUC_SELL_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
$t4Open = $t4Auction->getOpenAuctions();
|
||||
$t4MyBids = $t4Auction->getMyBids($session->uid);
|
||||
$t4MySales = $t4Auction->getMySales($session->uid);
|
||||
$t4Now = time();
|
||||
|
||||
// Unequipped items sellable from the inventory.
|
||||
$t4Sellable = array();
|
||||
foreach ($t4HeroItems->getInventory($session->uid) as $t4Row) {
|
||||
if ($t4Row['equipped'] == 0 && !$t4Row['orphan']) {
|
||||
$t4Sellable[] = $t4Row;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if ($t4Msg !== '') { ?>
|
||||
<p class="message" style="font-weight:bold;"><?php echo $t4Msg; ?></p>
|
||||
<?php } ?>
|
||||
|
||||
<table id="distribution" cellpadding="1" cellspacing="1">
|
||||
<thead>
|
||||
<tr><th colspan="5"><?php echo HERO_AUC_OPEN; ?></th></tr>
|
||||
<tr>
|
||||
<td><b><?php echo HERO_AUC_ITEM; ?></b></td>
|
||||
<td><b><?php echo HERO_QUANTITY; ?></b></td>
|
||||
<td><b><?php echo HERO_AUC_PRICE; ?></b></td>
|
||||
<td><b><?php echo HERO_AUC_TIME_LEFT; ?></b></td>
|
||||
<td><b><?php echo HERO_AUC_BID; ?></b></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (count($t4Open)) { ?>
|
||||
<?php foreach ($t4Open as $t4A) { ?>
|
||||
<tr>
|
||||
<td><span class="heroT4Item item<?php echo (int) $t4A['itemid']; ?>"></span> <?php echo $t4A['name']; ?>
|
||||
<?php if ((int) $t4A['seller'] === 0) { ?><small>(<?php echo HERO_AUC_SELLER_NPC; ?>)</small><?php } ?>
|
||||
</td>
|
||||
<td style="text-align:center;"><?php echo (int) $t4A['quantity']; ?></td>
|
||||
<td style="text-align:right;"><?php echo number_format((int) $t4A['silver_current']); ?></td>
|
||||
<td><span id="timer<?php echo ++$session->timer; ?>"><?php echo $generator->getTimeFormat(max(0, $t4A['time_end'] - $t4Now)); ?></span></td>
|
||||
<td style="width:170px;text-align:center;">
|
||||
<?php if ((int) $t4A['seller'] !== $session->uid) { ?>
|
||||
<form action="" method="POST" style="margin:0;">
|
||||
<input type="hidden" name="t4action" value="bid">
|
||||
<input type="hidden" name="aucid" value="<?php echo (int) $t4A['id']; ?>">
|
||||
<input type="text" name="maxbid" size="6" style="text-align:right;"
|
||||
value="<?php echo (int) $t4A['silver_current'] + ((int) $t4A['bidder'] > 0 ? 1 : 0); ?>">
|
||||
<input type="submit" value="<?php echo HERO_AUC_BID; ?>">
|
||||
</form>
|
||||
<?php } else { ?>-<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php } else { ?>
|
||||
<tr><td colspan="5"><?php echo HERO_AUC_NONE; ?></td></tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if (count($t4MyBids)) { ?>
|
||||
<table id="distribution" cellpadding="1" cellspacing="1" style="margin-top:10px;">
|
||||
<thead>
|
||||
<tr><th colspan="4"><?php echo HERO_AUC_MY_BIDS; ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($t4MyBids as $t4A) { ?>
|
||||
<tr>
|
||||
<td><?php echo $t4A['name']; ?> (<?php echo (int) $t4A['quantity']; ?>x)</td>
|
||||
<td style="text-align:right;"><?php echo HERO_AUC_PRICE; ?>: <?php echo number_format((int) $t4A['silver_current']); ?></td>
|
||||
<td style="text-align:right;"><?php echo HERO_AUC_YOUR_MAX; ?>: <?php echo number_format((int) $t4A['bid_max']); ?></td>
|
||||
<td><span id="timer<?php echo ++$session->timer; ?>"><?php echo $generator->getTimeFormat(max(0, $t4A['time_end'] - $t4Now)); ?></span></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (count($t4MySales)) { ?>
|
||||
<table id="distribution" cellpadding="1" cellspacing="1" style="margin-top:10px;">
|
||||
<thead>
|
||||
<tr><th colspan="3"><?php echo HERO_AUC_MY_SALES; ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($t4MySales as $t4A) { ?>
|
||||
<tr>
|
||||
<td><?php echo $t4A['name']; ?> (<?php echo (int) $t4A['quantity']; ?>x)</td>
|
||||
<td style="text-align:right;"><?php echo HERO_AUC_PRICE; ?>: <?php echo number_format((int) $t4A['silver_current']); ?></td>
|
||||
<td><span id="timer<?php echo ++$session->timer; ?>"><?php echo $generator->getTimeFormat(max(0, $t4A['time_end'] - $t4Now)); ?></span></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } ?>
|
||||
|
||||
<?php if (count($t4Sellable)) { ?>
|
||||
<table id="distribution" cellpadding="1" cellspacing="1" style="margin-top:10px;">
|
||||
<thead>
|
||||
<tr><th colspan="5"><?php echo HERO_AUC_SELL; ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<form action="" method="POST" style="margin:0;">
|
||||
<input type="hidden" name="t4action" value="sell">
|
||||
<td>
|
||||
<select name="rowid">
|
||||
<?php foreach ($t4Sellable as $t4Row) { ?>
|
||||
<option value="<?php echo (int) $t4Row['id']; ?>">
|
||||
<?php echo $t4Row['name']; ?><?php if ((int) $t4Row['quantity'] > 1) echo ' (' . (int) $t4Row['quantity'] . 'x)'; ?>
|
||||
</option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
</td>
|
||||
<td><?php echo HERO_QUANTITY; ?>: <input type="text" name="qty" value="1" size="3" style="text-align:center;"></td>
|
||||
<td><?php echo HERO_AUC_START_PRICE; ?>: <input type="text" name="price" value="10" size="5" style="text-align:right;"></td>
|
||||
<td><?php echo HERO_AUC_DURATION; ?>:
|
||||
<select name="duration">
|
||||
<option value="14400">4h</option>
|
||||
<option value="28800">8h</option>
|
||||
<option value="86400">24h</option>
|
||||
</select>
|
||||
</td>
|
||||
<td style="text-align:center;"><input type="submit" value="<?php echo HERO_AUC_LIST; ?>"></td>
|
||||
</form>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php } ?>
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# T4 hero inventory & equipment tab (37_items.tpl) - Phase 6 #
|
||||
# Inline POST handling (same convention as 37_hero.tpl's rename form): #
|
||||
# t4action=equip|unequip|useitem, rowid, qty #
|
||||
# Law tablets target the CURRENT village ($village->wid). #
|
||||
#################################################################################
|
||||
|
||||
$t4Msg = '';
|
||||
|
||||
if (isset($_POST['t4action'], $_POST['rowid'])) {
|
||||
$t4RowId = (int) $_POST['rowid'];
|
||||
|
||||
switch ($_POST['t4action']) {
|
||||
case 'equip':
|
||||
$t4Msg = $t4HeroItems->equipItem($session->uid, $t4RowId)
|
||||
? HERO_EQUIP_OK : HERO_EQUIP_FAIL;
|
||||
break;
|
||||
|
||||
case 'unequip':
|
||||
$t4Msg = $t4HeroItems->unequipItem($session->uid, $t4RowId)
|
||||
? HERO_UNEQUIP_OK : HERO_ITEM_USE_FAIL;
|
||||
break;
|
||||
|
||||
case 'useitem':
|
||||
$t4Qty = max(1, (int) ($_POST['qty'] ?? 1));
|
||||
$t4Result = $t4HeroItems->useItem($session->uid, $t4RowId, $t4Qty, $village->wid);
|
||||
if ($t4Result === HeroItems::USE_OK) {
|
||||
$t4Msg = HERO_ITEM_USED_OK;
|
||||
} elseif ($t4Result === HeroItems::USE_DEFERRED) {
|
||||
$t4Msg = HERO_ITEM_USE_BATTLE;
|
||||
} else {
|
||||
$t4Msg = HERO_ITEM_USE_FAIL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$t4Inventory = $t4HeroItems->getInventory($session->uid);
|
||||
$t4Equipped = $t4HeroItems->getEquipped($session->uid);
|
||||
?>
|
||||
|
||||
<?php if ($t4Msg !== '') { ?>
|
||||
<p class="message" style="font-weight:bold;"><?php echo $t4Msg; ?></p>
|
||||
<?php } ?>
|
||||
|
||||
<table id="distribution" cellpadding="1" cellspacing="1">
|
||||
<thead>
|
||||
<tr><th colspan="4"><?php echo HERO_ITEMS_EQUIPPED; ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php for ($t4Slot = 1; $t4Slot <= 6; $t4Slot++) { ?>
|
||||
<tr>
|
||||
<td style="width:110px;"><b><?php echo constant('HERO_SLOT_' . $t4Slot); ?></b></td>
|
||||
<?php if (isset($t4Equipped[$t4Slot])) { $t4Row = $t4Equipped[$t4Slot]; ?>
|
||||
<td><span class="heroT4Item item<?php echo (int) $t4Row['itemid']; ?>"></span> <?php echo $t4Row['name']; ?></td>
|
||||
<td style="width:60px;text-align:center;"><?php echo 'T' . (int) $t4Row['def']['tier']; ?></td>
|
||||
<td style="width:110px;text-align:center;">
|
||||
<form action="" method="POST" style="margin:0;">
|
||||
<input type="hidden" name="t4action" value="unequip">
|
||||
<input type="hidden" name="rowid" value="<?php echo (int) $t4Row['id']; ?>">
|
||||
<input type="submit" value="<?php echo HERO_UNEQUIP; ?>">
|
||||
</form>
|
||||
</td>
|
||||
<?php } else { ?>
|
||||
<td colspan="3" style="color:#888;">-</td>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table id="distribution" cellpadding="1" cellspacing="1" style="margin-top:10px;">
|
||||
<thead>
|
||||
<tr><th colspan="4"><?php echo HERO_ITEMS_BAG; ?></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$t4HasUnequipped = false;
|
||||
foreach ($t4Inventory as $t4Row) {
|
||||
if ($t4Row['equipped'] == 1 || $t4Row['orphan']) {
|
||||
continue;
|
||||
}
|
||||
$t4HasUnequipped = true;
|
||||
$t4IsBag = ((int) $t4Row['def']['slot'] === HSLOT_BAG);
|
||||
?>
|
||||
<tr>
|
||||
<td><span class="heroT4Item item<?php echo (int) $t4Row['itemid']; ?>"></span> <?php echo $t4Row['name']; ?></td>
|
||||
<td style="width:80px;text-align:center;">
|
||||
<?php echo $t4IsBag ? (int) $t4Row['quantity'] . 'x' : 'T' . (int) $t4Row['def']['tier']; ?>
|
||||
</td>
|
||||
<td style="width:110px;text-align:center;"><?php echo constant('HERO_SLOT_' . (int) $t4Row['def']['slot']); ?></td>
|
||||
<td style="width:170px;text-align:center;">
|
||||
<form action="" method="POST" style="margin:0;">
|
||||
<input type="hidden" name="rowid" value="<?php echo (int) $t4Row['id']; ?>">
|
||||
<?php if ($t4IsBag) { ?>
|
||||
<input type="hidden" name="t4action" value="useitem">
|
||||
<input type="text" name="qty" value="1" size="3" style="text-align:center;">
|
||||
<input type="submit" value="<?php echo HERO_USE_ITEM; ?>">
|
||||
<?php } else { ?>
|
||||
<input type="hidden" name="t4action" value="equip">
|
||||
<input type="submit" value="<?php echo HERO_EQUIP; ?>">
|
||||
<?php } ?>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if (!$t4HasUnequipped) { ?>
|
||||
<tr><td colspan="4"><?php echo HERO_ITEMS_EMPTY; ?></td></tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# T4 hero mansion tab navigation (37_t4nav.tpl) - Phase 6 #
|
||||
# Included by 37.tpl with $t4tab set to 'hero'|'items'|'adventures'|'auction'. #
|
||||
# Silver balance shown on the right; reuses existing anchor styling. #
|
||||
#################################################################################
|
||||
|
||||
$t4HeroItems = new HeroItems();
|
||||
$t4Silver = $t4HeroItems->getSilver($session->uid);
|
||||
$t4Tabs = [
|
||||
'hero' => ['label' => HERO_T4_TAB_HERO, 'url' => 'build.php?id=' . $id],
|
||||
'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'],
|
||||
];
|
||||
?>
|
||||
<link rel="stylesheet" href="css/hero_items.css" type="text/css">
|
||||
<div class="heroT4Nav" style="margin:6px 0 10px 0;">
|
||||
<?php foreach ($t4Tabs as $key => $tab) { ?>
|
||||
<?php if ($key === $t4tab) { ?>
|
||||
<span style="font-weight:bold;margin-right:14px;"><?php echo $tab['label']; ?></span>
|
||||
<?php } else { ?>
|
||||
<a href="<?php echo $tab['url']; ?>" style="margin-right:14px;"><?php echo $tab['label']; ?></a>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
<span style="float:right;"><b><?php echo HERO_SILVER; ?>:</b> <?php echo number_format($t4Silver); ?></span>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# Hero adventure report (26.tpl) - T4 hero port, Phase 1 #
|
||||
# Handles ntype 26. The reward payload is stored in ndata.data as a #
|
||||
# query-string, e.g.: #
|
||||
# difficulty=1&exp=22&silver=80&wood=300&clay=0&iron=450&crop=120&hp=18 #
|
||||
# &item=HERO_ITEM_OINTMENT&itemqty=2 #
|
||||
# Any missing key simply renders as 0 / hidden, so partial payloads are safe. #
|
||||
# #
|
||||
# Lang constants (HERO_ADV_*, HERO_ITEM_*) arrive in the Phase 6 lang pass; #
|
||||
# the tz() helper below falls back to a readable literal until then so this #
|
||||
# template never emits "Undefined constant" before the rest of the port lands. #
|
||||
#################################################################################
|
||||
|
||||
// Tiny local fallback so this file is self-contained in Phase 1.
|
||||
if (!function_exists('tz_hero_adv')) {
|
||||
function tz_hero_adv($const, $fallback) {
|
||||
return defined($const) ? constant($const) : $fallback;
|
||||
}
|
||||
}
|
||||
|
||||
parse_str((string)($message->readingNotice['data'] ?? ''), $adv);
|
||||
|
||||
$difficulty = (int)($adv['difficulty'] ?? 0);
|
||||
$died = (int)($adv['died'] ?? 0);
|
||||
$exp = (int)($adv['exp'] ?? 0);
|
||||
$silver = (int)($adv['silver'] ?? 0);
|
||||
$hp = (int)($adv['hp'] ?? 0);
|
||||
$wood = (int)($adv['wood'] ?? 0);
|
||||
$clay = (int)($adv['clay'] ?? 0);
|
||||
$iron = (int)($adv['iron'] ?? 0);
|
||||
$crop = (int)($adv['crop'] ?? 0);
|
||||
$itemId = (int)($adv['itemid'] ?? 0);
|
||||
$itemQty = (int)($adv['itemqty'] ?? 0);
|
||||
|
||||
// Resolve the item name from the catalog (heroItemName falls back safely).
|
||||
// berichte.php runs from the web root, so the relative path is stable.
|
||||
if ($itemId > 0 && !function_exists('heroItemName')) {
|
||||
include_once 'GameEngine/Data/hero_items.php';
|
||||
}
|
||||
|
||||
$diffLabel = $difficulty === 1
|
||||
? tz_hero_adv('HERO_ADV_HARD', 'a hard adventure')
|
||||
: tz_hero_adv('HERO_ADV_NORMAL', 'a normal adventure');
|
||||
?>
|
||||
|
||||
<table cellpadding="1" cellspacing="1" id="report_surround">
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th><?php echo defined('SUBJECT') ? SUBJECT : 'Subject'; ?>:</th>
|
||||
<th><?php echo tz_loc_topic($message->readingNotice['topic']); ?></th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<?php $date = $generator->procMtime($message->readingNotice['time']); ?>
|
||||
<td class="sent"><?php echo defined('TZ_SENT') ? TZ_SENT : 'Sent'; ?></td>
|
||||
<td><?php echo (defined('ON') ? ON : 'on') . ' '; ?><span><?php echo $date[0] . " " . $date[1]; ?></span></td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr><td colspan="2" class="empty"></td></tr>
|
||||
<tr><td colspan="2" class="report_content">
|
||||
|
||||
<?php if ($died) { ?>
|
||||
|
||||
<p><?php echo tz_hero_adv('HERO_ADV_DIED', 'Your hero fell on'); ?> <b><?php echo $diffLabel; ?></b>.</p>
|
||||
<p><?php echo tz_hero_adv('HERO_ADV_DIED_INFO', 'All loot was lost. The hero can be revived at the Hero\'s Mansion.'); ?></p>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<p><?php echo tz_hero_adv('HERO_ADV_RETURNED', 'Your hero has returned from'); ?> <b><?php echo $diffLabel; ?></b>.</p>
|
||||
|
||||
<table class="adventureReward" cellpadding="1" cellspacing="1" style="width:100%;margin-top:8px;">
|
||||
<tr>
|
||||
<th style="text-align:left;"><?php echo tz_hero_adv('HERO_ADV_REWARD', 'Reward'); ?></th>
|
||||
<th style="text-align:right;"><?php echo tz_hero_adv('HERO_ADV_AMOUNT', 'Amount'); ?></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_adv('HERO_EXPERIENCE', 'Experience'); ?></td>
|
||||
<td style="text-align:right;">+<?php echo $exp; ?></td>
|
||||
</tr>
|
||||
<?php if ($silver > 0) { ?>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_adv('HERO_SILVER', 'Silver'); ?></td>
|
||||
<td style="text-align:right;">+<?php echo number_format($silver); ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if ($wood || $clay || $iron || $crop) { ?>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_adv('RESOURCES', 'Resources'); ?></td>
|
||||
<td style="text-align:right;">
|
||||
<span class="res wood"><?php echo number_format($wood); ?></span>
|
||||
<span class="res clay"><?php echo number_format($clay); ?></span>
|
||||
<span class="res iron"><?php echo number_format($iron); ?></span>
|
||||
<span class="res crop"><?php echo number_format($crop); ?></span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if ($itemId > 0 && $itemQty > 0) { ?>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_adv('HERO_ADV_ITEM_FOUND', 'Item found'); ?></td>
|
||||
<td style="text-align:right;"><?php echo $itemQty; ?>× <?php echo heroItemName($itemId); ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<?php if ($hp > 0) { ?>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_adv('HERO_ADV_HP_LOST', 'Health lost'); ?></td>
|
||||
<td style="text-align:right;color:#a00;">-<?php echo $hp; ?>%</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
|
||||
<?php } // end survived branch ?>
|
||||
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# Hero auction report (27.tpl) - T4 hero port, Phase 4 #
|
||||
# Handles ntype 27. Payload (ndata.data, query-string): #
|
||||
# role=winner&itemid=71&qty=1&price=120&refund=30 #
|
||||
# role=seller&itemid=71&qty=1&price=120&fee=12 #
|
||||
# role=seller&expired=1&itemid=71&qty=1 #
|
||||
# Same defensive-lang conventions as 26.tpl. #
|
||||
#################################################################################
|
||||
|
||||
if (!function_exists('tz_hero_auc')) {
|
||||
function tz_hero_auc($const, $fallback) {
|
||||
return defined($const) ? constant($const) : $fallback;
|
||||
}
|
||||
}
|
||||
|
||||
parse_str((string)($message->readingNotice['data'] ?? ''), $auc);
|
||||
|
||||
$role = (string)($auc['role'] ?? '');
|
||||
$expired = (int)($auc['expired'] ?? 0);
|
||||
$itemId = (int)($auc['itemid'] ?? 0);
|
||||
$qty = (int)($auc['qty'] ?? 0);
|
||||
$price = (int)($auc['price'] ?? 0);
|
||||
$refund = (int)($auc['refund'] ?? 0);
|
||||
$fee = (int)($auc['fee'] ?? 0);
|
||||
|
||||
if ($itemId > 0 && !function_exists('heroItemName')) {
|
||||
include_once 'GameEngine/Data/hero_items.php';
|
||||
}
|
||||
$itemLabel = $qty . '× ' . ($itemId > 0 ? heroItemName($itemId) : '?');
|
||||
?>
|
||||
|
||||
<table cellpadding="1" cellspacing="1" id="report_surround">
|
||||
<thead>
|
||||
|
||||
<tr>
|
||||
<th><?php echo defined('SUBJECT') ? SUBJECT : 'Subject'; ?>:</th>
|
||||
<th><?php echo tz_loc_topic($message->readingNotice['topic']); ?></th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<?php $date = $generator->procMtime($message->readingNotice['time']); ?>
|
||||
<td class="sent"><?php echo defined('TZ_SENT') ? TZ_SENT : 'Sent'; ?></td>
|
||||
<td><?php echo (defined('ON') ? ON : 'on') . ' '; ?><span><?php echo $date[0] . " " . $date[1]; ?></span></td>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr><td colspan="2" class="empty"></td></tr>
|
||||
<tr><td colspan="2" class="report_content">
|
||||
|
||||
<?php if ($role === 'winner') { ?>
|
||||
|
||||
<p><?php echo tz_hero_auc('HERO_AUC_WON', 'You won the auction for'); ?> <b><?php echo $itemLabel; ?></b>.</p>
|
||||
<table class="auctionResult" cellpadding="1" cellspacing="1" style="width:100%;margin-top:8px;">
|
||||
<tr>
|
||||
<td><?php echo tz_hero_auc('HERO_AUC_PRICE', 'Final price'); ?></td>
|
||||
<td style="text-align:right;"><?php echo number_format($price); ?> <?php echo tz_hero_auc('HERO_SILVER', 'silver'); ?></td>
|
||||
</tr>
|
||||
<?php if ($refund > 0) { ?>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_auc('HERO_AUC_REFUND', 'Refunded from your maximum bid'); ?></td>
|
||||
<td style="text-align:right;">+<?php echo number_format($refund); ?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
|
||||
<?php } elseif ($role === 'seller' && !$expired) { ?>
|
||||
|
||||
<p><?php echo tz_hero_auc('HERO_AUC_SOLD', 'Your auction sold:'); ?> <b><?php echo $itemLabel; ?></b>.</p>
|
||||
<table class="auctionResult" cellpadding="1" cellspacing="1" style="width:100%;margin-top:8px;">
|
||||
<tr>
|
||||
<td><?php echo tz_hero_auc('HERO_AUC_PRICE', 'Final price'); ?></td>
|
||||
<td style="text-align:right;"><?php echo number_format($price); ?> <?php echo tz_hero_auc('HERO_SILVER', 'silver'); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo tz_hero_auc('HERO_AUC_FEE', 'Auction fee'); ?></td>
|
||||
<td style="text-align:right;color:#a00;">-<?php echo number_format($fee); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b><?php echo tz_hero_auc('HERO_AUC_PAYOUT', 'Payout'); ?></b></td>
|
||||
<td style="text-align:right;"><b>+<?php echo number_format($price - $fee); ?></b></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php } else { ?>
|
||||
|
||||
<p><?php echo tz_hero_auc('HERO_AUC_EXPIRED', 'Your auction ended without bids. The item was returned to your inventory:'); ?> <b><?php echo $itemLabel; ?></b>.</p>
|
||||
|
||||
<?php } ?>
|
||||
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -112,6 +112,12 @@ $movement3_0 = $database->getMovement(3, $village->wid, 0);
|
||||
$movement5_0 = $database->getMovement(5, $village->wid, 0);
|
||||
$movement8_1 = $database->getMovement(8, $village->wid, 1);
|
||||
$movement9_0 = $database->getMovement(9, $village->wid, 0);
|
||||
// T4 hero port: hero adventure movements are only relevant for the hero's
|
||||
// home village and only when the feature is on.
|
||||
$movement20_0 = (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4)
|
||||
? $database->getMovement(20, $village->wid, 0) : [];
|
||||
$movement21_1 = (defined('NEW_FUNCTIONS_HERO_T4') && NEW_FUNCTIONS_HERO_T4)
|
||||
? $database->getMovement(21, $village->wid, 1) : [];
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------
|
||||
@@ -285,6 +291,42 @@ renderMovementRow(
|
||||
$session
|
||||
);
|
||||
|
||||
/**
|
||||
* =========================================================
|
||||
* HERO ADVENTURE (T4 hero port): outbound + returning
|
||||
* =========================================================
|
||||
*/
|
||||
|
||||
$advOutArrival = array();
|
||||
foreach ($movement20_0 as $receive) {
|
||||
$advOutArrival[] = $receive['endtime'];
|
||||
}
|
||||
renderMovementRow(
|
||||
'att2',
|
||||
'a2',
|
||||
defined('HERO_ADV_MOV_OUT') ? HERO_ADV_MOV_OUT : 'Hero on an adventure',
|
||||
defined('HERO_ADV_MOV_SHORT') ? HERO_ADV_MOV_SHORT : 'Adventure',
|
||||
count($movement20_0),
|
||||
!empty($advOutArrival) ? min($advOutArrival) : 0,
|
||||
$generator,
|
||||
$session
|
||||
);
|
||||
|
||||
$advBackArrival = array();
|
||||
foreach ($movement21_1 as $receive) {
|
||||
$advBackArrival[] = $receive['endtime'];
|
||||
}
|
||||
renderMovementRow(
|
||||
'def1',
|
||||
'd1',
|
||||
defined('HERO_ADV_MOV_BACK') ? HERO_ADV_MOV_BACK : 'Hero returning from an adventure',
|
||||
defined('HERO_ADV_MOV_SHORT') ? HERO_ADV_MOV_SHORT : 'Adventure',
|
||||
count($movement21_1),
|
||||
!empty($advBackArrival) ? min($advBackArrival) : 0,
|
||||
$generator,
|
||||
$session
|
||||
);
|
||||
|
||||
/**
|
||||
* =========================================================
|
||||
* FOUNDING NEW VILLAGE
|
||||
|
||||
+15
-9
@@ -3,15 +3,21 @@
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Project: TravianZ ##
|
||||
## Filename activate.php ##
|
||||
## Developed by: Dixie ##
|
||||
## Developed by: Dzoki ##
|
||||
## License: TravianZ Project ##
|
||||
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## URLs: http://travian.shadowss.ro ##
|
||||
## Source code: https://github.com/Shadowss/TravianZ ##
|
||||
## ##
|
||||
## Filename : activate.php ##
|
||||
## Type : In Game Activate File ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Developed by : Dzoki & Advocaite & Donnchadh ##
|
||||
## Refactored by : Shadow ##
|
||||
## Redesign by : Shadow ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## 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. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
|
||||
use App\Utils\AccessLogger;
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename ajax.php ##
|
||||
## Developed by: Dzoki ##
|
||||
## License: TravianZ Project ##
|
||||
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## URLs: http://travian.shadowss.ro ##
|
||||
## Source code: https://github.com/Shadowss/TravianZ ##
|
||||
## ##
|
||||
## Filename : ajax.php ##
|
||||
## Type : In Game Ajax File ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Developed by : Dzoki & Advocaite & Donnchadh ##
|
||||
## Refactored by : Shadow ##
|
||||
## Redesign by : Shadow ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## 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. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
|
||||
// even with autoloader created, we can't use it here yet, as it's not been created
|
||||
|
||||
+16
-8
@@ -1,15 +1,23 @@
|
||||
<?php
|
||||
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Project: TravianZ ##
|
||||
## Filename anleitung.php ##
|
||||
## Developed by: Dzoki ##
|
||||
## License: TravianZ Project ##
|
||||
## Copyright: TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## URLs: http://travian.shadowss.ro ##
|
||||
## Source code: https://github.com/Shadowss/TravianZ ##
|
||||
## ##
|
||||
## Filename : anleitung.php ##
|
||||
## Type : In Game Part of Main Page ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Developed by : Dzoki & Advocaite & Donnchadh ##
|
||||
## Refactored by : Shadow ##
|
||||
## Redesign by : Shadow ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## 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. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
|
||||
use App\Utils\AccessLogger;
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ $worldLabel = worldSizeLabel();
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title><?php echo SERVER_NAME ?> - Mass Message</title>
|
||||
<title><?php echo SERVER_NAME ?> - Build Croppers</title>
|
||||
<link rel="shortcut icon" href="favicon.ico"/>
|
||||
<meta http-equiv="cache-control" content="max-age=0" />
|
||||
<meta http-equiv="pragma" content="no-cache" />
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* AUTO-GENERATED from GameEngine/Data/hero_items.php - regenerate via var/tools/generate_hero_sprites.php
|
||||
Placeholder art: replace img/hero/items.png with real 48x48 sprites on the SAME grid
|
||||
(16 columns, catalog order) and this CSS keeps working unchanged. */
|
||||
.heroT4Item{display:inline-block;width:48px;height:48px;vertical-align:middle;
|
||||
background-image:url('../img/hero/items.png');background-repeat:no-repeat;}
|
||||
.heroT4Item.item1{background-position:-0px -0px;} /* Helmet of Awareness */
|
||||
.heroT4Item.item2{background-position:-48px -0px;} /* Helmet of Enlightenment */
|
||||
.heroT4Item.item3{background-position:-96px -0px;} /* Helmet of Wisdom */
|
||||
.heroT4Item.item4{background-position:-144px -0px;} /* Helmet of Regeneration */
|
||||
.heroT4Item.item5{background-position:-192px -0px;} /* Helmet of Health */
|
||||
.heroT4Item.item6{background-position:-240px -0px;} /* Helmet of Healing */
|
||||
.heroT4Item.item7{background-position:-288px -0px;} /* Helmet of the Gladiator */
|
||||
.heroT4Item.item8{background-position:-336px -0px;} /* Helmet of the Tribune */
|
||||
.heroT4Item.item9{background-position:-384px -0px;} /* Helmet of the Consul */
|
||||
.heroT4Item.item10{background-position:-432px -0px;} /* Helmet of the Horseman */
|
||||
.heroT4Item.item11{background-position:-480px -0px;} /* Helmet of the Cavalry */
|
||||
.heroT4Item.item12{background-position:-528px -0px;} /* Helmet of the Heavy Cavalry */
|
||||
.heroT4Item.item13{background-position:-576px -0px;} /* Helmet of the Mercenary */
|
||||
.heroT4Item.item14{background-position:-624px -0px;} /* Helmet of the Warrior */
|
||||
.heroT4Item.item15{background-position:-672px -0px;} /* Helmet of the Ruler */
|
||||
.heroT4Item.item20{background-position:-720px -0px;} /* Light Breastplate of Regeneration */
|
||||
.heroT4Item.item21{background-position:-0px -48px;} /* Breastplate of Regeneration */
|
||||
.heroT4Item.item22{background-position:-48px -48px;} /* Heavy Breastplate of Regeneration */
|
||||
.heroT4Item.item23{background-position:-96px -48px;} /* Light Armor */
|
||||
.heroT4Item.item24{background-position:-144px -48px;} /* Armor */
|
||||
.heroT4Item.item25{background-position:-192px -48px;} /* Heavy Armor */
|
||||
.heroT4Item.item26{background-position:-240px -48px;} /* Light Cuirass */
|
||||
.heroT4Item.item27{background-position:-288px -48px;} /* Cuirass */
|
||||
.heroT4Item.item28{background-position:-336px -48px;} /* Heavy Cuirass */
|
||||
.heroT4Item.item29{background-position:-384px -48px;} /* Light Segmented Armor */
|
||||
.heroT4Item.item30{background-position:-432px -48px;} /* Segmented Armor */
|
||||
.heroT4Item.item31{background-position:-480px -48px;} /* Heavy Segmented Armor */
|
||||
.heroT4Item.item40{background-position:-528px -48px;} /* Small Shield */
|
||||
.heroT4Item.item41{background-position:-576px -48px;} /* Shield */
|
||||
.heroT4Item.item42{background-position:-624px -48px;} /* Great Shield */
|
||||
.heroT4Item.item43{background-position:-672px -48px;} /* Small Hunting Horn */
|
||||
.heroT4Item.item44{background-position:-720px -48px;} /* Hunting Horn */
|
||||
.heroT4Item.item45{background-position:-0px -96px;} /* Great Hunting Horn */
|
||||
.heroT4Item.item46{background-position:-48px -96px;} /* Thief's Satchel */
|
||||
.heroT4Item.item47{background-position:-96px -96px;} /* Thief's Bag */
|
||||
.heroT4Item.item48{background-position:-144px -96px;} /* Thief's Sack */
|
||||
.heroT4Item.item49{background-position:-192px -96px;} /* Small Map */
|
||||
.heroT4Item.item50{background-position:-240px -96px;} /* Map */
|
||||
.heroT4Item.item51{background-position:-288px -96px;} /* Great Map */
|
||||
.heroT4Item.item52{background-position:-336px -96px;} /* Small Pennant */
|
||||
.heroT4Item.item53{background-position:-384px -96px;} /* Pennant */
|
||||
.heroT4Item.item54{background-position:-432px -96px;} /* Great Pennant */
|
||||
.heroT4Item.item55{background-position:-480px -96px;} /* Small Standard */
|
||||
.heroT4Item.item56{background-position:-528px -96px;} /* Standard */
|
||||
.heroT4Item.item57{background-position:-576px -96px;} /* Great Standard */
|
||||
.heroT4Item.item60{background-position:-624px -96px;} /* Boots of Regeneration */
|
||||
.heroT4Item.item61{background-position:-672px -96px;} /* Boots of Recovery */
|
||||
.heroT4Item.item62{background-position:-720px -96px;} /* Boots of Healing */
|
||||
.heroT4Item.item63{background-position:-0px -144px;} /* Boots of the Mercenary */
|
||||
.heroT4Item.item64{background-position:-48px -144px;} /* Boots of the Warrior */
|
||||
.heroT4Item.item65{background-position:-96px -144px;} /* Boots of the Ruler */
|
||||
.heroT4Item.item66{background-position:-144px -144px;} /* Small Spurs */
|
||||
.heroT4Item.item67{background-position:-192px -144px;} /* Spurs */
|
||||
.heroT4Item.item68{background-position:-240px -144px;} /* Great Spurs */
|
||||
.heroT4Item.item70{background-position:-288px -144px;} /* Riding Horse */
|
||||
.heroT4Item.item71{background-position:-336px -144px;} /* Thoroughbred */
|
||||
.heroT4Item.item72{background-position:-384px -144px;} /* Warhorse */
|
||||
.heroT4Item.item101{background-position:-432px -144px;} /* Legionnaire's Short Sword */
|
||||
.heroT4Item.item102{background-position:-480px -144px;} /* Legionnaire's Sword */
|
||||
.heroT4Item.item103{background-position:-528px -144px;} /* Legionnaire's Long Sword */
|
||||
.heroT4Item.item104{background-position:-576px -144px;} /* Praetorian's Short Sword */
|
||||
.heroT4Item.item105{background-position:-624px -144px;} /* Praetorian's Sword */
|
||||
.heroT4Item.item106{background-position:-672px -144px;} /* Praetorian's Long Sword */
|
||||
.heroT4Item.item107{background-position:-720px -144px;} /* Imperian's Short Sword */
|
||||
.heroT4Item.item108{background-position:-0px -192px;} /* Imperian's Sword */
|
||||
.heroT4Item.item109{background-position:-48px -192px;} /* Imperian's Long Sword */
|
||||
.heroT4Item.item110{background-position:-96px -192px;} /* Imperatoris' Short Saber */
|
||||
.heroT4Item.item111{background-position:-144px -192px;} /* Imperatoris' Saber */
|
||||
.heroT4Item.item112{background-position:-192px -192px;} /* Imperatoris' Long Saber */
|
||||
.heroT4Item.item113{background-position:-240px -192px;} /* Caesaris' Light Lance */
|
||||
.heroT4Item.item114{background-position:-288px -192px;} /* Caesaris' Lance */
|
||||
.heroT4Item.item115{background-position:-336px -192px;} /* Caesaris' Heavy Lance */
|
||||
.heroT4Item.item116{background-position:-384px -192px;} /* Clubswinger's Cudgel */
|
||||
.heroT4Item.item117{background-position:-432px -192px;} /* Clubswinger's Club */
|
||||
.heroT4Item.item118{background-position:-480px -192px;} /* Clubswinger's Mace */
|
||||
.heroT4Item.item119{background-position:-528px -192px;} /* Spearman's Pitchfork */
|
||||
.heroT4Item.item120{background-position:-576px -192px;} /* Spearman's Pike */
|
||||
.heroT4Item.item121{background-position:-624px -192px;} /* Spearman's Spear */
|
||||
.heroT4Item.item122{background-position:-672px -192px;} /* Axeman's Hatchet */
|
||||
.heroT4Item.item123{background-position:-720px -192px;} /* Axeman's Axe */
|
||||
.heroT4Item.item124{background-position:-0px -240px;} /* Axeman's Battle Axe */
|
||||
.heroT4Item.item125{background-position:-48px -240px;} /* Paladin's Small Hammer */
|
||||
.heroT4Item.item126{background-position:-96px -240px;} /* Paladin's Hammer */
|
||||
.heroT4Item.item127{background-position:-144px -240px;} /* Paladin's Sledgehammer */
|
||||
.heroT4Item.item128{background-position:-192px -240px;} /* Teutonic Knight's Short Sword */
|
||||
.heroT4Item.item129{background-position:-240px -240px;} /* Teutonic Knight's Sword */
|
||||
.heroT4Item.item130{background-position:-288px -240px;} /* Teutonic Knight's Long Sword */
|
||||
.heroT4Item.item131{background-position:-336px -240px;} /* Phalanx's Pitchfork */
|
||||
.heroT4Item.item132{background-position:-384px -240px;} /* Phalanx's Spear */
|
||||
.heroT4Item.item133{background-position:-432px -240px;} /* Phalanx's Lance */
|
||||
.heroT4Item.item134{background-position:-480px -240px;} /* Swordsman's Short Sword */
|
||||
.heroT4Item.item135{background-position:-528px -240px;} /* Swordsman's Sword */
|
||||
.heroT4Item.item136{background-position:-576px -240px;} /* Swordsman's Long Sword */
|
||||
.heroT4Item.item137{background-position:-624px -240px;} /* Thunder's Short Bow */
|
||||
.heroT4Item.item138{background-position:-672px -240px;} /* Thunder's Bow */
|
||||
.heroT4Item.item139{background-position:-720px -240px;} /* Thunder's Long Bow */
|
||||
.heroT4Item.item140{background-position:-0px -288px;} /* Druidrider's Baton */
|
||||
.heroT4Item.item141{background-position:-48px -288px;} /* Druidrider's Staff */
|
||||
.heroT4Item.item142{background-position:-96px -288px;} /* Druidrider's Great Staff */
|
||||
.heroT4Item.item143{background-position:-144px -288px;} /* Haeduan's Light Lance */
|
||||
.heroT4Item.item144{background-position:-192px -288px;} /* Haeduan's Lance */
|
||||
.heroT4Item.item145{background-position:-240px -288px;} /* Haeduan's Heavy Lance */
|
||||
.heroT4Item.item200{background-position:-288px -288px;} /* Ointment */
|
||||
.heroT4Item.item201{background-position:-336px -288px;} /* Scroll */
|
||||
.heroT4Item.item202{background-position:-384px -288px;} /* Bucket of Water */
|
||||
.heroT4Item.item203{background-position:-432px -288px;} /* Law Tablet */
|
||||
.heroT4Item.item204{background-position:-480px -288px;} /* Book of Wisdom */
|
||||
.heroT4Item.item205{background-position:-528px -288px;} /* Artwork */
|
||||
.heroT4Item.item206{background-position:-576px -288px;} /* Small Bandage */
|
||||
.heroT4Item.item207{background-position:-624px -288px;} /* Bandage */
|
||||
.heroT4Item.item208{background-position:-672px -288px;} /* Cage */
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"helmet 1.gif": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"helmet 2.gif": [
|
||||
4,
|
||||
5,
|
||||
6
|
||||
],
|
||||
"helmet 3.gif": [
|
||||
7,
|
||||
8,
|
||||
9
|
||||
],
|
||||
"helmet 4.gif": [
|
||||
10,
|
||||
11,
|
||||
12
|
||||
],
|
||||
"helmet 5.gif": [
|
||||
13,
|
||||
14,
|
||||
15
|
||||
],
|
||||
"armour 1.gif": [
|
||||
20,
|
||||
21,
|
||||
22
|
||||
],
|
||||
"armour 2.gif": [
|
||||
23,
|
||||
24,
|
||||
25
|
||||
],
|
||||
"armour 3.gif": [
|
||||
26,
|
||||
27,
|
||||
28
|
||||
],
|
||||
"armour 4.gif": [
|
||||
29,
|
||||
30,
|
||||
31
|
||||
],
|
||||
"scout.gif": [
|
||||
40,
|
||||
41,
|
||||
42
|
||||
],
|
||||
"horn.gif": [
|
||||
43,
|
||||
44,
|
||||
45
|
||||
],
|
||||
"bag.gif": [
|
||||
46,
|
||||
47,
|
||||
48
|
||||
],
|
||||
"map.gif": [
|
||||
49,
|
||||
50,
|
||||
51
|
||||
],
|
||||
"item64.gif": [
|
||||
52,
|
||||
53,
|
||||
54
|
||||
],
|
||||
"item67.gif": [
|
||||
55,
|
||||
56,
|
||||
57
|
||||
],
|
||||
"shoes 1.gif": [
|
||||
60,
|
||||
61,
|
||||
62
|
||||
],
|
||||
"shoes 2.gif": [
|
||||
63,
|
||||
64,
|
||||
65
|
||||
],
|
||||
"shoes 3.gif": [
|
||||
66,
|
||||
67,
|
||||
68
|
||||
],
|
||||
"horse.gif": [
|
||||
70,
|
||||
71,
|
||||
72
|
||||
],
|
||||
"sabie 1 roman legionar.gif": [
|
||||
101,
|
||||
102,
|
||||
103
|
||||
],
|
||||
"sabie 2 roman praetorian.gif": [
|
||||
104,
|
||||
105,
|
||||
106
|
||||
],
|
||||
"sabie 3 roman imperian.gif": [
|
||||
107,
|
||||
108,
|
||||
109
|
||||
],
|
||||
"sabie 4 romani imperatoris.gif": [
|
||||
110,
|
||||
111,
|
||||
112
|
||||
],
|
||||
"sabie 4 romani caesaris.gif": [
|
||||
113,
|
||||
114,
|
||||
115
|
||||
],
|
||||
"sabie 1 teutoni clubswinger.gif": [
|
||||
116,
|
||||
117,
|
||||
118
|
||||
],
|
||||
"sabie 2 teutoni spearman.gif": [
|
||||
119,
|
||||
120,
|
||||
121
|
||||
],
|
||||
"sabie 2 teutoni axeman.gif": [
|
||||
122,
|
||||
123,
|
||||
124
|
||||
],
|
||||
"Paladin teutons.gif": [
|
||||
125,
|
||||
126,
|
||||
127
|
||||
],
|
||||
"Teutonic Knight teutons.gif": [
|
||||
128,
|
||||
129,
|
||||
130
|
||||
],
|
||||
"Phalanx gauls.gif": [
|
||||
131,
|
||||
132,
|
||||
133
|
||||
],
|
||||
"Swordsman gauls.gif": [
|
||||
134,
|
||||
135,
|
||||
136
|
||||
],
|
||||
"Theutates Thunder gauls.gif": [
|
||||
137,
|
||||
138,
|
||||
139
|
||||
],
|
||||
"Druidrider gauls.gif": [
|
||||
140,
|
||||
141,
|
||||
142
|
||||
],
|
||||
"Haeduan teutons.gif": [
|
||||
143,
|
||||
144,
|
||||
145
|
||||
],
|
||||
"ointment.gif": [
|
||||
200
|
||||
],
|
||||
"scroll.gif": [
|
||||
201
|
||||
],
|
||||
"water.gif": [
|
||||
202
|
||||
],
|
||||
"tablet.gif": [
|
||||
203
|
||||
],
|
||||
"book.gif": [
|
||||
204
|
||||
],
|
||||
"artwork.gif": [
|
||||
205
|
||||
],
|
||||
"bandage25.gif": [
|
||||
206
|
||||
],
|
||||
"bandage33.gif": [
|
||||
207
|
||||
],
|
||||
"cage.gif": [
|
||||
208
|
||||
]
|
||||
}
|
||||
@@ -335,6 +335,7 @@ define("NEW_FUNCTIONS_MEDAL_10YEAR", %NEW_FUNCTIONS_MEDAL_10YEAR%);
|
||||
define("NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM", %NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%);
|
||||
define("NEW_FUNCTIONS_MILESTONES", %NEW_FUNCTIONS_MILESTONES%);
|
||||
define("NEW_FUNCTIONS_MEDAL_RESET", %NEW_FUNCTIONS_MEDAL_RESET%);
|
||||
define("NEW_FUNCTIONS_HERO_T4", %NEW_FUNCTIONS_HERO_T4%);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// **** DO NOT EDIT SETTINGS **** //
|
||||
|
||||
@@ -171,6 +171,7 @@ class Process {
|
||||
$findReplace["%NEW_FUNCTIONS_SPECIAL_MEDALS_SYSTEM%"] = $_POST['new_functions_special_medals_system'];
|
||||
$findReplace["%NEW_FUNCTIONS_MILESTONES%"] = $_POST['new_functions_milestones'];
|
||||
$findReplace["%NEW_FUNCTIONS_MEDAL_RESET%"] = $_POST['new_functions_medal_reset'];
|
||||
$findReplace["%NEW_FUNCTIONS_HERO_T4%"] = $_POST['new_functions_hero_t4'];
|
||||
|
||||
fwrite($gameConfig, str_replace(array_keys($findReplace), array_values($findReplace), $text));
|
||||
|
||||
|
||||
@@ -207,7 +207,8 @@ $mechs = [
|
||||
'new_functions_medal_10year' => 'Medal 10y',
|
||||
'new_functions_special_medals_system' => 'Special Medals System',
|
||||
'new_functions_medal_reset' => 'Medal Reset',
|
||||
'new_functions_milestones' => 'Server Milestones'
|
||||
'new_functions_milestones' => 'Server Milestones',
|
||||
'new_functions_hero_t4' => 'T4 Hero (items, adventures, auction)'
|
||||
];
|
||||
|
||||
foreach($mechs as $k => $l){
|
||||
|
||||
@@ -951,6 +951,8 @@ CREATE TABLE IF NOT EXISTS `%PREFIX%hero` (
|
||||
`level` tinyint(3) NULL,
|
||||
`points` int(3) NULL,
|
||||
`experience` int(11) NULL,
|
||||
`silver` int(11) NOT NULL DEFAULT 0,
|
||||
`production` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`dead` tinyint(1) NULL,
|
||||
`health` float(12,9) NULL,
|
||||
`attack` tinyint(3) NULL,
|
||||
@@ -976,6 +978,76 @@ CREATE TABLE IF NOT EXISTS `%PREFIX%hero` (
|
||||
|
||||
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `%prefix%hero_items` (T4 hero port)
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `%PREFIX%hero_items` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`uid` int(11) NOT NULL,
|
||||
`heroid` int(11) NOT NULL DEFAULT 0,
|
||||
`itemid` smallint(5) NOT NULL,
|
||||
`slot` tinyint(2) NOT NULL DEFAULT 0,
|
||||
`stat_value` int(11) NOT NULL DEFAULT 0,
|
||||
`quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`equipped` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`tstamp` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uid_equipped` (`uid`,`equipped`),
|
||||
KEY `uid_slot` (`uid`,`slot`),
|
||||
KEY `itemid` (`itemid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `%prefix%hero_adventure` (T4 hero port)
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `%PREFIX%hero_adventure` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`uid` int(11) NOT NULL,
|
||||
`wref` int(11) NOT NULL DEFAULT 0,
|
||||
`difficulty` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`duration` int(11) NOT NULL DEFAULT 0,
|
||||
`created` int(11) NOT NULL DEFAULT 0,
|
||||
`expire` int(11) NOT NULL DEFAULT 0,
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`moveid` int(11) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uid_status` (`uid`,`status`),
|
||||
KEY `expire` (`expire`),
|
||||
KEY `moveid` (`moveid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `%prefix%auction` (T4 hero port)
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `%PREFIX%auction` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`seller` int(11) NOT NULL DEFAULT 0,
|
||||
`itemid` smallint(5) NOT NULL,
|
||||
`slot` tinyint(2) NOT NULL DEFAULT 0,
|
||||
`stat_value` int(11) NOT NULL DEFAULT 0,
|
||||
`quantity` int(11) NOT NULL DEFAULT 1,
|
||||
`silver_start` int(11) NOT NULL DEFAULT 0,
|
||||
`silver_current` int(11) NOT NULL DEFAULT 0,
|
||||
`bidder` int(11) NOT NULL DEFAULT 0,
|
||||
`bid_max` int(11) NOT NULL DEFAULT 0,
|
||||
`created` int(11) NOT NULL DEFAULT 0,
|
||||
`time_end` int(11) NOT NULL DEFAULT 0,
|
||||
`status` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `status_end` (`status`,`time_end`),
|
||||
KEY `bidder` (`bidder`),
|
||||
KEY `seller` (`seller`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
# Regenerates css/hero_items.css from GameEngine/Data/hero_items.php so the #
|
||||
# sprite grid can never drift from the catalog. Run from the repo root: #
|
||||
# php var/tools/generate_hero_sprites.php #
|
||||
# Grid contract: img/hero/items.png is a 16-column sheet of 32x32 cells in #
|
||||
# CATALOG ORDER. Replace the PNG with real art on the same grid and rerun #
|
||||
# nothing - only rerun this script when the CATALOG changes. #
|
||||
#################################################################################
|
||||
define('TB_PREFIX', 'x');
|
||||
include __DIR__ . '/../../GameEngine/Data/hero_items.php';
|
||||
|
||||
$cell = 48; $cols = 16; $idx = 0;
|
||||
$css = "/* AUTO-GENERATED from GameEngine/Data/hero_items.php - regenerate via var/tools/generate_hero_sprites.php\n"
|
||||
. " Placeholder art: replace img/hero/items.png with real 48x48 sprites on the SAME grid\n"
|
||||
. " (16 columns, catalog order) and this CSS keeps working unchanged. */\n"
|
||||
. ".heroT4Item{display:inline-block;width:48px;height:48px;vertical-align:middle;\n"
|
||||
. " background-image:url('../img/hero/items.png');background-repeat:no-repeat;}\n";
|
||||
|
||||
foreach ($heroItemCatalog as $iid => $def) {
|
||||
$x = ($idx % $cols) * $cell;
|
||||
$y = intdiv($idx, $cols) * $cell;
|
||||
$css .= ".heroT4Item.item{$iid}{background-position:-{$x}px -{$y}px;} /* {$def['name']} */\n";
|
||||
$idx++;
|
||||
}
|
||||
file_put_contents(__DIR__ . '/../../css/hero_items.css', $css);
|
||||
echo "css/hero_items.css written: $idx items\n";
|
||||
Reference in New Issue
Block a user