Files
TravianZ/GameEngine/HeroAdventure.php
T
novgorodschi catalin a7bb7d8dbd Hero T4 — First Adventure Horse Reward by @Sk4r7z fix #391
The first adventure now guarantees one tier-1 Riding Horse (item ID 70), replacing the random item reward.

Behavior:
The horse is added to the player’s inventory and included in the adventure report.
Experience, silver, and resource rewards remain unchanged.
Subsequent adventures use the existing random item rewards.
Eligibility is based on the player’s completed adventure history. Selling the horse or retraining the hero does not reset eligibility.
If the hero dies on the first adventure, all loot is forfeited, including the horse. The next adventure does not receive the guaranteed reward.
This change is not retroactive for players with completed adventures.
2026-09-09 14:34:40 +03:00

697 lines
28 KiB
PHP

<?php
#################################################################################
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
## --------------------------------------------------------------------------- ##
## Filename : HeroAdventure.php ##
## Type : T4 Hero port - adventures backend (Phase 3) ##
## --------------------------------------------------------------------------- ##
## Developed 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;
/** Guaranteed item on the player's first adventure: tier-1 Riding Horse. */
const FIRST_ADVENTURE_ITEM = 70;
/** 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($uid);
$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();
/**
* Eroul trebuie scos si din units.hero, nu doar marcat mort in
* tabela "hero". Altfel ramane vizibil in sat si in punctul de
* adunare, desi Conacul il arata mort.
*
* Aceeasi scapare exista in KillMyHero(), reassignHero() si in
* Battle::applyHeroBattleDamage() - toate reparate.
*/
$heroOwner = (int) $uid;
if ($heroOwner > 0) {
mysqli_query($this->db,
"UPDATE " . TB_PREFIX . "units u
JOIN " . TB_PREFIX . "vdata v ON v.wref = u.vref
SET u.hero = 0
WHERE v.owner = " . $heroOwner . " AND u.hero > 0");
mysqli_query($this->db,
"UPDATE " . TB_PREFIX . "enforcement e
JOIN " . TB_PREFIX . "vdata v ON v.wref = e.`from`
SET e.hero = 0
WHERE v.owner = " . $heroOwner . " AND e.hero > 0");
}
$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 ---- */
// Completed adventures are retained (including deaths). Use player
// history rather than inventory so selling a horse or retraining a
// hero cannot grant the introductory reward again. Available/expired
// offers do not count as adventures the player has undertaken.
$stmt = $this->db->prepare(
"SELECT id FROM " . TB_PREFIX . "hero_adventure
WHERE uid = ? AND status = 2 LIMIT 1"
);
$stmt->bind_param('i', $uid);
$stmt->execute();
$previousAdventure = $stmt->get_result()->fetch_assoc();
$stmt->close();
if (!$previousAdventure) {
// Replace the random item, keeping the one-item reward limit.
$itemId = self::FIRST_ADVENTURE_ITEM;
$itemQty = 1;
}
$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. Universal gear (helmets, armors,
* boots, horses, left-hand) drops for everyone; right-hand WEAPONS only
* drop for the owner's own tribe (T4 behavior - a Roman never finds a
* Teuton club). 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($uid = 0)
{
global $database, $heroItemCatalog, $heroAdventureConfig;
$tribe = ((int) $uid > 0) ? (int) $database->getUserField((int) $uid, 'tribe', 0) : 0;
$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) {
continue;
}
$itemTribe = heroItemTribe($iid);
if ($itemTribe !== 0 && $tribe > 0 && $itemTribe !== $tribe) {
continue; // foreign-tribe weapon
}
$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;
}
}