MCCodes - Chapter 7: Cron Job System
Welcome to Chapter 7! In Chapter 6: Gang System, we learned how players can band together to form groups, share resources, and engage in collective activities like organized crimes. But many game mechanics need to happen automatically, even when players aren't actively doing something. How does the game handle things like refilling your energy overnight, paying out job wages, or making sure your jail sentence ticks down? That's the job of the Cron Job System.
The Problem: Keeping the Game World Alive Automatically
Imagine your character's energy bar. It refills slowly over time. Or maybe you're stuck in jail for 10 minutes. How does the game know when to add more energy or reduce your jail time? If these things only happened when you clicked a button, the game wouldn't feel very alive. We need a system that runs background tasks on a regular schedule, like clockwork, without needing a player or admin to trigger them manually every time.
The Solution: MCCodes uses a Cron Job System to handle these scheduled tasks. Think of it as the game's background heartbeat or its automated maintenance crew. It ensures that time-based events occur reliably, resources regenerate, and penalties expire, keeping the game world ticking along smoothly.
What Do Crons Do? The Automatic Tasks
The Cron Job System, primarily managed by CronHandler.php
and related class files, performs many essential background tasks at different intervals:
- Every Minute (
CronOneMinute.php
):- Reduces the time remaining for players in jail or the hospital.
- Every 5 Minutes (
CronFiveMinute.php
):- Regenerates player stats like Energy, Brave, Health (HP), and Will, up to their maximums.
- (Also resets a 'verified' flag used for bot checks).
- Every Hour (
CronOneHour.php
):- Processes the results of ongoing Gang Organized Crimes (OCs).
- (Resets the verified flag again, just in case).
- Every Day (
CronOneDay.php
):- Pays out daily wages to players based on their job and rank.
- Calculates and adds interest earned on money stored in the bank.
- Processes completed educational courses, awarding stat gains.
- Reduces the duration of penalties like mail bans or federal jail sentences.
- Increments counters like
daysold
(player age) anddaysingang
. - Resets daily limits (like vote counts or special item uses).
Without these automated tasks, the game would quickly grind to a halt or feel very static.
How It Works: Scheduled Execution
The core idea is scheduling: telling the game server to run specific pieces of code at specific times or intervals. There are two main ways MCCodes achieves this:
- Server Cron Jobs (The Traditional Way)
- Cronless Crons (The Fallback Way)
Let's look at the traditional method first.
1. Server Cron Jobs: The System's Alarm Clock
Most web servers have a built-in utility (often called cron
on Linux systems) that acts like a very powerful alarm clock. You can tell it: "Hey, at exactly this minute, of this hour, on this day, run this command."
- Analogy: Imagine setting multiple alarms on your phone: one for every minute, one for every 5 minutes, one for every hour, and one for midnight every day. Each alarm triggers a specific action.
In MCCodes, the server's cron utility is typically configured to "poke" specific PHP files at set intervals. The README.md
file included with MCCodes gives examples of the commands you might set up on your server:
# Example commands for server cron setup (from README.md, simplified path)
* * * * * php /path/to/game/crons/CronHandler.php cron=minute-1 code=YOUR_SECRET_CODE
*/5 * * * * php /path/to/game/crons/CronHandler.php cron=minute-5 code=YOUR_SECRET_CODE
0 * * * * php /path/to/game/crons/CronHandler.php cron=hour-1 code=YOUR_SECRET_CODE
0 0 * * * php /path/to/game/crons/CronHandler.php cron=day-1 code=YOUR_SECRET_CODE
Let's break down the first line:
-
* * * * *
: This part tells the server's cron utility "Run this command every minute of every hour of every day...". The other lines use different patterns (*/5
for every 5 minutes,0 *
for the start of every hour,0 0
for midnight). -
php /path/to/game/crons/CronHandler.php
: This tells the server to execute theCronHandler.php
script using the PHP interpreter. -
cron=minute-1 code=YOUR_SECRET_CODE
: These are parameters passed to theCronHandler.php
script.-
cron=minute-1
tells the script which set of tasks to run (the 1-minute tasks). -
code=YOUR_SECRET_CODE
is a security measure. The script checks if this code matches the one stored in your game's configuration ($_CONFIG['code']
). This prevents random people on the internet from triggering your cron tasks just by visiting the URL.
-
Setting Up Server Crons: Configuring these server-level cron jobs can sometimes be tricky, especially for beginners. It often involves using control panels like cPanel or accessing the server via SSH (command line), as described in the MCCodes README.md
. The key takeaway is that the server's job is just to trigger the PHP script at the right time.
2. The PHP Side: CronHandler.php
- The Coordinator
When the server's cron utility runs one of the commands above, it essentially makes a web request or runs the PHP script CronHandler.php
with the specified parameters (cron
and code
).
CronHandler.php
acts as the central coordinator within the MCCodes application:
- Setup: It includes
globals_nonauth.php
to connect to the database ($db
) and load basic settings ($set
). It doesn't need full user login (globals.php
) because these tasks run for the whole game, not just one player. - Security Check: It immediately checks if the provided
code
parameter matches the secret$_CONFIG['code']
. If not, it stops to prevent unauthorized execution. - Identify Task: It looks at the
cron
parameter (minute-1
,minute-5
,hour-1
,day-1
) to determine which interval's tasks need to be run. - Delegate: Based on the interval, it loads and calls the appropriate class file from the
crons/classes/
directory (e.g.,CronOneMinute.php
,CronFiveMinute.php
, etc.).
Here's a very simplified look at the structure inside CronHandler.php
:
<?php
// File: crons/CronHandler.php (Simplified Concept)
// --- Initial Setup & Parameter Reading ---
// (Code to get $cron and $code from URL or command line arguments)
// Example: $cron = $_GET['cron']; $code = $_GET['code'];
// --- Database Connection (via globals_nonauth.php) ---
global $db, $_CONFIG;
if (empty($db)) {
require_once dirname(__DIR__) . '/globals_nonauth.php';
}
// --- Security Check ---
if ($code !== $_CONFIG['code']) {
die('Access denied'); // Stop if the secret code is wrong
}
// --- Autoload Class Files ---
// (Code to automatically load classes like CronOneMinute when needed)
spl_autoload_register(/* ... */);
// --- Main Class ---
class CronHandler
{
// ... (Helper methods for timing, logging) ...
public function run(string $cron_interval)
{
echo "Beginning: " . $cron_interval . "\n";
try {
// --- Delegate to the Correct Class ---
$handler_class = null;
if ($cron_interval == 'minute-1') {
$handler_class = CronOneMinute::getInstance($this->db);
} elseif ($cron_interval == 'minute-5') {
$handler_class = CronFiveMinute::getInstance($this->db);
} elseif ($cron_interval == 'hour-1') {
$handler_class = CronOneHour::getInstance($this->db);
} elseif ($cron_interval == 'day-1') {
$handler_class = CronOneDay::getInstance($this->db);
} else {
throw new RuntimeException('Invalid cron ID');
}
// --- Execute the Tasks ---
$handler_class->doFullRun(1); // Run the tasks in that class
echo "Complete: " . $cron_interval . "\n";
// ... (Log success/runtime) ...
} catch (Exception $e) {
echo "Fail: " . $cron_interval . ": " . $e->getMessage() . "\n";
// ... (Log error) ...
}
}
}
// --- Trigger the Run ---
if ($cron !== null) { // If a cron interval was specified
$mainHandler = CronHandler::getInstance($db);
$mainHandler->run($cron);
}
?>
This script verifies the request, figures out which interval is being run (minute-1
, minute-5
, etc.), gets an instance of the corresponding class (e.g., CronOneMinute
), and tells that class to execute its tasks (doFullRun
).
3. The Worker Classes: classes/Cron*.php
- The Actual Tasks
The real game logic for each scheduled interval lives inside the class files in the crons/classes/
directory. Each class handles the tasks for its specific interval.
Example: CronOneMinute.php
(Jail/Hospital Times)
<?php
// File: crons/classes/CronOneMinute.php (Simplified)
final class CronOneMinute extends CronHandler
{
// ... (Code to get a single instance of this class) ...
// Called by CronHandler->run('minute-1')
public function doFullRun(int $increments): void
{
// Call the actual task methods
$this->updateJailHospitalTimes($increments);
// ... (potentially other 1-minute tasks) ...
}
// The specific task
public function updateJailHospitalTimes(int $increments): void
{
// Decrease jail/hospital time for users who are currently jailed/hospitalized
// GREATEST(..., 0) ensures the time doesn't go below zero
$this->db->query(
"UPDATE users
SET hospital = GREATEST(hospital - {$increments}, 0),
jail = GREATEST(jail - {$increments}, 0)
WHERE jail > 0 OR hospital > 0"
);
// (Also updates counts in settings table)
}
}
?>
This class has a method (updateJailHospitalTimes
) that performs a simple database update: find all users with jail
or hospital
time greater than 0, and decrease those values by 1 (or more, if the cron missed cycles, handled by $increments
), making sure they don't become negative.
Example: CronFiveMinute.php
(Stat Regeneration)
<?php
// File: crons/classes/CronFiveMinute.php (Simplified)
final class CronFiveMinute extends CronHandler
{
// ... (Code to get a single instance) ...
public function doFullRun(int $increments): void
{
$this->updateUserStatBars($increments);
// ... (other 5-minute tasks) ...
}
public function updateUserStatBars(int $increments): void
{
// Update energy, brave, hp, will based on their max values and regeneration rates
// LEAST(..., max...) ensures the value doesn't exceed the maximum
$this->db->query(
"UPDATE users SET
brave = LEAST(brave + ((maxbrave / 10) * {$increments}), maxbrave),
hp = LEAST(hp + ((maxhp / 3) * {$increments}), maxhp),
will = LEAST(will + (10 * {$increments}), maxwill),
energy = LEAST(energy + ((maxenergy / /* rate */) * {$increments}), maxenergy)
/* ... WHERE conditions omitted for simplicity ... */"
);
}
}
?>
This class updates user stats. It calculates how much each stat should regenerate in 5 minutes (based on formulas involving the maximum stat value) and adds it to the current value, capped by the maximum.
Example: CronOneDay.php
(Job Wages)
<?php
// File: crons/classes/CronOneDay.php (Simplified)
final class CronOneDay extends CronHandler
{
// ... (Code to get a single instance) ...
public function doFullRun(int $increments): void
{
$this->payJobWages($increments);
$this->updateBankInterests($increments);
$this->processCourses();
// ... (other daily tasks) ...
}
public function payJobWages(int $increments): void
{
// Give users money based on their job rank's pay rate
$this->db->query(
"UPDATE users u
JOIN jobranks jr ON u.jobrank = jr.jrID
SET u.money = u.money + (jr.jrPAY * {$increments})
/* ... also updates XP and some stats ... */
WHERE u.job > 0 AND u.jobrank > 0"
);
}
// ... other methods like updateBankInterests, processCourses ...
}
?>
The daily cron handles tasks like paying wages by joining the users
table with jobranks
to find the correct pay rate and adding it to the user's money.
4. The Cronless Cron Alternative (cronless_crons.php
)
What if setting up server cron jobs is too difficult or not possible on your hosting? MCCodes provides an alternative mechanism using the file crons/cronless_crons.php
.
- How it Works: This script is designed to be included at the bottom of a frequently visited page, like
index.php
(the main page after login). Every time a player loads that page, this script runs. - Time Check: It checks a database table called
cron_times
, which records the last time each cron interval (minute-1
,minute-5
, etc.) was successfully run. - Catching Up: It compares the current time to the last run time for each interval. If enough time has passed (e.g., more than 60 seconds for
minute-1
, more than 300 seconds forminute-5
), it calls the mainCronHandler::run()
function for that interval, telling it how many intervals have been missed ($increments
).
<?php
// File: crons/cronless_crons.php (Simplified Concept)
require_once __DIR__ . '/CronHandler.php'; // Ensure handler is available
// --- Load Last Run Times ---
$q = $db->query("SELECT name, last_run FROM cron_times");
$last_runs = [];
while ($row = $db->fetch_row($q)) { $last_runs[$row['name']] = $row['last_run']; }
$db->free_result($q);
// --- Define Intervals & Check Differences ---
$intervals = [
'minute-1' => 60, // 1 minute = 60 seconds
'minute-5' => 300, // 5 minutes = 300 seconds
'hour-1' => 3600, // 1 hour = 3600 seconds
'day-1' => 86400 // 1 day = 86400 seconds
];
$now = time(); // Current time as a Unix timestamp
foreach ($intervals as $name => $seconds) {
$last_run_time = strtotime($last_runs[$name]); // Convert DB timestamp to Unix timestamp
$time_diff = $now - $last_run_time;
// --- If Enough Time Has Passed ---
if ($time_diff >= $seconds) {
$times_to_run = floor($time_diff / $seconds); // How many intervals missed?
echo "<!-- Cronless: Running {$name} x{$times_to_run} -->"; // Hidden HTML comment
// --- Run the Cron Tasks ---
$handler = CronHandler::getInstance($db);
$handler->run($name, (int)$times_to_run);
}
}
?>
This script checks the time difference for each interval. If, for example, 70 seconds have passed since minute-1
last ran, $time_diff
will be 70, $times_to_run
will be floor(70/60) = 1
, and it will call CronHandler::getInstance($db)->run('minute-1', 1)
. If 400 seconds passed since minute-5
ran, it would call run('minute-5', 1)
. If 125 seconds passed since minute-1
last ran, it would call run('minute-1', 2)
.
Pros & Cons of Cronless Crons:
- Pros: Much easier setup -- just include the file on a busy page. No need to configure server cron jobs. Enabled via
Basic Settings -> Use Timestamps Over Crons
. - Cons:
- Less Precise: Tasks only run when players visit the page where it's included. If no one visits for an hour, the 1-minute and 5-minute tasks won't run during that time.
- Potential Slowdown: When the script does run the cron tasks (especially the hourly or daily ones), it might slightly slow down the page load for the player who triggered it.
Generally, using server cron jobs is preferred for reliability and precision if you can set them up. The cronless method is a useful fallback.
Walkthrough: A 1-Minute Cron Event
Let's trace what happens when a server cron job triggers the 1-minute tasks:
sequenceDiagram participant Sched as Server Cron Scheduler participant Serv as Web Server / PHP participant CH as CronHandler.php participant CM1 as CronOneMinute.php participant DB as Database Sched->>Serv: Run "php CronHandler.php cron=minute-1 code=SECRET" Serv->>CH: Start executing CronHandler.php CH->>DB: Connect (via globals_nonauth.php) CH->>CH: Read parameters (cron=minute-1, code=SECRET) CH->>CH: Verify code against $_CONFIG['code'] (Success) CH->>CM1: Get instance of CronOneMinute CH->>CM1: Call doFullRun(1) CM1->>CM1: Call updateJailHospitalTimes(1) CM1->>DB: UPDATE users SET jail = GREATEST(jail - 1, 0), hospital = GREATEST(hospital - 1, 0) WHERE ... DB-->>CM1: Update successful CM1-->>CH: Finished updateJailHospitalTimes CM1-->>CH: Finished doFullRun CH->>DB: Log cron success and runtime in logs_cron_runtimes CH->>DB: Update last_run time in cron_times table for 'minute-1' DB-->>CH: Logging complete CH-->>Serv: Script finished Serv-->>Sched: Process completed
This shows the flow: the server scheduler triggers the script, CronHandler
validates and delegates to CronOneMinute
, which executes the database query to decrease jail/hospital times, and finally CronHandler
logs the successful run.
Key Files and Tables
- Core PHP Files:
-
crons/CronHandler.php
: The main coordinator. Receives triggers, validates, delegates to interval classes. -
crons/classes/CronOneMinute.php
: Handles 1-minute tasks (jail/hospital). -
crons/classes/CronFiveMinute.php
: Handles 5-minute tasks (stat regen). -
crons/classes/CronOneHour.php
: Handles hourly tasks (gang OCs). -
crons/classes/CronOneDay.php
: Handles daily tasks (wages, interest, courses, etc.). -
crons/cronless_crons.php
: The alternative mechanism that runs based on player activity and time checks. -
globals_nonauth.php
: Used byCronHandler
for basic DB connection and settings without requiring login.
-
- Database Tables:
-
users
: Contains the fields being updated (energy, hp, will, brave, jail, hospital, money, bankmoney, cdays, etc.). -
cron_times
: Stores the timestamp of the last successful run for each interval (minute-1
,minute-5
,hour-1
,day-1
). Used bycronless_crons.php
and for logging. -
logs_cron_runtimes
: Records the start time, end time, and number of rows affected for each cron run. Useful for monitoring performance. -
logs_cron_fails
: Records any errors that occur during cron execution. Useful for debugging. - Other tables read/updated by specific tasks (e.g.,
gangs
,orgcrimes
,courses
,jobranks
).
-
Conclusion
You've now learned about the crucial Cron Job System in MCCodes! This automated system acts as the game's heartbeat, ensuring that essential time-based processes occur regularly.
- It handles tasks like stat regeneration, timer countdowns (jail/hospital), wage payments, bank interest, and more.
- It can be triggered either by server-level cron jobs (the preferred, precise method) or by the cronless_crons.php script (an easier-to-set-up fallback).
-
CronHandler.php
acts as the central coordinator, validating requests and delegating tasks to specific classes likeCronOneMinute
,CronFiveMinute
, etc., which contain the actual game logic.
This system keeps the game world dynamic and progressing even when players are offline.
Now that we've covered major gameplay systems and the background processes that keep them running, let's turn our attention to how game administrators manage the game: the Staff Panel.
Next Up: Chapter 8: Staff Panel & Permissions
Previously: Chapter 6: Gang System
First published April 21, 2025
Tags: MCCodes Walkthrough Tutorial