mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-07-24 05:26:19 +00:00
Refactor GameEngine files
fix some GameEngine bugs and new improovements
This commit is contained in:
+613
-870
File diff suppressed because it is too large
Load Diff
@@ -383,6 +383,11 @@ if (!isset($SAJAX_INCLUDED)) {
|
||||
function get_data() {
|
||||
global $session,$database;
|
||||
|
||||
// FIX: $data initializat inainte de concatenare - fara asta, PHP 8+
|
||||
// arunca "Warning: Undefined variable $data" la fiecare refresh de chat
|
||||
// (umple error log-ul; pe PHP 9 devine mai strict).
|
||||
$data = '';
|
||||
|
||||
$alliance = $database->escape($session->alliance);
|
||||
$query = mysqli_query($database->dblink,"select id_user, name, date, msg from ".TB_PREFIX."chat where alli='$alliance' order by id desc limit 0,13");
|
||||
while ($r = mysqli_fetch_array($query)) {
|
||||
|
||||
@@ -30,6 +30,91 @@ trait DatabaseBuildingQueries {
|
||||
return mysqli_query($this->dblink,$q);
|
||||
}
|
||||
|
||||
/**
|
||||
* B-W2: finalizeaza o cladire (nivel + tip) in fdata SI invalideaza cache-ul
|
||||
* de resource levels. Inlocuieste UPDATE-ul raw din Building::finishAll()
|
||||
* care ocolea invalidarea din Faza B - recountCP() citea nivelele vechi din
|
||||
* cache si persista un CP gresit in vdata.
|
||||
*/
|
||||
function finishBuildingLevel($wid, $field, $level, $type) {
|
||||
list($wid, $field, $level, $type) = $this->escape_input((int) $wid, (int) $field, (int) $level, (int) $type);
|
||||
|
||||
$q = "UPDATE " . TB_PREFIX . "fdata SET f" . $field . " = " . $level . ", f" . $field . "t = " . $type . " WHERE vref = " . $wid;
|
||||
$result = mysqli_query($this->dblink, $q);
|
||||
|
||||
// invalidare: urmatorul getResourceLevel()/getFieldLevel() reciteste din DB
|
||||
self::clearResourseLevelsCache();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* B-W2: sterge joburi din bdata dupa id-uri SI invalideaza cache-ul de
|
||||
* joburi al satului. Inlocuieste DELETE-ul raw din Building::finishAll().
|
||||
*/
|
||||
function deleteBuildings($wid, array $ids) {
|
||||
$wid = (int) $wid;
|
||||
$ids = array_map('intval', $ids);
|
||||
|
||||
if (empty($ids)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = mysqli_query($this->dblink, "DELETE FROM " . TB_PREFIX . "bdata WHERE id IN(" . implode(',', $ids) . ")");
|
||||
unset(self::$buildingsUnderConstructionCache[$wid]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* B-W2: promoveaza un job din coada de asteptare (loopcon = 1) in coada
|
||||
* activa SI invalideaza cache-ul de joburi al satului.
|
||||
*/
|
||||
function promoteLoopJob($wid, $id) {
|
||||
list($wid, $id) = $this->escape_input((int) $wid, (int) $id);
|
||||
|
||||
$result = mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "bdata SET loopcon = 0 WHERE id = " . $id);
|
||||
unset(self::$buildingsUnderConstructionCache[$wid]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* B-W2: actualizeaza numarul maxim de membri ai aliantei conduse de $leader
|
||||
* (efectul Embassy-ului). Inlocuieste UPDATE-ul raw din Building::finishAll().
|
||||
*/
|
||||
function updateAllianceMax($leader, $max) {
|
||||
list($leader, $max) = $this->escape_input((int) $leader, (int) $max);
|
||||
|
||||
return mysqli_query($this->dblink, "UPDATE " . TB_PREFIX . "alidata SET max = " . $max . " WHERE leader = " . $leader);
|
||||
}
|
||||
|
||||
/**
|
||||
* B-W2: log de gold (prepared statement). Inlocuieste INSERT-ul raw din
|
||||
* Building::finishAll(); acelasi format ca logger-ul din UserQueries.
|
||||
*/
|
||||
function addGoldFinLog($wid, $uid, $action, $gold, $details) {
|
||||
$stmt = $this->dblink->prepare(
|
||||
"INSERT INTO `" . TB_PREFIX . "gold_fin_log` (wid, uid, action, gold, time, details) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
if (!$stmt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$wid = (int) $wid;
|
||||
$uid = (int) $uid;
|
||||
$gold = (int) $gold;
|
||||
$now = time();
|
||||
|
||||
$stmt->bind_param("iisiis", $wid, $uid, $action, $gold, $now, $details);
|
||||
$ok = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
return $ok;
|
||||
}
|
||||
|
||||
|
||||
function getBuildLock($wid) {
|
||||
$wid = (int) $wid;
|
||||
$result = mysqli_query($this->dblink, "SELECT GET_LOCK('build_village_$wid', 10) AS locked");
|
||||
|
||||
@@ -288,4 +288,44 @@ References: User ID/Message ID, Mode
|
||||
$result = mysqli_query($this->dblink,$q);
|
||||
return $this->mysqli_fetch_all($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* UM-W1: numarul de mesaje trimise de $uid in ultimele $seconds secunde.
|
||||
* Muta flood-check-ul inline din Message::sendMessage()/sendAMessage().
|
||||
*/
|
||||
function countRecentMessages($uid, $seconds = 60) {
|
||||
list($uid, $seconds) = $this->escape_input((int) $uid, (int) $seconds);
|
||||
|
||||
$q = "SELECT COUNT(*) AS Total FROM " . TB_PREFIX . "mdata WHERE owner = $uid AND time > " . (time() - $seconds);
|
||||
$row = mysqli_fetch_assoc(mysqli_query($this->dblink, $q));
|
||||
return (int) ($row['Total'] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* UM-W1: id-urile membrilor unei aliante. Muta query-ul inline din
|
||||
* Message::sendAMessage().
|
||||
*/
|
||||
function getAllianceMemberIds($alliance) {
|
||||
list($alliance) = $this->escape_input((int) $alliance);
|
||||
|
||||
$q = "SELECT id FROM " . TB_PREFIX . "users WHERE alliance = $alliance";
|
||||
$result = mysqli_query($this->dblink, $q);
|
||||
$ids = [];
|
||||
while ($row = mysqli_fetch_assoc($result)) {
|
||||
$ids[] = $row['id'];
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* UM-W1: target + owner ale unui mesaj (pentru a decide modul de stergere).
|
||||
* Muta SELECT-ul inline din Message::removeMessage().
|
||||
*/
|
||||
function getMessageOwnership($id) {
|
||||
list($id) = $this->escape_input((int) $id);
|
||||
|
||||
$q = "SELECT target, owner FROM " . TB_PREFIX . "mdata WHERE id = $id LIMIT 1";
|
||||
$row = mysqli_fetch_assoc(mysqli_query($this->dblink, $q));
|
||||
return $row ?: ['target' => 0, 'owner' => 0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,11 +299,16 @@ class Market
|
||||
? PUSH_PROTECTION_LIMIT
|
||||
: 'This account has reached its resource-transfer limit for now and cannot receive that many resources. Send less, or wait for the limit to recover.');
|
||||
} elseif (
|
||||
$availableWood >= $post['r1'] &&
|
||||
$availableClay >= $post['r2'] &&
|
||||
$availableIron >= $post['r3'] &&
|
||||
$availableCrop >= $post['r4']
|
||||
$availableWood >= $wtrans &&
|
||||
$availableClay >= $ctrans &&
|
||||
$availableIron >= $itrans &&
|
||||
$availableCrop >= $crtrans
|
||||
) {
|
||||
// UM-D1: comparatia foloseste valorile curatate de semnul minus
|
||||
// ($wtrans/... in loc de $post['r1']/...). Inainte, comparatia se
|
||||
// facea pe POST-ul brut (potential negativ), dar deducerea se facea
|
||||
// pe valoarea pozitiva - un "r1" negativ trecea verificarea si scadea
|
||||
// resurse pozitive.
|
||||
$reqMerc = ceil((array_sum($resource) - 0.1) / $this->maxcarry);
|
||||
|
||||
// Acquire merchant lock to prevent race conditions
|
||||
|
||||
+28
-58
@@ -307,7 +307,6 @@ class Message
|
||||
private function removeMessage($post)
|
||||
{
|
||||
global $database, $session;
|
||||
$post = $database->escape($post);
|
||||
$mode5updates = [];
|
||||
$mode7updates = [];
|
||||
$mode8updates = [];
|
||||
@@ -316,14 +315,9 @@ class Message
|
||||
continue;
|
||||
}
|
||||
$messageId = (int)$post['n' . $i];
|
||||
$query = mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT target, owner
|
||||
FROM " . TB_PREFIX . "mdata
|
||||
WHERE id = " . $messageId . "
|
||||
LIMIT 1"
|
||||
);
|
||||
$message = mysqli_fetch_array($query);
|
||||
// UM-W1: SELECT-ul de ownership mutat in DB (getMessageOwnership).
|
||||
// $messageId e deja cast la int, deci nu mai e nevoie de escape pe tot $post.
|
||||
$message = $database->getMessageOwnership($messageId);
|
||||
if (
|
||||
$message['target'] == $session->uid &&
|
||||
$message['owner'] == $session->uid
|
||||
@@ -595,34 +589,15 @@ class Message
|
||||
{
|
||||
global $session, $database;
|
||||
|
||||
// Flood protection
|
||||
$q = "
|
||||
SELECT COUNT(*) AS Total
|
||||
FROM " . TB_PREFIX . "mdata
|
||||
WHERE owner='" . $session->uid . "'
|
||||
AND time > " . (time() - 60);
|
||||
$res = mysqli_fetch_array(
|
||||
mysqli_query($database->dblink, $q),
|
||||
MYSQLI_ASSOC
|
||||
);
|
||||
if ($res['Total'] > 5) {
|
||||
// Flood protection (UM-W1: query mutat in DB)
|
||||
if ($database->countRecentMessages($session->uid, 60) > 5) {
|
||||
return;
|
||||
}
|
||||
$allmembersQ = mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT id
|
||||
FROM " . TB_PREFIX . "users
|
||||
WHERE alliance='" . $session->alliance . "'"
|
||||
);
|
||||
$userally = $database->getUserField($session->uid, "alliance", 0);
|
||||
$permission = mysqli_fetch_array(
|
||||
mysqli_query(
|
||||
$database->dblink,
|
||||
"SELECT opt7
|
||||
FROM " . TB_PREFIX . "ali_permission
|
||||
WHERE uid='" . $session->uid . "'"
|
||||
)
|
||||
);
|
||||
|
||||
$allmembers = $database->getAllianceMemberIds($session->alliance);
|
||||
$userally = $database->getUserField($session->uid, "alliance", 0);
|
||||
$permission = $database->getAllyMessagePermission($session->uid);
|
||||
|
||||
if (defined('WORD_CENSOR')) {
|
||||
$topic = $this->wordCensor($topic);
|
||||
$text = $this->wordCensor($text);
|
||||
@@ -646,11 +621,11 @@ class Message
|
||||
$coor,
|
||||
$report
|
||||
);
|
||||
if ($permission['opt7'] == 1) {
|
||||
if ($permission == 1) {
|
||||
if ($userally > 0) {
|
||||
while ($allmembers = mysqli_fetch_array($allmembersQ)) {
|
||||
foreach ($allmembers as $memberId) {
|
||||
$database->sendMessage(
|
||||
$allmembers['id'],
|
||||
$memberId,
|
||||
$session->uid,
|
||||
htmlspecialchars(addslashes($topic)),
|
||||
htmlspecialchars(addslashes($text)),
|
||||
@@ -671,19 +646,9 @@ class Message
|
||||
global $session, $database;
|
||||
$user = $database->getUserField($recieve, "id", 1);
|
||||
|
||||
// Flood protection
|
||||
// Flood protection (UM-W1: query mutat in DB)
|
||||
if ($security_check) {
|
||||
$q = "
|
||||
SELECT COUNT(*) AS Total
|
||||
FROM " . TB_PREFIX . "mdata
|
||||
WHERE owner='" . $session->uid . "'
|
||||
AND time > " . (time() - 60);
|
||||
|
||||
$res = mysqli_fetch_array(
|
||||
mysqli_query($database->dblink, $q),
|
||||
MYSQLI_ASSOC
|
||||
);
|
||||
if ($res['Total'] > 5) {
|
||||
if ($database->countRecentMessages($session->uid, 60) > 5) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -842,7 +807,12 @@ class Message
|
||||
|
||||
public function addFriends($post)
|
||||
{
|
||||
global $database;
|
||||
global $database, $session;
|
||||
// FIX securitate (UM-D1): identitatea proprie se ia din sesiune, NU din
|
||||
// $post['myid'] (hidden field, deci falsificabil de un client modificat).
|
||||
// Inainte, un POST forjat cu alt myid modifica lista de prieteni a altui
|
||||
// jucator (IDOR) - SQL-ul era escapat, dar ownership-ul nu era verificat.
|
||||
$myid = (int) $session->uid;
|
||||
for ($i = 0; $i <= 19; $i++) {
|
||||
if (empty($post['addfriends' . $i])) {
|
||||
continue;
|
||||
@@ -859,12 +829,12 @@ class Message
|
||||
continue;
|
||||
}
|
||||
$user = $database->getUserField(
|
||||
$post['myid'],
|
||||
$myid,
|
||||
"friend" . $j,
|
||||
0
|
||||
);
|
||||
$userwait = $database->getUserField(
|
||||
$post['myid'],
|
||||
$myid,
|
||||
"friend" . $j . "wait",
|
||||
0
|
||||
);
|
||||
@@ -872,11 +842,11 @@ class Message
|
||||
for ($k = 0; $k <= 19; $k++) {
|
||||
|
||||
$user1 = $database->getUserField(
|
||||
$post['myid'],
|
||||
$myid,
|
||||
"friend" . $k,
|
||||
0
|
||||
);
|
||||
if ($user1 == $uid || $uid == $post['myid']) {
|
||||
if ($user1 == $uid || $uid == $myid) {
|
||||
$exist = 1;
|
||||
}
|
||||
}
|
||||
@@ -905,18 +875,18 @@ class Message
|
||||
$database->addFriend(
|
||||
$uid,
|
||||
"friend" . $l . "wait",
|
||||
$post['myid']
|
||||
$myid
|
||||
);
|
||||
$added1 = 1;
|
||||
}
|
||||
}
|
||||
$database->addFriend(
|
||||
$post['myid'],
|
||||
$myid,
|
||||
"friend" . $j,
|
||||
$uid
|
||||
);
|
||||
$database->addFriend(
|
||||
$post['myid'],
|
||||
$myid,
|
||||
"friend" . $j . "wait",
|
||||
$uid
|
||||
);
|
||||
|
||||
+11
-2
@@ -432,10 +432,19 @@ class Profile {
|
||||
private function removeSitter($get) {
|
||||
global $database, $session;
|
||||
|
||||
// UM-W1: validam type in {1,2} inainte de a-l folosi in numele coloanei
|
||||
// (sit1/sit2). DB escapeaza campul, deci nu era injectabil, dar un type
|
||||
// invalid ar fi tintit o coloana inexistenta.
|
||||
$type = isset($get['type']) ? (int)$get['type'] : 0;
|
||||
if ($type !== 1 && $type !== 2) {
|
||||
header("Location: spieler.php?s=" . $get['s']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($get['a'] == $session->checker) {
|
||||
|
||||
if ($session->userinfo['sit' . $get['type']] == $get['id']) {
|
||||
$database->updateUserField($session->uid, "sit" . $get['type'], 0, 1);
|
||||
if ($session->userinfo['sit' . $type] == $get['id']) {
|
||||
$database->updateUserField($session->uid, "sit" . $type, 0, 1);
|
||||
// Invalidate the 30s session user-cache (see Session::PopulateVar) so the
|
||||
// removed sitter disappears immediately, without a re-login.
|
||||
unset($_SESSION['cache_user_' . ($_SESSION['username'] ?? '')]);
|
||||
|
||||
+102
-55
@@ -399,40 +399,16 @@ class Units {
|
||||
$rivalsGreatConfusion = $database->getArtifactsSumByKind($to_owner, $data['to_vid'], 7);
|
||||
|
||||
$rallyPointLevel = ($village->resarray)['f39'];
|
||||
$invalidBuildings = [];
|
||||
$invalidBuildings = $this->catapultInvalidBuildings($rallyPointLevel);
|
||||
|
||||
// fill the array with the invalid buildings
|
||||
if($rallyPointLevel >= 3 && $rallyPointLevel < 5){
|
||||
for($i = 1; $i <= 50; $i++){
|
||||
if(!in_array($i, [10, 11])) $invalidBuildings[] = $i;
|
||||
}
|
||||
}
|
||||
else if($rallyPointLevel >= 5 && $rallyPointLevel < 10){
|
||||
for($i = 12; $i <= 50; $i++) $invalidBuildings[] = $i;
|
||||
}
|
||||
else if($rallyPointLevel >= 10){
|
||||
// zidurile nu pot fi tinta directa (31,32,33 + cele noi 42,43,47,50)
|
||||
$invalidBuildings = [23, 31, 32, 33, 34, 36, 42, 43, 47, 50];
|
||||
}
|
||||
|
||||
if(isset($post['ctar1']) && $post['ctar1'] != 0){
|
||||
// check if the player has selected a valid building
|
||||
if($rallyPointLevel < 3 || $data['u8'] == 0 || in_array($post['ctar1'], $invalidBuildings) || $post['ctar1'] < 0 || $post['ctar1'] > 50){
|
||||
$post['ctar1'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($post['ctar2']) && $post['ctar2'] != 0){
|
||||
// check if there are atleast 20 catapults
|
||||
if($data['u8'] < 20 || $rallyPointLevel != 20){
|
||||
$post['ctar2'] = 0;
|
||||
}else{
|
||||
// check if the player has selected a valid building
|
||||
if(in_array($post['ctar2'], $invalidBuildings) || ($post['ctar2'] < 0 || $post['ctar2'] > 50 && $post['ctar2'] != 99)){
|
||||
$post['ctar2'] = 99;
|
||||
}
|
||||
}
|
||||
}
|
||||
// UM-W1: validarea per-tinta extrasa in catapultTargetAllowed(); comportament
|
||||
// identic cu vechile blocuri inline (aceleasi praguri, aceleasi fallback-uri).
|
||||
$post['ctar1'] = $this->normalizeCatapultTarget(
|
||||
$post['ctar1'] ?? 0, 1, $data, $rallyPointLevel, $invalidBuildings
|
||||
);
|
||||
$post['ctar2'] = $this->normalizeCatapultTarget(
|
||||
$post['ctar2'] ?? 0, 2, $data, $rallyPointLevel, $invalidBuildings
|
||||
);
|
||||
|
||||
// Bug fix: Brewery (35) is Teuton-only, capital-only, but empire-wide —
|
||||
// the catapult-randomization side effect must be checked on the SENDER'S
|
||||
@@ -447,31 +423,96 @@ class Units {
|
||||
&& $database->getFieldLevelInVillage($senderCapital['wref'], 35) > 0;
|
||||
}
|
||||
|
||||
if(isset($post['ctar1'])) {
|
||||
//Is the Mead-Festival active?
|
||||
if(!$hasActiveBrewery){
|
||||
if($rivalsGreatConfusion['totals'] > 0) {
|
||||
if($post['ctar1'] != 40 && ($post['ctar1'] != 27 || ($post['ctar1'] == 27 && $rivalsGreatConfusion['unique'] > 0))) {
|
||||
$post['ctar1'] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else $post['ctar1'] = 0;
|
||||
}
|
||||
else $post['ctar1'] = 0;
|
||||
$post['ctar1'] = $this->applyConfusion(
|
||||
$post['ctar1'] ?? 0, 1, $hasActiveBrewery, $rivalsGreatConfusion
|
||||
);
|
||||
$post['ctar2'] = $this->applyConfusion(
|
||||
$post['ctar2'] ?? 0, 2, $hasActiveBrewery, $rivalsGreatConfusion
|
||||
);
|
||||
}
|
||||
|
||||
if(isset($post['ctar2']) && $post['ctar2'] > 0) {
|
||||
//Is the Mead-Festival active?
|
||||
if(!$hasActiveBrewery){
|
||||
if($rivalsGreatConfusion['totals'] > 0) {
|
||||
if ($post['ctar2'] != 40 && ($post['ctar2'] != 27 || ($post['ctar2'] == 27 && $rivalsGreatConfusion['unique'] > 0))) {
|
||||
$post['ctar2'] = 99;
|
||||
}
|
||||
/**
|
||||
* UM-W1: lista cladirilor care nu pot fi tinta directa de catapulta, in
|
||||
* functie de nivelul Punctului de Adunare. Extrasa 1:1 din vechiul switch.
|
||||
*/
|
||||
private function catapultInvalidBuildings($rallyPointLevel) {
|
||||
if ($rallyPointLevel >= 3 && $rallyPointLevel < 5) {
|
||||
$invalid = [];
|
||||
for ($i = 1; $i <= 50; $i++) {
|
||||
if (!in_array($i, [10, 11])) $invalid[] = $i;
|
||||
}
|
||||
return $invalid;
|
||||
}
|
||||
|
||||
if ($rallyPointLevel >= 5 && $rallyPointLevel < 10) {
|
||||
$invalid = [];
|
||||
for ($i = 12; $i <= 50; $i++) $invalid[] = $i;
|
||||
return $invalid;
|
||||
}
|
||||
|
||||
if ($rallyPointLevel >= 10) {
|
||||
// zidurile nu pot fi tinta directa (31,32,33 + cele noi 42,43,47,50)
|
||||
return [23, 31, 32, 33, 34, 36, 42, 43, 47, 50];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* UM-W1: validarea de baza a unei tinte de catapulta (ctar1 sau ctar2),
|
||||
* inainte de efectul de confuzie. $slot = 1 sau 2. Pastreaza exact regulile
|
||||
* vechi: ctar1 cere RP>=3, minim 1 catapulta si tinta valida; ctar2 cere
|
||||
* minim 20 catapulte, RP == 20 si cade pe 99 (random) cand tinta e invalida.
|
||||
*/
|
||||
private function normalizeCatapultTarget($target, $slot, $data, $rallyPointLevel, array $invalidBuildings) {
|
||||
if ($slot == 1) {
|
||||
if ($target != 0) {
|
||||
if ($rallyPointLevel < 3 || $data['u8'] == 0 || in_array($target, $invalidBuildings) || $target < 0 || $target > 50) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else $post['ctar2'] = 99;
|
||||
return $target;
|
||||
}
|
||||
else $post['ctar2'] = 0;
|
||||
|
||||
// slot 2
|
||||
if ($target != 0) {
|
||||
if ($data['u8'] < 20 || $rallyPointLevel != 20) {
|
||||
return 0;
|
||||
}
|
||||
if (in_array($target, $invalidBuildings) || ($target < 0 || $target > 50 && $target != 99)) {
|
||||
return 99;
|
||||
}
|
||||
}
|
||||
return $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* UM-W1: efectul artefactului "Rivals' Great Confusion" / festivalul de bere.
|
||||
* ctar1 -> 0 (random), ctar2 -> 99 (random) cand tinta nu e WW(40) sau
|
||||
* Trezorerie(27, exceptata doar de artefactul unic). Comportament 1:1.
|
||||
*/
|
||||
private function applyConfusion($target, $slot, $hasActiveBrewery, $rivalsGreatConfusion) {
|
||||
$random = ($slot == 1) ? 0 : 99;
|
||||
|
||||
if ($slot == 2 && $target <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($hasActiveBrewery) {
|
||||
return $random;
|
||||
}
|
||||
|
||||
// Conditie pastrata IDENTIC cu originalul (verificata prin test de
|
||||
// echivalenta). Nota: ramura "$target == 27 && unique > 0" nu schimba
|
||||
// rezultatul - orice tinta != 40 ajunge random - dar o pastram 1:1 ca
|
||||
// sa nu introducem o schimbare de comportament subtila.
|
||||
if ($rivalsGreatConfusion['totals'] > 0) {
|
||||
if ($target != 40 && ($target != 27 || ($target == 27 && $rivalsGreatConfusion['unique'] > 0))) {
|
||||
return $random;
|
||||
}
|
||||
}
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
private function sendTroopsBack($post) {
|
||||
@@ -575,10 +616,16 @@ class Units {
|
||||
|
||||
public function Settlers($post) {
|
||||
global $form, $database, $village, $session;
|
||||
// FIX securitate (UM-D1): tablourile de CP (cp0..cpN) sunt globale, definite
|
||||
// in GameEngine/Data/cp.php. Fara aceste "global", ${'cp'.$mode} era null in
|
||||
// scope-ul metodei, deci $need_cps = null si "$cps >= $need_cps" trecea mereu -
|
||||
// verificarea de puncte de cultura la intemeierea satului era ocolita
|
||||
// server-side (doar UI-ul o respecta). Importam exact tabloul folosit.
|
||||
global ${'cp'.CP};
|
||||
|
||||
$mode = CP;
|
||||
$total = count($database->getProfileVillages($session->uid));
|
||||
$need_cps = ${'cp'.$mode}[$total + 1];
|
||||
$need_cps = isset(${'cp'.$mode}[$total + 1]) ? ${'cp'.$mode}[$total + 1] : PHP_INT_MAX;
|
||||
$cps = $session->cp;
|
||||
$rallypoint = $database->getResourceLevel($village->wid);
|
||||
|
||||
|
||||
@@ -27,8 +27,12 @@ Massive Code Refactor
|
||||
|
||||
* Fully refactored Templates folder/system.
|
||||
* Added 🇫🇷 French and 🇷🇴 Romanian languages.
|
||||
* Large portions of GameEngine fully refactored.
|
||||
* Remaining work planned for parts of Automation, Database, Units and Technology.
|
||||
* GameEngine fully refactored, file by file, with randomized equivalence testing (20k-30k cases per critical function).
|
||||
* Database.php (~450 methods) split into 14 domain traits under GameEngine/Database/ - zero call-site changes, validated via reflection parity.
|
||||
* Automation.php (~116 methods) split into 11 domain traits under GameEngine/Automation/ using the same method.
|
||||
* Village.php: production getters unified, one UPDATE per resource tick instead of two, side-effect-free constructor with explicit tick(), automation lock race (TOCTOU) fixed.
|
||||
* Building.php: build requirements rewritten as a declarative table, per-village instance caches, dead code removed (2,085 -> 1,829 lines).
|
||||
* Units.php, Market.php, Message.php, Profile.php and Chat.php refactored and hardened.
|
||||
* starvation() function in Automation refactored and split into multiple methods.
|
||||
* sendUnitsComplete() function fully refactored, reducing a 1711-line function into 26 separate methods.
|
||||
* checkAllianceEmbassiesStatus() in Database redesigned and split into multiple functions.
|
||||
@@ -37,6 +41,32 @@ Massive Code Refactor
|
||||
|
||||
⸻
|
||||
|
||||
New Playable Tribes
|
||||
|
||||
Four fully playable tribes added on top of the original three, across the data layer, game engine and templates:
|
||||
|
||||
* Huns (tribe 6, units u51-u60) - Command Center (gid 44) replaces Residence/Palace, Makeshift Wall.
|
||||
* Egyptians (tribe 7, units u61-u70) - Waterworks, Stone Wall.
|
||||
* Spartans (tribe 8, units u71-u80) - Defensive Wall.
|
||||
* Vikings (tribe 9, units u81-u90) - Barricade.
|
||||
* New buildings integrated across gids 42-50 (incl. the Hospital family and Great Workshop) with per-tribe build requirements.
|
||||
* Unit-range formula (tribe-1)*10+1; Great Building training queue offset moved to +500 to avoid unit-ID conflicts.
|
||||
* Per-tribe Academy templates, troops.tpl unit cap raised 50 -> 90, statistics routing and CSS, profile/ranking/map tribe arrays, admin dropdowns, install feature flags.
|
||||
* Three-step registration wizard: visual tribe cards -> starting quadrant -> confirmation.
|
||||
|
||||
⸻
|
||||
|
||||
T4 Hero System
|
||||
|
||||
Travian 4-style hero system ported into the T3.6 engine, in phases:
|
||||
|
||||
* Items backend (HeroItems.php) with equippable hero items and data layer.
|
||||
* Adventures backend (HeroAdventure.php) with adventure generation and rewards.
|
||||
* Auction house (HeroAuction.php) for player-to-player item trading.
|
||||
* Battle and speed integration (HeroBattleBonus.php + Battle.php hooks): item fighting strength, per-unit weapon bonuses, armor damage reduction, hunting horn and speed effects, artwork culture-point item.
|
||||
|
||||
⸻
|
||||
|
||||
Admin Panel
|
||||
|
||||
* Complete frontend and backend redesign.
|
||||
@@ -85,6 +115,8 @@ Gameplay Improvements
|
||||
* Reports
|
||||
* Auto-completions
|
||||
* Other player settings
|
||||
* Server Milestones system (first village, first artifact, WW progress...) with ranking widget and SVG badges.
|
||||
* Mead-Festival mechanic for the Brewery (Teuton-only, 72-hour timer) with correct catapult-confusion integration.
|
||||
* New Special Medal System:
|
||||
* Artifact Owner
|
||||
* WW Owner
|
||||
@@ -108,6 +140,11 @@ Bug Fixes
|
||||
* Various game mechanics
|
||||
* Winner registration issues fixed.
|
||||
* Punish troop deletion fixed.
|
||||
* Huns can now build the Stonemason's Lodge (via Command Center >= 3) - engine and templates.
|
||||
* Master Builder gold finish now always deducts resources (was free with <= 2 queued jobs) and validates the building type instead of the slot number.
|
||||
* finishAll() no longer recomputes culture points from stale cached data (wrong CP was being persisted).
|
||||
* isCastleBuilt() strict type check - a WW at level 26 or a matching village ref no longer falsely blocks Palace construction.
|
||||
* Undefined-variable warnings on PHP 8 fixed (chat refresh wrote to the error log on every request).
|
||||
|
||||
⸻
|
||||
|
||||
@@ -118,6 +155,10 @@ Security Improvements
|
||||
* New CSRF protection system implemented.
|
||||
* Added protection against race conditions.
|
||||
* Improved validation across critical systems.
|
||||
* Server-side culture-point check on village founding was silently bypassed - fixed (client-side was the only enforcement).
|
||||
* IDOR in friend-list operations fixed: client-supplied user ID replaced with the session user.
|
||||
* Negative-amount guard on marketplace resource sending.
|
||||
* Repo-wide audit (342 PHP files, ~66k lines): systemic cache-invalidation gaps closed via a central invalidateCachesFor(), reflected XSS fixes (warsim.php, Preferences), hardened .htaccess, error_reporting bitmask and MD5-fallback guards.
|
||||
|
||||
⸻
|
||||
|
||||
@@ -158,18 +199,19 @@ Completed
|
||||
* Templates system.
|
||||
* Admin Panel.
|
||||
* Frontend and backend redesign.
|
||||
* Major parts of GameEngine.
|
||||
* Automation functions.
|
||||
* Database alliance systems.
|
||||
* GameEngine (full sweep - all files audited, refactored where needed).
|
||||
* Automation functions + split into 11 domain traits.
|
||||
* Database split into 14 domain traits, with request-level caching and central invalidation.
|
||||
* Units, Technology, Village, Building, Market, Message, Profile and Chat refactors.
|
||||
* Medal system.
|
||||
* Logging system.
|
||||
|
||||
Still Planned
|
||||
|
||||
* Remaining Automation cleanup.
|
||||
* Database optimization.
|
||||
* Units system refactor.
|
||||
* Technology system refactor.
|
||||
* Automation moved to a real cron job (currently triggered by page loads).
|
||||
* Database cleanup with configurable retention (battle reports, chat, stale rows).
|
||||
* SQL index audit for hot columns.
|
||||
* Static asset compression and browser caching (.htaccess).
|
||||
|
||||
⸻
|
||||
|
||||
|
||||
@@ -65,6 +65,9 @@ $greatworkshop = $building->getTypeLevel(49);
|
||||
$stonewall = $building->getTypeLevel(42);
|
||||
$makeshiftwall = $building->getTypeLevel(43);
|
||||
$commandcenter = $building->getTypeLevel(44);
|
||||
// FIX (confirmat): Hunii au Command Center in loc de Palace - echivalentul
|
||||
// folosit de cerinta Stonemason (Palace >= 3, respectiv CC >= 3 la tribul 6)
|
||||
$palaceEquiv = ($session->tribe == 6 ? $commandcenter : $palace);
|
||||
$waterworks = $building->getTypeLevel(45);
|
||||
$hospital = $building->getTypeLevel(46);
|
||||
$defensivewall = $building->getTypeLevel(47);
|
||||
@@ -219,7 +222,7 @@ if($palace == 0 && $palace1 == 0 && !$building->isCastleBuilt() && $village->nat
|
||||
if($blacksmith == 0 && $blacksmith1 == 0 && $academy >= 3 && $mainbuilding >= 3 && $id != 39 && $id != 40) {
|
||||
include("avaliable/blacksmith.tpl");
|
||||
}
|
||||
if($stonemasonslodge == 0 && $stonemasonslodge1 == 0 && $palace >= 3 && $mainbuilding >= 5 && $id != 39 && $id != 40) {
|
||||
if($stonemasonslodge == 0 && $stonemasonslodge1 == 0 && $palaceEquiv >= 3 && $mainbuilding >= 5 && $id != 39 && $id != 40) {
|
||||
include("avaliable/stonemason.tpl");
|
||||
}
|
||||
if($stable == 0 && $stable1 == 0 && $blacksmith >= 3 && $academy >= 5 && $id != 39 && $id != 40) {
|
||||
@@ -321,7 +324,7 @@ if($embassy == 0 || $mainbuilding >= 2 && $mainbuilding <= 4 && !$building->isCa
|
||||
if($blacksmith == 0 && ($academy <= 2 || $mainbuilding <= 2)) {
|
||||
include("soon/blacksmith.tpl");
|
||||
}
|
||||
if($stonemasonslodge == 0 && $palace <= 2 && $palace != 0 && $mainbuilding >= 2 && $mainbuilding <= 4 && $residence == 0 && $village->capital == 1) {
|
||||
if($stonemasonslodge == 0 && $palaceEquiv <= 2 && $palaceEquiv != 0 && $mainbuilding >= 2 && $mainbuilding <= 4 && $residence == 0 && $village->capital == 1) {
|
||||
include("soon/stonemason.tpl");
|
||||
}
|
||||
if($stable == 0 && (($blacksmith <= 2 && $blacksmith != 0) || ($academy >= 2 && $academy <= 4))) {
|
||||
@@ -395,7 +398,7 @@ if($palace == 0 && ($embassy == 0 || $mainbuilding <= 2) && $village->natar == 0
|
||||
if($blacksmith == 0 && ($academy == 0 || $mainbuilding == 1)) {
|
||||
include_once("soon/blacksmith.tpl");
|
||||
}
|
||||
if($stonemasonslodge == 0 && ($palace == 0 || $mainbuilding <= 2) && $residence == 0) {
|
||||
if($stonemasonslodge == 0 && ($palaceEquiv == 0 || $mainbuilding <= 2) && $residence == 0) {
|
||||
include_once("soon/stonemason.tpl");
|
||||
}
|
||||
if($stable == 0 && ($blacksmith == 0 || $academy <= 2)) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#" onClick="return Popup(15,4);"><?php echo MAINBUILDING ?></a> <span title="+4"><?php echo LEVEL ?> 5</span>, <a href="#" onClick="return Popup(26,4);"><?php echo PALACE ?></a> <span title="+3"><?php echo LEVEL ?> 3</span>
|
||||
<a href="#" onClick="return Popup(15,4);"><?php echo MAINBUILDING ?></a> <span title="+4"><?php echo LEVEL ?> 5</span>, <?php if($session->tribe == 6) { /* FIX (confirmat): Hunii au Command Center in loc de Palace */ ?><a href="#" onClick="return Popup(44,4);"><?php echo COMMANDCENTER ?></a><?php } else { ?><a href="#" onClick="return Popup(26,4);"><?php echo PALACE ?></a><?php } ?> <span title="+3"><?php echo LEVEL ?> 3</span>
|
||||
</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
Reference in New Issue
Block a user