From c7ba50036fa549ad1be17e78f03f0fe46a10d653 Mon Sep 17 00:00:00 2001 From: novgorodschi catalin Date: Thu, 16 Jul 2026 11:09:48 +0300 Subject: [PATCH] Add (Admin Panel) : Multi-account detection, Push protection, Heatmap, Gold Shop and Blacklist user names Add (Admin Panel) : Multi-account detection, Push protection, Heatmap, Gold Shop (redem codes), Blacklist user names and Quest Editor + fix some php notice Fix quest_core for mission 33,36, train 2 units, etc (NEW TRIBE) There are some temporary bugs that will be fixed in next commit (pagination for new heatmap, quest reward) --- Admin/Templates/blockReg.tpl | 144 ++++++++++ Admin/Templates/goldShop.tpl | 204 ++++++++++++++ Admin/Templates/heatmap.tpl | 265 ++++++++++++++++++ Admin/Templates/questEditor.tpl | 126 +++++++++ Admin/admin.php | 33 ++- GameEngine/Account.php | 10 + GameEngine/Admin/Mods/blockReg.php | 80 ++++++ GameEngine/Admin/Mods/goldPromo.php | 81 ++++++ GameEngine/Admin/Mods/questSave.php | 75 +++++ GameEngine/Automation.php | 3 +- GameEngine/Data/festival.php | 4 +- GameEngine/Data/hero_items.php | 2 +- GameEngine/GoldShop.php | 350 ++++++++++++++++++++++++ GameEngine/Heatmap.php | 212 +++++++++++++++ GameEngine/HeroAdventure.php | 2 +- GameEngine/HeroAuction.php | 2 +- GameEngine/HeroBattleBonus.php | 2 +- GameEngine/HeroItems.php | 18 +- GameEngine/PushProtection.php | 2 +- GameEngine/QuestConfig.php | 408 ++++++++++++++++++++++++++++ GameEngine/RegBlock.php | 226 +++++++++++++++ Templates/Ajax/quest_core.tpl | 102 ++++--- Templates/Ajax/quest_core25.tpl | 72 +++-- Templates/Plus/3.tpl | 25 ++ a2b2.php | 23 +- plus.php | 12 + var/db/struct.sql | 82 ++++++ 27 files changed, 2450 insertions(+), 115 deletions(-) create mode 100644 Admin/Templates/blockReg.tpl create mode 100644 Admin/Templates/goldShop.tpl create mode 100644 Admin/Templates/heatmap.tpl create mode 100644 Admin/Templates/questEditor.tpl create mode 100644 GameEngine/Admin/Mods/blockReg.php create mode 100644 GameEngine/Admin/Mods/goldPromo.php create mode 100644 GameEngine/Admin/Mods/questSave.php create mode 100644 GameEngine/GoldShop.php create mode 100644 GameEngine/Heatmap.php create mode 100644 GameEngine/QuestConfig.php create mode 100644 GameEngine/RegBlock.php diff --git a/Admin/Templates/blockReg.tpl b/Admin/Templates/blockReg.tpl new file mode 100644 index 00000000..c9cb51fb --- /dev/null +++ b/Admin/Templates/blockReg.tpl @@ -0,0 +1,144 @@ +Access denied.

'; + return; +} + +$blocks = RegBlock::all(); +$msg = isset($_GET['msg']) ? (string)$_GET['msg'] : ''; + +$typeLabel = [ + RegBlock::T_USERNAME => 'Username', + RegBlock::T_EMAIL => 'E-mail address', + RegBlock::T_DOMAIN => 'E-mail domain', +]; +?> + + +
+

Registration Blocklist

+

+ Block new registrations by a specific username, a specific e-mail + address, or a whole e-mail domain (e.g. an obscene username, or the + entire yahoo.com domain). Matching is exact and + case-insensitive. Existing accounts are not affected — only new sign-ups. +

+ + +
+ + +
+

Add a block

+
+ + +
+ + +
+
+ + +
+
+ + +
+ +
+
+ Domain: enter just the domain (yahoo.com), with or without the leading “@”. + E-mail: the full address. Username: the exact name. +
+
+ +
+ +
No registration blocks yet.
+ + + + + + + + + + + + + + + + + + + + + + +
TypeValueNoteAdded
+
+ + + + +
+
+ +
+
diff --git a/Admin/Templates/goldShop.tpl b/Admin/Templates/goldShop.tpl new file mode 100644 index 00000000..49562e36 --- /dev/null +++ b/Admin/Templates/goldShop.tpl @@ -0,0 +1,204 @@ +Access denied.

'; + return; +} + +$codes = GoldShop::listCodes(); +$redeems = GoldShop::recentRedemptions(25); +$msg = isset($_GET['msg']) ? (string)$_GET['msg'] : ''; +?> + + +
+

Gold Shop & Promo Codes

+

+ Create promo / voucher codes players redeem for gold on the Plus page. Set a fixed + gold amount, an optional total use-limit, whether each player may redeem it once, + and an optional expiry. For a one-off bug-hunter reward, create a code with + max-uses 1 — or use + Give Free Gold To Specific User directly. +

+ + +
+ + +
+

Create a code

+
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
Codes are case-insensitive; allowed characters: A-Z 0-9 . _ –
+
+ +
+ +
No promo codes yet.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeGoldUsesScopeExpiresStatusNote
0) ? ' / ' . (int)$cd['max_uses'] : ''; ?> 0) ? date('Y-m-d', (int)$cd['expires']) : '–'; ?> +
+
+ + + + + +
+
+ + + + +
+
+
+
+ +
+ +
+

Recent redemptions

+ +
No redemptions yet.
+ +
+ + + + + + + + + + + + + + +
WhenPlayerCodeGold
+ + + +
+
+ +
+
diff --git a/Admin/Templates/heatmap.tpl b/Admin/Templates/heatmap.tpl new file mode 100644 index 00000000..271f844a --- /dev/null +++ b/Admin/Templates/heatmap.tpl @@ -0,0 +1,265 @@ +Access denied.

'; + return; +} + +$res = isset($_GET['res']) && ctype_digit((string)$_GET['res']) ? (int)$_GET['res'] : Heatmap::DEFAULT_RES; +$days = isset($_GET['days']) && ctype_digit((string)$_GET['days']) ? (int)$_GET['days'] : Heatmap::DEFAULT_INACTIVE_DAYS; + +$data = Heatmap::grid(['res' => $res, 'inactive_days' => $days]); +$tribeNames = Heatmap::tribeNames(); + +// Compact payload for the client: [cx,cy,villages,inactive,attacks,pop,dom,x0,y0,x1,y1] +$payload = []; +foreach ($data['cells'] as $c) { + $payload[] = [$c['cx'], $c['cy'], $c['villages'], $c['inactive'], $c['attacks'], $c['pop'], $c['dom'], $c['x0'], $c['y0'], $c['x1'], $c['y1']]; +} +$js = [ + 'res' => $data['res'], + 'max' => $data['max'], + 'cells' => $payload, + 'tribeNames' => $tribeNames, +]; +?> + + +
+

World Map Heatmap

+

+ Density overlays on the world grid to help with starting balance: where villages + cluster, which tribes dominate a region, where inactive players sit (dead zones / + good spawn spots), and where attacks are currently landing. North (+y) is up. +

+ +
+ +
+ + +
+
+ + +
+ +
+ +
+ + + + +
+ +
+
+ +
+
+ +
+
+

Summary

+
Player villages
+
Players
+
Inactive villages
+
Attacks in flight
+
Map × radius, × grid (≈ tiles/cell).
+
+ +
+

Legend

+
+
+ +
+

Tribe totals

+ '#dc2626',2=>'#2563eb',3=>'#16a34a',6=>'#a16207',7=>'#d97706',8=>'#7c3aed',9=>'#0891b2']; + if ($sum > 0): + ?> +
+ $n): if ($n<=0) continue; ?> + + +
+ $n): if ($n<=0) continue; ?> +
+ + +
No player villages found.
+ +
+
+
+
+ + diff --git a/Admin/Templates/questEditor.tpl b/Admin/Templates/questEditor.tpl new file mode 100644 index 00000000..4435e591 --- /dev/null +++ b/Admin/Templates/questEditor.tpl @@ -0,0 +1,126 @@ +Access denied.

'; + return; +} + +$variant = (isset($_GET['variant']) && $_GET['variant'] === QuestConfig::V_EXTENDED) + ? QuestConfig::V_EXTENDED : QuestConfig::V_STANDARD; + +$rows = QuestConfig::all($variant); +$native = QuestConfig::nativeQuests($variant); +$msg = isset($_GET['msg']) ? (string)$_GET['msg'] : ''; +?> + + +
+

Quest Editor

+

+ Edit the reward each quest grants (wood / clay / iron / crop / gold / Plus days) and + the requirement level (e.g. main-building level for building quests). Values are + seeded from the shipped defaults, so nothing changes until you edit. The two quest + variants have different quests and rewards — pick the one your server uses + (players on qtyp 37 get extended, everyone else standard). +

+ +
+ +
+ Reward values are live via QuestConfig::grantReward() in the quest + templates. Quests marked fixed keep their original hardcoded logic + (conditional rewards, atomic milestone claims, special mechanics) and are not + affected by edits here. The reward numbers shown inside each quest's on-screen + text are separate template strings — edits here change what is actually + granted; update the quest language strings if you want the preview to match. +
+ + + +
+ + + + + + + + + + + + + + + + $r): $isNative = in_array((int)$qid, $native, true); $dis = $isNative ? 'disabled' : ''; ?> + > + + + + + + + + + + + + + +
QuestOnWoodClayIronCropGoldPlus (days)Req. levelNote

fixed
>>>>>>>>>
+ +
+ + Saves every row for the variant. +
+
+ +
+ + + + +
+
diff --git a/Admin/admin.php b/Admin/admin.php index e0dc9f0c..f8b7812b 100644 --- a/Admin/admin.php +++ b/Admin/admin.php @@ -44,6 +44,10 @@ include_once("../GameEngine/Data/buidata.php"); include_once("../GameEngine/Artifacts.php"); include_once("../GameEngine/MultiAccount.php"); include_once("../GameEngine/PushProtection.php"); +include_once("../GameEngine/RegBlock.php"); +include_once("../GameEngine/Heatmap.php"); +include_once("../GameEngine/GoldShop.php"); +include_once("../GameEngine/QuestConfig.php"); // ─── SECURITY HELPERS ──────────────────────────────────────────────────────── @@ -99,6 +103,10 @@ function admin_validated_page(string $raw): string 'alliance', 'editAli', 'delAli','editNewFunctions', 'multiacc', 'pushprot', + 'blockReg', + 'heatmap', + 'goldShop', + 'questEditor', ]; return in_array($raw, $whitelist, true) ? $raw : ''; @@ -181,6 +189,22 @@ if ($page !== '') { $subpage = 'Push Protection'; break; + case 'blockReg': + $subpage = 'Registration Blocklist'; + break; + + case 'heatmap': + $subpage = 'World Map Heatmap'; + break; + + case 'goldShop': + $subpage = 'Gold Shop & Promo Codes'; + break; + + case 'questEditor': + $subpage = 'Quest Editor'; + break; + case 'massmessage': $subpage = 'Mass Message'; break; @@ -839,6 +863,7 @@ body.app #menu li.sub ul li a:hover{color:#d97706!important} @@ -854,13 +879,16 @@ body.app #menu li.sub ul li a:hover{color:#d97706!important}
  • Admin diff --git a/GameEngine/Account.php b/GameEngine/Account.php index f91e24c7..a142fc5e 100755 --- a/GameEngine/Account.php +++ b/GameEngine/Account.php @@ -98,6 +98,11 @@ class Account { $form->addError("name", USRNM_TAKEN); } elseif (User::exists($database, $_POST['name'])) { $form->addError("name", USRNM_TAKEN); + } elseif (class_exists('RegBlock') && RegBlock::isBlockedName($database, $_POST['name'])) { + // Registration blocklist (admin: blockReg). + $form->addError("name", defined('REG_BLOCKED_NAME') + ? REG_BLOCKED_NAME + : 'This username is not allowed. Please choose another.'); } } @@ -119,6 +124,11 @@ class Account { $form->addError("email", EMAIL_INVALID); } elseif (User::exists($database, $_POST['email'])) { $form->addError("email", EMAIL_TAKEN); + } elseif (class_exists('RegBlock') && RegBlock::isBlockedEmail($database, $_POST['email'])) { + // Registration blocklist: exact address or whole domain (admin: blockReg). + $form->addError("email", defined('REG_BLOCKED_EMAIL') + ? REG_BLOCKED_EMAIL + : 'Registration with this e-mail address or domain is not allowed.'); } // Tribe diff --git a/GameEngine/Admin/Mods/blockReg.php b/GameEngine/Admin/Mods/blockReg.php new file mode 100644 index 00000000..027c0418 --- /dev/null +++ b/GameEngine/Admin/Mods/blockReg.php @@ -0,0 +1,80 @@ + 0 && RegBlock::remove($id)) { + mysqli_query($GLOBALS['link'], + "INSERT INTO " . TB_PREFIX . "admin_log VALUES (0, " . $admid . ", 'Registration block removed (id " . $id . ")', " . time() . ")"); + header("Location: ../../../Admin/admin.php?p=blockReg&msg=" . urlencode('Block removed.')); + exit; + } + header("Location: ../../../Admin/admin.php?p=blockReg&msg=" . urlencode('Could not remove block.')); + exit; +} + +header("Location: ../../../Admin/admin.php?p=blockReg"); +exit; + +/** Small helper: keep the admin-log message readable/short. */ +function RegBlock_safe($v) +{ + $v = strtolower(trim((string)$v)); + return strlen($v) > 80 ? substr($v, 0, 77) . '...' : $v; +} +?> diff --git a/GameEngine/Admin/Mods/goldPromo.php b/GameEngine/Admin/Mods/goldPromo.php new file mode 100644 index 00000000..3044ce22 --- /dev/null +++ b/GameEngine/Admin/Mods/goldPromo.php @@ -0,0 +1,81 @@ + 0 ? time() + $expDays * 86400 : 0; + + $note = $_POST['note'] ?? ''; + + list($ok, $msg) = GoldShop::createCode($code, $gold, $maxUses, $perUser, $expires, $note, $admid); + if ($ok) { + $logMsg = mysqli_real_escape_string($GLOBALS['link'], + 'Promo code created: ' . GoldShop::normCode($code) . ' (' . (int)$gold . ' gold)'); + mysqli_query($GLOBALS['link'], + "INSERT INTO " . TB_PREFIX . "admin_log VALUES (0, " . $admid . ", '" . $logMsg . "', " . time() . ")"); + } +} elseif ($do === 'toggle') { + $id = (int)($_POST['id'] ?? 0); + $active = (int)($_POST['active'] ?? 0); + if ($id > 0) { + GoldShop::setActive($id, $active); + $msg = $active ? 'Code enabled.' : 'Code disabled.'; + } +} elseif ($do === 'delete') { + $id = (int)($_POST['id'] ?? 0); + if ($id > 0) { + GoldShop::deleteCode($id); + mysqli_query($GLOBALS['link'], + "INSERT INTO " . TB_PREFIX . "admin_log VALUES (0, " . $admid . ", 'Promo code deleted (id " . $id . ")', " . time() . ")"); + $msg = 'Code deleted.'; + } +} + +header("Location: ../../../Admin/admin.php?p=goldShop&msg=" . urlencode($msg)); +exit; +?> diff --git a/GameEngine/Admin/Mods/questSave.php b/GameEngine/Admin/Mods/questSave.php new file mode 100644 index 00000000..d0605f78 --- /dev/null +++ b/GameEngine/Admin/Mods/questSave.php @@ -0,0 +1,75 @@ + $fields) { + if (!is_array($fields)) { + continue; + } + QuestConfig::setQuest($variant, (int)$qid, [ + 'enabled' => !empty($fields['enabled']), + 'wood' => $fields['wood'] ?? 0, + 'clay' => $fields['clay'] ?? 0, + 'iron' => $fields['iron'] ?? 0, + 'crop' => $fields['crop'] ?? 0, + 'gold' => $fields['gold'] ?? 0, + 'plus_days' => $fields['plus_days'] ?? 0, + 'req_level' => $fields['req_level'] ?? 0, + 'note' => $fields['note'] ?? '', + ]); + $n++; + } + mysqli_query($GLOBALS['link'], + "INSERT INTO " . TB_PREFIX . "admin_log VALUES (0, " . $admid . ", 'Quest editor: saved " . $n . " " . $variant . " quests', " . time() . ")"); + $msg = $n . ' quests saved (' . $variant . ').'; +} elseif ($do === 'reset') { + QuestConfig::resetVariant($variant); + mysqli_query($GLOBALS['link'], + "INSERT INTO " . TB_PREFIX . "admin_log VALUES (0, " . $admid . ", 'Quest editor: reset " . $variant . " to defaults', " . time() . ")"); + $msg = $variant . ' quests reset to defaults.'; +} + +header("Location: ../../../Admin/admin.php?p=questEditor&variant=" . urlencode($variant) . "&msg=" . urlencode($msg)); +exit; +?> diff --git a/GameEngine/Automation.php b/GameEngine/Automation.php index ab2feef0..ffdaeafc 100644 --- a/GameEngine/Automation.php +++ b/GameEngine/Automation.php @@ -3250,6 +3250,7 @@ class Automation { $Attacker = $trapResult['Attacker']; // we need to save the attacker heroid before the battle + $AttackerHeroID = 0; if(isset($Attacker['uhero']) && $Attacker['uhero'] > 0){ $AttackerHeroID = $database->getHeroField($from['owner'], "heroid"); } @@ -3690,7 +3691,7 @@ class Automation { $reinf = $database->getEnforce($data['to'], $data['from']); $database->modifyEnforce($reinf['id'], 31, 1, 1); $data_fail = '0,0,4,1,0,0,0,0,0,0,0,0,0,0'; - $database->addNotice($to['owner'], $to['wref'], (isset($targetally) ? $targetally : 0), 8, 'village of the elders reinforcement ' . addslashes($to['name']), $data_fail, $AttackArrivalTime); + $database->addNotice($to['owner'], $to['wref'], (isset($targetally) ? $targetally : 0), 8, 'village of the elders reinforcement ' . addslashes($to['name']), $data_fail, isset($AttackArrivalTime) ? $AttackArrivalTime : time()); } // Flow 2 of sendreinfunitsComplete(): a standard reinforcement delivery. Handles diff --git a/GameEngine/Data/festival.php b/GameEngine/Data/festival.php index aa5f1439..94f8f9e4 100644 --- a/GameEngine/Data/festival.php +++ b/GameEngine/Data/festival.php @@ -6,9 +6,7 @@ ## Filename : festival.php ## ## Type : Data Page for Festival ## ## --------------------------------------------------------------------------- ## -## Developed by : Dzoki ## -## Refactored by : Shadow ## -## Redesign by : Shadow ## +## Developed by : Shadow ## ## --------------------------------------------------------------------------- ## ## Contact : cata7007@gmail.com ## ## Project : TravianZ ## diff --git a/GameEngine/Data/hero_items.php b/GameEngine/Data/hero_items.php index 9db1e291..a6c05ba7 100644 --- a/GameEngine/Data/hero_items.php +++ b/GameEngine/Data/hero_items.php @@ -6,7 +6,7 @@ ## Filename : hero_items.php ## ## Type : Static Data / Catalog for T4 Hero port ## ## --------------------------------------------------------------------------- ## -## Refactored by : Shadow ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## diff --git a/GameEngine/GoldShop.php b/GameEngine/GoldShop.php new file mode 100644 index 00000000..f6c703ac --- /dev/null +++ b/GameEngine/GoldShop.php @@ -0,0 +1,350 @@ +dblink)) { + return $GLOBALS['database']->dblink; + } + return null; + } + + public static function ensureSchema() + { + $link = self::link(); + if (!$link) { + return; + } + @mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "gold_promo` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(64) NOT NULL, + `gold` int(11) NOT NULL DEFAULT 0, + `max_uses` int(11) NOT NULL DEFAULT 0, + `uses` int(11) NOT NULL DEFAULT 0, + `per_user` tinyint(1) NOT NULL DEFAULT 1, + `expires` int(11) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 1, + `note` varchar(255) NOT NULL DEFAULT '', + `created_by` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + + @mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "gold_promo_redeem` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `promo_id` int(11) NOT NULL DEFAULT 0, + `uid` int(11) NOT NULL DEFAULT 0, + `gold` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `promo_uid` (`promo_id`,`uid`), + KEY `uid` (`uid`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + } + + /** Normalise a code: trim, uppercase, collapse to allowed charset. */ + public static function normCode($code) + { + $code = strtoupper(trim((string) $code)); + return preg_replace('/[^A-Z0-9._-]/', '', $code); + } + + /* ---- Admin management ---------------------------------------------- */ + + /** + * Create a promo code. + * @return array [ok(bool), message(string)] + */ + public static function createCode($code, $gold, $maxUses, $perUser, $expires, $note, $adminId) + { + $link = self::link(); + if (!$link) { + return [false, 'No database connection.']; + } + self::ensureSchema(); + + $code = self::normCode($code); + if ($code === '' || strlen($code) < 3) { + return [false, 'Code must be at least 3 characters (A-Z 0-9 . _ -).']; + } + $gold = (int) $gold; + if ($gold <= 0) { + return [false, 'Gold amount must be positive.']; + } + $maxUses = max(0, (int) $maxUses); + $perUser = $perUser ? 1 : 0; + $expires = max(0, (int) $expires); + $note = substr((string) $note, 0, 255); + $admin = (int) $adminId; + $now = time(); + + $stmt = mysqli_prepare($link, + "INSERT INTO `" . TB_PREFIX . "gold_promo` + (code, gold, max_uses, uses, per_user, expires, active, note, created_by, time) + VALUES (?,?,?,0,?,?,1,?,?,?)"); + if (!$stmt) { + return [false, 'Could not prepare statement.']; + } + mysqli_stmt_bind_param($stmt, 'siiiisii', + $code, $gold, $maxUses, $perUser, $expires, $note, $admin, $now); + $ok = mysqli_stmt_execute($stmt); + $err = mysqli_stmt_errno($stmt); + mysqli_stmt_close($stmt); + + if (!$ok) { + if ($err === 1062) { + return [false, 'A code with that name already exists.']; + } + return [false, 'Could not create code.']; + } + return [true, 'Code "' . $code . '" created.']; + } + + /** All codes, newest first, each with a resolved status string. */ + public static function listCodes() + { + $link = self::link(); + $out = []; + if (!$link) { + return $out; + } + self::ensureSchema(); + $r = @mysqli_query($link, + "SELECT * FROM `" . TB_PREFIX . "gold_promo` ORDER BY `time` DESC, id DESC"); + if ($r) { + $now = time(); + while ($row = mysqli_fetch_assoc($r)) { + $row['status'] = self::statusOf($row, $now); + $out[] = $row; + } + mysqli_free_result($r); + } + return $out; + } + + private static function statusOf($row, $now) + { + if ((int) $row['active'] !== 1) { + return 'Disabled'; + } + if ((int) $row['expires'] > 0 && $now > (int) $row['expires']) { + return 'Expired'; + } + if ((int) $row['max_uses'] > 0 && (int) $row['uses'] >= (int) $row['max_uses']) { + return 'Used up'; + } + return 'Active'; + } + + public static function setActive($id, $active) + { + $link = self::link(); + if (!$link) { + return false; + } + self::ensureSchema(); + $stmt = mysqli_prepare($link, + "UPDATE `" . TB_PREFIX . "gold_promo` SET active = ? WHERE id = ?"); + if (!$stmt) { + return false; + } + $a = $active ? 1 : 0; + $id = (int) $id; + mysqli_stmt_bind_param($stmt, 'ii', $a, $id); + $ok = mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + return $ok; + } + + public static function deleteCode($id) + { + $link = self::link(); + if (!$link) { + return false; + } + self::ensureSchema(); + $id = (int) $id; + $stmt = mysqli_prepare($link, "DELETE FROM `" . TB_PREFIX . "gold_promo` WHERE id = ?"); + if ($stmt) { + mysqli_stmt_bind_param($stmt, 'i', $id); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + } + return true; + } + + /** Recent redemptions (joined with code + username) for the admin view. */ + public static function recentRedemptions($limit = 30) + { + $link = self::link(); + $out = []; + if (!$link) { + return $out; + } + self::ensureSchema(); + $limit = max(1, min(200, (int) $limit)); + $r = @mysqli_query($link, + "SELECT rr.time, rr.gold, rr.uid, u.username, p.code + FROM `" . TB_PREFIX . "gold_promo_redeem` rr + LEFT JOIN `" . TB_PREFIX . "users` u ON u.id = rr.uid + LEFT JOIN `" . TB_PREFIX . "gold_promo` p ON p.id = rr.promo_id + ORDER BY rr.id DESC LIMIT " . $limit); + if ($r) { + while ($row = mysqli_fetch_assoc($r)) { + $out[] = $row; + } + mysqli_free_result($r); + } + return $out; + } + + /* ---- Player redemption --------------------------------------------- */ + + /** + * Redeem a code for a player. + * @return array [ok(bool), message(string), gold(int)] + */ + public static function redeem($uid, $code) + { + $uid = (int) $uid; + if ($uid <= 0) { + return [false, 'You must be logged in.', 0]; + } + $link = self::link(); + if (!$link) { + return [false, 'Service unavailable, try again later.', 0]; + } + self::ensureSchema(); + + $code = self::normCode($code); + if ($code === '') { + return [false, 'Please enter a code.', 0]; + } + + // Fetch the code row. + $stmt = mysqli_prepare($link, + "SELECT * FROM `" . TB_PREFIX . "gold_promo` WHERE code = ? LIMIT 1"); + if (!$stmt) { + return [false, 'Service unavailable, try again later.', 0]; + } + mysqli_stmt_bind_param($stmt, 's', $code); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + $promo = $res ? mysqli_fetch_assoc($res) : null; + mysqli_stmt_close($stmt); + + if (!$promo) { + return [false, 'Invalid code.', 0]; + } + $now = time(); + if ((int) $promo['active'] !== 1) { + return [false, 'This code is no longer available.', 0]; + } + if ((int) $promo['expires'] > 0 && $now > (int) $promo['expires']) { + return [false, 'This code has expired.', 0]; + } + if ((int) $promo['max_uses'] > 0 && (int) $promo['uses'] >= (int) $promo['max_uses']) { + return [false, 'This code has already been fully redeemed.', 0]; + } + + $promoId = (int) $promo['id']; + $gold = (int) $promo['gold']; + + // Per-user-once check. + if ((int) $promo['per_user'] === 1) { + $chk = mysqli_prepare($link, + "SELECT 1 FROM `" . TB_PREFIX . "gold_promo_redeem` WHERE promo_id = ? AND uid = ? LIMIT 1"); + if ($chk) { + mysqli_stmt_bind_param($chk, 'ii', $promoId, $uid); + mysqli_stmt_execute($chk); + mysqli_stmt_store_result($chk); + $already = mysqli_stmt_num_rows($chk) > 0; + mysqli_stmt_close($chk); + if ($already) { + return [false, 'You have already redeemed this code.', 0]; + } + } + } + + // Atomically claim a use (guards against concurrent over-redemption). + $claim = mysqli_prepare($link, + "UPDATE `" . TB_PREFIX . "gold_promo` + SET uses = uses + 1 + WHERE id = ? AND active = 1 + AND (max_uses = 0 OR uses < max_uses) + AND (expires = 0 OR expires >= ?)"); + if (!$claim) { + return [false, 'Service unavailable, try again later.', 0]; + } + mysqli_stmt_bind_param($claim, 'ii', $promoId, $now); + mysqli_stmt_execute($claim); + $claimed = mysqli_stmt_affected_rows($claim) === 1; + mysqli_stmt_close($claim); + + if (!$claimed) { + return [false, 'This code has already been fully redeemed.', 0]; + } + + // Grant the gold. Prefer the Database helper (also writes gold_fin_log). + $granted = false; + if (isset($GLOBALS['database']) && method_exists($GLOBALS['database'], 'modifyGold')) { + $GLOBALS['database']->modifyGold($uid, $gold, 1); + $granted = true; + } + if (!$granted) { + $g = mysqli_prepare($link, + "UPDATE `" . TB_PREFIX . "users` SET gold = gold + ? WHERE id = ?"); + if ($g) { + mysqli_stmt_bind_param($g, 'ii', $gold, $uid); + mysqli_stmt_execute($g); + mysqli_stmt_close($g); + } + } + + // Record the redemption. + $ins = mysqli_prepare($link, + "INSERT INTO `" . TB_PREFIX . "gold_promo_redeem` (promo_id, uid, gold, time) + VALUES (?,?,?,?)"); + if ($ins) { + mysqli_stmt_bind_param($ins, 'iiii', $promoId, $uid, $gold, $now); + mysqli_stmt_execute($ins); + mysqli_stmt_close($ins); + } + + return [true, 'Success! ' . $gold . ' gold has been added to your account.', $gold]; + } +} diff --git a/GameEngine/Heatmap.php b/GameEngine/Heatmap.php new file mode 100644 index 00000000..df3df1da --- /dev/null +++ b/GameEngine/Heatmap.php @@ -0,0 +1,212 @@ + display name (for legend / tooltips). */ + public static function tribeNames() + { + return [ + 1 => 'Romans', 2 => 'Teutons', 3 => 'Gauls', + 6 => 'Huns', 7 => 'Egyptians', 8 => 'Spartans', 9 => 'Vikings', + ]; + } + + private static function link() + { + if (isset($GLOBALS['link']) && $GLOBALS['link']) { + return $GLOBALS['link']; + } + if (isset($GLOBALS['database']) && isset($GLOBALS['database']->dblink)) { + return $GLOBALS['database']->dblink; + } + return null; + } + + /** Map radius: coordinates run -MAX .. +MAX on both axes. */ + private static function worldMax() + { + if (defined('WORLD_MAX') && (int) WORLD_MAX > 0) { + return (int) WORLD_MAX; + } + return 200; // sane fallback + } + + /** + * Build the heatmap grid. + * + * @param array $opts 'res' (cells per side), 'inactive_days'. + * @return array { + * res, world_max, cell_span, inactive_days, + * cells => list of non-empty cells (see below), + * max => ['villages'=>, 'inactive'=>, 'attacks'=>, 'pop'=>], + * tribe_totals => [tribe => count], + * totals => ['villages'=>, 'inactive'=>, 'attacks'=>, 'players'=>], + * } + * + * Each cell: + * cx, cy grid indices (0..res-1, cy from top) + * x0,y0,x1,y1 world-coordinate bounds of the cell + * villages, inactive, attacks, pop + * tribes => [tribe => count] + * dom => dominant tribe id (0 if none) + */ + public static function grid(array $opts = []) + { + $res = isset($opts['res']) ? (int) $opts['res'] : self::DEFAULT_RES; + $res = max(self::MIN_RES, min(self::MAX_RES, $res)); + $days = isset($opts['inactive_days']) ? max(1, (int) $opts['inactive_days']) : self::DEFAULT_INACTIVE_DAYS; + + $max = self::worldMax(); + $span = 2 * $max + 1; // total coordinate width + $cellSpan = $span / $res; // coords per cell + + $empty = [ + 'res' => $res, 'world_max' => $max, 'cell_span' => $cellSpan, + 'inactive_days' => $days, 'cells' => [], + 'max' => ['villages' => 0, 'inactive' => 0, 'attacks' => 0, 'pop' => 0], + 'tribe_totals' => [], 'totals' => ['villages' => 0, 'inactive' => 0, 'attacks' => 0, 'players' => 0], + ]; + + $link = self::link(); + if (!$link) { + return $empty; + } + + // cellIndex: map a coordinate to 0..res-1. + $idx = function ($coord) use ($max, $span, $res) { + $i = (int) floor(($coord + $max) / $span * $res); + if ($i < 0) { $i = 0; } + if ($i >= $res) { $i = $res - 1; } + return $i; + }; + + $cells = []; // "cx:cy" => cell + $tribeTotals = []; + $totVil = $totInact = $totAtk = 0; + $players = []; + + $touch = function ($cx, $cy) use (&$cells, $max, $cellSpan, $res) { + $key = $cx . ':' . $cy; + if (!isset($cells[$key])) { + $x0 = -$max + $cx * $cellSpan; + // cy is measured from the TOP (north = +y), so invert for y bounds. + $y1 = $max - $cy * $cellSpan; + $cells[$key] = [ + 'cx' => $cx, 'cy' => $cy, + 'x0' => (int) round($x0), 'x1' => (int) round($x0 + $cellSpan), + 'y0' => (int) round($y1 - $cellSpan), 'y1' => (int) round($y1), + 'villages' => 0, 'inactive' => 0, 'attacks' => 0, 'pop' => 0, + 'tribes' => [], + ]; + } + return $key; + }; + + // ---- Villages + tribe + inactivity ---- + $now = time(); + $cut = $now - $days * 86400; + $q = "SELECT w.x AS x, w.y AS y, u.tribe AS tribe, v.pop AS pop, u.timestamp AS ts, u.id AS uid + FROM `" . TB_PREFIX . "vdata` v + JOIN `" . TB_PREFIX . "wdata` w ON v.wref = w.id + JOIN `" . TB_PREFIX . "users` u ON v.owner = u.id + WHERE v.owner > 3 AND w.x IS NOT NULL AND w.y IS NOT NULL"; + if ($r = @mysqli_query($link, $q)) { + while ($row = mysqli_fetch_assoc($r)) { + $cx = $idx((int) $row['x']); + // North is +y at the top of the map, so top row (cy=0) = +max. + $cy = $idx(-(int) $row['y']); + $key = $touch($cx, $cy); + + $cells[$key]['villages']++; + $cells[$key]['pop'] += (int) $row['pop']; + $tribe = (int) $row['tribe']; + if ($tribe > 0) { + if (!isset($cells[$key]['tribes'][$tribe])) { $cells[$key]['tribes'][$tribe] = 0; } + $cells[$key]['tribes'][$tribe]++; + if (!isset($tribeTotals[$tribe])) { $tribeTotals[$tribe] = 0; } + $tribeTotals[$tribe]++; + } + if ((int) $row['ts'] > 0 && (int) $row['ts'] < $cut) { + $cells[$key]['inactive']++; + $totInact++; + } + $totVil++; + $players[(int) $row['uid']] = true; + } + mysqli_free_result($r); + } + + // ---- Incoming attacks in flight (sort_type 3 = attack, 4 = raid) ---- + $q2 = "SELECT w.x AS x, w.y AS y, COUNT(*) AS c + FROM `" . TB_PREFIX . "movement` m + JOIN `" . TB_PREFIX . "wdata` w ON m.`to` = w.id + WHERE m.sort_type IN (3,4) AND m.proc = 0 + GROUP BY w.x, w.y"; + if ($r = @mysqli_query($link, $q2)) { + while ($row = mysqli_fetch_assoc($r)) { + $cx = $idx((int) $row['x']); + $cy = $idx(-(int) $row['y']); + $key = $touch($cx, $cy); + $cells[$key]['attacks'] += (int) $row['c']; + $totAtk += (int) $row['c']; + } + mysqli_free_result($r); + } + + // ---- Finalise: dominant tribe + maxima ---- + $maxV = $maxI = $maxA = $maxP = 0; + $list = []; + foreach ($cells as $c) { + $dom = 0; $domCount = 0; + foreach ($c['tribes'] as $t => $n) { + if ($n > $domCount) { $domCount = $n; $dom = $t; } + } + $c['dom'] = $dom; + if ($c['villages'] > $maxV) { $maxV = $c['villages']; } + if ($c['inactive'] > $maxI) { $maxI = $c['inactive']; } + if ($c['attacks'] > $maxA) { $maxA = $c['attacks']; } + if ($c['pop'] > $maxP) { $maxP = $c['pop']; } + $list[] = $c; + } + + return [ + 'res' => $res, 'world_max' => $max, 'cell_span' => $cellSpan, + 'inactive_days' => $days, + 'cells' => $list, + 'max' => ['villages' => $maxV, 'inactive' => $maxI, 'attacks' => $maxA, 'pop' => $maxP], + 'tribe_totals' => $tribeTotals, + 'totals' => [ + 'villages' => $totVil, 'inactive' => $totInact, + 'attacks' => $totAtk, 'players' => count($players), + ], + ]; + } +} diff --git a/GameEngine/HeroAdventure.php b/GameEngine/HeroAdventure.php index 6435d2d2..673789b1 100644 --- a/GameEngine/HeroAdventure.php +++ b/GameEngine/HeroAdventure.php @@ -6,7 +6,7 @@ ## Filename : HeroAdventure.php ## ## Type : T4 Hero port - adventures backend (Phase 3) ## ## --------------------------------------------------------------------------- ## -## Refactored by : Shadow ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## diff --git a/GameEngine/HeroAuction.php b/GameEngine/HeroAuction.php index f3d10019..7f9f6872 100644 --- a/GameEngine/HeroAuction.php +++ b/GameEngine/HeroAuction.php @@ -6,7 +6,7 @@ ## Filename : HeroAuction.php ## ## Type : T4 Hero port - auction house backend (Phase 4) ## ## --------------------------------------------------------------------------- ## -## Refactored by : Shadow ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## diff --git a/GameEngine/HeroBattleBonus.php b/GameEngine/HeroBattleBonus.php index f1365d1b..51ed65f5 100644 --- a/GameEngine/HeroBattleBonus.php +++ b/GameEngine/HeroBattleBonus.php @@ -6,7 +6,7 @@ ## Filename : HeroBattleBonus.php ## ## Type : T4 Hero port - battle & speed integration (Phase 5) ## ## --------------------------------------------------------------------------- ## -## Refactored by : Shadow ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## diff --git a/GameEngine/HeroItems.php b/GameEngine/HeroItems.php index 8a0b9264..cdb2cdb2 100644 --- a/GameEngine/HeroItems.php +++ b/GameEngine/HeroItems.php @@ -6,7 +6,7 @@ ## Filename : HeroItems.php ## ## Type : T4 Hero port - items backend (Phase 2) ## ## --------------------------------------------------------------------------- ## -## Refactored by : Shadow ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## @@ -15,14 +15,14 @@ ## --------------------------------------------------------------------------- ## ################################################################################# ## ## -## 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. ## +## 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. ## ## ## ################################################################################# diff --git a/GameEngine/PushProtection.php b/GameEngine/PushProtection.php index 79af2393..ee768061 100644 --- a/GameEngine/PushProtection.php +++ b/GameEngine/PushProtection.php @@ -5,7 +5,7 @@ ## Filename : PushProtection.php ## ## Type : Push-Protection engine (Admin/Multihunter dashboard) ## ## --------------------------------------------------------------------------- ## -## Developed by : Shadow (Cătălin) ## +## Developed by : Shadow ## ## Project : TravianZ ## ## GitHub : https://github.com/Shadowss/TravianZ ## ## --------------------------------------------------------------------------- ## diff --git a/GameEngine/QuestConfig.php b/GameEngine/QuestConfig.php new file mode 100644 index 00000000..a61969d3 --- /dev/null +++ b/GameEngine/QuestConfig.php @@ -0,0 +1,408 @@ +dblink)) { + return $GLOBALS['database']->dblink; + } + return null; + } + + /** Which quest variant is active for the current player/session. */ + public static function activeVariant() + { + return (isset($_SESSION['qtyp']) && (int) $_SESSION['qtyp'] === 37) + ? self::V_EXTENDED : self::V_STANDARD; + } + + public static function ensureSchema() + { + $link = self::link(); + if (!$link) { + return; + } + @mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "quest_config` ( + `variant` varchar(16) NOT NULL DEFAULT 'standard', + `quest_id` int(11) NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 1, + `wood` int(11) NOT NULL DEFAULT 0, + `clay` int(11) NOT NULL DEFAULT 0, + `iron` int(11) NOT NULL DEFAULT 0, + `crop` int(11) NOT NULL DEFAULT 0, + `gold` int(11) NOT NULL DEFAULT 0, + `plus_days` float NOT NULL DEFAULT 0, + `req_level` int(11) NOT NULL DEFAULT 0, + `note` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`variant`,`quest_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + } + + /** Seed the table with the current (extracted) hardcoded values, once. */ + public static function seedDefaults() + { + $link = self::link(); + if (!$link) { + return; + } + self::ensureSchema(); + + // Only seed when empty, so admin edits are never overwritten. + $r = @mysqli_query($link, "SELECT 1 FROM `" . TB_PREFIX . "quest_config` LIMIT 1"); + if ($r && mysqli_num_rows($r) > 0) { + mysqli_free_result($r); + return; + } + if ($r) { mysqli_free_result($r); } + + $stmt = mysqli_prepare($link, + "INSERT IGNORE INTO `" . TB_PREFIX . "quest_config` + (variant, quest_id, enabled, wood, clay, iron, crop, gold, plus_days, req_level, note) + VALUES (?,?,1,?,?,?,?,?,?,0,'')"); + if (!$stmt) { + return; + } + foreach (self::defaults() as $variant => $quests) { + foreach ($quests as $qid => $v) { + $qid = (int) $qid; + $w = (int) $v['wood']; $c = (int) $v['clay']; $i = (int) $v['iron']; + $cr = (int) $v['crop']; $g = (int) $v['gold']; $pd = (float) $v['plus_days']; + mysqli_stmt_bind_param($stmt, 'siiiiiid', $variant, $qid, $w, $c, $i, $cr, $g, $pd); + mysqli_stmt_execute($stmt); + } + } + mysqli_stmt_close($stmt); + } + + /* ---- Reads --------------------------------------------------------- */ + + /** All quest rows for a variant (seeds first). */ + public static function all($variant) + { + $variant = self::normVariant($variant); + $link = self::link(); + $out = []; + if (!$link) { + return $out; + } + self::seedDefaults(); + $stmt = mysqli_prepare($link, + "SELECT * FROM `" . TB_PREFIX . "quest_config` WHERE variant = ? ORDER BY quest_id ASC"); + if (!$stmt) { + return $out; + } + mysqli_stmt_bind_param($stmt, 's', $variant); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + while ($res && $row = mysqli_fetch_assoc($res)) { + $out[(int) $row['quest_id']] = $row; + } + mysqli_stmt_close($stmt); + return $out; + } + + /** One quest's config, falling back to the hardcoded default if absent. */ + public static function get($variant, $qid) + { + $variant = self::normVariant($variant); + $qid = (int) $qid; + $link = self::link(); + if ($link) { + self::ensureSchema(); + $stmt = mysqli_prepare($link, + "SELECT * FROM `" . TB_PREFIX . "quest_config` WHERE variant = ? AND quest_id = ? LIMIT 1"); + if ($stmt) { + mysqli_stmt_bind_param($stmt, 'si', $variant, $qid); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + $row = $res ? mysqli_fetch_assoc($res) : null; + mysqli_stmt_close($stmt); + if ($row) { + return $row; + } + } + } + // Fallback to compiled-in defaults. + $d = self::defaults(); + if (isset($d[$variant][$qid])) { + $v = $d[$variant][$qid]; + return [ + 'variant' => $variant, 'quest_id' => $qid, 'enabled' => 1, + 'wood' => $v['wood'], 'clay' => $v['clay'], 'iron' => $v['iron'], + 'crop' => $v['crop'], 'gold' => $v['gold'], 'plus_days' => $v['plus_days'], + 'req_level' => 0, 'note' => '', + ]; + } + return null; + } + + /* ---- Write --------------------------------------------------------- */ + + public static function setQuest($variant, $qid, array $fields) + { + $variant = self::normVariant($variant); + $link = self::link(); + if (!$link) { + return false; + } + self::ensureSchema(); + + $qid = (int) $qid; + $enabled = !empty($fields['enabled']) ? 1 : 0; + $wood = (int) ($fields['wood'] ?? 0); + $clay = (int) ($fields['clay'] ?? 0); + $iron = (int) ($fields['iron'] ?? 0); + $crop = (int) ($fields['crop'] ?? 0); + $gold = (int) ($fields['gold'] ?? 0); + $plus = (float) ($fields['plus_days'] ?? 0); + $req = (int) ($fields['req_level'] ?? 0); + $note = substr((string) ($fields['note'] ?? ''), 0, 255); + + $stmt = mysqli_prepare($link, + "INSERT INTO `" . TB_PREFIX . "quest_config` + (variant, quest_id, enabled, wood, clay, iron, crop, gold, plus_days, req_level, note) + VALUES (?,?,?,?,?,?,?,?,?,?,?) + ON DUPLICATE KEY UPDATE enabled=VALUES(enabled), wood=VALUES(wood), clay=VALUES(clay), + iron=VALUES(iron), crop=VALUES(crop), gold=VALUES(gold), plus_days=VALUES(plus_days), + req_level=VALUES(req_level), note=VALUES(note)"); + if (!$stmt) { + return false; + } + mysqli_stmt_bind_param($stmt, 'siiiiiiidis', + $variant, $qid, $enabled, $wood, $clay, $iron, $crop, $gold, $plus, $req, $note); + $ok = mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + return $ok; + } + + /** Reset a whole variant back to compiled-in defaults. */ + public static function resetVariant($variant) + { + $variant = self::normVariant($variant); + $link = self::link(); + if (!$link) { + return false; + } + self::ensureSchema(); + $stmt = mysqli_prepare($link, + "DELETE FROM `" . TB_PREFIX . "quest_config` WHERE variant = ?"); + if ($stmt) { + mysqli_stmt_bind_param($stmt, 's', $variant); + mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + } + self::seedDefaults(); + return true; + } + + /* ---- Reward granting (called from quest_core*.tpl) ----------------- */ + + /** + * Grant a quest's reward from config. Replaces the old hardcoded + * modifyResource()/gold/plus lines in the quest switch. Special side + * effects (messages, FinishWoodcutter, etc.) stay in the template. + * + * @param object $database + * @param object $session + * @param int $qid + * @param string|null $variant Auto-detected from session if null. + */ + public static function grantReward($database, $session, $qid, $variant = null) + { + try { + $variant = $variant ? self::normVariant($variant) : self::activeVariant(); + $cfg = self::get($variant, $qid); + if (!$cfg) { + return; + } + + $vil = (isset($session->villages) && isset($session->villages[0])) ? $session->villages[0] : 0; + $uid = 0; + if (isset($session->uid)) { + $uid = (int) $session->uid; + } elseif (isset($session->userinfo) && isset($session->userinfo['id'])) { + $uid = (int) $session->userinfo['id']; + } + + $w = (int) $cfg['wood']; $c = (int) $cfg['clay']; + $i = (int) $cfg['iron']; $cr = (int) $cfg['crop']; + if (($w || $c || $i || $cr) && $vil && method_exists($database, 'modifyResource')) { + $database->modifyResource($vil, $w, $c, $i, $cr, 1); + } + + $gold = (int) $cfg['gold']; + if ($gold > 0 && $uid > 0 && method_exists($database, 'modifyGold')) { + $database->modifyGold($uid, $gold, 1); + } + + $plusDays = (float) $cfg['plus_days']; + if ($plusDays > 0 && $uid > 0) { + $sec = (int) round($plusDays * 86400); + $now = time(); + $link = self::link(); + if ($link) { + @mysqli_query($link, "UPDATE `" . TB_PREFIX . "users` + SET plus = IF(plus > $now, plus + $sec, $now + $sec) WHERE id = $uid"); + } + } + } catch (\Throwable $e) { + // never break the quest flow + } + } + + /* ---- Helpers ------------------------------------------------------- */ + + public static function normVariant($v) + { + return ($v === self::V_EXTENDED) ? self::V_EXTENDED : self::V_STANDARD; + } + + /** + * Quests that keep their original hardcoded logic in quest_core*.tpl and are + * therefore NOT affected by edits here (conditional rewards, atomic-claim + * milestones, special mechanics). Shown as read-only in the editor. + * 2 = FinishWoodcutter (completes a building, not a resource grant) + * 9 = conditional crop cost + spawned attack + * 26 = (standard only) two-branch reward depending on a session flag + * 91 = atomic-claim milestone (gold + Plus, issue #129) + * 97 = atomic-claim milestone (gold + Plus, issue #129) + */ + public static function nativeQuests($variant) + { + $variant = self::normVariant($variant); + return ($variant === self::V_STANDARD) + ? [2, 9, 26, 91, 97] + : [2, 9, 91, 97]; + } + + /** + * Compiled-in default reward values, extracted from the shipped quest + * templates (extended = quest_core.tpl, standard = quest_core25.tpl). + * Used to seed the table and as a fallback. + */ + public static function defaults() + { + return [ + 'extended' => [ + 2 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 3 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>1], + 4 => ['wood'=>30,'clay'=>60,'iron'=>30,'crop'=>20,'gold'=>0,'plus_days'=>0], + 5 => ['wood'=>40,'clay'=>30,'iron'=>20,'crop'=>30,'gold'=>0,'plus_days'=>0], + 6 => ['wood'=>50,'clay'=>60,'iron'=>30,'crop'=>30,'gold'=>0,'plus_days'=>0], + 7 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>20,'plus_days'=>0], + 8 => ['wood'=>75,'clay'=>80,'iron'=>30,'crop'=>50,'gold'=>0,'plus_days'=>0], + 9 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>-200,'gold'=>0,'plus_days'=>0], + 10 => ['wood'=>75,'clay'=>90,'iron'=>30,'crop'=>50,'gold'=>0,'plus_days'=>0], + 11 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>2], + 12 => ['wood'=>60,'clay'=>30,'iron'=>40,'crop'=>90,'gold'=>0,'plus_days'=>0], + 13 => ['wood'=>150,'clay'=>180,'iron'=>30,'crop'=>130,'gold'=>0,'plus_days'=>0], + 14 => ['wood'=>60,'clay'=>50,'iron'=>40,'crop'=>30,'gold'=>0,'plus_days'=>0], + 15 => ['wood'=>50,'clay'=>30,'iron'=>60,'crop'=>20,'gold'=>0,'plus_days'=>0], + 16 => ['wood'=>75,'clay'=>75,'iron'=>40,'crop'=>40,'gold'=>0,'plus_days'=>0], + 17 => ['wood'=>100,'clay'=>90,'iron'=>100,'crop'=>60,'gold'=>0,'plus_days'=>0], + 18 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 19 => ['wood'=>80,'clay'=>90,'iron'=>60,'crop'=>40,'gold'=>0,'plus_days'=>0], + 20 => ['wood'=>70,'clay'=>120,'iron'=>90,'crop'=>50,'gold'=>0,'plus_days'=>0], + 21 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 22 => ['wood'=>200,'clay'=>200,'iron'=>700,'crop'=>450,'gold'=>0,'plus_days'=>0], + 23 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 24 => ['wood'=>300,'clay'=>320,'iron'=>360,'crop'=>570,'gold'=>0,'plus_days'=>0], + 28 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>15,'plus_days'=>0], + 29 => ['wood'=>240,'clay'=>280,'iron'=>180,'crop'=>100,'gold'=>0,'plus_days'=>0], + 30 => ['wood'=>600,'clay'=>750,'iron'=>600,'crop'=>300,'gold'=>0,'plus_days'=>0], + 31 => ['wood'=>900,'clay'=>850,'iron'=>600,'crop'=>300,'gold'=>0,'plus_days'=>0], + 32 => ['wood'=>1800,'clay'=>2000,'iron'=>1650,'crop'=>800,'gold'=>0,'plus_days'=>0], + 33 => ['wood'=>1600,'clay'=>1800,'iron'=>1950,'crop'=>1200,'gold'=>0,'plus_days'=>0], + 34 => ['wood'=>3400,'clay'=>2800,'iron'=>3600,'crop'=>2200,'gold'=>0,'plus_days'=>0], + 35 => ['wood'=>1050,'clay'=>800,'iron'=>900,'crop'=>750,'gold'=>0,'plus_days'=>0], + 36 => ['wood'=>1600,'clay'=>2000,'iron'=>1800,'crop'=>1300,'gold'=>0,'plus_days'=>0], + 37 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 91 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>15,'plus_days'=>1], + 92 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 93 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 94 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 95 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 96 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 97 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>20,'plus_days'=>2], + ], + 'standard' => [ + 2 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 3 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>1], + 4 => ['wood'=>30,'clay'=>60,'iron'=>30,'crop'=>20,'gold'=>0,'plus_days'=>0], + 5 => ['wood'=>40,'clay'=>30,'iron'=>20,'crop'=>30,'gold'=>0,'plus_days'=>0], + 6 => ['wood'=>50,'clay'=>60,'iron'=>30,'crop'=>30,'gold'=>0,'plus_days'=>0], + 7 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>20,'plus_days'=>0], + 8 => ['wood'=>60,'clay'=>30,'iron'=>40,'crop'=>90,'gold'=>0,'plus_days'=>0], + 9 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>-200,'gold'=>0,'plus_days'=>0], + 10 => ['wood'=>100,'clay'=>80,'iron'=>40,'crop'=>40,'gold'=>0,'plus_days'=>0], + 11 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>2], + 12 => ['wood'=>75,'clay'=>140,'iron'=>40,'crop'=>40,'gold'=>0,'plus_days'=>0], + 13 => ['wood'=>75,'clay'=>80,'iron'=>30,'crop'=>50,'gold'=>0,'plus_days'=>0], + 14 => ['wood'=>120,'clay'=>200,'iron'=>140,'crop'=>100,'gold'=>0,'plus_days'=>0], + 15 => ['wood'=>150,'clay'=>180,'iron'=>30,'crop'=>130,'gold'=>0,'plus_days'=>0], + 16 => ['wood'=>60,'clay'=>50,'iron'=>40,'crop'=>30,'gold'=>0,'plus_days'=>0], + 17 => ['wood'=>50,'clay'=>30,'iron'=>60,'crop'=>20,'gold'=>0,'plus_days'=>0], + 18 => ['wood'=>75,'clay'=>75,'iron'=>40,'crop'=>40,'gold'=>0,'plus_days'=>0], + 19 => ['wood'=>100,'clay'=>90,'iron'=>100,'crop'=>60,'gold'=>0,'plus_days'=>0], + 20 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>0,'plus_days'=>0], + 21 => ['wood'=>80,'clay'=>90,'iron'=>60,'crop'=>40,'gold'=>0,'plus_days'=>0], + 22 => ['wood'=>70,'clay'=>120,'iron'=>90,'crop'=>50,'gold'=>0,'plus_days'=>0], + 23 => ['wood'=>80,'clay'=>90,'iron'=>60,'crop'=>40,'gold'=>0,'plus_days'=>0], + 24 => ['wood'=>80,'clay'=>90,'iron'=>60,'crop'=>40,'gold'=>0,'plus_days'=>0], + 25 => ['wood'=>70,'clay'=>100,'iron'=>90,'crop'=>100,'gold'=>0,'plus_days'=>0], + 26 => ['wood'=>200,'clay'=>200,'iron'=>700,'crop'=>250,'gold'=>0,'plus_days'=>0], + 27 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>15,'plus_days'=>0], + 28 => ['wood'=>80,'clay'=>70,'iron'=>60,'crop'=>40,'gold'=>0,'plus_days'=>0], + 29 => ['wood'=>100,'clay'=>60,'iron'=>90,'crop'=>40,'gold'=>0,'plus_days'=>0], + 30 => ['wood'=>100,'clay'=>140,'iron'=>90,'crop'=>50,'gold'=>0,'plus_days'=>0], + 91 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>15,'plus_days'=>1], + 92 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 93 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 94 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 95 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 96 => ['wood'=>217,'clay'=>247,'iron'=>177,'crop'=>207,'gold'=>0,'plus_days'=>0], + 97 => ['wood'=>0,'clay'=>0,'iron'=>0,'crop'=>0,'gold'=>20,'plus_days'=>2], + ], + ]; + } +} diff --git a/GameEngine/RegBlock.php b/GameEngine/RegBlock.php new file mode 100644 index 00000000..ac5ba2ec --- /dev/null +++ b/GameEngine/RegBlock.php @@ -0,0 +1,226 @@ +dblink)) { + return $GLOBALS['database']->dblink; + } + return null; + } + + /** Create the reg_block table if missing. */ + public static function ensureSchema() + { + $link = self::link(); + if (!$link) { + return; + } + @mysqli_query($link, "CREATE TABLE IF NOT EXISTS `" . TB_PREFIX . "reg_block` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(16) NOT NULL DEFAULT 'username', + `value` varchar(255) NOT NULL DEFAULT '', + `note` varchar(255) NOT NULL DEFAULT '', + `added_by` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `type_value` (`type`,`value`), + KEY `type` (`type`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + } + + /** Normalise a value: trim + lowercase. */ + private static function norm($v) + { + return strtolower(trim((string) $v)); + } + + /** Valid type check. */ + private static function validType($t) + { + return in_array($t, [self::T_USERNAME, self::T_EMAIL, self::T_DOMAIN], true); + } + + /* ---- Enforcement (called from registration) ------------------------ */ + + /** True if this username is blocked. */ + public static function isBlockedName($database, $name) + { + return self::matches(self::T_USERNAME, self::norm($name)); + } + + /** True if this e-mail address OR its domain is blocked. */ + public static function isBlockedEmail($database, $email) + { + $email = self::norm($email); + if ($email === '') { + return false; + } + if (self::matches(self::T_EMAIL, $email)) { + return true; + } + $at = strrpos($email, '@'); + if ($at !== false) { + $domain = substr($email, $at + 1); + if ($domain !== '' && self::matches(self::T_DOMAIN, $domain)) { + return true; + } + } + return false; + } + + /** Low-level exact lookup. */ + private static function matches($type, $value) + { + $link = self::link(); + if (!$link || $value === '') { + return false; + } + self::ensureSchema(); + $stmt = mysqli_prepare($link, + "SELECT 1 FROM `" . TB_PREFIX . "reg_block` WHERE `type` = ? AND `value` = ? LIMIT 1"); + if (!$stmt) { + return false; + } + mysqli_stmt_bind_param($stmt, 'ss', $type, $value); + mysqli_stmt_execute($stmt); + mysqli_stmt_store_result($stmt); + $hit = mysqli_stmt_num_rows($stmt) > 0; + mysqli_stmt_close($stmt); + return $hit; + } + + /* ---- Admin management ---------------------------------------------- */ + + /** All blocks, newest first. */ + public static function all() + { + $link = self::link(); + $out = []; + if (!$link) { + return $out; + } + self::ensureSchema(); + $r = @mysqli_query($link, + "SELECT * FROM `" . TB_PREFIX . "reg_block` ORDER BY `time` DESC, id DESC"); + if ($r) { + while ($row = mysqli_fetch_assoc($r)) { + $out[] = $row; + } + mysqli_free_result($r); + } + return $out; + } + + /** + * Add a block. Returns [ok(bool), message(string)]. + * Domain values may be given with or without a leading "@". + */ + public static function add($type, $value, $note, $adminId) + { + $link = self::link(); + if (!$link) { + return [false, 'No database connection.']; + } + self::ensureSchema(); + + $type = (string) $type; + if (!self::validType($type)) { + return [false, 'Invalid block type.']; + } + + $value = self::norm($value); + if ($type === self::T_DOMAIN) { + $value = ltrim($value, '@'); + } + if ($value === '') { + return [false, 'Value cannot be empty.']; + } + + // Light sanity checks. + if ($type === self::T_EMAIL && strpos($value, '@') === false) { + return [false, 'That does not look like a full e-mail address.']; + } + if ($type === self::T_DOMAIN && strpos($value, '.') === false) { + return [false, 'That does not look like a domain (e.g. yahoo.com).']; + } + if (strlen($value) > 255) { + return [false, 'Value too long.']; + } + + $note = substr((string) $note, 0, 255); + $admin = (int) $adminId; + $now = time(); + + $stmt = mysqli_prepare($link, + "INSERT INTO `" . TB_PREFIX . "reg_block` (`type`,`value`,`note`,`added_by`,`time`) + VALUES (?,?,?,?,?) + ON DUPLICATE KEY UPDATE `note` = VALUES(`note`), `added_by` = VALUES(`added_by`), `time` = VALUES(`time`)"); + if (!$stmt) { + return [false, 'Could not prepare statement.']; + } + mysqli_stmt_bind_param($stmt, 'sssii', $type, $value, $note, $admin, $now); + $ok = mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + + return $ok ? [true, 'Block saved.'] : [false, 'Could not save block.']; + } + + /** Remove a block by id. */ + public static function remove($id) + { + $link = self::link(); + if (!$link) { + return false; + } + self::ensureSchema(); + $stmt = mysqli_prepare($link, + "DELETE FROM `" . TB_PREFIX . "reg_block` WHERE id = ?"); + if (!$stmt) { + return false; + } + $id = (int) $id; + mysqli_stmt_bind_param($stmt, 'i', $id); + $ok = mysqli_stmt_execute($stmt); + mysqli_stmt_close($stmt); + return $ok; + } +} diff --git a/Templates/Ajax/quest_core.tpl b/Templates/Ajax/quest_core.tpl index 76fcaafb..e1e5f9ab 100644 --- a/Templates/Ajax/quest_core.tpl +++ b/Templates/Ajax/quest_core.tpl @@ -1,4 +1,5 @@ updateUserField($_SESSION['username'],'quest','3',0,false); $_SESSION['qst']= 3; + QuestConfig::grantReward($database,$session,3); //Give Reward if(!$session->plus){ - mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."users set plus = ('".mktime(date("H"),date("i"), date("s"),date("m") , date("d"), date("Y"))."')+86400 where `username`='".$user_sanitized."'") or die(mysqli_error($database->dblink)); } else { - $plus=$database->getUserField($_SESSION['username'],'plus','username'); - $plus+=86400; - $database->updateUserField($_SESSION['username'],'plus',$plus,0); } break; case '4': $database->updateUserField($_SESSION['username'],'quest','4',0); $_SESSION['qst']= 4; + QuestConfig::grantReward($database,$session,4); //Give Reward - $database->modifyResource($session->villages[0],30,60,30,20,1); break; case 'rank': @@ -108,36 +106,34 @@ if (isset($qact)){ case '5': $database->updateUserField($_SESSION['username'],'quest','5',0); $_SESSION['qst']= 5; + QuestConfig::grantReward($database,$session,5); //Give Reward - $database->modifyResource($session->villages[0],40,30,20,30,1); break; case '6': $database->updateUserField($_SESSION['username'],'quest','6',0); $_SESSION['qst']= 6; + QuestConfig::grantReward($database,$session,6); $Subject="Message From The Taskmaster"; $Subject=Q6_SUBJECT; $Message=Q6_MESSAGE; $database->sendMessage($session->userinfo['id'],4,$Subject,$Message,0,0,0,0,0); $RB=true; //Give Reward - $database->modifyResource($session->villages[0],50,60,30,30,1); break; case '7': $database->updateUserField($_SESSION['username'],'quest','7',0); $_SESSION['qst']= 7; + QuestConfig::grantReward($database,$session,7); //Give Reward - $gold=$database->getUserField($_SESSION['username'],'gold','username'); - $gold+=20; - $database->updateUserField($_SESSION['username'],'gold',$gold,0,false); break; case '8': $database->updateUserField($_SESSION['username'],'quest','8',0,false); $_SESSION['qst']= 8; + QuestConfig::grantReward($database,$session,8); //Give Reward - $database->modifyResource($session->villages[0],75,80,30,50,1); break; case '9': @@ -158,20 +154,17 @@ if (isset($qact)){ case '10': $database->updateUserField($_SESSION['username'],'quest','10',0); $_SESSION['qst']= 10; + QuestConfig::grantReward($database,$session,10); //Give Reward - $database->modifyResource($session->villages[0],75,90,30,50,1); break; case '11': $database->updateUserField($_SESSION['username'],'quest','11',0); $_SESSION['qst']= 11; + QuestConfig::grantReward($database,$session,11); //Give Reward if(!$session->plus){ - mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."users set plus = ('".mktime(date("H"),date("i"), date("s"),date("m") , date("d"), date("Y"))."')+172800 where `username`='".$user_sanitized."'") or die(mysqli_error()); } else { - $plus=$database->getUserField($_SESSION['username'],'plus','username'); - $plus+=172800; - $database->updateUserField($_SESSION['username'],'plus',$plus,0); } break; @@ -183,22 +176,22 @@ if (isset($qact)){ case '12': $database->updateUserField($_SESSION['username'],'quest','12',0); $_SESSION['qst']= 12; + QuestConfig::grantReward($database,$session,12); //Give Reward - $database->modifyResource($session->villages[0],60,30,40,90,1); break; case '13': $database->updateUserField($_SESSION['username'],'quest','13',0); $_SESSION['qst']= 13; + QuestConfig::grantReward($database,$session,13); //Give Reward - $database->modifyResource($session->villages[0],150,180,30,130,1); break; case '14': $database->updateUserField($_SESSION['username'],'quest','14',0); $_SESSION['qst']= 14; + QuestConfig::grantReward($database,$session,14); //Give Reward - $database->modifyResource($session->villages[0],60,50,40,30,1); break; case 'lumber': @@ -208,143 +201,145 @@ if (isset($qact)){ case '15': $database->updateUserField($_SESSION['username'],'quest','15',0); $_SESSION['qst']= 15; + QuestConfig::grantReward($database,$session,15); //Give Reward - $database->modifyResource($session->villages[0],50,30,60,20,1); break; case '16': $database->updateUserField($_SESSION['username'],'quest','16',0); $_SESSION['qst']= 16; + QuestConfig::grantReward($database,$session,16); //Give Reward - $database->modifyResource($session->villages[0],75,75,40,40,1); break; case '17': $database->updateUserField($_SESSION['username'],'quest','17',0); $_SESSION['qst']= 17; + QuestConfig::grantReward($database,$session,17); //Give Reward - $database->modifyResource($session->villages[0],100,90,100,60,1); break; case '18': $database->updateUserField($_SESSION['username'],'quest','18',0); $_SESSION['qst']= 18; + QuestConfig::grantReward($database,$session,18); break; case '19': $database->updateUserField($_SESSION['username'],'quest','19',0); $_SESSION['qst']= 19; + QuestConfig::grantReward($database,$session,19); //Give Reward - $database->modifyResource($session->villages[0],80,90,60,40,1); break; case '20': $database->updateUserField($_SESSION['username'],'quest','20',0); $_SESSION['qst']= 20; + QuestConfig::grantReward($database,$session,20); //Give Reward - $database->modifyResource($session->villages[0],70,120,90,50,1); break; case '21': $database->updateUserField($_SESSION['username'],'quest','21',0); $_SESSION['qst']= 21; + QuestConfig::grantReward($database,$session,21); break; case '22': $database->updateUserField($_SESSION['username'],'quest','22',0); $_SESSION['qst']= 22; + QuestConfig::grantReward($database,$session,22); //Give Reward - $database->modifyResource($session->villages[0],200,200,700,450,1); break; case '23': $database->updateUserField($_SESSION['username'],'quest','23',0); $_SESSION['qst']= 23; + QuestConfig::grantReward($database,$session,23); break; case '24': $database->updateUserField($_SESSION['username'],'quest','24',0); $_SESSION['qst']= 24; + QuestConfig::grantReward($database,$session,24); //Give Reward - $database->modifyResource($session->villages[0],300,320,360,570,1); break; case '28': $dataarray[3] = 1; $database->updateUserField($_SESSION['username'],'quest','28',0); $_SESSION['qst']= 28; + QuestConfig::grantReward($database,$session,28); //Give Reward - $gold=$database->getUserField($_SESSION['username'],'gold','username'); - $gold+=15; - $database->updateUserField($_SESSION['username'],'gold',$gold,0); break; case '29': $dataarray[4] = 1; $database->updateUserField($_SESSION['username'],'quest','29',0); $_SESSION['qst']= 29; + QuestConfig::grantReward($database,$session,29); //Give Reward - $database->modifyResource($session->villages[0],240,280,180,100,1); break; case '30': $dataarray[5] = 1; $database->updateUserField($_SESSION['username'],'quest','30',0); $_SESSION['qst']= 30; + QuestConfig::grantReward($database,$session,30); //Give Reward - $database->modifyResource($session->villages[0],600,750,600,300,1); break; case '31': $dataarray[6] = 1; $database->updateUserField($_SESSION['username'],'quest','31',0); $_SESSION['qst']= 31; + QuestConfig::grantReward($database,$session,31); //Give Reward - $database->modifyResource($session->villages[0],900,850,600,300,1); break; case '32': $dataarray[7] = 1; $database->updateUserField($_SESSION['username'],'quest','32',0); $_SESSION['qst']= 32; + QuestConfig::grantReward($database,$session,32); //Give Reward - $database->modifyResource($session->villages[0],1800,2000,1650,800,1); break; case '33': $dataarray[8] = 1; $database->updateUserField($_SESSION['username'],'quest','33',0); $_SESSION['qst']= 33; + QuestConfig::grantReward($database,$session,33); //Give Reward - $database->modifyResource($session->villages[0],1600,1800,1950,1200,1); break; case '34': $dataarray[9] = 1; $database->updateUserField($_SESSION['username'],'quest','34',0); $_SESSION['qst']= 34; + QuestConfig::grantReward($database,$session,34); //Give Reward - $database->modifyResource($session->villages[0],3400,2800,3600,2200,1); break; case '35': $dataarray[10] = 1; $database->updateUserField($_SESSION['username'],'quest','35',0); $_SESSION['qst']= 35; + QuestConfig::grantReward($database,$session,35); //Give Reward - $database->modifyResource($session->villages[0],1050,800,900,750,1); break; case '36': $database->updateUserField($_SESSION['username'],'quest','36',0); $_SESSION['qst']= 36; + QuestConfig::grantReward($database,$session,36); //Give Reward - $database->modifyResource($session->villages[0],1600,2000,1800,1300,1); break; case '37': $database->updateUserField($_SESSION['username'],'quest','37',0); $_SESSION['qst']= 37; + QuestConfig::grantReward($database,$session,37); break; case '91': @@ -366,45 +361,45 @@ if (isset($qact)){ $database->updateUserField($_SESSION['username'],'quest','92',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 92; + QuestConfig::grantReward($database,$session,92); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '93': $database->updateUserField($_SESSION['username'],'quest','93',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 93; + QuestConfig::grantReward($database,$session,93); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '94': $database->updateUserField($_SESSION['username'],'quest','94',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 94; + QuestConfig::grantReward($database,$session,94); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '95': $database->updateUserField($_SESSION['username'],'quest','95',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 95; + QuestConfig::grantReward($database,$session,95); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '96': $database->updateUserField($_SESSION['username'],'quest','96',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 96; + QuestConfig::grantReward($database,$session,96); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '97': @@ -661,8 +656,26 @@ if ($barrack==0){ ?> // Checking 2 warrior trained or no $units = $village->unitall; -$unarray=array("",U1,U11,U21); -$unarray2=array("","u1", "u11","u21"); +$unarray = array( + "", + U1, + U11, + U21, + U51, + U61, + U71, + U81 +); +$unarray2 = array( + "", + "u1", + "u11", + "u21", + "u51", + "u61", + "u71", + "u81" +); if ($units[$unarray2[$session->userinfo['tribe']]]<2){ ?> {"markup":"\n\t\t

    \"\" <\/h1>
    userinfo['tribe']];?>.”<\/i>

    userinfo['tribe']];?>.<\/div>
    <\/span><\/div>\n\t\t

    <\/div>\n\t\t","number":"-20","reward":false,"qgsrc":"q_luserinfo['tribe'];?>","msrc":"","altstep":99} @@ -779,7 +792,6 @@ if ($palace >= 10) { } elseif ($commandCenter >= 10) { $text = COMMANDCENTER; } - if ($residence < 10 && $palace < 10 && $commandCenter < 10) { ?> {"markup":"\n\t\t

    \"\" <\/h1>
    ”<\/i>

    <\/div>
    <\/span><\/div>\n\t\t

    <\/div>\n\t\t","number":"-33","reward":false,"qgsrc":"q_luserinfo['tribe'];?>","msrc":"","altstep":99} diff --git a/Templates/Ajax/quest_core25.tpl b/Templates/Ajax/quest_core25.tpl index 84d11657..a55b7c51 100644 --- a/Templates/Ajax/quest_core25.tpl +++ b/Templates/Ajax/quest_core25.tpl @@ -1,4 +1,5 @@ updateUserField($_SESSION['username'],'quest','3',0); $_SESSION['qst']= 3; + QuestConfig::grantReward($database,$session,3); //Give Reward if(!$session->plus){ - mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."users set plus = ('".mktime(date("H"),date("i"), date("s"),date("m") , date("d"), date("Y"))."')+86400 where `username`='".$user_sanitized."'") or die(mysqli_error($database->dblink)); } else { - $plus=$database->getUserField($_SESSION['username'],'plus','username'); - $plus+=86400; - $database->updateUserField($_SESSION['username'],'plus',$plus,0); } break; case '4': $database->updateUserField($_SESSION['username'],'quest','4',0); $_SESSION['qst']= 4; + QuestConfig::grantReward($database,$session,4); //Give Reward - $database->modifyResource($session->villages[0],30,60,30,20,1); break; case 'rank': @@ -105,28 +103,26 @@ if (isset($qact)){ case '5': $database->updateUserField($_SESSION['username'],'quest','5',0); $_SESSION['qst']= 5; + QuestConfig::grantReward($database,$session,5); //Give Reward - $database->modifyResource($session->villages[0],40,30,20,30,1); break; case '6': $database->updateUserField($_SESSION['username'],'quest','6',0); $_SESSION['qst']= 6; + QuestConfig::grantReward($database,$session,6); $Subject=Q6_SUBJECT; $Message=Q6_MESSAGE; $database->sendMessage($session->userinfo['id'],4,$Subject,$Message,0,0,0,0,0); $RB=true; //Give Reward - $database->modifyResource($session->villages[0],50,60,30,30,1); break; case '7': $database->updateUserField($_SESSION['username'],'quest','7',0); $_SESSION['qst']= 7; + QuestConfig::grantReward($database,$session,7); //Give Reward - $gold=$database->getUserField($_SESSION['username'],'gold','username'); - $gold+=20; - $database->updateUserField($_SESSION['username'],'gold',$gold,0); break; case 'coor': @@ -137,8 +133,8 @@ if (isset($qact)){ case '8': $database->updateUserField($_SESSION['username'],'quest','8',0); $_SESSION['qst']= 8; + QuestConfig::grantReward($database,$session,8); //Give Reward - $database->modifyResource($session->villages[0],60,30,40,90,1); break; case '9': @@ -159,56 +155,53 @@ if (isset($qact)){ case '10': $database->updateUserField($_SESSION['username'],'quest','10',0); $_SESSION['qst']= 10; + QuestConfig::grantReward($database,$session,10); //Give Reward - $database->modifyResource($session->villages[0],100,80,40,40,1); break; case '11': $database->updateUserField($_SESSION['username'],'quest','11',0); $_SESSION['qst']= 11; + QuestConfig::grantReward($database,$session,11); //Give Reward if(!$session->plus){ - mysqli_query($database->dblink,"UPDATE ".TB_PREFIX."users set plus = ('".mktime(date("H"),date("i"), date("s"),date("m") , date("d"), date("Y"))."')+172800 where `username`='".$user_sanitized."'") or die(mysqli_error($database->dblink)); } else { - $plus=$database->getUserField($_SESSION['username'],'plus','username'); - $plus+=172800; - $database->updateUserField($_SESSION['username'],'plus',$plus,0); } break; case '12': $database->updateUserField($_SESSION['username'],'quest','12',0); $_SESSION['qst']= 12; + QuestConfig::grantReward($database,$session,12); //Give Reward - $database->modifyResource($session->villages[0],75,140,40,40,1); break; case '13': $database->updateUserField($_SESSION['username'],'quest','13',0); $_SESSION['qst']= 13; + QuestConfig::grantReward($database,$session,13); //Give Reward - $database->modifyResource($session->villages[0],75,80,30,50,1); break; case '14': $database->updateUserField($_SESSION['username'],'quest','14',0); $_SESSION['qst']= 14; + QuestConfig::grantReward($database,$session,14); //Give Reward - $database->modifyResource($session->villages[0],120,200,140,100,1); break; case '15': $database->updateUserField($_SESSION['username'],'quest','15',0); $_SESSION['qst']= 15; + QuestConfig::grantReward($database,$session,15); //Give Reward - $database->modifyResource($session->villages[0],150,180,30,130,1); break; case '16': $database->updateUserField($_SESSION['username'],'quest','16',0); $_SESSION['qst']= 16; + QuestConfig::grantReward($database,$session,16); //Give Reward - $database->modifyResource($session->villages[0],60,50,40,30,1); break; case 'lumber': @@ -218,64 +211,65 @@ if (isset($qact)){ case '17': $database->updateUserField($_SESSION['username'],'quest','17',0); $_SESSION['qst']= 17; + QuestConfig::grantReward($database,$session,17); //Give Reward - $database->modifyResource($session->villages[0],50,30,60,20,1); break; case '18': $database->updateUserField($_SESSION['username'],'quest','18',0); $_SESSION['qst']= 18; + QuestConfig::grantReward($database,$session,18); //Give Reward - $database->modifyResource($session->villages[0],75,75,40,40,1); break; case '19': $database->updateUserField($_SESSION['username'],'quest','19',0); $_SESSION['qst']= 19; + QuestConfig::grantReward($database,$session,19); //Give Reward - $database->modifyResource($session->villages[0],100,90,100,60,1); break; case '20': $database->updateUserField($_SESSION['username'],'quest','20',0); $_SESSION['qst']= 20; + QuestConfig::grantReward($database,$session,20); break; case '21': $_SESSION['dough']= true; $database->updateUserField($_SESSION['username'],'quest','21',0); $_SESSION['qst']= 21; + QuestConfig::grantReward($database,$session,21); //Give Reward granary - $database->modifyResource($session->villages[0],80,90,60,40,1); break; case '22': $database->updateUserField($_SESSION['username'],'quest','22',0); $_SESSION['qst']= 22; + QuestConfig::grantReward($database,$session,22); //Give Reward warehouse - $database->modifyResource($session->villages[0],70,120,90,50,1); break; case '23': $database->updateUserField($_SESSION['username'],'quest','23',0); $_SESSION['qst']= 23; + QuestConfig::grantReward($database,$session,23); //Give Reward - //$database->modifyResource($session->villages[0],80,90,60,40,1); break; case '24': $database->updateUserField($_SESSION['username'],'quest','24',0); $_SESSION['qst']= 24; + QuestConfig::grantReward($database,$session,24); //Give Reward rally point - $database->modifyResource($session->villages[0],80,90,60,40,1); break; case '25': //$dataarray[3] = 1; $database->updateUserField($_SESSION['username'],'quest','25',0); $_SESSION['qst']= 25; + QuestConfig::grantReward($database,$session,25); //Give Reward barrack - $database->modifyResource($session->villages[0],70,100,90,100,1); break; case '26': @@ -295,10 +289,8 @@ if (isset($qact)){ //$dataarray[5] = 1; $database->updateUserField($_SESSION['username'],'quest','27',0); $_SESSION['qst']= 27; + QuestConfig::grantReward($database,$session,27); //Give Reward - $gold=$database->getUserField($_SESSION['username'],'gold',1); - $gold+=15; - $database->updateUserField($_SESSION['username'],'gold',$gold,0); break; case 'gold': @@ -309,24 +301,24 @@ if (isset($qact)){ //$dataarray[6] = 1; $database->updateUserField($_SESSION['username'],'quest','28',0); $_SESSION['qst']= 28; + QuestConfig::grantReward($database,$session,28); //Give Reward - $database->modifyResource($session->villages[0],80,70,60,40,1); break; case '29': //$dataarray[7] = 1; $database->updateUserField($_SESSION['username'],'quest','29',0); $_SESSION['qst']= 29; + QuestConfig::grantReward($database,$session,29); //Give Reward - $database->modifyResource($session->villages[0],100,60,90,40,1); break; case '30': //$dataarray[8] = 1; $database->updateUserField($_SESSION['username'],'quest','30',0); $_SESSION['qst']= 30; + QuestConfig::grantReward($database,$session,30); //Give Reward - $database->modifyResource($session->villages[0],100,140,90,50,1); break; case '91': @@ -348,45 +340,45 @@ if (isset($qact)){ $database->updateUserField($_SESSION['username'],'quest','92',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 92; + QuestConfig::grantReward($database,$session,92); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '93': $database->updateUserField($_SESSION['username'],'quest','93',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 93; + QuestConfig::grantReward($database,$session,93); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '94': $database->updateUserField($_SESSION['username'],'quest','94',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 94; + QuestConfig::grantReward($database,$session,94); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '95': $database->updateUserField($_SESSION['username'],'quest','95',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 95; + QuestConfig::grantReward($database,$session,95); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '96': $database->updateUserField($_SESSION['username'],'quest','96',0); $database->updateUserField($_SESSION['username'],'quest_time',''.(time()+$skipp_time).'',0); $_SESSION['qst']= 96; + QuestConfig::grantReward($database,$session,96); $_SESSION['qst_time'] = time()+$skipp_time; //Give Reward - $database->modifyResource($session->villages[0],217,247,177,207,1); break; case '97': diff --git a/Templates/Plus/3.tpl b/Templates/Plus/3.tpl index 601ca95a..8d7ec6e7 100644 --- a/Templates/Plus/3.tpl +++ b/Templates/Plus/3.tpl @@ -162,6 +162,31 @@ function formatRemainingTime($endTimestamp, $nowTimestamp) { + + + + + + + + +
    Redeem a gold code
    + +
    + +
    + + +
    +
    + + diff --git a/a2b2.php b/a2b2.php index caf04f0e..8690f21c 100644 --- a/a2b2.php +++ b/a2b2.php @@ -5,16 +5,19 @@ $start_timer = $generator->pageLoadTimeStart(); ################################################################################# ## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ## ## --------------------------------------------------------------------------- ## -## Project: TravianZ ## -## Version: 18.05.2026 ## -## Filename: a2b2.php ## -## Developed by: Dzoki ## -## Refactored by: Shadow ## -## License: TravianZ Project ## -## Copyright: TravianZ (c) 2010-2026. All rights reserved. ## -## URLs: https://travianz.org ## -## https://github.com/Shadowss/TravianZ ## -## ## +## Filename : a2b2.php ## +## Type : In Game Account Gold Statement ## +## --------------------------------------------------------------------------- ## +## Developed 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; diff --git a/plus.php b/plus.php index 7c3bf0c5..a2fa6210 100644 --- a/plus.php +++ b/plus.php @@ -35,6 +35,18 @@ if(isset($_GET['newdid'])) { } else $building->procBuild($_GET); +// Gold shop: promo-code redemption (player-side). Best-effort; the engine +// self-creates its tables and validates the code (active / expiry / uses / +// once-per-player) before granting gold. +$promoMsg = ''; +$promoOk = false; +if (isset($_POST['redeem_code']) && class_exists('GoldShop')) { + $__uid = isset($session) && isset($session->uid) ? (int)$session->uid : 0; + $rr = GoldShop::redeem($__uid, $_POST['redeem_code']); + $promoOk = $rr[0]; + $promoMsg = $rr[1]; +} + ?> diff --git a/var/db/struct.sql b/var/db/struct.sql index 6838f163..6ef41d29 100644 --- a/var/db/struct.sql +++ b/var/db/struct.sql @@ -1183,6 +1183,88 @@ CREATE TABLE IF NOT EXISTS `%PREFIX%push_override` ( PRIMARY KEY (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- -------------------------------------------------------- + +-- +-- Table structure for table `%prefix%reg_block` +-- (Registration blocklist: blocked usernames / e-mails / e-mail domains) +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%reg_block` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `type` varchar(16) NOT NULL DEFAULT 'username', + `value` varchar(255) NOT NULL DEFAULT '', + `note` varchar(255) NOT NULL DEFAULT '', + `added_by` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `type_value` (`type`,`value`), + KEY `type` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `%prefix%gold_promo` +-- (Gold shop: redeemable promo / voucher codes) +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%gold_promo` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(64) NOT NULL, + `gold` int(11) NOT NULL DEFAULT 0, + `max_uses` int(11) NOT NULL DEFAULT 0, + `uses` int(11) NOT NULL DEFAULT 0, + `per_user` tinyint(1) NOT NULL DEFAULT 1, + `expires` int(11) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 1, + `note` varchar(255) NOT NULL DEFAULT '', + `created_by` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `%prefix%gold_promo_redeem` +-- (Gold shop: per-player redemption log) +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%gold_promo_redeem` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `promo_id` int(11) NOT NULL DEFAULT 0, + `uid` int(11) NOT NULL DEFAULT 0, + `gold` int(11) NOT NULL DEFAULT 0, + `time` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `promo_uid` (`promo_id`,`uid`), + KEY `uid` (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `%prefix%quest_config` +-- (Quest editor: editable per-variant quest rewards / requirements) +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%quest_config` ( + `variant` varchar(16) NOT NULL DEFAULT 'standard', + `quest_id` int(11) NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT 1, + `wood` int(11) NOT NULL DEFAULT 0, + `clay` int(11) NOT NULL DEFAULT 0, + `iron` int(11) NOT NULL DEFAULT 0, + `crop` int(11) NOT NULL DEFAULT 0, + `gold` int(11) NOT NULL DEFAULT 0, + `plus_days` float NOT NULL DEFAULT 0, + `req_level` int(11) NOT NULL DEFAULT 0, + `note` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`variant`,`quest_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- -- Dumping data for table `%prefix%login_log` --