diff --git a/Admin/admin.php b/Admin/admin.php index f7ce709b..718fd565 100644 --- a/Admin/admin.php +++ b/Admin/admin.php @@ -1,20 +1,23 @@ getUserArray($village['owner'], 1); $subpage = ADMIN_EDIT_RESOURCES . ' (' . e($village['name']) . ' » ' . e($user['username']) . ')'; } else { - // BUGFIX: original used $did which was only set in 'village' case, - // causing an undefined variable notice here. Now always defined above. $subpage = ADMIN_EDIT_RESOURCES . $did . ' not found)'; $village = null; } @@ -568,7 +560,6 @@ if ($page !== '') { } break; - // ── Alliance-context pages (require a valid ?aid=) ─────────────────── case 'alliance': $aid = admin_input_id($_GET, 'aid'); if ($aid !== null) { diff --git a/GameEngine/Automation/AutomationBattleResolution.php b/GameEngine/Automation/AutomationBattleResolution.php index 28711bd5..eff4c54e 100644 --- a/GameEngine/Automation/AutomationBattleResolution.php +++ b/GameEngine/Automation/AutomationBattleResolution.php @@ -1227,7 +1227,13 @@ trait AutomationBattleResolution { */ private function applyOwnDefenceCasualties($data, $targettribe, $battlepart) { global $database; - + + $targettribe = (int)$targettribe; + // FIX: daca satul a fost sters / owner 0 / oaza fara trib, nu avem ce sterge + if($targettribe < 1 || $targettribe > 9) { + return []; + } + $owndead = []; $unitlist = $database->getUnit($data['to'], false); $start = ($targettribe - 1) * 10 + 1; @@ -1319,7 +1325,7 @@ trait AutomationBattleResolution { } else { $tribe = 4; } - + if($tribe < 1 || $tribe > 9) $tribe = 4; // fallback la natura $start = ($tribe - 1) * 10 + 1; $end = ($tribe * 10); unset($dead); diff --git a/GameEngine/Database.php b/GameEngine/Database.php index cfce26ef..c7a7614e 100755 --- a/GameEngine/Database.php +++ b/GameEngine/Database.php @@ -60,6 +60,8 @@ include_once __DIR__ . '/Database/DatabaseHeroQueries.php'; include_once __DIR__ . '/Database/DatabaseStatisticsQueries.php'; include_once __DIR__ . '/Database/DatabaseArtefactQueries.php'; include_once __DIR__ . '/Database/DatabaseSystemQueries.php'; +// Chat general (server-wide), cerut de Catalin 07.09.2026 - separat de alliance chat (chat.php) +include_once __DIR__ . '/Database/DatabaseGlobalChatQueries.php'; use App\Database\IDbConnection; use App\Utils\Math; @@ -80,6 +82,7 @@ class MYSQLi_DB implements IDbConnection { use DatabaseStatisticsQueries; use DatabaseArtefactQueries; use DatabaseSystemQueries; + use DatabaseGlobalChatQueries; diff --git a/GameEngine/Database/DatabaseGlobalChatQueries.php b/GameEngine/Database/DatabaseGlobalChatQueries.php new file mode 100644 index 00000000..12a536fb --- /dev/null +++ b/GameEngine/Database/DatabaseGlobalChatQueries.php @@ -0,0 +1,176 @@ +bool, 'reason'=>string|null, 'mutedUntil'=>int|null] + */ + function postGlobalChatMessage($uid, $msg) { + $uid = (int) $uid; + $msg = trim((string) $msg); + + if ($uid <= 0) { + return ['ok' => false, 'reason' => 'notloggedin']; + } + + if ($msg === '') { + return ['ok' => false, 'reason' => 'empty']; + } + + // acelasi plafon ca la chat-ul de alianta existent (chat.msg e varchar(255); + // 250 lasa loc de siguranta pentru escaping/multi-byte) + $msg = function_exists('mb_substr') ? mb_substr($msg, 0, 250) : substr($msg, 0, 250); + + $mutedUntil = $this->getGlobalChatMuteStatus($uid); + if ($mutedUntil !== null) { + return ['ok' => false, 'reason' => 'muted', 'mutedUntil' => $mutedUntil]; + } + + list($euid) = $this->escape_input($uid); + + $lastRow = mysqli_fetch_assoc($this->query( + "SELECT date FROM " . TB_PREFIX . "chat_global WHERE id_user = $euid ORDER BY id DESC LIMIT 1" + )); + + $now = time(); + // rate-limit: minim 3 secunde intre 2 mesaje ale aceluiasi user + if ($lastRow && ($now - (int) $lastRow['date']) < 3) { + return ['ok' => false, 'reason' => 'ratelimit']; + } + + list($emsg) = $this->escape_input($msg); + + $this->query( + "INSERT INTO " . TB_PREFIX . "chat_global (id_user, date, msg) VALUES ($euid, $now, '$emsg')" + ); + + return ['ok' => true]; + } + + /** + * Mesajele din chat-ul general, gata de afisat (ordine cronologica). + * + * Mod 1 - incarcare initiala: ultimele $limit mesaje ($sinceId = 0). + * Mod 2 - poll incremental: doar mesajele noi, mai mari decat $sinceId + * (evita re-trimiterea ferestrei intregi la fiecare interogare din JS). + * + * Tag-ul de alianta si nivelul de access sunt rezolvate live, printr-un + * singur JOIN (nu o interogare per mesaj) - reflecta mereu alianta + * curenta a jucatorului, nu una "inghetata" la momentul postarii. + */ + function getGlobalChatMessages($limit = 30, $sinceId = 0) { + $limit = (int) $limit; + $sinceId = (int) $sinceId; + + $where = $sinceId > 0 ? "WHERE c.id > $sinceId" : ""; + $order = $sinceId > 0 ? "ORDER BY c.id ASC" : "ORDER BY c.id DESC"; + // plafon si pe polling-ul incremental (nu doar la incarcarea initiala) - + // daca un tab a stat minimizat/in background ore intregi (throttling de + // browser pe mobil), la revenire nu vrem un SELECT nemarginit + $limitSql = "LIMIT " . ($sinceId > 0 ? 200 : $limit); + + $q = "SELECT c.id, c.id_user, c.date, c.msg, u.username, u.access, a.tag AS ally_tag, a.id AS ally_id + FROM " . TB_PREFIX . "chat_global c + LEFT JOIN " . TB_PREFIX . "users u ON u.id = c.id_user + LEFT JOIN " . TB_PREFIX . "alidata a ON a.id = u.alliance AND u.alliance > 0 + $where + $order + $limitSql"; + + $rows = $this->mysqli_fetch_all($this->query($q)); + + // la incarcarea initiala am luat descrescator (ca sa prindem exact + // ultimele $limit), le intoarcem in ordine cronologica normala + if ($sinceId <= 0) { + $rows = array_reverse($rows); + } + + return $rows; + } + + /** + * Timestamp-ul (unix) pana la care userul e mutat din chat-ul general, + * sau null daca nu e mutat (inclusiv daca o mutare veche a expirat). + */ + function getGlobalChatMuteStatus($uid) { + $uid = (int) $uid; + + $row = mysqli_fetch_assoc($this->query( + "SELECT muted_until FROM " . TB_PREFIX . "chat_mutes WHERE id_user = $uid" + )); + + if (!$row) { + return null; + } + + $until = (int) $row['muted_until']; + return $until > time() ? $until : null; + } + + /** + * Info despre viewer-ul curent, folosita de client ca sa stie daca + * afiseaza controalele de moderare si daca dezactiveaza inputul (mutat). + */ + function getGlobalChatViewerInfo($uid) { + $uid = (int) $uid; + + $access = (int) $this->getUserField($uid, 'access', 0); + + return [ + 'uid' => $uid, + 'access' => $access, + 'isMod' => $access >= MULTIHUNTER, + 'mutedUntil' => $this->getGlobalChatMuteStatus($uid), + ]; + } + + /** + * Muteaza (temporar sau permanent, dupa $untilTimestamp) un user din + * chat-ul general. Ierarhia de rang (cine pe cine poate muta) se + * verifica in ajax.php inainte de a apela asta - metoda doar scrie + * starea, ca sa ramana usor de testat separat. + */ + function muteGlobalChatUser($targetUid, $mutedBy, $untilTimestamp, $reason = '') { + $targetUid = (int) $targetUid; + $mutedBy = (int) $mutedBy; + $untilTimestamp = (int) $untilTimestamp; + + list($ereason) = $this->escape_input((string) $reason); + $now = time(); + + $q = "INSERT INTO " . TB_PREFIX . "chat_mutes (id_user, muted_until, muted_by, reason, created) + VALUES ($targetUid, $untilTimestamp, $mutedBy, '$ereason', $now) + ON DUPLICATE KEY UPDATE muted_until = $untilTimestamp, muted_by = $mutedBy, reason = '$ereason', created = $now"; + + return $this->query($q) ? true : false; + } + + function unmuteGlobalChatUser($targetUid) { + $targetUid = (int) $targetUid; + + return $this->query( + "DELETE FROM " . TB_PREFIX . "chat_mutes WHERE id_user = $targetUid" + ) ? true : false; + } +} diff --git a/GameEngine/Lang/en.php b/GameEngine/Lang/en.php index edbde9f4..5b57607a 100755 --- a/GameEngine/Lang/en.php +++ b/GameEngine/Lang/en.php @@ -4404,3 +4404,22 @@ tz_def('TRAIN_BONUS_ARTIFACT', 'Artifact bonus'); tz_def('TRAIN_BONUS_HERO', 'Hero bonus'); tz_def('TRAIN_BONUS_ALLIANCE', 'Alliance bonus'); tz_def('TRAIN_BONUS_FINAL', 'Training time'); + +////////////////////////////////////////////////////////////////////////////////////////////////////// +// GENERAL (SERVER-WIDE) CHAT - floating widget, footer.tpl + Templates/GlobalChat/widget.tpl +////////////////////////////////////////////////////////////////////////////////////////////////////// +tz_def('GCHAT_TITLE', 'World Chat'); +tz_def('GCHAT_PLACEHOLDER', 'Message...'); +tz_def('GCHAT_SEND', 'Send'); +tz_def('GCHAT_EMPTY', 'No messages yet.'); +tz_def('GCHAT_MUTED', 'You are muted from chat.'); +tz_def('GCHAT_RATELIMIT', 'You are sending messages too fast.'); +tz_def('GCHAT_CONFIRM_BLOCK', 'Block this player from chat permanently?'); +tz_def('GCHAT_MUTE_5M', 'Mute 5m'); +tz_def('GCHAT_MUTE_30M', 'Mute 30m'); +tz_def('GCHAT_MUTE_1H', 'Mute 1h'); +tz_def('GCHAT_MUTE_24H', 'Mute 24h'); +tz_def('GCHAT_BLOCK', 'Block'); +tz_def('GCHAT_UNMUTE', 'Unmute'); +tz_def('GCHAT_ADMIN_BADGE', 'Admin'); +tz_def('GCHAT_MH_BADGE', 'MH'); diff --git a/GameEngine/Lang/ro.php b/GameEngine/Lang/ro.php index 96c7f9f1..db5ab125 100644 --- a/GameEngine/Lang/ro.php +++ b/GameEngine/Lang/ro.php @@ -4141,4 +4141,22 @@ tz_def('SITTER_P_NOT_SITTING', 'Nu esti sitter pe niciun cont.'); tz_def('TRAIN_BONUS_ARTIFACT', 'Bonus artefact'); tz_def('TRAIN_BONUS_HERO', 'Bonus erou'); tz_def('TRAIN_BONUS_ALLIANCE', 'Bonus alianta'); -tz_def('TRAIN_BONUS_FINAL', 'Timp instruire'); \ No newline at end of file +tz_def('TRAIN_BONUS_FINAL', 'Timp instruire'); +////////////////////////////////////////////////////////////////////////////////////////////////////// +// CHAT GENERAL (server-wide) - widget plutitor, footer.tpl + Templates/GlobalChat/widget.tpl +////////////////////////////////////////////////////////////////////////////////////////////////////// +tz_def('GCHAT_TITLE', 'Chat general'); +tz_def('GCHAT_PLACEHOLDER', 'Mesaj...'); +tz_def('GCHAT_SEND', 'Trimite'); +tz_def('GCHAT_EMPTY', 'Niciun mesaj inca.'); +tz_def('GCHAT_MUTED', 'Esti mutat din chat.'); +tz_def('GCHAT_RATELIMIT', 'Trimiti mesaje prea repede.'); +tz_def('GCHAT_CONFIRM_BLOCK', 'Blochezi permanent acest jucator din chat?'); +tz_def('GCHAT_MUTE_5M', 'Mute 5m'); +tz_def('GCHAT_MUTE_30M', 'Mute 30m'); +tz_def('GCHAT_MUTE_1H', 'Mute 1h'); +tz_def('GCHAT_MUTE_24H', 'Mute 24h'); +tz_def('GCHAT_BLOCK', 'Blocheaza'); +tz_def('GCHAT_UNMUTE', 'Anuleaza mute'); +tz_def('GCHAT_ADMIN_BADGE', 'Admin'); +tz_def('GCHAT_MH_BADGE', 'MH'); diff --git a/Templates/GlobalChat/widget.tpl b/Templates/GlobalChat/widget.tpl new file mode 100644 index 00000000..d7d67f27 --- /dev/null +++ b/Templates/GlobalChat/widget.tpl @@ -0,0 +1,459 @@ + +
+ + + +
+ + + + diff --git a/Templates/footer.tpl b/Templates/footer.tpl index 3f39c576..0dfae6ab 100644 --- a/Templates/footer.tpl +++ b/Templates/footer.tpl @@ -91,4 +91,17 @@ $serverVersion = 'v.11 Full Refactor';
- \ No newline at end of file + + +uid) && (!isset($session->access) || $session->access != BANNED)) { + include __DIR__ . '/GlobalChat/widget.tpl'; +} +?> \ No newline at end of file diff --git a/ajax.php b/ajax.php index 09108bad..53eca02a 100644 --- a/ajax.php +++ b/ajax.php @@ -107,5 +107,86 @@ switch(isset($_GET['f']) ? $_GET['f'] : '') { $ok = $database->setMovementMarker($_POST['moveid'] ?? 0, $_POST['marker'] ?? 0, $uid); echo json_encode(['ok' => $ok ? 1 : 0]); break; + + // Chat general (server-wide), cerut de Catalin 07.09.2026. Separat de + // alliance chat (Chat.php, SAJAX) - vazut/scris de orice user logat, + // moderat de MH (access 8) si Admin (access 9). + case 'gchat_poll': + header('Content-Type: application/json'); + if (!isset($_SESSION)) { + session_start(); + } + include_once($autoprefix.'GameEngine/Database.php'); + $uid = (int) ($_SESSION['id_user'] ?? 0); + if (!$uid) { + http_response_code(403); + echo json_encode(['ok' => 0, 'reason' => 'notloggedin']); + break; + } + $sinceId = (int) ($_GET['sinceId'] ?? 0); + echo json_encode([ + 'ok' => 1, + 'messages' => $database->getGlobalChatMessages(30, $sinceId), + 'viewer' => $database->getGlobalChatViewerInfo($uid), + ]); + break; + + case 'gchat_send': + header('Content-Type: application/json'); + if (!isset($_SESSION)) { + session_start(); + } + include_once($autoprefix.'GameEngine/Database.php'); + $uid = (int) ($_SESSION['id_user'] ?? 0); + if (!$uid) { + http_response_code(403); + echo json_encode(['ok' => 0, 'reason' => 'notloggedin']); + break; + } + echo json_encode($database->postGlobalChatMessage($uid, $_POST['msg'] ?? '')); + break; + + case 'gchat_mute': + case 'gchat_block': + case 'gchat_unmute': + header('Content-Type: application/json'); + if (!isset($_SESSION)) { + session_start(); + } + include_once($autoprefix.'GameEngine/Database.php'); + $modUid = (int) ($_SESSION['id_user'] ?? 0); + $targetUid = (int) ($_POST['target'] ?? 0); + + if (!$modUid || !$targetUid) { + http_response_code(403); + echo json_encode(['ok' => 0, 'reason' => 'notloggedin']); + break; + } + + $modAccess = (int) $database->getUserField($modUid, 'access', 0); + $targetAccess = (int) $database->getUserField($targetUid, 'access', 0); + + // doar MH (8) si Admin (9) modereaza chat-ul general, si nimeni nu + // poate modera pe cineva de rang egal sau mai mare - deci MH nu + // poate muta alt MH/Admin, iar Admin nu poate muta alt Admin + if ($modAccess < MULTIHUNTER || $targetAccess >= $modAccess) { + http_response_code(403); + echo json_encode(['ok' => 0, 'reason' => 'forbidden']); + break; + } + + if ($_GET['f'] == 'gchat_unmute') { + $ok = $database->unmuteGlobalChatUser($targetUid); + } else { + $reason = substr((string) ($_POST['reason'] ?? ''), 0, 255); + // 'gchat_block' = permanent (~100 de ani); 'gchat_mute' = temporar, in minute (POST['minutes']) + $until = ($_GET['f'] == 'gchat_block') + ? (time() + 3600 * 24 * 365 * 100) + : (time() + max(1, (int) ($_POST['minutes'] ?? 0)) * 60); + $ok = $database->muteGlobalChatUser($targetUid, $modUid, $until, $reason); + } + + echo json_encode(['ok' => $ok ? 1 : 0]); + break; } ?> diff --git a/var/db/struct.sql b/var/db/struct.sql index 158e5535..77bf3334 100644 --- a/var/db/struct.sql +++ b/var/db/struct.sql @@ -575,6 +575,41 @@ CREATE TABLE IF NOT EXISTS `%PREFIX%chat` ( -- Dumping data for table `%prefix%chat` -- +-- -------------------------------------------------------- + +-- +-- Chat general (server-wide), cerut de Catalin 07.09.2026 - separat de +-- chat-ul de alianta de mai sus (acela e filtrat pe `alli`, asta e vazut +-- de toti jucatorii de pe server, indiferent de alianta) +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%chat_global` ( + `id` int(20) NOT NULL AUTO_INCREMENT, + `id_user` int(11) NOT NULL, + `date` int(11) NOT NULL, + `msg` varchar(250) NOT NULL, + PRIMARY KEY (`id`), + KEY `id_user_date` (`id_user`,`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Stare activa de mute/block pe chat-ul general - un singur rand per user +-- (id_user e PRIMARY KEY, deci un nou mute suprascrie direct pe cel vechi +-- via ON DUPLICATE KEY UPDATE, fara sa acumuleze istoric). +-- muted_until in viitorul indepartat (~100 ani) = block permanent. +-- + +CREATE TABLE IF NOT EXISTS `%PREFIX%chat_mutes` ( + `id_user` int(11) NOT NULL, + `muted_until` int(11) NOT NULL, + `muted_by` int(11) NOT NULL, + `reason` varchar(255) NULL, + `created` int(11) NOT NULL, + PRIMARY KEY (`id_user`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + -- --------------------------------------------------------