Skip to content

Scheduler

wezzzyrek1 edited this page Mar 12, 2026 · 2 revisions

Scheduler API

Overview

Event scheduler system for managing timed events with XML configuration support. Supports recurring events, notices, and duration-based events.

Functions

Scheduler.LoadFromXML

Load event schedule from XML configuration file.

success = Scheduler.LoadFromXML(filePath)

Parameters:

  • filePath (string): Path to XML configuration file

Returns: boolean - Success/failure

Example:

local success = Scheduler.LoadFromXML("Config/EventScheduler.xml")
if success then
    Log.Add("Scheduler loaded successfully")
else
    Log.AddC(2, "Failed to load scheduler!")
end

Scheduler.GetEventCount

Get total number of loaded events.

count = Scheduler.GetEventCount()

Returns: number - Number of events

Example:

local count = Scheduler.GetEventCount()
Log.Add(string.format("Loaded %d scheduled events", count))

Scheduler.CheckEventStarts

Check which events should start at current time.

eventTypes = Scheduler.CheckEventStarts()

Returns: table - Array of event type IDs that triggered

Example:

local startedEvents = Scheduler.CheckEventStarts()
for _, eventType in ipairs(startedEvents) do
    local name = Scheduler.GetEventName(eventType)
    Log.Add(string.format("Event started: %s (Type: %d)", name, eventType))
    OnEventStart(eventType)
end

Scheduler.CheckEventEnds

Check which events should end at current time (based on duration).

eventTypes = Scheduler.CheckEventEnds()

Returns: table - Array of event type IDs that ended

Example:

local endedEvents = Scheduler.CheckEventEnds()
for _, eventType in ipairs(endedEvents) do
    local name = Scheduler.GetEventName(eventType)
    Log.Add(string.format("Event ended: %s (Type: %d)", name, eventType))
    OnEventEnd(eventType)
end

Scheduler.CheckEventNotices

Check which events should send notice at current time, with remaining time information.

notices = Scheduler.CheckEventNotices()

Returns: table - Array of EventNotice objects

EventNotice Structure:

  • type (number): Event type ID
  • secondsRemaining (number): Seconds until event starts

Example:

local notices = Scheduler.CheckEventNotices()
for _, notice in ipairs(notices) do
    local name = Scheduler.GetEventName(notice.type)
    local timeStr = FormatTimeRemaining(notice.secondsRemaining)
    
    Message.Send(0, -1, 0, name .. " starts in " .. timeStr .. "!")
    Log.Add(string.format("[Notice] %s starting in %s", name, timeStr))
end

Helper Function Example:

-- Format seconds into human-readable time string
local function FormatTimeRemaining(seconds)
    if seconds < 60 then
        return string.format("%d second%s", seconds, seconds ~= 1 and "s" or "")
    elseif seconds < 3600 then
        local minutes = math.floor(seconds / 60)
        return string.format("%d minute%s", minutes, minutes ~= 1 and "s" or "")
    else
        local hours = math.floor(seconds / 3600)
        local minutes = math.floor((seconds % 3600) / 60)
        if minutes == 0 then
            return string.format("%d hour%s", hours, hours ~= 1 and "s" or "")
        else
            return string.format("%d hour%s %d minute%s", 
                hours, hours ~= 1 and "s" or "",
                minutes, minutes ~= 1 and "s" or "")
        end
    end
end

Output Examples:

  • "Blood Castle starts in 5 minutes!"
  • "Devil Square starts in 1 hour 30 minutes!"
  • "Golden Invasion starts in 45 seconds!"

Scheduler.GetEventName

Get event name by type ID.

name = Scheduler.GetEventName(eventType)

Parameters:

  • eventType (number): Event type ID

Returns: string - Event name or empty string if not found

Example:

local name = Scheduler.GetEventName(100)
Log.Add(string.format("Event name: %s", name))

Scheduler.HasSecondPrecisionEvents

Check if any event requires second-precision timing.

hasSecondPrecision = Scheduler.HasSecondPrecisionEvents()

Returns: boolean - True if any event has second-precision schedule

Example:

if Scheduler.HasSecondPrecisionEvents() then
    Log.Add("Scheduler requires per-second checks")
    -- Call Check functions every second
else
    Log.Add("Scheduler can run per-minute checks")
    -- Call Check functions every minute
end

Scheduler.IsEventActive

Check if event is currently running.

isActive = Scheduler.IsEventActive(eventType)

Parameters:

  • eventType (number): Event type ID

Returns: boolean - True if event is active, false otherwise

Example:

if Scheduler.IsEventActive(100) then
    Log.Add("Blood Castle is currently active")
else
    Log.Add("Blood Castle is not running")
end

Scheduler.GetElapsedTime

Get elapsed time since event started (in seconds).

elapsed = Scheduler.GetElapsedTime(eventType)

Parameters:

  • eventType (number): Event type ID

Returns: number - Elapsed time in seconds, or -1 if event is not active

Example:

local elapsed = Scheduler.GetElapsedTime(100)
if elapsed >= 0 then
    local mins = math.floor(elapsed / 60)
    local secs = elapsed % 60
    Log.Add(string.format("Event running for %d:%02d", mins, secs))
end

Scheduler.GetRemainingTime

Get remaining time until event ends (in seconds).

remaining = Scheduler.GetRemainingTime(eventType)

Parameters:

  • eventType (number): Event type ID

Returns: number - Remaining time in seconds, or -1 if event is not active or has no duration

Example:

local remaining = Scheduler.GetRemainingTime(100)
if remaining >= 0 then
    local mins = math.floor(remaining / 60)
    local secs = remaining % 60
    Message.Send(-1, 0, string.format(
        "Event ends in %d:%02d", mins, secs
    ))
end

Scheduler.GetProgress

Get event progress as percentage (0-100).

progress = Scheduler.GetProgress(eventType)

Parameters:

  • eventType (number): Event type ID

Returns: number - Progress percentage (0-100), or -1 if event is not active or has no duration

Example:

local progress = Scheduler.GetProgress(100)
if progress >= 0 then
    Log.Add(string.format("Event progress: %d%%", progress))
end

Complete Examples

Example 1: Basic Event Monitoring

-- Called every second/minute from game loop
function CheckScheduledEvents()
    -- Check for starting events
    local startedEvents = Scheduler.CheckEventStarts()
    for _, eventType in ipairs(startedEvents) do
        OnEventStart(eventType)
    end
    
    -- Check for ending events
    local endedEvents = Scheduler.CheckEventEnds()
    for _, eventType in ipairs(endedEvents) do
        OnEventEnd(eventType)
    end
    
    -- Check for notices
    local noticeEvents = Scheduler.CheckEventNotices()
    for _, eventType in ipairs(noticeEvents) do
        OnEventNotice(eventType)
    end
end

function OnEventStart(eventType)
    local name = Scheduler.GetEventName(eventType)
    Message.Send(-1, 1, string.format("%s has started!", name))
    Log.Add(string.format("Event %d started: %s", eventType, name))
end

function OnEventEnd(eventType)
    local name = Scheduler.GetEventName(eventType)
    Message.Send(-1, 1, string.format("%s has ended!", name))
    Log.Add(string.format("Event %d ended: %s", eventType, name))
end

function OnEventNotice(eventType)
    local name = Scheduler.GetEventName(eventType)
    local remaining = Scheduler.GetRemainingTime(eventType)
    
    if remaining > 0 then
        local mins = math.floor(remaining / 60)
        Message.Send(-1, 1, string.format(
            "%s starts in %d minutes!", name, mins
        ))
    end
end

Example 2: Event Timer Display

function DisplayEventTimer(eventType)
    if not Scheduler.IsEventActive(eventType) then
        Message.Send(-1, 0, "No event is currently running")
        return
    end
    
    local name = Scheduler.GetEventName(eventType)
    local elapsed = Scheduler.GetElapsedTime(eventType)
    local remaining = Scheduler.GetRemainingTime(eventType)
    local progress = Scheduler.GetProgress(eventType)
    
    if remaining >= 0 then
        local mins = math.floor(remaining / 60)
        local secs = remaining % 60
        
        Message.Send(-1, 1, string.format(
            "%s | Time Left: %d:%02d | Progress: %d%%",
            name, mins, secs, progress
        ))
    else
        Message.Send(-1, 1, string.format(
            "%s | Elapsed: %d seconds",
            name, elapsed
        ))
    end
end

-- Display timer every 30 seconds
Timer.CreateRepeating(30000, "event_timer_display", function()
    if Scheduler.IsEventActive(100) then
        DisplayEventTimer(100)
    end
end)

Example 3: Event Phase System

function GetEventPhase(eventType)
    local progress = Scheduler.GetProgress(eventType)
    
    if progress < 0 then
        return "INACTIVE"
    elseif progress < 25 then
        return "STARTING"
    elseif progress < 75 then
        return "ACTIVE"
    elseif progress < 100 then
        return "ENDING"
    else
        return "FINISHED"
    end
end

function OnEventTick(eventType)
    local phase = GetEventPhase(eventType)
    
    if phase == "STARTING" then
        -- Event just started, spawn monsters
        SpawnEventMonsters()
    elseif phase == "ENDING" then
        -- Event ending soon, final warning
        local remaining = Scheduler.GetRemainingTime(eventType)
        if remaining <= 60 then
            Message.Send(-1, 1, "Event ending in 1 minute!")
        end
    end
end

Example 4: Dynamic Rewards Based on Progress

function CalculateEventReward(eventType, baseReward)
    local progress = Scheduler.GetProgress(eventType)
    
    if progress < 0 then
        return 0
    end
    
    -- Bonus for completing event
    local multiplier = 1.0
    
    if progress >= 100 then
        multiplier = 2.0  -- Double reward for full completion
    elseif progress >= 75 then
        multiplier = 1.5  -- 50% bonus for 75%+ completion
    elseif progress >= 50 then
        multiplier = 1.25 -- 25% bonus for 50%+ completion
    end
    
    return math.floor(baseReward * multiplier)
end

function GiveEventCompletionReward(oPlayer, eventType)
    local reward = CalculateEventReward(eventType, 100000)

    if reward > 0 then
        Player.SetMoney(oPlayer.Index, reward, false)
        Message.Send(0, oPlayer.Index, 0, string.format(
            "Event Reward: +%d Zen!", reward
        ))
    end
end

Example 5: Countdown Announcements

-- Announce remaining time at specific intervals
Timer.CreateRepeating(60000, "event_countdown", function()
    local eventType = 100
    
    if not Scheduler.IsEventActive(eventType) then
        return
    end
    
    local remaining = Scheduler.GetRemainingTime(eventType)
    
    if remaining < 0 then
        return
    end
    
    local mins = math.floor(remaining / 60)
    local name = Scheduler.GetEventName(eventType)
    
    -- Announce at specific time marks
    if remaining == 600 then
        Message.Send(-1, 1, string.format("%s ends in 10 minutes!", name))
    elseif remaining == 300 then
        Message.Send(-1, 1, string.format("%s ends in 5 minutes!", name))
    elseif remaining == 60 then
        Message.Send(-1, 1, string.format("%s ends in 1 minute!", name))
    elseif remaining % 600 == 0 and mins > 10 then
        Message.Send(-1, 0, string.format("%s: %d minutes remaining", name, mins))
    end
end)

Example 6: Event Statistics Tracking

local eventStats = {}

function OnEventStart(eventType)
    eventStats[eventType] = {
        startTime = os.time(),
        participants = 0,
        completions = 0
    }
    
    local name = Scheduler.GetEventName(eventType)
    Message.Send(-1, 1, string.format("%s has started!", name))
end

function OnEventEnd(eventType)
    local stats = eventStats[eventType]
    if stats then
        local name = Scheduler.GetEventName(eventType)
        local duration = os.time() - stats.startTime
        
        Log.Add(string.format(
            "Event %s ended | Duration: %ds | Participants: %d | Completions: %d",
            name, duration, stats.participants, stats.completions
        ))
        
        Message.Send(-1, 1, string.format(
            "%s ended! %d players participated, %d completed",
            name, stats.participants, stats.completions
        ))
    end
end

function TrackEventParticipation(oPlayer, eventType)
    if eventStats[eventType] then
        eventStats[eventType].participants = eventStats[eventType].participants + 1
    end
end

function TrackEventCompletion(oPlayer, eventType)
    if eventStats[eventType] then
        eventStats[eventType].completions = eventStats[eventType].completions + 1
    end
end

Example 7: Multiple Event Management

function GetActiveEvents()
    local activeEvents = {}
    local totalEvents = Scheduler.GetEventCount()
    
    -- Check all possible event types (adjust range as needed)
    for eventType = 1, 200 do
        if Scheduler.IsEventActive(eventType) then
            table.insert(activeEvents, {
                type = eventType,
                name = Scheduler.GetEventName(eventType),
                elapsed = Scheduler.GetElapsedTime(eventType),
                remaining = Scheduler.GetRemainingTime(eventType),
                progress = Scheduler.GetProgress(eventType)
            })
        end
    end
    
    return activeEvents
end

function DisplayAllActiveEvents()
    local events = GetActiveEvents()
    
    if #events == 0 then
        Message.Send(-1, 0, "No events currently active")
        return
    end
    
    Message.Send(-1, 1, string.format("=== %d Active Events ===", #events))
    
    for _, event in ipairs(events) do
        if event.remaining >= 0 then
            local mins = math.floor(event.remaining / 60)
            Message.Send(-1, 0, string.format(
                "%s: %d mins left (%d%%)",
                event.name, mins, event.progress
            ))
        else
            Message.Send(-1, 0, string.format(
                "%s: Active (%d seconds elapsed)",
                event.name, event.elapsed
            ))
        end
    end
end

XML Configuration Format

<?xml version="1.0" encoding="utf-8"?>
<EventScheduler>
    <!-- Event with duration and notice -->
    <Event Type="100" Name="Blood Castle" NoticePeriod="300" Duration="900">
        <Start Hour="12" Minute="0" />
        <Start Hour="18" Minute="0" />
        <Start Hour="22" Minute="0" />
    </Event>
    
    <!-- Event every day at specific time -->
    <Event Type="101" Name="Devil Square" Duration="600">
        <Start Hour="14" Minute="30" />
        <Start Hour="20" Minute="30" />
    </Event>
    
    <!-- Event on specific day of week -->
    <Event Type="102" Name="Castle Siege" Duration="7200">
        <Start DayOfWeek="0" Hour="20" Minute="0" />  <!-- Sunday -->
    </Event>
    
    <!-- Event every hour -->
    <Event Type="103" Name="Golden Invasion" Duration="300">
        <Start Minute="0" />  <!-- Every hour at :00 -->
    </Event>
    
    <!-- Event with second precision -->
    <Event Type="104" Name="Test Event" Duration="60">
        <Start Hour="15" Minute="30" Second="0" />
    </Event>
</EventScheduler>

Time Component Values:

  • Year: Specific year (e.g., 2025) or -1 for any year
  • Month: 1-12 or -1 for any month
  • Day: 1-31 or -1 for any day
  • DayOfWeek: 0-6 (0=Sunday, 6=Saturday) or -1 for any day
  • Hour: 0-23 or -1 for any hour
  • Minute: 0-59 or -1 for any minute
  • Second: 0-59 or -1 for any second

Note: Use -1 or omit attribute to match "any" value for that component.

Data Structures

EventNotice

Returned by Scheduler.CheckEventNotices(). Contains event type and time until start.

Properties:

  • type (number): Event type ID
  • secondsRemaining (number): Seconds until event starts

Example:

local notices = Scheduler.CheckEventNotices()
for _, notice in ipairs(notices) do
    print(string.format("Event %d starts in %d seconds", notice.type, notice.secondsRemaining))
end

Note: EventNotice is a C++ usertype registered with Sol3. Access fields directly as shown above.

Best Practices

  1. Check Frequency: Call CheckEventStarts(), CheckEventEnds(), CheckEventNotices() every second if using second-precision, otherwise every minute is sufficient

  2. Event IDs: Use consistent event type IDs (1-999 recommended) and document them

  3. Duration: Always specify duration (in seconds) for events that should auto-end

  4. Notice Period: Set notice period (in seconds) for events that need pre-start announcements

  5. Active Check: Always check IsEventActive() before using time functions

  6. Error Handling: Check return values (-1 indicates inactive/invalid event)

  7. Notice Formatting: Use helper functions like FormatTimeRemaining() to display user-friendly time strings instead of raw seconds

  8. EventNotice Access: Access EventNotice fields directly (notice.type, notice.secondsRemaining), not as function calls

Notes

  • Scheduler automatically prevents duplicate triggers within the same second
  • Events with no duration remain active until manually ended
  • Multiple start times can be defined per event
  • Time components support wildcards (-1 = any)
  • All time functions return -1 for inactive events
  • Progress and remaining time require event to have duration defined
  • CheckEventNotices() returns EventNotice objects (not just event type IDs) with time information included
  • EventNotice is a C++ usertype - access fields directly, not as methods

See Also


📦 Structures

⚙️ Functions

⏱️ Event & Timing Systems


Version 1.5 | 2026-05-06 14:10 GMT+2

Clone this wiki locally