private bool SynchronizeWithVanillaEvents()
        {
            bool result            = false;
            var  oneMinuteInterval = TimeSpan.FromMinutes(1);

            foreach (ushort eventId in eventManager.GetUpcomingEvents(timeInfo.Now.Date, timeInfo.Now.AddDays(1)))
            {
                if (!eventManager.TryGetEventInfo(eventId, out ushort buildingId, out DateTime startTime, out float duration, out float ticketPrice))
                {
                    continue;
                }

                VanillaEvent existingVanillaEvent = CityEvents
                                                    .OfType <VanillaEvent>()
                                                    .FirstOrDefault(e => e.BuildingId == buildingId && e.EventId == eventId);

                if (existingVanillaEvent != null)
                {
                    if (existingVanillaEvent.StartTime.RoundCeil(oneMinuteInterval) == startTime.RoundCeil(oneMinuteInterval))
                    {
                        continue;
                    }
                    else
                    {
                        upcomingEvents.Remove(existingVanillaEvent);
                    }
                }

                DateTime adjustedStartTime = AdjustEventStartTime(startTime, false);
                if (adjustedStartTime != startTime)
                {
                    startTime = adjustedStartTime;
                    eventManager.SetStartTime(eventId, startTime);
                }

                var newEvent = new VanillaEvent(eventId, duration, ticketPrice);
                newEvent.Configure(buildingId, buildingManager.GetBuildingName(buildingId), startTime);
                result = true;
                Log.Debug(LogCategory.Events, timeInfo.Now, $"Vanilla event registered for {newEvent.BuildingId}, start time {newEvent.StartTime}, end time {newEvent.EndTime}");

                LinkedListNode <ICityEvent> existingEvent = upcomingEvents.FirstOrDefaultNode(e => e.StartTime > startTime);
                if (existingEvent == null)
                {
                    upcomingEvents.AddLast(newEvent);
                }
                else
                {
                    upcomingEvents.AddBefore(existingEvent, newEvent);
                    if (existingEvent.Value.StartTime < newEvent.EndTime)
                    {
                        // Avoid multiple events at the same time - vanilla events have priority
                        upcomingEvents.Remove(existingEvent);
                        earliestEvent = newEvent.EndTime.AddHours(12f);
                    }
                }
            }

            return(result);
        }
        private bool SynchronizeWithVanillaEvent(ushort eventId)
        {
            if (!eventManager.TryGetEventInfo(eventId, out var eventInfo))
            {
                return(false);
            }

            var startTime = eventInfo.StartTime;

            if (startTime.AddHours(eventInfo.Duration) < timeInfo.Now)
            {
                return(false);
            }

            var existingVanillaEvent = GetVanillaEvent(AllEvents, eventId, eventInfo.BuildingId);

            if (existingVanillaEvent != null)
            {
                if (Math.Abs((startTime - existingVanillaEvent.StartTime).TotalMinutes) <= 5d)
                {
                    return(false);
                }
                else if (activeEvents.Contains(existingVanillaEvent))
                {
                    activeEvents.Remove(existingVanillaEvent);
                }
                else
                {
                    upcomingEvents.Remove(existingVanillaEvent);
                }
            }

            var newStartTime = EnsureUniqueStartTime(startTime);

            if (newStartTime != startTime)
            {
                startTime = newStartTime;
                eventManager.SetStartTime(eventId, startTime);
            }

            var newEvent = new VanillaEvent(eventId, eventInfo.Duration, eventInfo.TicketPrice, eventManager);

            newEvent.Configure(eventInfo.BuildingId, buildingManager.GetBuildingName(eventInfo.BuildingId), startTime);
            Log.Debug(LogCategory.Events, timeInfo.Now, $"Vanilla event registered for {newEvent.BuildingId}, start time {newEvent.StartTime}, end time {newEvent.EndTime}");

            var existingEvent = upcomingEvents.FirstOrDefaultNode(e => e.StartTime >= startTime);

            if (existingEvent == null)
            {
                upcomingEvents.AddLast(newEvent);
            }
            else
            {
                upcomingEvents.AddBefore(existingEvent, newEvent);
            }

            return(true);
        }
        private void Update()
        {
            if (activeEvent != null && activeEvent.EndTime <= timeInfo.Now)
            {
                Log.Debug(timeInfo.Now, $"Event finished in {activeEvent.BuildingId}, started at {activeEvent.StartTime}, end time {activeEvent.EndTime}");
                lastActiveEvent = activeEvent;
                activeEvent     = null;
            }

            bool eventsChanged = false;

            foreach (ushort eventId in eventManager.GetUpcomingEvents(timeInfo.Now, timeInfo.Now.AddDays(1)))
            {
                eventManager.TryGetEventInfo(eventId, out ushort buildingId, out DateTime startTime, out float duration, out float ticketPrice);

                if (upcomingEvents.Concat(new[] { activeEvent })
                    .OfType <VanillaEvent>()
                    .Any(e => e.BuildingId == buildingId && e.StartTime == startTime))
                {
                    continue;
                }

                var newEvent = new VanillaEvent(duration, ticketPrice);
                newEvent.Configure(buildingId, buildingManager.GetBuildingName(buildingId), startTime);
                eventsChanged = true;
                Log.Debug(timeInfo.Now, $"Vanilla event registered for {newEvent.BuildingId}, start time {newEvent.StartTime}, end time {newEvent.EndTime}");

                LinkedListNode <ICityEvent> existingEvent = upcomingEvents.FirstOrDefaultNode(e => e.StartTime > startTime);
                if (existingEvent == null)
                {
                    upcomingEvents.AddLast(newEvent);
                }
                else
                {
                    upcomingEvents.AddBefore(existingEvent, newEvent);
                }
            }

            if (upcomingEvents.Count == 0)
            {
                return;
            }

            ICityEvent upcomingEvent = upcomingEvents.First.Value;

            if (upcomingEvent.StartTime <= timeInfo.Now)
            {
                activeEvent = upcomingEvent;
                upcomingEvents.RemoveFirst();
                eventsChanged = true;
                Log.Debug(timeInfo.Now, $"Event started! Building {activeEvent.BuildingId}, ends on {activeEvent.EndTime}");
            }

            if (eventsChanged)
            {
                OnEventsChanged();
            }
        }