| name |
description |
| modernuo-event-scheduler |
Trigger when creating holiday events, seasonal content, scheduled maintenance, daily/weekly resets, or any wall-clock/calendar-based scheduling. When using EventScheduler, ScheduledEvent, YearlyScheduledEvent, or IRecurrencePattern.
|
ModernUO EventScheduler (Wall-Clock / Calendar Scheduling)
When This Activates
- Creating holiday or seasonal events (Halloween, Christmas, etc.)
- Scheduling daily/weekly/monthly resets or activities
- Any event that must fire at a real-world time or date
- Working with
EventScheduler, ScheduledEvent, YearlyScheduledEvent, CallbackScheduledEvent
- Working with
IRecurrencePattern, AllowedDays, AllowedMonths, MonthDay
Key Rules
- EventScheduler for wall-clock/calendar, Timer for game-tick delays — if the event is "at 9 AM every Monday," use EventScheduler; if it's "5 seconds from now," use Timer
- Always specify timezone for local-time events — omitting defaults to UTC
- Prefer
CallbackScheduledEvent (via static methods) for simple recurring actions
- Use
YearlyScheduledEvent for seasonal windows (e.g., Oct 15 - Nov 1 each year)
- Cancel events on cleanup — call
Cancel() when disabling or shutting down
- 1-second granularity — EventScheduler ticks every second, not suitable for sub-second precision
Timer vs EventScheduler Decision
| Need |
Use |
| "Every 5 seconds" |
Timer.StartTimer |
| "At 6:00 AM daily" |
EventScheduler.DailyAt |
| "Delete after 10 seconds" |
Timer.StartTimer |
| "Every Monday at noon" |
EventScheduler.WeeklyAt |
| "Combat tick every 250ms" |
Timer.StartTimer |
| "Oct 15 - Nov 1 each year" |
YearlyScheduledEvent |
| "First Tuesday of each month" |
MonthlyOrdinalRecurrencePattern |
Quick Patterns
Daily Event at a Specific Time
var eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var startOn = new DateTime(2025, 1, 1, 6, 0, 0); // 6:00 AM
EventScheduler.DailyAt(startOn, ResetDailyQuests, eastern);
Weekly Event
var startOn = new DateTime(2025, 1, 6, 18, 0, 0); // Monday 6:00 PM
EventScheduler.WeeklyAt(startOn, StartWeeklyTournament, eastern);
Monthly Event on a Specific Day
var startOn = new DateTime(2025, 1, 15, 12, 0, 0); // 15th at noon
EventScheduler.MonthlyAt(startOn, MonthlyRewards, eastern);
Yearly Seasonal Event (with Window)
var halloween = new YearlyCallbackScheduledEvent(
new TimeOnly(0, 0),
new MonthDay(2025, 10, 15), // Start: Oct 15
new MonthDay(2025, 11, 1), // End: Nov 1
SpawnHalloweenContent,
EventScheduler.Daily
);
halloween.Schedule(DateTime.UtcNow, eastern);
Filtered Weekly (Specific Days/Months)
var pattern = new WeeklyRecurrencePattern(
intervalWeeks: 1,
allowedMonths: AllowedMonths.June | AllowedMonths.July | AllowedMonths.August,
allowedDays: AllowedDays.Friday | AllowedDays.Saturday
);
var evt = new CallbackScheduledEvent(new TimeOnly(20, 0), SummerWeekendEvent, pattern);
evt.Schedule(DateTime.UtcNow, eastern);
Ordinal Monthly (e.g., "Second Tuesday")
var pattern = new MonthlyOrdinalRecurrencePattern(
OrdinalDayOccurrence.Second,
DayOfWeek.Tuesday
);
var evt = new CallbackScheduledEvent(new TimeOnly(12, 0), MonthlyMeeting, pattern);
evt.Schedule(DateTime.UtcNow, eastern);
Custom Event Class Template
using System;
using Server.Engines.Events;
public class MyScheduledEvent : ScheduledEvent
{
public MyScheduledEvent(TimeOnly time, IRecurrencePattern recurrence)
: base(time, recurrence)
{
}
public override void OnEvent()
{
// Your event logic here
}
}
// Schedule it:
var evt = new MyScheduledEvent(new TimeOnly(9, 0), EventScheduler.Daily);
evt.Schedule(DateTime.UtcNow, timeZone);
// Cancel it:
evt.Cancel();
Custom Yearly Seasonal Event Template
public class MySeasonalEvent : YearlyScheduledEvent
{
protected MySeasonalEvent(
TimeOnly time,
MonthDay yearlyStart,
MonthDay yearlyEnd,
IRecurrencePattern recurrence
) : base(time, yearlyStart, yearlyEnd, recurrence)
{
}
public override void OnEvent()
{
// Only fires when date is within [yearlyStart, yearlyEnd]
}
}
Anti-Patterns
- Using
Timer.StartTimer for calendar events: Timers drift across restarts and have no timezone support — use EventScheduler
- Forgetting to specify timezone: Event fires at UTC instead of expected local time — always pass
TimeZoneInfo
- Not cancelling events on cleanup: Scheduled events keep firing after the system is disabled — call
Cancel()
- Using EventScheduler for sub-second timing: 1-second granularity is too coarse — use
Timer.StartTimer
- Constructing
MonthDay with invalid day: Throws ArgumentOutOfRangeException — validate against DateTime.DaysInMonth
See Also
dev-docs/event-scheduler.md — Complete EventScheduler documentation
dev-docs/timers.md — Game-tick timer system (Timer.StartTimer, TimerExecutionToken)
dev-docs/claude-skills/modernuo-timers.md — Timer skill for game-tick delays