mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-07-24 05:26:19 +00:00
Add Cron Jobs for Automation
Add Cron Jobs for Automation
This commit is contained in:
@@ -95,6 +95,95 @@ $editIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke=
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- CRON / AUTOMATION -->
|
||||
<?php
|
||||
// Starea reala a cron-ului, citita din markerul scris de cron.php la fiecare tick.
|
||||
// Pragul de 90s e acelasi cu cel din GameEngine/Village.php: sub el, paginile
|
||||
// jucatorilor NU mai declanseaza automation (il face cron-ul); peste el, paginile
|
||||
// preiau automat ca fallback.
|
||||
$cronMarkerFile = __DIR__ . '/../../GameEngine/Prevention/cron_active.txt';
|
||||
$cronLastRun = @is_file($cronMarkerFile) ? (int) @file_get_contents($cronMarkerFile) : 0;
|
||||
$cronAge = $cronLastRun > 0 ? (time() - $cronLastRun) : -1;
|
||||
$cronActive = ($cronAge >= 0 && $cronAge < 90);
|
||||
|
||||
$cronLoop = defined('CRON_LOOP_SECONDS') ? (int) CRON_LOOP_SECONDS : 300;
|
||||
$cronTick = defined('CRON_TICK_SECONDS') ? (int) CRON_TICK_SECONDS : 60;
|
||||
$cronKey = defined('CRON_KEY') ? (string) CRON_KEY : '';
|
||||
|
||||
$cronBase = defined('HOMEPAGE') ? rtrim(HOMEPAGE, '/') : '';
|
||||
$cronUrl = ($cronBase !== '' && $cronKey !== '') ? $cronBase . '/cron.php?key=' . $cronKey : '';
|
||||
|
||||
$cronPath = realpath(__DIR__ . '/../../cron.php');
|
||||
$cronCmd = $cronPath ? '*/5 * * * * /usr/local/bin/php ' . $cronPath . ' >/dev/null 2>&1' : '';
|
||||
|
||||
// cheia afisata mascat; se dezvaluie la cerere (evita expunerea in screenshot-uri)
|
||||
$cronKeyMasked = ($cronKey === '')
|
||||
? ''
|
||||
: (strlen($cronKey) > 12 ? substr($cronKey, 0, 6) . str_repeat('•', 8) . substr($cronKey, -4) : str_repeat('•', 8));
|
||||
?>
|
||||
<div class="config-card">
|
||||
<div class="config-head"><span>Cron & Automation</span><a href="admin.php?p=editCronSet" title="Edit Cron & Automation" class="edit-btn"><?php echo $editIcon; ?></a></div>
|
||||
<table class="config-table">
|
||||
<tr><td class="b"><?php echo SERV_VARIABLE ?></td><td class="b"><?php echo SERV_VALUE ?></td></tr>
|
||||
|
||||
<tr>
|
||||
<td>Automation source <em class="tooltip">?<span class="classic">Where the game tick (battles, movements, training, construction) is processed from. When the cron job is running, players' page loads no longer carry the processing.</span></em></td>
|
||||
<td><?php echo $cronActive
|
||||
? "<span class='badge green'>Cron job</span>"
|
||||
: "<span class='badge red'>Page loads (fallback)</span>"; ?></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Last cron tick <em class="tooltip">?<span class="classic">Written by cron.php on every tick into GameEngine/Prevention/cron_active.txt. Under 90 seconds means the cron is alive.</span></em></td>
|
||||
<td><?php
|
||||
if ($cronLastRun > 0) {
|
||||
echo date('d.m.Y H:i:s', $cronLastRun) . ' <span style="color:#777">(' . $cronAge . 's ago)</span>';
|
||||
} else {
|
||||
echo "<span class='badge red'>Never</span>";
|
||||
}
|
||||
?></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Invocation length <em class="tooltip">?<span class="classic">CRON_LOOP_SECONDS - how long one cron.php invocation keeps running. Hosts that only allow a cron every 5 minutes still get a tick every minute this way. 0 = a single tick per invocation.</span></em></td>
|
||||
<td><?php echo $cronLoop > 0
|
||||
? $cronLoop . ' s <span style="color:#777">(' . (int) floor($cronLoop / max($cronTick, 1)) . ' ticks per invocation)</span>'
|
||||
: "<span class='badge blue'>Single tick</span>"; ?></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Tick interval <em class="tooltip">?<span class="classic">CRON_TICK_SECONDS - how often Automation runs inside one invocation. Automation itself expects to run about every 60 seconds.</span></em></td>
|
||||
<td><?php echo $cronTick; ?> s</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>HTTP trigger key <em class="tooltip">?<span class="classic">CRON_KEY - only needed to call cron.php over HTTP (external cron service). Running it from the server's own cron job does not use this key. Generated automatically at install.</span></em></td>
|
||||
<td><?php if ($cronKey === '') { ?>
|
||||
<span class='badge red'>Not set</span>
|
||||
<?php } else { ?>
|
||||
<span id="cronKeyMask"><?php echo $cronKeyMasked; ?></span>
|
||||
<span id="cronKeyFull" style="display:none;word-break:break-all"><?php echo htmlspecialchars($cronKey, ENT_QUOTES, 'UTF-8'); ?></span>
|
||||
<a href="#" style="margin-left:6px" onclick="var m=document.getElementById('cronKeyMask'),f=document.getElementById('cronKeyFull'),u=document.getElementById('cronUrlFull');var hidden=(f.style.display==='none');m.style.display=hidden?'none':'';f.style.display=hidden?'':'none';if(u){u.style.display=hidden?'':'none';}this.textContent=hidden?'hide':'show';return false;">show</a>
|
||||
<?php } ?></td>
|
||||
</tr>
|
||||
|
||||
<?php if ($cronUrl !== '') { ?>
|
||||
<tr>
|
||||
<td>HTTP trigger URL <em class="tooltip">?<span class="classic">Optional. Use this with an external cron service if the host cannot run a local cron job. One HTTP call = one tick.</span></em></td>
|
||||
<td><span style="color:#777">hidden – use "show" above</span>
|
||||
<span id="cronUrlFull" style="display:none;word-break:break-all"><?php echo htmlspecialchars($cronUrl, ENT_QUOTES, 'UTF-8'); ?></span></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
|
||||
<?php if ($cronCmd !== '') { ?>
|
||||
<tr>
|
||||
<td>Cron job command <em class="tooltip">?<span class="classic">Add this in cPanel → Cron Jobs. The path is detected from this installation.</span></em></td>
|
||||
<td style="word-break:break-all"><code><?php echo htmlspecialchars($cronCmd, ENT_QUOTES, 'UTF-8'); ?></code></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- NEW FUNCTIONS -->
|
||||
<div class="config-card">
|
||||
<div class="config-head"><span>New Mechanics and Functions</span><a href="admin.php?p=editNewFunctions" title="Edit New Mechanics and Functions" class="edit-btn"><?php echo $editIcon; ?></a></div>
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : editCronSet.tpl ##
|
||||
## Type : Admin Panel Frontend ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Contact : cata7007@gmail.com ##
|
||||
## Project : TravianZ ##
|
||||
## GitHub : https://github.com/Shadowss/TravianZ ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
#################################################################################
|
||||
if (!isset($_SESSION)) {
|
||||
session_start();
|
||||
}
|
||||
if ($_SESSION['access'] < 9) die(ACCESS_DENIED_ADMIN);
|
||||
|
||||
$cronMarkerFile = __DIR__ . '/../../GameEngine/Prevention/cron_active.txt';
|
||||
$cronLastRun = @is_file($cronMarkerFile) ? (int) @file_get_contents($cronMarkerFile) : 0;
|
||||
$cronAge = $cronLastRun > 0 ? (time() - $cronLastRun) : -1;
|
||||
$cronActive = ($cronAge >= 0 && $cronAge < 90);
|
||||
|
||||
$cronLoop = defined('CRON_LOOP_SECONDS') ? (int) CRON_LOOP_SECONDS : 300;
|
||||
$cronTick = defined('CRON_TICK_SECONDS') ? (int) CRON_TICK_SECONDS : 60;
|
||||
$cronKey = defined('CRON_KEY') ? (string) CRON_KEY : '';
|
||||
|
||||
$cronPath = realpath(__DIR__ . '/../../cron.php');
|
||||
$cronCmd = $cronPath ? '*/5 * * * * /usr/local/bin/php ' . $cronPath . ' >> ' . dirname(dirname($cronPath)) . '/cron.log 2>&1' : '';
|
||||
?>
|
||||
<style>
|
||||
.config-wrap{max-width:1100px;margin:0 auto;font-family:system-ui,-apple-system,Segoe UI,Roboto}
|
||||
.config-title{text-align:center;font-size:20px;font-weight:800;margin:8px 0 12px;color:#ffffff}
|
||||
.config-card{background:#fff;border:1px solid #e5e7eb;border-radius:8px;margin-bottom:10px;overflow:hidden;box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
||||
.config-head{display:flex;justify-content:space-between;align-items:center;padding:7px 10px;background:#0f172a;color:#fff;font-weight:600;font-size:13px;line-height:1}
|
||||
.config-table{width:100%;border-collapse:collapse}
|
||||
.config-table tr{border-top:1px solid #f1f5f9}
|
||||
.config-table tr:first-child{border-top:0}
|
||||
.config-table td{padding:6px 8px;vertical-align:middle;font-size:13px;line-height:1.3}
|
||||
.config-table td.b{background:#f8fafc;font-weight:700;color:#334155;width:45%}
|
||||
.config-table td:last-child{color:#0f172a}
|
||||
.fm{border:1px solid #cbd5e1;border-radius:5px;padding:5px 7px;font-size:13px}
|
||||
.hint{display:block;color:#64748b;font-size:11px;margin-top:3px;font-weight:400}
|
||||
.badge{display:inline-block;padding:2px 7px;border-radius:10px;font-size:11px;font-weight:700}
|
||||
.badge.green{background:#dcfce7;color:#166534}
|
||||
.badge.red{background:#fee2e2;color:#991b1b}
|
||||
.badge.blue{background:#dbeafe;color:#1e40af}
|
||||
.tooltip{position:relative;cursor:help;color:#64748b;border:1px solid #cbd5e1;border-radius:50%;padding:0 5px;font-size:10px;font-style:normal}
|
||||
.tooltip span{display:none;position:absolute;left:20px;top:-4px;width:280px;background:#0f172a;color:#fff;padding:7px 9px;border-radius:6px;font-size:11px;z-index:9;line-height:1.4}
|
||||
.tooltip:hover span{display:block}
|
||||
.config-actions{display:flex;justify-content:space-between;align-items:center;margin-top:12px}
|
||||
.btn-back,.btn-save{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:6px;font-weight:600;font-size:13px;text-decoration:none;cursor:pointer;transition:.15s;border:1px solid transparent}
|
||||
.btn-back{background:#f1f5f9;color:#0f172a;border-color:#e5e7eb}
|
||||
.btn-back:hover{background:#e2e8f0}
|
||||
.btn-save{background:#0f172a;color:#fff;box-shadow:0 1px 2px rgba(0,0,0,.08)}
|
||||
.btn-save:hover{background:#1e293b;transform:translateY(-1px)}
|
||||
.btn-save svg{width:14px;height:14px}
|
||||
code{background:#f1f5f9;padding:2px 5px;border-radius:4px;font-size:11px;word-break:break-all}
|
||||
</style>
|
||||
|
||||
<div class="config-wrap">
|
||||
<div class="config-title">Cron & Automation</div>
|
||||
|
||||
<form action="../GameEngine/Admin/Mods/editCronSet.php" method="POST">
|
||||
<?php echo csrf_field(); ?>
|
||||
<input type="hidden" name="id" id="id" value="<?php echo $_SESSION['id']; ?>">
|
||||
|
||||
<div class="config-card">
|
||||
<div class="config-head"><span>Current status</span></div>
|
||||
<table class="config-table">
|
||||
<tr>
|
||||
<td class="b">Automation source</td>
|
||||
<td><?php echo $cronActive
|
||||
? "<span class='badge green'>Cron job</span>"
|
||||
: "<span class='badge red'>Page loads (fallback)</span>"; ?>
|
||||
<span class="hint">When the cron job is not running, the game still works: players' page loads process the tick, exactly as before the cron was introduced.</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="b">Last cron tick</td>
|
||||
<td><?php
|
||||
if ($cronLastRun > 0) {
|
||||
echo date('d.m.Y H:i:s', $cronLastRun) . ' <span style="color:#777">(' . $cronAge . 's ago)</span>';
|
||||
} else {
|
||||
echo "<span class='badge red'>Never</span>";
|
||||
}
|
||||
?></td>
|
||||
</tr>
|
||||
<?php if ($cronCmd !== '') { ?>
|
||||
<tr>
|
||||
<td class="b">Cron job command</td>
|
||||
<td><code><?php echo htmlspecialchars($cronCmd, ENT_QUOTES, 'UTF-8'); ?></code>
|
||||
<span class="hint">Add this in cPanel → Cron Jobs. Writing to cron.log instead of /dev/null lets you see errors if it fails to start.</span></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="config-card">
|
||||
<div class="config-head"><span>Settings</span></div>
|
||||
<table class="config-table">
|
||||
<tr>
|
||||
<td class="b">Invocation length (seconds)
|
||||
<em class="tooltip">?<span>How long one cron.php invocation keeps working. Most shared hosts only allow a cron every 5 minutes, while Automation expects to run about every minute — so one invocation runs several ticks in a row. Use 300 for a "*/5" cron. Set 0 only if your host allows a cron every minute.</span></em>
|
||||
</td>
|
||||
<td><input class="fm" type="number" name="cron_loop" min="0" max="3300" value="<?php echo $cronLoop; ?>" style="width:120px">
|
||||
<span class="hint">0 = a single tick per invocation. Maximum 3300 (55 minutes).</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="b">Tick interval (seconds)
|
||||
<em class="tooltip">?<span>How often Automation runs inside one invocation. Automation's own guard expects roughly 60 seconds; going lower mostly adds load without processing anything new.</span></em>
|
||||
</td>
|
||||
<td><input class="fm" type="number" name="cron_tick" min="15" max="900" value="<?php echo $cronTick; ?>" style="width:120px">
|
||||
<span class="hint">Recommended: 60. Allowed range 15–900.</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="b">HTTP trigger key
|
||||
<em class="tooltip">?<span>Only needed when calling cron.php over HTTP (for example from an external cron service). The server's own cron job does not use it. Regenerating invalidates any URL you configured elsewhere.</span></em>
|
||||
</td>
|
||||
<td>
|
||||
<?php if ($cronKey === '') { ?>
|
||||
<span class="badge red">Not set</span> – one will be generated on save.
|
||||
<?php } else { ?>
|
||||
<span id="k1"><?php echo htmlspecialchars(substr($cronKey, 0, 6), ENT_QUOTES, 'UTF-8'); ?>••••••••<?php echo htmlspecialchars(substr($cronKey, -4), ENT_QUOTES, 'UTF-8'); ?></span>
|
||||
<span id="k2" style="display:none;word-break:break-all"><?php echo htmlspecialchars($cronKey, ENT_QUOTES, 'UTF-8'); ?></span>
|
||||
<a href="#" style="margin-left:6px" onclick="var a=document.getElementById('k1'),b=document.getElementById('k2');var h=(b.style.display==='none');a.style.display=h?'none':'';b.style.display=h?'':'none';this.textContent=h?'hide':'show';return false;">show</a>
|
||||
<?php } ?>
|
||||
<span class="hint"><label><input type="checkbox" name="regenerate_key" value="1"> Generate a new key on save</label></span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="config-actions">
|
||||
<a href="../Admin/admin.php?p=config" class="btn-back">‹ <?php echo EDIT_BACK ?></a>
|
||||
<button type="submit" class="btn-save">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||
<polyline points="17 21 17 13 7 13 7 21"/>
|
||||
</svg>
|
||||
SAVE
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -94,6 +94,7 @@ function admin_validated_page(string $raw): string
|
||||
'maintenenceResetPlus', 'givePlusRes', 'maintenenceResetPlusBonus',
|
||||
'addUsers', 'users', 'admin_log', 'config', 'debug_log',
|
||||
'editServerSet', 'editPlusSet', 'editLogSet', 'editNewsboxSet',
|
||||
'editCronSet',
|
||||
'editExtraSet', 'editAdminInfo', 'resetServer', 'player', 'editUser',
|
||||
'deletion', 'Newmessage', 'editPlus', 'editSitter', 'editPassword',
|
||||
'editProtection', 'editOverall',
|
||||
@@ -302,6 +303,10 @@ if ($page !== '') {
|
||||
$subpage = 'Server Configuration';
|
||||
break;
|
||||
|
||||
case 'editCronSet':
|
||||
$subpage = 'Cron & Automation';
|
||||
break;
|
||||
|
||||
case 'editPlusSet':
|
||||
$subpage = 'PLUS Settings';
|
||||
break;
|
||||
|
||||
@@ -39,9 +39,52 @@ if (!function_exists('admin_config_template_path')) {
|
||||
return admin_config_template_path() !== false;
|
||||
}
|
||||
|
||||
// Raw template contents (placeholders untouched), or false when unavailable.
|
||||
// Raw template contents, or false when unavailable.
|
||||
//
|
||||
// Placeholders are left untouched for the caller to fill in, with ONE
|
||||
// exception: %CRONKEY%. Every edit*.php mod regenerates config.php from this
|
||||
// template and replaces only the placeholders it knows about, so a value that
|
||||
// no mod handles would be written to config.php literally — breaking the key
|
||||
// used to call cron.php over HTTP. CRON_KEY is not something the admin edits
|
||||
// in any of those forms, so it is resolved here, once, for all callers:
|
||||
// - existing server -> keep the current CRON_KEY (survives config saves)
|
||||
// - key not defined -> provision a fresh random one (servers installed
|
||||
// before CRON_KEY existed in the template)
|
||||
function admin_config_template_contents() {
|
||||
$path = admin_config_template_path();
|
||||
return $path !== false ? file_get_contents($path) : false;
|
||||
|
||||
if ($path === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$text = file_get_contents($path);
|
||||
|
||||
if ($text === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strpos($text, '%CRONKEY%') !== false) {
|
||||
$cronKey = (defined('CRON_KEY') && CRON_KEY !== '' && CRON_KEY !== '%CRONKEY%')
|
||||
? CRON_KEY
|
||||
: bin2hex(random_bytes(24));
|
||||
|
||||
$text = str_replace('%CRONKEY%', $cronKey, $text);
|
||||
}
|
||||
|
||||
// Acelasi tratament pentru durata ciclului si intervalul de tick: sunt
|
||||
// editate din ACP (editCronSet), deci orice alt mod care regenereaza
|
||||
// config.php trebuie sa PASTREZE valorile curente, nu sa le reseteze la
|
||||
// cele din template.
|
||||
if (strpos($text, '%CRONLOOP%') !== false) {
|
||||
$cronLoop = defined('CRON_LOOP_SECONDS') ? (int) CRON_LOOP_SECONDS : 300;
|
||||
$text = str_replace('%CRONLOOP%', (string) $cronLoop, $text);
|
||||
}
|
||||
|
||||
if (strpos($text, '%CRONTICK%') !== false) {
|
||||
$cronTick = defined('CRON_TICK_SECONDS') ? (int) CRON_TICK_SECONDS : 60;
|
||||
$text = str_replace('%CRONTICK%', (string) $cronTick, $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename editCronSet.php ##
|
||||
## Type Admin Panel - Cron & Automation settings ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Contact cata7007@gmail.com ##
|
||||
## Project TravianZ ##
|
||||
## GitHub https://github.com/Shadowss/TravianZ ##
|
||||
## License TravianZ Project ##
|
||||
## Copyright TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## NOTA DE PROIECTARE - de ce acest mod NU regenereaza tot config.php: ##
|
||||
## Celelalte module edit*.php reconstruiesc config.php din constant_format ##
|
||||
## .tpl si trebuie sa reumple TOATE placeholderele (peste 100). Aici avem ##
|
||||
## de schimbat trei valori, iar o gresala intr-o mapare fara legatura ar ##
|
||||
## strica setari de joc. De aceea modificam DOAR liniile de cron din ##
|
||||
## config.php existent; restul fisierului ramane bit-cu-bit neatins. ##
|
||||
## Daca liniile lipsesc (server instalat inainte de aceasta versiune), ele ##
|
||||
## sunt inserate dupa AUTOMATION_LOCK_FILE_NAME. ##
|
||||
#################################################################################
|
||||
|
||||
if (!isset($_SESSION)) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if ($_SESSION['access'] < 9) {
|
||||
die(ACCESS_DENIED_ADMIN);
|
||||
}
|
||||
|
||||
// Acest mod primeste POST direct, deci isi verifica singur tokenul CSRF
|
||||
// (nu trece prin csrf_verify()-ul central din admin.php). Vezi issue #139.
|
||||
require_once(__DIR__ . '/../csrf.php');
|
||||
csrf_verify();
|
||||
|
||||
include_once("../../Database.php");
|
||||
|
||||
$id = (int) $_POST['id'];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validarea valorilor primite
|
||||
// ---------------------------------------------------------------------------
|
||||
$loop = isset($_POST['cron_loop']) ? (int) $_POST['cron_loop'] : 300;
|
||||
$tick = isset($_POST['cron_tick']) ? (int) $_POST['cron_tick'] : 60;
|
||||
|
||||
// 0 = o singura rulare per invocare (host care permite cron la fiecare minut).
|
||||
// Peste 0, limitam la 55 de minute ca sa nu ramana procese agatate.
|
||||
if ($loop < 0) { $loop = 0; }
|
||||
if ($loop > 3300) { $loop = 3300; }
|
||||
|
||||
// Automation se asteapta sa ruleze pe la 60s; sub 15s doar incarcam serverul.
|
||||
if ($tick < 15) { $tick = 15; }
|
||||
if ($tick > 900) { $tick = 900; }
|
||||
|
||||
// Un ciclu care nu incape nici macar un tick nu are sens - il tratam ca "o
|
||||
// singura rulare", ca sa nu iasa o configuratie care pare activa dar nu e.
|
||||
if ($loop > 0 && $loop < $tick) {
|
||||
$loop = 0;
|
||||
}
|
||||
|
||||
$regenerateKey = !empty($_POST['regenerate_key']);
|
||||
|
||||
$cronKey = (defined('CRON_KEY') && CRON_KEY !== '' && CRON_KEY !== '%CRONKEY%')
|
||||
? CRON_KEY
|
||||
: '';
|
||||
|
||||
if ($regenerateKey || $cronKey === '') {
|
||||
$cronKey = bin2hex(random_bytes(24));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Editarea chirurgicala a fisierului de configuratie
|
||||
// ---------------------------------------------------------------------------
|
||||
$configFile = "../../config.php";
|
||||
$config = @file_get_contents($configFile);
|
||||
|
||||
if ($config === false) {
|
||||
die("<br/><br/><br/>Can't read file: GameEngine\\config.php");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inlocuieste valoarea unei constante daca linia exista; altfel intoarce false
|
||||
* ca apelantul sa stie ca trebuie inserata.
|
||||
*
|
||||
* $rawValue se scrie exact asa cum e dat (deja citat pentru siruri).
|
||||
*/
|
||||
function cron_set_define(&$text, $constant, $rawValue)
|
||||
{
|
||||
$pattern = "/define\s*\(\s*(['\"])" . preg_quote($constant, '/') . "\\1\s*,.*?\)\s*;/s";
|
||||
|
||||
if (!preg_match($pattern, $text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// str_replace pe potrivirea gasita, ca sa nu interpretam $ sau \ din valoare
|
||||
// ca referinte de grup (capcana clasica a lui preg_replace).
|
||||
preg_match($pattern, $text, $m);
|
||||
$text = str_replace($m[0], "define('" . $constant . "', " . $rawValue . ");", $text);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$missing = array();
|
||||
|
||||
if (!cron_set_define($config, 'CRON_LOOP_SECONDS', (string) $loop)) {
|
||||
$missing[] = "define('CRON_LOOP_SECONDS', " . $loop . ");";
|
||||
}
|
||||
|
||||
if (!cron_set_define($config, 'CRON_TICK_SECONDS', (string) $tick)) {
|
||||
$missing[] = "define('CRON_TICK_SECONDS', " . $tick . ");";
|
||||
}
|
||||
|
||||
if (!cron_set_define($config, 'CRON_KEY', "'" . addcslashes($cronKey, "'\\") . "'")) {
|
||||
$missing[] = "define('CRON_KEY', '" . addcslashes($cronKey, "'\\") . "');";
|
||||
}
|
||||
|
||||
// Server instalat inainte ca aceste constante sa existe: le adaugam dupa
|
||||
// AUTOMATION_LOCK_FILE_NAME (locul lor din template).
|
||||
if (!empty($missing)) {
|
||||
$anchorPattern = "/define\s*\(\s*(['\"])AUTOMATION_LOCK_FILE_NAME\\1\s*,.*?\)\s*;/s";
|
||||
|
||||
$block = "\n\n//////////////////////////////////\n"
|
||||
. "// ***** CRON / AUTOMATION *****//\n"
|
||||
. "//////////////////////////////////\n"
|
||||
. implode("\n", $missing);
|
||||
|
||||
if (preg_match($anchorPattern, $config, $anchor)) {
|
||||
$config = str_replace($anchor[0], $anchor[0] . $block, $config);
|
||||
} else {
|
||||
// fara ancora: adaugam la finalul fisierului, inaintea eventualului
|
||||
// tag de inchidere PHP (atentie: nu-l scriem literal in comentariu,
|
||||
// fiindca intr-un comentariu "//" ar inchide chiar blocul PHP curent)
|
||||
$config = preg_replace('/\?>\s*$/', '', $config) . $block . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$fh = @fopen($configFile, 'w') or die("<br/><br/><br/>Can't open file: GameEngine\\config.php");
|
||||
fwrite($fh, $config);
|
||||
fclose($fh);
|
||||
|
||||
$database->query(
|
||||
"Insert into " . TB_PREFIX . "admin_log values (0," . $id . ",'Changed Cron & Automation Settings'," . time() . ")"
|
||||
);
|
||||
|
||||
header("Location: ../../../Admin/admin.php?p=config");
|
||||
+102
-84
@@ -18,12 +18,12 @@
|
||||
## License : TravianZ Project ##
|
||||
## Copyright : TravianZ (c) 2010-2026. All rights reserved. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Refactor D1 : clamp productie in memorie = clamp SQL; 1 UPDATE/hit; ##
|
||||
## eliminat wid=wref; flock pe lock-ul de automation ##
|
||||
## Refactor W1 : getProdFor() unic (fara cele 4 metode copy-paste); ##
|
||||
## scan f1..f38 o singura data; globals trase in constructor ##
|
||||
## Refactor W2 : constructor fara side effects; tick() + ActionControl() ##
|
||||
## explicite in bootstrap; garduri statice eliminate ##
|
||||
## Refactor D1 : in-memory production clamp = SQL clamp; 1 UPDATE/hit; ##
|
||||
## removed wid=wref; flock on the automation lock ##
|
||||
## Refactor W1 : single getProdFor() (no 4 copy-paste methods); ##
|
||||
## scan f1..f38 only once; globals pulled in constructor ##
|
||||
## Refactor W2 : constructor without side effects; explicit tick() + ##
|
||||
## ActionControl() in bootstrap; static guards removed ##
|
||||
#################################################################################
|
||||
|
||||
include_once("Session.php");
|
||||
@@ -52,20 +52,20 @@ class Village {
|
||||
private $oasisowned, $ocounter = [];
|
||||
private $oasisFactor = 0.25;
|
||||
|
||||
// W1 #6: dependinte trase o singura data din globals, in constructor
|
||||
// W1 #6: dependencies pulled from globals once, in the constructor
|
||||
private $db;
|
||||
private $sess;
|
||||
private $tech;
|
||||
private $bids = [];
|
||||
|
||||
// W1 #4: index-ul campurilor f1..f38, construit lazy, o data per incarcare
|
||||
// W1 #4: index of the f1..f38 fields, built lazily, once per load
|
||||
private $fieldIndex = null;
|
||||
|
||||
/**
|
||||
* W1 #4: configuratia per resursa pentru getProdFor().
|
||||
* field = tipul campului de productie (si nr. tabelei $bidN de productie),
|
||||
* bonus = gid-urile cladirilor bonus (Sawmill/Brickyard/Foundry/Grain Mill+Bakery),
|
||||
* oasis = indexul in $this->ocounter, sessionBonus = flag-ul de bonus 25%.
|
||||
* W1 #4: per-resource configuration for getProdFor().
|
||||
* field = the production field type (and the number of the $bidN production table),
|
||||
* bonus = the gids of the bonus buildings (Sawmill/Brickyard/Foundry/Grain Mill+Bakery),
|
||||
* oasis = the index into $this->ocounter, sessionBonus = the 25% bonus flag.
|
||||
*/
|
||||
private const PROD_CONFIG = [
|
||||
'wood' => ['field' => 1, 'bonus' => [5], 'oasis' => 0, 'sessionBonus' => 'bonus1'],
|
||||
@@ -75,11 +75,11 @@ class Village {
|
||||
];
|
||||
|
||||
/**
|
||||
* W2 #7: constructorul DOAR incarca. Fara scrieri in DB, fara redirect,
|
||||
* fara tick de productie. "new Village" e sigur de apelat oriunde
|
||||
* (instante multiple, admin switch). Gardurile statice loadedWid /
|
||||
* cacheCleared au fost eliminate (W2 #10) - nu mai sunt necesare cu
|
||||
* side effects explicite.
|
||||
* W2 #7: the constructor ONLY loads. No DB writes, no redirect,
|
||||
* no production tick. "new Village" is safe to call anywhere
|
||||
* (multiple instances, admin switch). The static loadedWid /
|
||||
* cacheCleared guards were removed (W2 #10) - they are no longer
|
||||
* needed now that side effects are explicit.
|
||||
*/
|
||||
function __construct() {
|
||||
global $database, $session, $technology,
|
||||
@@ -91,7 +91,7 @@ class Village {
|
||||
$this->bids = [
|
||||
1 => $bid1, 2 => $bid2, 3 => $bid3, 4 => $bid4,
|
||||
5 => $bid5, 6 => $bid6, 7 => $bid7, 8 => $bid8, 9 => $bid9,
|
||||
45 => $bid45, // Waterworks (Egipteni) - folosit de getOasisBonusFactor()
|
||||
45 => $bid45, // Waterworks (Egyptians) - used by getOasisBonusFactor()
|
||||
];
|
||||
|
||||
// set village id
|
||||
@@ -113,15 +113,15 @@ class Village {
|
||||
}
|
||||
|
||||
/**
|
||||
* W2 #8: tick explicit - calculeaza si aplica productia. Apelat din
|
||||
* bootstrap-ul de la finalul fisierului (nu din dorf1/dorf2 individual:
|
||||
* 28 de pagini includ Village.php si toate au nevoie de resurse la zi).
|
||||
* W2 #8: explicit tick - computes and applies production. Called from the
|
||||
* bootstrap at the end of this file (not from dorf1/dorf2 individually:
|
||||
* 28 pages include Village.php and all of them need up-to-date resources).
|
||||
*/
|
||||
public function tick(): void {
|
||||
// W2 #10: clear-ul de cache era gardat de static::$cacheCleared ca sa
|
||||
// ruleze o data per request; cu tick explicit (apelat o data, din
|
||||
// bootstrap) gardul nu mai e necesar. Un clear repetat la o a doua
|
||||
// instanta inseamna doar cache rece, nu un bug de corectitudine.
|
||||
// W2 #10: the cache clear used to be guarded by static::$cacheCleared so it
|
||||
// would run once per request; with an explicit tick (called once, from the
|
||||
// bootstrap) that guard is no longer needed. A repeated clear on a second
|
||||
// instance only means a cold cache, not a correctness bug.
|
||||
$db = get_class($this->db);
|
||||
$db::clearVillageCache();
|
||||
|
||||
@@ -150,8 +150,8 @@ class Village {
|
||||
|
||||
/**
|
||||
* LOAD VILLAGE DATA (single source of truth)
|
||||
* W2: parametrul mort $second_run eliminat (nu era pasat de nicaieri;
|
||||
* getResearching primea mereu use_cache = true).
|
||||
* W2: the dead $second_run parameter was removed (it was not passed from
|
||||
* anywhere; getResearching always received use_cache = true).
|
||||
*/
|
||||
private function LoadTown(): void {
|
||||
global $logging;
|
||||
@@ -187,9 +187,10 @@ class Village {
|
||||
$this->natar = $this->infoarray['natar'];
|
||||
$this->currentcel = $this->infoarray['celebration'];
|
||||
$this->currentfestival = $this->infoarray['festival'];
|
||||
// FIX #2 (Day 1): eliminat "$this->wid = $this->infoarray['wref']" - suprascria
|
||||
// id-ul satului la mijlocul incarcarii; in flux normal era no-op (wref == wid),
|
||||
// dar masca orice rand gresit venit din cache. wid ramane sursa de adevar.
|
||||
// FIX #2 (Day 1): removed "$this->wid = $this->infoarray['wref']" - it overwrote
|
||||
// the village id in the middle of the load; in the normal flow it was a no-op
|
||||
// (wref == wid), but it masked any wrong row coming from the cache. wid stays
|
||||
// the source of truth.
|
||||
$this->vname = $this->infoarray['name'];
|
||||
|
||||
$this->awood = $this->infoarray['wood'];
|
||||
@@ -208,15 +209,15 @@ class Village {
|
||||
|
||||
$this->master = count($this->db->getMasterJobs($this->wid));
|
||||
|
||||
// FIX #1a (Day 1): overflow fix doar in memorie - fara updateResource() aici.
|
||||
// Clamp-ul SQL din modifyResource() (IF > maxstore -> maxstore) corecteaza
|
||||
// oricum DB-ul in acelasi request, in processProduction(). Scapa un UPDATE.
|
||||
// FIX #1a (Day 1): overflow fix in memory only - no updateResource() here.
|
||||
// The SQL clamp in modifyResource() (IF > maxstore -> maxstore) corrects the
|
||||
// DB in the same request anyway, in processProduction(). Saves one UPDATE.
|
||||
if ($this->awood > $this->maxstore) $this->awood = $this->maxstore;
|
||||
if ($this->aclay > $this->maxstore) $this->aclay = $this->maxstore;
|
||||
if ($this->airon > $this->maxstore) $this->airon = $this->maxstore;
|
||||
if ($this->acrop > $this->maxcrop) $this->acrop = $this->maxcrop;
|
||||
// NOTA (pre-existent, nemodificat): $this->atotal e calculat mai sus din
|
||||
// valorile ne-clamp-uite si inainte de tick-ul de productie.
|
||||
// NOTE (pre-existing, unchanged): $this->atotal is computed above from the
|
||||
// un-clamped values and before the production tick.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +240,7 @@ class Village {
|
||||
* APPLY PRODUCTION (NO MORE LOADTOWN RELOAD)
|
||||
*/
|
||||
private function processProduction(): void {
|
||||
// hardening: nu lasa un lastupdate din viitor (clock skew) sa produca delta negativ
|
||||
// hardening: do not let a lastupdate from the future (clock skew) produce a negative delta
|
||||
$timepast = max(0, time() - $this->infoarray['lastupdate']);
|
||||
|
||||
$nwood = min(($this->production['wood'] / 3600) * $timepast, $this->maxstore);
|
||||
@@ -247,34 +248,34 @@ class Village {
|
||||
$niron = min(($this->production['iron'] / 3600) * $timepast, $this->maxstore);
|
||||
$ncrop = min(($this->production['crop'] / 3600) * $timepast, $this->maxcrop);
|
||||
|
||||
// FIX #1b (Day 1): un singur UPDATE - lastupdate e setat in aceeasi interogare
|
||||
// cu resursele (param $setLastupdate in modifyResource); updateVillage() eliminat.
|
||||
// FIX #1b (Day 1): a single UPDATE - lastupdate is set in the same query as the
|
||||
// resources (the $setLastupdate param in modifyResource); updateVillage() removed.
|
||||
$this->db->modifyResource($this->wid, $nwood, $nclay, $niron, $ncrop, 1, true);
|
||||
|
||||
// lightweight sync ONLY (no DB reload)
|
||||
$this->infoarray['lastupdate'] = time();
|
||||
|
||||
// FIX #0 (Day 1): sincronizarea in memorie oglindeste exact clamp-ul SQL
|
||||
// din modifyResource: podea la 0 (inainte, crop-ul negativ din starvation
|
||||
// putea cobori sub 0 in memorie in timp ce DB-ul ramanea la 0 -> divergenta).
|
||||
// FIX #0 (Day 1): the in-memory sync mirrors exactly the SQL clamp from
|
||||
// modifyResource: floor at 0 (before, negative crop from starvation could
|
||||
// drop below 0 in memory while the DB stayed at 0 -> divergence).
|
||||
$this->awood = min(max($this->awood + $nwood, 0), $this->maxstore);
|
||||
$this->aclay = min(max($this->aclay + $nclay, 0), $this->maxstore);
|
||||
$this->airon = min(max($this->airon + $niron, 0), $this->maxstore);
|
||||
$this->acrop = min(max($this->acrop + $ncrop, 0), $this->maxcrop);
|
||||
}
|
||||
|
||||
// W1 #4: cele 4 metode raman ca wrappere de o linie - zero schimbari de API,
|
||||
// toata logica e in getProdFor().
|
||||
// W1 #4: the 4 methods remain as one-line wrappers - zero API changes,
|
||||
// all the logic lives in getProdFor().
|
||||
private function getWoodProd() { return $this->getProdFor('wood'); }
|
||||
private function getClayProd() { return $this->getProdFor('clay'); }
|
||||
private function getIronProd() { return $this->getProdFor('iron'); }
|
||||
private function getCropProd() { return $this->getProdFor('crop'); }
|
||||
|
||||
/**
|
||||
* W1 #4: productia bruta pentru o resursa. Inlocuieste cele 4 metode
|
||||
* copy-paste (~120 linii). Ordinea operatiilor e pastrata 1:1 cu originalul:
|
||||
* suma campuri -> bonus oaze -> bonus cladire (%) -> bonus cont (x1.25)
|
||||
* -> round(x * SPEED).
|
||||
* W1 #4: gross production for one resource. Replaces the 4 copy-paste
|
||||
* methods (~120 lines). The order of the operations is kept 1:1 with the
|
||||
* original: sum of fields -> oasis bonus -> building bonus (%) ->
|
||||
* account bonus (x1.25) -> round(x * SPEED).
|
||||
*/
|
||||
private function getProdFor(string $type): float {
|
||||
$cfg = self::PROD_CONFIG[$type];
|
||||
@@ -289,10 +290,11 @@ class Village {
|
||||
|
||||
$amount += $amount * $this->oasisFactor * $this->ocounter[$cfg['oasis']];
|
||||
|
||||
// NOTA: garda isset() pe 'attri' exista in original doar la crop; aici e
|
||||
// aplicata peste tot (identic numeric pe date valide, fara warning pe date
|
||||
// corupte). Nivelul cladirii bonus = valoarea ULTIMULUI camp de acel tip,
|
||||
// exact ca in original (in date reale exista cel mult o cladire per tip).
|
||||
// NOTE: the isset() guard on 'attri' existed in the original only for crop;
|
||||
// here it is applied everywhere (numerically identical on valid data, no
|
||||
// warning on corrupted data). The bonus building level = the value of the
|
||||
// LAST field of that type, exactly as in the original (on real data there is
|
||||
// at most one building per type).
|
||||
$bonusPct = 0;
|
||||
foreach ($cfg['bonus'] as $buildingType) {
|
||||
$level = $index['last'][$buildingType] ?? 0;
|
||||
@@ -310,11 +312,11 @@ class Village {
|
||||
}
|
||||
|
||||
/**
|
||||
* W1 #4: scaneaza campurile f1..f38 O SINGURA data per incarcare (in loc de
|
||||
* 4 treceri) si le bucket-uieste dupa tip.
|
||||
* 'levels' => [tip => [nivelele fiecarui camp de acel tip]]
|
||||
* 'last' => [tip => nivelul (raw) al ultimului camp de acel tip] -
|
||||
* semantica identica cu atribuirile suprascrise din original.
|
||||
* W1 #4: scans the f1..f38 fields ONLY once per load (instead of 4 passes)
|
||||
* and buckets them by type.
|
||||
* 'levels' => [type => [the levels of every field of that type]]
|
||||
* 'last' => [type => the (raw) level of the last field of that type] -
|
||||
* identical semantics to the overwritten assignments in the original.
|
||||
*/
|
||||
private function getFieldIndex(): array {
|
||||
if ($this->fieldIndex !== null) {
|
||||
@@ -337,10 +339,10 @@ class Village {
|
||||
* OASIS
|
||||
*/
|
||||
/**
|
||||
* Bonusul de baza al oazei (25%) crescut de Waterworks (gid 45, Egipteni):
|
||||
* attri = +5% relativ per nivel, pana la +100% la nivel 20 (0.25 -> 0.50).
|
||||
* NOTA: ramane intentionat in afara getFieldIndex() - scaneaza f19..f40
|
||||
* (include f39/f40, peste limita de 38 a index-ului).
|
||||
* The base oasis bonus (25%) increased by the Waterworks (gid 45, Egyptians):
|
||||
* attri = +5% relative per level, up to +100% at level 20 (0.25 -> 0.50).
|
||||
* NOTE: intentionally kept outside getFieldIndex() - it scans f19..f40
|
||||
* (includes f39/f40, past the index limit of 38).
|
||||
*/
|
||||
private function getOasisBonusFactor(): float {
|
||||
$wwLevel = 0;
|
||||
@@ -375,9 +377,9 @@ class Village {
|
||||
}
|
||||
|
||||
/**
|
||||
* W2 #9: scos din constructor; apelat explicit din bootstrap-ul de mai jos.
|
||||
* Isi pastreaza singur garda pe build.php, deci efectul e identic cu a-l
|
||||
* pune la inceputul build.php, dar fara sa editam paginile de intrare.
|
||||
* W2 #9: moved out of the constructor; called explicitly from the bootstrap
|
||||
* below. It keeps its own guard on build.php, so the effect is identical to
|
||||
* putting it at the top of build.php, but without editing the entry pages.
|
||||
*/
|
||||
public function ActionControl(): void {
|
||||
$page = $_SERVER['SCRIPT_NAME'];
|
||||
@@ -392,32 +394,48 @@ class Village {
|
||||
}
|
||||
}
|
||||
|
||||
// W2: bootstrap cu side effects EXPLICITE, in aceeasi ordine ca vechiul
|
||||
// constructor (load -> tick productie -> ActionControl). Village.php e
|
||||
// include_once-uit, deci blocul ruleaza o singura data per request.
|
||||
// "new Village" ramane sigur pentru instante suplimentare (admin switch).
|
||||
// W2: bootstrap with EXPLICIT side effects, in the same order as the old
|
||||
// constructor (load -> production tick -> ActionControl). Village.php is
|
||||
// include_once-d, so this block runs only once per request.
|
||||
// "new Village" stays safe for additional instances (admin switch).
|
||||
$village = new Village;
|
||||
$village->tick();
|
||||
$village->ActionControl();
|
||||
$building = new Building;
|
||||
|
||||
// automation
|
||||
// FIX #3 (Day 1): file_exists() + file_put_contents() era un TOCTOU clasic - doua
|
||||
// request-uri simultane treceau amandoua de check si rulau Automation dublu.
|
||||
// fopen('c') + flock(LOCK_EX|LOCK_NB) e atomic; lock-ul se elibereaza singur si
|
||||
// daca procesul moare. Se foloseste ACELASI fisier, deci gardul intern din
|
||||
// Automation.php (calea cron/directa: file_exists + mtime < 60s) ramane functional;
|
||||
// touch() tine mtime proaspat cat rulam noi.
|
||||
$__automationLock = @fopen(AUTOMATION_LOCK_FILE_NAME, 'c');
|
||||
if ($__automationLock !== false) {
|
||||
if (flock($__automationLock, LOCK_EX | LOCK_NB)) {
|
||||
define('AUTOMATION_MANUAL_RUN', true);
|
||||
@touch(AUTOMATION_LOCK_FILE_NAME);
|
||||
include_once("Automation.php");
|
||||
// Automation.php sterge singur fisierul la final (comportament pastrat
|
||||
// pentru calea cron); pe Linux flock ramane valid pe inode pana la fclose.
|
||||
flock($__automationLock, LOCK_UN);
|
||||
// P2: if a dedicated cron is running (cron.php), we no longer trigger automation
|
||||
// from the player pages - cron.php writes a timestamped marker on every run.
|
||||
// A fresh marker (< 90s) => cron is active, so we skip. Fallback: if the cron
|
||||
// stops (marker old or missing), the pages take over automatically, as before, so
|
||||
// the game does not stall if you forget to set up the cron or if it dies.
|
||||
$__cronMarker = __DIR__ . '/Prevention/cron_active.txt';
|
||||
$__cronActive = false;
|
||||
if (@is_file($__cronMarker)) {
|
||||
$__cronTime = (int)@file_get_contents($__cronMarker);
|
||||
if ($__cronTime > 0 && (time() - $__cronTime) < 90) {
|
||||
$__cronActive = true;
|
||||
}
|
||||
fclose($__automationLock);
|
||||
}
|
||||
?>
|
||||
|
||||
if (!$__cronActive) {
|
||||
// FIX #3 (Day 1): file_exists() + file_put_contents() was a classic TOCTOU - two
|
||||
// simultaneous requests both passed the check and ran Automation twice.
|
||||
// fopen('c') + flock(LOCK_EX|LOCK_NB) is atomic; the lock is released by itself
|
||||
// even if the process dies. It uses the SAME file, so the internal guard in
|
||||
// Automation.php (cron/direct path: file_exists + mtime < 60s) keeps working;
|
||||
// touch() keeps mtime fresh while we are running.
|
||||
$__automationLock = @fopen(AUTOMATION_LOCK_FILE_NAME, 'c');
|
||||
if ($__automationLock !== false) {
|
||||
if (flock($__automationLock, LOCK_EX | LOCK_NB)) {
|
||||
define('AUTOMATION_MANUAL_RUN', true);
|
||||
@touch(AUTOMATION_LOCK_FILE_NAME);
|
||||
include_once("Automation.php");
|
||||
// Automation.php deletes the file itself at the end (behavior kept for the
|
||||
// cron path); on Linux flock stays valid on the inode until fclose.
|
||||
flock($__automationLock, LOCK_UN);
|
||||
}
|
||||
fclose($__automationLock);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -265,6 +265,8 @@ Notes:
|
||||
- World data
|
||||
- Croppers build
|
||||
5. After success, access the game root.
|
||||
6. Installation in cPanel (Cron Jobs → Add New Cron Job, every minute):
|
||||
- * * * * * /usr/bin/php /home/USER/public_html/cron.php >/dev/null 2>&1 (REPLACE USER WITH YOUR USERNAME)
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
|
||||
@@ -3,18 +3,40 @@
|
||||
#################################################################################
|
||||
## -= YOU MAY NOT REMOVE OR CHANGE THIS NOTICE =- ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Filename : cron.php ##
|
||||
## Type : Standalone automation runner (P2 - performance) ##
|
||||
## Filename : cron.php ##
|
||||
## Type : Standalone automation runner (P2 - Performance) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Scop : Ruleaza Automation.php separat de request-urile ##
|
||||
## jucatorilor. Se apeleaza dintr-un cron job cPanel la ##
|
||||
## fiecare minut, astfel incat procesarea (batalii, miscari, ##
|
||||
## antrenamente) nu mai e carata de pagina unui jucator. ##
|
||||
## Purpose : Runs Automation.php independently from player requests ##
|
||||
## so processing (battles, troop movements, training, etc.) ##
|
||||
## is no longer handled by a player's page request. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Instalare cron cPanel (la fiecare minut): ##
|
||||
## * * * * * /usr/bin/php /home/USER/public_html/cron.php >/dev/null 2>&1 ##
|
||||
## sau prin wget/curl daca php-cli nu e disponibil: ##
|
||||
## * * * * * wget -q -O /dev/null "https://DOMENIU/cron.php?key=SECRET" ##
|
||||
## WHY AN INTERNAL LOOP: ##
|
||||
## Automation is designed to run approximately every 60 seconds (its ##
|
||||
## internal guard checks "time() - filemtime > 60"). Many cPanel hosting ##
|
||||
## providers do not allow 1-minute cron jobs, only a minimum interval of ##
|
||||
## 5 minutes. If Automation were executed only once every 5 minutes, ##
|
||||
## attacks and building completions could be delayed by up to 5 minutes. ##
|
||||
## For this reason, a single cron invocation performs one TICK every 60 ##
|
||||
## seconds for approximately 5 minutes, then exits and waits for the next ##
|
||||
## scheduled cron execution. ##
|
||||
## ##
|
||||
## Each tick rewrites Prevention/cron_active.txt, keeping the marker file ##
|
||||
## fresh so player page requests will no longer trigger Automation at all. ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## cPanel Cron Installation (every 5 minutes - recommended with the ##
|
||||
## internal loop): ##
|
||||
##*/5 * * * * /usr/local/bin/php /home/USER/public_html/cron.php>/dev/null 2>&1##
|
||||
## ##
|
||||
## If your hosting provider allows a 1-minute cron, set the Minute field to ##
|
||||
## "*" and add the following to GameEngine/config.php: ##
|
||||
## define('CRON_LOOP_SECONDS', 0); ##
|
||||
## (0 = a single tick per invocation, without the internal loop) ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Optional configuration in GameEngine/config.php: ##
|
||||
## define('CRON_LOOP_SECONDS', 300); // Duration of one invocation ##
|
||||
## // (0 = single tick) ##
|
||||
## define('CRON_TICK_SECONDS', 60); // Tick interval inside the loop ##
|
||||
## define('CRON_KEY', 'secret'); // Required only for HTTP execution ##
|
||||
## --------------------------------------------------------------------------- ##
|
||||
## Contact : cata7007@gmail.com ##
|
||||
## Project : TravianZ ##
|
||||
@@ -22,18 +44,44 @@
|
||||
#################################################################################
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 1. Detectare mod de rulare
|
||||
// - CLI (php cron.php): mereu permis, e apelat doar de cron-ul serverului.
|
||||
// - HTTP (wget/curl): permis DOAR daca cheia din config se potriveste, ca sa
|
||||
// nu poata declansa oricine automation-ul de pe internet.
|
||||
// 1. Execution Mode
|
||||
// - CLI (php cron.php): Invoked by the server's cron scheduler and always
|
||||
// allowed.
|
||||
// - HTTP (wget/curl): Allowed ONLY when the correct CRON_KEY is provided,
|
||||
// preventing anyone on the Internet from triggering Automation.
|
||||
// - "--once" / "?once=1": Executes a single tick without the internal loop
|
||||
// (useful for a "Force Run" button in the admin panel).
|
||||
// -----------------------------------------------------------------------------
|
||||
$isCli = (PHP_SAPI === 'cli');
|
||||
|
||||
if ($isCli) {
|
||||
$forceSingleTick = isset($argv) && in_array('--once', $argv, true);
|
||||
} else {
|
||||
$forceSingleTick = isset($_GET['once']);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 2. Bootstrap minimal - NU includem Session.php (porneste sesiune, se leaga de
|
||||
// request si de un user logat). Automation are nevoie doar de config (constante
|
||||
// SQL_*, TB_PREFIX, AUTOMATION_LOCK_FILE_NAME) si de $database; restul (buidata,
|
||||
// unitdata, Units, Battle, etc.) si le include Automation.php singur.
|
||||
// 1b. IMPORTANT: The server's cron launches this script with the current working
|
||||
// directory set to the account's HOME directory (e.g. /home/user), NOT the
|
||||
// game's directory. Without the line below, the relative lookups in step 2
|
||||
// (file_exists('autoloader.php'), etc.) fail, the script exits with exit(1),
|
||||
// and the error message is swallowed by ">/dev/null"—making it appear that
|
||||
// the cron job "does nothing" with no trace in the logs.
|
||||
// This problem does not occur when accessed via HTTP (where the current
|
||||
// working directory is the script's directory), which is why running it from
|
||||
// the browser works while the cron job does not.
|
||||
// -----------------------------------------------------------------------------
|
||||
if (@chdir(__DIR__) === false) {
|
||||
if ($isCli) { fwrite(STDERR, "cron: unable to change to directory " . __DIR__ . "\n"); }
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 2. Minimal Bootstrap
|
||||
// - We do NOT include Session.php, as it starts a session and is tied to
|
||||
// HTTP requests and authenticated users.
|
||||
// - Automation.php loads everything else it needs on its own
|
||||
// (buidata, unitdata, Units, Battle, etc.).
|
||||
// -----------------------------------------------------------------------------
|
||||
$autoprefix = '';
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
@@ -46,7 +94,7 @@ for ($i = 0; $i < 5; $i++) {
|
||||
require_once($autoprefix . 'autoloader.php');
|
||||
|
||||
if (!file_exists($autoprefix . 'GameEngine/config.php')) {
|
||||
// instalarea nu e finalizata - nu avem ce rula
|
||||
// Installation is not yet complete - there is nothing to run.
|
||||
if ($isCli) { fwrite(STDERR, "cron: config.php lipseste, jocul nu e instalat\n"); }
|
||||
exit(1);
|
||||
}
|
||||
@@ -54,61 +102,185 @@ if (!file_exists($autoprefix . 'GameEngine/config.php')) {
|
||||
include_once($autoprefix . 'GameEngine/config.php');
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 3. Verificare cheie pentru accesul HTTP (nu se aplica in CLI)
|
||||
// Defineste in GameEngine/config.php: define('CRON_KEY', 'un-secret-lung');
|
||||
// Apoi apeleaza: https://domeniu/cron.php?key=un-secret-lung
|
||||
// Daca CRON_KEY nu e definit, accesul HTTP este refuzat (doar CLI merge).
|
||||
// 3. Verify the access key for HTTP requests (not applicable in CLI mode)
|
||||
// -----------------------------------------------------------------------------
|
||||
if (!$isCli) {
|
||||
$providedKey = isset($_GET['key']) ? (string)$_GET['key'] : '';
|
||||
|
||||
if (!defined('CRON_KEY') || CRON_KEY === '' || !hash_equals(CRON_KEY, $providedKey)) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
exit('Forbidden');
|
||||
}
|
||||
// nu tinem conexiunea deschisa pentru client - raspundem si continuam in fundal
|
||||
// Do not keep the client connection open - send the response immediately and continue processing in the background.
|
||||
ignore_user_abort(true);
|
||||
@set_time_limit(0);
|
||||
header('Content-Type: text/plain');
|
||||
echo "OK\n";
|
||||
|
||||
if (function_exists('fastcgi_finish_request')) {
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
|
||||
// When executed via HTTP, we do not keep the process running for 5 minutes
|
||||
// (PHP-FPM has its own execution timeouts).
|
||||
// An HTTP request performs only a single tick.
|
||||
// The internal loop is intended for CLI execution only.
|
||||
$forceSingleTick = true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 4. Conexiune la baza de date (acelasi mod ca in Database.php - variabila globala
|
||||
// $database folosita de Automation prin "global $database").
|
||||
// 4. Loop Parameters
|
||||
// -----------------------------------------------------------------------------
|
||||
$loopSeconds = defined('CRON_LOOP_SECONDS') ? (int)CRON_LOOP_SECONDS : 300;
|
||||
$tickSeconds = defined('CRON_TICK_SECONDS') ? (int)CRON_TICK_SECONDS : 60;
|
||||
|
||||
if ($tickSeconds < 15) { $tickSeconds = 15; } // sub 15s nu aduce nimic, doar incarca serverul
|
||||
if ($loopSeconds < 0) { $loopSeconds = 0; }
|
||||
if ($forceSingleTick) { $loopSeconds = 0; }
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 5. Overlap Lock
|
||||
// If the previous invocation is still running its loop (e.g., on a slow
|
||||
// server), the next invocation exits immediately instead of processing
|
||||
// everything twice. The flock() lock is automatically released, even if
|
||||
// the process terminates unexpectedly.
|
||||
// -----------------------------------------------------------------------------
|
||||
$runLockPath = $autoprefix . 'GameEngine/Prevention/cron_running.lock';
|
||||
$runLockHandle = @fopen($runLockPath, 'c');
|
||||
|
||||
if ($runLockHandle === false) {
|
||||
if ($isCli) { fwrite(STDERR, "cron: nu pot deschide lock-ul ($runLockPath)\n"); }
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!flock($runLockHandle, LOCK_EX | LOCK_NB)) {
|
||||
fclose($runLockHandle);
|
||||
if ($isCli) { echo "cron: o invocare anterioara inca ruleaza, ies\n"; }
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 6. Database Connection
|
||||
// Initializes the database connection ($database), which is accessed by
|
||||
// Automation.php through the global scope.
|
||||
// -----------------------------------------------------------------------------
|
||||
include_once($autoprefix . 'GameEngine/Database.php');
|
||||
|
||||
if (!isset($database) || !$database) {
|
||||
if ($isCli) { fwrite(STDERR, "cron: conexiunea la baza de date a esuat\n"); }
|
||||
flock($runLockHandle, LOCK_UN);
|
||||
fclose($runLockHandle);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 5. Marcam ca suntem sursa oficiala de automation. AUTOMATION_MANUAL_RUN spune
|
||||
// Automation.php sa NU mai treaca prin gardul de lock file (acela era pentru
|
||||
// coordonarea request-urilor concurente ale jucatorilor; cron-ul e sursa unica,
|
||||
// o data pe minut, deci nu are nevoie de el).
|
||||
//
|
||||
// In plus scriem un marker cu timestamp, ca Village.php sa stie ca exista un
|
||||
// cron activ si sa NU mai declanseze automation din paginile jucatorilor
|
||||
// (fallback: daca cron-ul se opreste > 90s, paginile preiau automat).
|
||||
// -----------------------------------------------------------------------------
|
||||
// AUTOMATION_MANUAL_RUN tells Automation.php to bypass its own lock-file guard.
|
||||
// That guard is designed to coordinate concurrent player requests, whereas
|
||||
// this script is the only execution source and is already protected by the
|
||||
// overlap lock implemented above.
|
||||
if (!defined('AUTOMATION_MANUAL_RUN')) {
|
||||
define('AUTOMATION_MANUAL_RUN', true);
|
||||
}
|
||||
|
||||
$cronMarker = $autoprefix . 'GameEngine/Prevention/cron_active.txt';
|
||||
@file_put_contents($cronMarker, (string)time(), LOCK_EX);
|
||||
$cronMarkerPath = $autoprefix . 'GameEngine/Prevention/cron_active.txt';
|
||||
|
||||
/**
|
||||
* Clears the database class's request-level caches.
|
||||
*
|
||||
* The static caches in MYSQLi_DB are designed to live only for the duration
|
||||
* of a single request. In a process that executes multiple ticks in sequence,
|
||||
* they would remain populated, causing subsequent ticks to operate on stale
|
||||
* data (for example, building levels already modified by the previous tick).
|
||||
* Therefore, the caches are reset between ticks so that each tick starts with
|
||||
* the same clean state as a new request.
|
||||
*
|
||||
* Only static array properties whose names contain "cache" are reset.
|
||||
*/
|
||||
function cron_reset_db_caches()
|
||||
{
|
||||
if (!class_exists('MYSQLi_DB')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$ref = new ReflectionClass('MYSQLi_DB');
|
||||
|
||||
foreach ($ref->getProperties(ReflectionProperty::IS_STATIC) as $prop) {
|
||||
if (stripos($prop->getName(), 'cache') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prop->setAccessible(true);
|
||||
|
||||
if (is_array($prop->getValue())) {
|
||||
$prop->setValue(null, []);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
// If reflection fails, it is better to continue than to stop Automation.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A tick represents one complete execution of Automation.
|
||||
*
|
||||
* The first tick includes Automation.php (the file ends with "new Automation",
|
||||
* so including it starts the processing automatically). Subsequent ticks simply
|
||||
* instantiate the Automation class again, since include_once would not execute
|
||||
* the file a second time.
|
||||
*/
|
||||
function cron_run_tick(&$firstTick, $autoprefix, $cronMarkerPath)
|
||||
{
|
||||
@file_put_contents($cronMarkerPath, (string)time(), LOCK_EX);
|
||||
|
||||
if ($firstTick) {
|
||||
include_once($autoprefix . 'GameEngine/Automation.php');
|
||||
$firstTick = false;
|
||||
return;
|
||||
}
|
||||
|
||||
cron_reset_db_caches();
|
||||
new Automation();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 6. Rulam automation-ul propriu-zis. Constructorul din Automation.php face toata
|
||||
// treaba (procNewClimbers, buildComplete, trainingComplete, batalii, miscari...).
|
||||
// 7. Execution
|
||||
// -----------------------------------------------------------------------------
|
||||
include_once($autoprefix . 'GameEngine/Automation.php');
|
||||
$firstTick = true;
|
||||
$startedAt = time();
|
||||
$ticks = 0;
|
||||
|
||||
do {
|
||||
$tickStart = time();
|
||||
|
||||
cron_run_tick($firstTick, $autoprefix, $cronMarkerPath);
|
||||
$ticks++;
|
||||
|
||||
if ($loopSeconds <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Is there enough time remaining in the invocation budget for another complete tick?
|
||||
$elapsed = time() - $startedAt;
|
||||
if (($elapsed + $tickSeconds) >= $loopSeconds) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Sleep until the next tick, subtracting the time spent executing the current tick.
|
||||
$spent = time() - $tickStart;
|
||||
$sleep = $tickSeconds - $spent;
|
||||
|
||||
if ($sleep > 0) {
|
||||
sleep($sleep);
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 8. Release the Lock
|
||||
// -----------------------------------------------------------------------------
|
||||
flock($runLockHandle, LOCK_UN);
|
||||
fclose($runLockHandle);
|
||||
|
||||
if ($isCli) {
|
||||
echo "cron: automation rulat la " . date('Y-m-d H:i:s') . "\n";
|
||||
echo "cron: " . $ticks . " tick(uri) in " . (time() - $startedAt) . "s, terminat la " . date('Y-m-d H:i:s') . "\n";
|
||||
}
|
||||
|
||||
@@ -22,6 +22,26 @@ define("ERROR_REPORT","%ERRORREPORT%");
|
||||
%ERROR%
|
||||
define('AUTOMATION_LOCK_FILE_NAME', 'automation.lck');
|
||||
|
||||
//////////////////////////////////
|
||||
// ***** CRON / AUTOMATION *****//
|
||||
//////////////////////////////////
|
||||
// Automation ruleaza din cron.php (cron job pe server), nu din paginile
|
||||
// jucatorilor. Vezi comentariile din cron.php pentru instalarea cron job-ului.
|
||||
//
|
||||
// CRON_LOOP_SECONDS = cat timp tine o invocare de cron.php.
|
||||
// Multe hosturi nu permit cron mai des de 5 minute, iar Automation vrea sa
|
||||
// ruleze la ~60s. De aceea o invocare ruleaza mai multe tick-uri la rand.
|
||||
// 300 = potrivit pentru un cron "*/5 * * * *". Pune 0 daca hostul tau permite
|
||||
// cron la fiecare minut (atunci o invocare = un singur tick).
|
||||
// CRON_TICK_SECONDS = la cat timp se repeta un tick in interiorul invocarii.
|
||||
define('CRON_LOOP_SECONDS', %CRONLOOP%);
|
||||
define('CRON_TICK_SECONDS', %CRONTICK%);
|
||||
|
||||
// Cheie pentru apelarea cron.php prin HTTP (wget/curl sau un serviciu extern de
|
||||
// cron). Rularea din linia de comanda (cron job cPanel) NU are nevoie de ea.
|
||||
// Generata automat la instalare; pastrata la salvarile de configuratie din ACP.
|
||||
define('CRON_KEY', '%CRONKEY%');
|
||||
|
||||
//////////////////////////////////
|
||||
// ***** SERVER SETTINGS *****//
|
||||
//////////////////////////////////
|
||||
|
||||
@@ -107,6 +107,13 @@ class Process {
|
||||
$findReplace["%QTYPE%"] = $_POST['qtype'];
|
||||
$findReplace["%BEGINNER%"] = $_POST['beginner'];
|
||||
$findReplace["%STARTTIME%"] = time();
|
||||
// Cheie aleatoare pentru apelul HTTP al cron.php (vezi CRON_KEY in config).
|
||||
// Generata o singura data, la instalare; ACP-ul o pastreaza la resalvari.
|
||||
$findReplace["%CRONKEY%"] = bin2hex(random_bytes(24));
|
||||
// Valori implicite pentru ciclul intern al cron.php; editabile ulterior
|
||||
// din ACP (Config -> Cron & Automation -> edit).
|
||||
$findReplace["%CRONLOOP%"] = 300;
|
||||
$findReplace["%CRONTICK%"] = 60;
|
||||
$findReplace["%DOMAIN%"] = $_POST['domain'];
|
||||
$findReplace["%HOMEPAGE%"] = $_POST['homepage'];
|
||||
$findReplace["%SERVER%"] = $_POST['server'];
|
||||
|
||||
Reference in New Issue
Block a user