/// <summary> /// Returns an instance of ErinnTime that's set to the next time the /// given hour and minute is. /// </summary> /// <example> /// If it's 00:00:00 in real-time (00:00 in Erinn-time), then /// GetNextTime(12, 00) would return Erinn-time 12:00, with a /// DateTime of 00:18:00. /// </example> /// <param name="hour"></param> /// <param name="minutes"></param> /// <returns></returns> public static ErinnTime GetNextTime(DateTime now, int hour, int minute) { if (hour < 0 || hour > 23) { throw new ArgumentException("Invalid hour."); } if (minute < 0 || minute > 59) { throw new ArgumentException("Invalid minute."); } var nowErinn = new ErinnTime(now); var hours = hour - nowErinn.Hour; var minutes = minute - nowErinn.Minute; if (hours <= 0) { hours += 24; } if (minutes < 0) { minutes = 60 + minutes; hours -= 1; } var thenDateTime = now.AddTicks(hours * TicksPerHour).AddTicks(minutes * TicksPerMinute); return(new ErinnTime(thenDateTime)); }
/// <summary> /// Called every 5 minutes to synchronize guild information. /// </summary> /// <param name="now"></param> private void OnMabiTick(ErinnTime now) { // Synchronizing the guilds can take a few milliseconds if there // are many in the db. Run it in a thread so we don't block // the world thread. // Should this be the default for all time events...? Task.Factory.StartNew(SynchronizeGuilds); }
public void ErinnTimeToString() { var time1 = new ErinnTime(DateTime.Parse("2015-01-01 00:00")); Assert.Equal("550-4-01 00:00", time1.ToString()); Assert.Equal("0550-04-01 0:0", time1.ToString("yyyy-MM-dd H:m")); Assert.Equal("00:00 AM", time1.ToString("HH:mm tt")); var time2 = new ErinnTime(DateTime.Parse("2015-01-01 23:59")); Assert.Equal("23:20 PM", time2.ToString("HH:mm tt")); }
/// <summary> /// Called once a minute, used for custom weather and the change weather event. /// </summary> /// <param name="time"></param> private void OnMinutesTimeTick(ErinnTime time) { if (time.DateTime.Minute % 20 == 0) { var ev = this.WeatherChange; if (ev != null) { foreach (var provider in _providers) ev(provider.Key, provider.Value.GetWeather(time.DateTime)); } } // TODO: Custom weather? }
public void BeginOfTimeCap() { var time1 = new ErinnTime(ErinnTime.BeginOfTime); Assert.Equal("1-1-01 00:00", time1.ToString()); var time2 = new ErinnTime(DateTime.MinValue); Assert.Equal("1-1-01 00:00", time2.ToString()); var time3 = new ErinnTime(new DateTime(2000, 1, 1)); Assert.Equal("1-1-01 00:00", time3.ToString()); var time4 = new ErinnTime(DateTime.MaxValue); Assert.Equal("417187-6-40 23:59", time4.ToString()); }
public void ErinnTimeCalculation() { var time1 = new ErinnTime(DateTime.Parse("2015-01-01 00:00")); Assert.Equal(550, time1.Year); Assert.Equal(4, time1.Month); Assert.Equal(1, time1.Day); Assert.Equal(0, time1.Hour); Assert.Equal(0, time1.Minute); var time2 = new ErinnTime(DateTime.Parse("2015-01-01 23:59")); Assert.Equal(550, time2.Year); Assert.Equal(4, time2.Month); Assert.Equal(40, time2.Day); Assert.Equal(23, time2.Hour); Assert.Equal(20, time2.Minute); var time3 = new ErinnTime(ErinnTime.BeginOfTime); Assert.Equal(1, time3.Year); Assert.Equal(2, time3.Month); Assert.Equal(1, time3.Day); Assert.Equal(0, time3.Hour); Assert.Equal(0, time3.Minute); }
/// <summary> /// Raised once ever RL hour. /// </summary> /// <param name="now"></param> private void OnHoursTimeTick(ErinnTime now) { // Update on midnight, for Erinn month checks if (now.DateTime.Hour == 0) this.UpdateStatBonuses(); }
/// <summary> /// Applies regens to creature. /// </summary> /// <remarks> /// (Should be) called once a second. /// - Hunger doesn't go beyond 50% of max stamina. /// - Stamina regens at 20% efficiency from StaminaHunger onwards. /// </remarks> public void OnSecondsTimeTick(ErinnTime time) { if (this.Creature.Region == Region.Limbo || this.Creature.IsDead) return; lock (_regens) { var toRemove = new List<int>(); foreach (var regen in _regens.Values) { if (regen.TimeLeft == 0) { toRemove.Add(regen.Id); continue; } switch (regen.Stat) { // TODO: Triple mana automatically at night? case Stat.Life: this.Creature.Life += regen.Change; break; case Stat.Mana: this.Creature.Mana += regen.Change; break; case Stat.Stamina: // Only positve regens are affected by the hunger multiplicator. this.Creature.Stamina += regen.Change * (regen.Change > 0 ? this.Creature.StaminaRegenMultiplicator : 1); break; case Stat.Hunger: // Regen can't lower hunger below a certain amount. this.Creature.Hunger += regen.Change; if (this.Creature.Hunger > this.Creature.StaminaMax / 2) this.Creature.Hunger = this.Creature.StaminaMax / 2; break; case Stat.LifeInjured: this.Creature.Injuries -= regen.Change; break; } } foreach (var id in toRemove) this.Remove(id); } }
/// <summary> /// Removes overdue conditions. /// </summary> /// <param name="time"></param> public void OnSecondsTimeTick(ErinnTime time) { lock (_durations) { var deactivate = _durations.Where(a => time.DateTime > a.Value).Select(a => a.Key).ToArray(); foreach (var conditionId in deactivate) { this.Deactivate(conditionId); _durations.Remove(conditionId); } } }
/// <summary> /// Raised every 5 minutes, removes empty dungeons. /// </summary> /// <remarks> /// TODO: Is removing on MabiTick what we want? How long do dungeons /// stay active before they're removed? This could remove a dungeon /// the minute, even the second, the last player leaves it. /// </remarks> /// <param name="time"></param> private void OnMabiTick(ErinnTime time) { lock (_createAndCleanUpLock) { List<long> remove; lock (_syncLock) remove = _dungeons.Values.Where(a => a.CountPlayers() == 0).Select(b => b.InstanceId).ToList(); foreach (var instanceId in remove) this.Remove(instanceId); } }
/// <summary> /// Returns true if the player can do a PTJ of type, because he hasn't /// done one of the same type today. /// </summary> /// <param name="type"></param> /// <returns></returns> public bool CanDoPtj(PtjType type, int remaining = 99) { // Always allow devCATs //if (this.Title == TitleId.devCAT) // return true; // Check remaining if (remaining <= 0) return false; // Check if PTJ has already been done this Erinn day var ptj = this.Player.Quests.GetPtjTrackRecord(type); var change = new ErinnTime(ptj.LastChange); var now = ErinnTime.Now; return (now.Day != change.Day || now.Month != change.Month || now.Year != change.Year); }
/// <summary> /// Called every 9 minutes, increases play points. /// </summary> /// <param name="now"></param> private void OnPlayTimeTick(ErinnTime now) { this.PlayPoints++; }
/// <summary> /// 5 min tick, global var saving. /// </summary> /// <param name="time"></param> public void OnMabiTick(ErinnTime time) { try { ChannelServer.Instance.Database.SaveVars("Aura System", 0, this.GlobalVars.Perm); Log.Info("Saved global script variables."); } catch (Exception ex) { Log.Exception(ex, "Failed to save global script variables."); } }
/// <summary> /// Called once a second, updates stat mods with timeout. /// </summary> /// <param name="time"></param> public void OnSecondsTimeTick(ErinnTime time) { lock (_mods) { foreach (var mod in _mods) { var stat = mod.Key; var list = mod.Value; var creature = _creature; var count = mod.Value.RemoveAll(a => a.TimeoutReached); if (count != 0) { this.UpdateCache(stat); Send.StatUpdate(creature, StatUpdateType.Private, stat); if (stat >= Stat.Life && stat <= Stat.LifeMaxMod) Send.StatUpdate(creature, StatUpdateType.Public, Stat.Life, Stat.LifeInjured, Stat.LifeMaxMod, Stat.LifeMax); } } } }
/// <summary> /// Returns an instance of ErinnTime that's set to the next time the /// given hour and minute is. /// </summary> /// <example> /// If it's 00:00:00 in real-time (00:00 in Erinn-time), then /// GetNextTime(12, 00) would return Erinn-time 12:00, with a /// DateTime of 00:18:00. /// </example> /// <param name="hour"></param> /// <param name="minutes"></param> /// <returns></returns> public static ErinnTime GetNextTime(DateTime now, int hour, int minute) { if (hour < 0 || hour > 23) throw new ArgumentException("Invalid hour."); if (minute < 0 || minute > 59) throw new ArgumentException("Invalid minute."); var nowErinn = new ErinnTime(now); var hours = hour - nowErinn.Hour; var minutes = minute - nowErinn.Minute; if (hours <= 0) { hours += 24; } if (minutes < 0) { minutes = 60 + minutes; hours -= 1; } var thenDateTime = now.AddTicks(hours * TicksPerHour).AddTicks(minutes * TicksPerMinute); return new ErinnTime(thenDateTime); }
/// <summary> /// Adds additional mana regen at night. /// </summary> /// <param name="time"></param> public void OnErinnDaytimeTick(ErinnTime time) { if (time.IsNight) { this.Add("NightMana", Stat.Mana, 0.1f, this.Creature.ManaMax); } else { this.Remove("NightMana"); } }
/// <summary> /// Handles regularly occuring events and raises time events. /// </summary> /// <remarks> /// On the first call all time events are raised, /// because lastHeartbeat is 0, and the events depend on the time /// since the last heartbeat. This also ensures that they aren't /// called multiple times. /// </remarks> private void Heartbeat(object _) { var now = new ErinnTime(DateTime.Now); var diff = (now.DateTime - _lastHeartbeat).TotalMilliseconds; if (diff != HeartbeatTime && Math.Abs(HeartbeatTime - diff) > HeartbeatTime && diff < 100000000) { Log.Debug("OMG, the server has an irregular heartbeat! ({0})", diff.ToInvariant()); } // Seconds event if ((_secondsTime += diff) >= Second) { _secondsTime = 0; ChannelServer.Instance.Events.OnSecondsTimeTick(now); } // Minutes event if ((_minutesTime += diff) >= Minute) { _minutesTime = (now.DateTime.Second * Second + now.DateTime.Millisecond); ChannelServer.Instance.Events.OnMinutesTimeTick(now); // Mabi tick event if (++_mabiTickCount >= 5) { ChannelServer.Instance.Events.OnMabiTick(now); _mabiTickCount = 0; } } // Hours event if ((_hoursTime += diff) >= Hour) { _hoursTime = (now.DateTime.Minute * Minute + now.DateTime.Second * Second + now.DateTime.Millisecond); ChannelServer.Instance.Events.OnHoursTimeTick(now); } // Erinn time event if ((_erinnTime += diff) >= ErinnMinute) { _erinnTime = 0; ChannelServer.Instance.Events.OnErinnTimeTick(now); // TODO: Dawn/Dusk/Midnight wouldn't be called if the server had a 500+ ms hickup. // Erinn daytime event if (now.IsDawn || now.IsDusk) { ChannelServer.Instance.Events.OnErinnDaytimeTick(now); this.OnErinnDaytimeTick(now); } // Erinn midnight event if (now.IsMidnight) ChannelServer.Instance.Events.OnErinnMidnightTick(now); } this.UpdateEntities(); _lastHeartbeat = now.DateTime; }
/// <summary> /// Called once per second, running updates on different /// creature components. /// </summary> /// <param name="obj"></param> private void OnSecondsTimeTick(ErinnTime time) { // TODO: General creature components in a list, with Update interface? this.Regens.OnSecondsTimeTick(time); this.StatMods.OnSecondsTimeTick(time); this.Conditions.OnSecondsTimeTick(time); this.Skills.OnSecondsTimeTick(time); this.PlayTime++; }
/// <summary> /// Broadcasts Eweca notice, called at 6:00 and 18:00. /// </summary> /// <param name="now"></param> private void OnErinnDaytimeTick(ErinnTime now) { var notice = now.IsNight ? Localization.Get("Eweca is rising.\nMana is starting to fill the air all around.") : Localization.Get("Eweca has disappeared.\nThe surrounding Mana is starting to fade away."); Send.Notice(NoticeType.MiddleTop, notice); }
/// <summary> /// Called at midnight (Erinn time). /// </summary> /// <param name="time"></param> protected virtual void OnErinnMidnightTick(ErinnTime time) { this.RandomizeItemColors(); }
/// <summary> /// Checks for skills to auto cancel. /// </summary> /// <param name="time"></param> public void OnSecondsTimeTick(ErinnTime time) { if (_autoCancel == SkillId.None) return; if (!this.IsActive(_autoCancel)) { _autoCancel = SkillId.None; return; } if (time.DateTime < _autoCancelTime) return; _autoCancel = SkillId.None; this.CancelActiveSkill(); }
/// <summary> /// Applies regens to creature. /// </summary> /// <remarks> /// (Should be) called once a second. /// - Hunger doesn't go beyond 50% of max stamina. /// - Stamina regens at 20% efficiency from StaminaHunger onwards. /// </remarks> public void OnSecondsTimeTick(ErinnTime time) { if (this.Creature.Region == Region.Limbo || this.Creature.IsDead) return; // Recover from knock back/down after stun ended if (this.Creature.WasKnockedBack && !this.Creature.IsStunned) { Send.RiseFromTheDead(this.Creature); this.Creature.WasKnockedBack = false; } lock (_regens) { var toRemove = new List<int>(); foreach (var regen in _regens.Values) { if (regen.TimeLeft == 0) { toRemove.Add(regen.Id); continue; } switch (regen.Stat) { case Stat.Life: this.Creature.Life += regen.Change; break; case Stat.Mana: this.Creature.Mana += regen.Change; break; case Stat.Stamina: // Only positve regens are affected by the hunger multiplicator. this.Creature.Stamina += regen.Change * (regen.Change > 0 ? this.Creature.StaminaRegenMultiplicator : 1); break; case Stat.Hunger: // Regen can't lower hunger below a certain amount. this.Creature.Hunger += regen.Change; if (this.Creature.Hunger > this.Creature.StaminaMax / 2) this.Creature.Hunger = this.Creature.StaminaMax / 2; break; case Stat.LifeInjured: this.Creature.Injuries -= regen.Change; break; } } foreach (var id in toRemove) this.Remove(id); } // Add additional mana regen at night if (_night != time.IsNight) { if (time.IsNight) this.Add("NightMana", Stat.Mana, 0.1f, this.Creature.ManaMax); else this.Remove("NightMana"); _night = time.IsNight; } this.UpdateToxicity(); }
public void ErinnTimeToString() { var time1 = new ErinnTime(DateTime.Parse("2015-01-01 00:00")); Assert.Equal("550-5-01 00:00", time1.ToString()); Assert.Equal("0550-05-01 0:0", time1.ToString("yyyy-MM-dd H:m")); Assert.Equal("00:00 AM", time1.ToString("HH:mm tt")); Assert.Equal("01 Lughnasadh 550", time1.ToString("dd MMMM yyy")); ErinnTime.SetMonthNames("0", "1", "2", "3", "Foobar", "5", "6"); Assert.Equal("01 Foobar 550", time1.ToString("dd MMMM yyy")); var time2 = new ErinnTime(DateTime.Parse("2015-01-01 23:59")); Assert.Equal("23:20 PM", time2.ToString("HH:mm tt")); var dt3 = DateTime.Parse("2016-06-26 00:00"); for (int year = 0; year < 2; ++year) { for (int month = 0; month < 7; ++month) { for (int day = 0; day < 40; ++day) { for (int hour = 0; hour < 24; ++hour) { for (int minute = 0; minute < 60; ++minute) { var time = new ErinnTime(dt3.AddMilliseconds((year * (7 * 40 * 24 * 60 * 1500)) + (month * (40 * 24 * 60 * 1500)) + (day * (24 * 60 * 1500)) + (hour * (60 * 1500)) + (minute * 1500))); Assert.Equal(string.Format("{0}-{1}-{2:00} {3:00}:{4:00}", 628 + year, month + 1, day + 1, hour, minute), time.ToString()); } } } } } }
/// <summary> /// Raised once every minute. /// </summary> /// <param name="time"></param> private void OnMinutesTimeTick(ErinnTime time) { if (this.Type == PartyType.Dungeon && this.IsOpen) { if ((_adTimer++) < 5) return; Send.PartyAdChat(this); } _adTimer = 0; }
public void ErinnTimeCalculation() { var time1 = new ErinnTime(DateTime.Parse("2015-01-01 00:00")); Assert.Equal(550, time1.Year); Assert.Equal(5, time1.Month); Assert.Equal(1, time1.Day); Assert.Equal(0, time1.Hour); Assert.Equal(0, time1.Minute); var time2 = new ErinnTime(DateTime.Parse("2015-01-01 23:59")); Assert.Equal(550, time2.Year); Assert.Equal(5, time2.Month); Assert.Equal(40, time2.Day); Assert.Equal(23, time2.Hour); Assert.Equal(20, time2.Minute); var time3 = new ErinnTime(ErinnTime.BeginOfTime); Assert.Equal(1, time3.Year); Assert.Equal(1, time3.Month); Assert.Equal(1, time3.Day); Assert.Equal(0, time3.Hour); Assert.Equal(0, time3.Minute); var dt4 = DateTime.Parse("2016-06-26 00:00"); for (int i = 0; i < 10; ++i) { var time = new ErinnTime(dt4.AddDays(i * 7)); Assert.Equal(628 + i * 1, time.Year); Assert.Equal(1, time.Month); Assert.Equal(1, time.Day); Assert.Equal(0, time.Hour); Assert.Equal(0, time.Minute); } var dt5 = DateTime.Parse("2016-06-26 00:00"); for (int i = 0; i < 7; ++i) { var time = new ErinnTime(dt5.AddDays(i)); Assert.Equal(628, time.Year); Assert.Equal(i + 1, time.Month); Assert.Equal(1, time.Day); Assert.Equal(0, time.Hour); Assert.Equal(0, time.Minute); } var dt6 = DateTime.Parse("2016-06-26 00:00"); for (int i = 0; i < 24; ++i) { var time = new ErinnTime(dt6.AddMilliseconds(i * (24 * 60 * 1500))); Assert.Equal(628, time.Year); Assert.Equal(1, time.Month); Assert.Equal(i + 1, time.Day); Assert.Equal(0, time.Hour); Assert.Equal(0, time.Minute); } var dt7 = DateTime.Parse("2016-06-26 00:00"); for (int i = 0; i < 24; ++i) { for (int j = 0; j < 60; ++j) { var time = new ErinnTime(dt6.AddMilliseconds((i * (60 * 1500)) + (j * 1500))); Assert.Equal(628, time.Year); Assert.Equal(1, time.Month); Assert.Equal(1, time.Day); Assert.Equal(i, time.Hour); Assert.Equal(j, time.Minute); } } }
/// <summary> /// Called every 5 minutes, checks changes through food. /// </summary> /// <param name="time"></param> public void OnMabiTick(ErinnTime time) { this.UpdateBody(); this.EquipmentDecay(); }
/// <summary> /// 5 min tick, global var saving. /// </summary> /// <param name="time"></param> public void OnMabiTick(ErinnTime time) { ChannelServer.Instance.Database.SaveVars("Aura System", 0, this.GlobalVars.Perm); Log.Info("Saved global script variables."); }