-
Notifications
You must be signed in to change notification settings - Fork 6
Scheduler
Event scheduler system for managing timed events with XML configuration support. Supports recurring events, notices, and duration-based events.
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!")
endGet 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))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)
endCheck 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)
endCheck 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))
endHelper 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
endOutput Examples:
- "Blood Castle starts in 5 minutes!"
- "Devil Square starts in 1 hour 30 minutes!"
- "Golden Invasion starts in 45 seconds!"
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))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
endCheck 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")
endGet 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))
endGet 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
))
endGet 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-- 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
endfunction 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)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
endfunction 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-- 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)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
endfunction 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 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.
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))
endNote: EventNotice is a C++ usertype registered with Sol3. Access fields directly as shown above.
-
Check Frequency: Call
CheckEventStarts(),CheckEventEnds(),CheckEventNotices()every second if using second-precision, otherwise every minute is sufficient -
Event IDs: Use consistent event type IDs (1-999 recommended) and document them
-
Duration: Always specify duration (in seconds) for events that should auto-end
-
Notice Period: Set notice period (in seconds) for events that need pre-start announcements
-
Active Check: Always check
IsEventActive()before using time functions -
Error Handling: Check return values (-1 indicates inactive/invalid event)
-
Notice Formatting: Use helper functions like
FormatTimeRemaining()to display user-friendly time strings instead of raw seconds -
EventNotice Access: Access EventNotice fields directly (
notice.type,notice.secondsRemaining), not as function calls
- 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
- Timer API - For custom timers
- Message API - For event announcements
MuLua Scripting Plugin | Home
Version 1.5 | 2026-05-06 14:10 GMT+2