MCCodes - Chapter 7: Cron Job System

MCCodes Walkthrough Tutorial

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:

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:

  1. Server Cron Jobs (The Traditional Way)
  2. 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."

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:

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:

  1. 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.
  2. Security Check: It immediately checks if the provided code parameter matches the secret $_CONFIG['code']. If not, it stops to prevent unauthorized execution.
  3. 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.
  4. 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.

<?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:

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

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.

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