Adventure: defer random-event roll to 1-3h after a player acts

Previous scheduler built one fixed roll-minute per player per UTC day in
the 10:00-16:00 window, regardless of when (or whether) the player
actually played. Players who acted late in the day might miss their roll
entirely; players who acted early still had to wait for an arbitrary
mid-day window.

New scheduler:
- Each ticker minute, scan for players who have acted today but don't
  yet have a scheduled roll. Assign each a one-shot roll-minute 60-180
  minutes in the future (capped to 23:50 UTC).
- At the assigned minute, fire the 0.5% trigger roll. Mark advEventRolled
  so it won't fire again that day.
- New day rebuilds both maps fresh.

Result: events trigger relative to the player's actual activity, not
clock time. Late-day actors get their chance; players who skip a day
don't get a roll.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-04-28 22:36:52 -07:00
parent 6eadb09c1e
commit 7878ce0999

View File

@@ -24,27 +24,17 @@ type advActiveEvent struct {
}
// ── In-memory schedule ───────────────────────────────────────────────────────
// Each day, every eligible player is assigned a random minute between 10:00
// and 16:00 UTC at which the 0.5% trigger roll happens.
// When a player acts on a given day, the next ticker iteration assigns them
// a one-shot roll-minute 60180 minutes in the future. At that minute, the
// 0.5% trigger roll fires. Each player rolls at most once per UTC day.
var (
advEventScheduleMu sync.Mutex
advEventSchedule map[string]int // userID string -> minute-of-day (600..959)
advEventScheduleDay string // "2006-01-02" the schedule was built for
advEventScheduleMu sync.Mutex
advEventSchedule map[string]int // userID -> minute-of-day for the deferred roll
advEventRolled map[string]bool // userID -> already rolled today
advEventScheduleDay string // "2006-01-02" the maps belong to
)
func advBuildEventSchedule(chars []AdventureCharacter) {
advEventSchedule = make(map[string]int, len(chars))
for _, c := range chars {
if !c.Alive {
continue
}
// Random minute between 600 (10:00) and 959 (15:59)
advEventSchedule[string(c.UserID)] = 600 + rand.IntN(360)
}
advEventScheduleDay = time.Now().UTC().Format("2006-01-02")
}
// ── Event Ticker ─────────────────────────────────────────────────────────────
func (p *AdventurePlugin) eventTicker() {
@@ -54,34 +44,51 @@ func (p *AdventurePlugin) eventTicker() {
for range ticker.C {
now := time.Now().UTC()
dateKey := now.Format("2006-01-02")
currentMinute := now.Hour()*60 + now.Minute()
// Expire stale pending events every tick
expireAdvPendingEvents()
// Outside the trigger window — nothing to do
if now.Hour() < 10 || now.Hour() >= 16 {
continue
}
// Rebuild schedule if it's a new day or uninitialised
advEventScheduleMu.Lock()
if advEventScheduleDay != dateKey {
chars, err := loadAllAdvCharacters()
if err != nil {
slog.Error("adventure: events: failed to load chars for schedule", "err", err)
advEventScheduleMu.Unlock()
continue
}
advBuildEventSchedule(chars)
slog.Info("adventure: event schedule built", "players", len(advEventSchedule))
advEventSchedule = make(map[string]int)
advEventRolled = make(map[string]bool)
advEventScheduleDay = dateKey
}
// Find players whose roll minute is now
currentMinute := now.Hour()*60 + now.Minute()
// Schedule deferred rolls for any newly-acted players
chars, err := loadAllAdvCharacters()
if err != nil {
slog.Error("adventure: events: failed to load chars", "err", err)
advEventScheduleMu.Unlock()
continue
}
for _, c := range chars {
uid := string(c.UserID)
if !c.Alive || advEventRolled[uid] {
continue
}
if _, scheduled := advEventSchedule[uid]; scheduled {
continue
}
if !c.HasActedToday() {
continue
}
// Assign a one-shot roll 60180 minutes from now, capped to 23:50 UTC.
rollMinute := currentMinute + 60 + rand.IntN(121)
if rollMinute > 23*60+50 {
rollMinute = 23*60 + 50
}
advEventSchedule[uid] = rollMinute
slog.Info("adventure: event roll scheduled", "user", uid, "minute", rollMinute)
}
// Find players whose roll-minute has arrived
var toRoll []id.UserID
for uid, minute := range advEventSchedule {
if minute == currentMinute {
if minute <= currentMinute && !advEventRolled[uid] {
toRoll = append(toRoll, id.UserID(uid))
advEventRolled[uid] = true
}
}
advEventScheduleMu.Unlock()