using System; using System.Collections.Generic; using Server.Items; using Server.Network; namespace Server.Misc { public class Weather { private static Map[] m_Facets; private static readonly Dictionary> m_WeatherByFacet = new(); private bool m_Active; private bool m_ExtremeTemperature; private int m_Stage; public Weather( Map facet, Rectangle2D[] area, int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, TimeSpan interval ) { Facet = facet; Area = area; Temperature = temperature; ChanceOfPercipitation = chanceOfPercipitation; ChanceOfExtremeTemperature = chanceOfExtremeTemperature; var list = GetWeatherList(facet); list?.Add(this); Timer.DelayCall( TimeSpan.FromSeconds((0.2 + Utility.RandomDouble() * 0.8) * interval.TotalSeconds), interval, OnTick ); } public Map Facet { get; } public Rectangle2D[] Area { get; set; } public int Temperature { get; set; } public int ChanceOfPercipitation { get; set; } public int ChanceOfExtremeTemperature { get; set; } // For dynamic weather: public Rectangle2D Bounds { get; set; } public int MoveSpeed { get; set; } public int MoveAngleX { get; set; } public int MoveAngleY { get; set; } public static void Initialize() { m_Facets = new[] { Map.Felucca, Map.Trammel }; /* Static weather: * * Format: * AddWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, ); */ // ice island AddWeather( -15, 100, 5, new Rectangle2D(3850, 160, 390, 320), new Rectangle2D(3900, 480, 380, 180), new Rectangle2D(4160, 660, 150, 110) ); // covetous entrance, around vesper and minoc AddWeather(+15, 50, 5, new Rectangle2D(2425, 725, 250, 250)); // despise entrance, north of britain AddWeather(+15, 50, 5, new Rectangle2D(1245, 1045, 250, 250)); /* Dynamic weather: * * Format: * AddDynamicWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, moveSpeed, width, height, bounds ); */ for (var i = 0; i < 15; ++i) { AddDynamicWeather(+15, 100, 5, 8, 400, 400, new Rectangle2D(0, 0, 5120, 4096)); } } public static List GetWeatherList(Map facet) { if (facet == null) { return null; } if (!m_WeatherByFacet.TryGetValue(facet, out var list)) { m_WeatherByFacet[facet] = list = new List(); } return list; } public static void AddDynamicWeather( int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, int moveSpeed, int width, int height, Rectangle2D bounds ) { for (var i = 0; i < m_Facets.Length; ++i) { var area = new Rectangle2D(); var isValid = false; for (var j = 0; j < 10; ++j) { area = new Rectangle2D( bounds.X + Utility.Random(bounds.Width - width), bounds.Y + Utility.Random(bounds.Height - height), width, height ); if (!CheckWeatherConflict(m_Facets[i], null, area)) { isValid = true; } if (isValid) { break; } } if (!isValid) { continue; } new Weather( m_Facets[i], new[] { area }, temperature, chanceOfPercipitation, chanceOfExtremeTemperature, TimeSpan.FromSeconds(30.0) ) { Bounds = bounds, MoveSpeed = moveSpeed }; } } public static void AddWeather( int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, params Rectangle2D[] area ) { for (var i = 0; i < m_Facets.Length; ++i) { new Weather( m_Facets[i], area, temperature, chanceOfPercipitation, chanceOfExtremeTemperature, TimeSpan.FromSeconds(30.0) ); } } public static bool CheckWeatherConflict(Map facet, Weather exclude, Rectangle2D area) { var list = GetWeatherList(facet); if (list == null) { return false; } for (var i = 0; i < list.Count; ++i) { var w = list[i]; if (w != exclude && w.IntersectsWith(area)) { return true; } } return false; } public static bool CheckIntersection(Rectangle2D r1, Rectangle2D r2) => r1.X < r2.X + r2.Width && r2.X < r1.X + r1.Width && r1.Y < r2.Y + r2.Height && r2.Y < r1.Y + r1.Height; public static bool CheckContains(Rectangle2D big, Rectangle2D small) => small.X >= big.X && small.Y >= big.Y && small.X + small.Width <= big.X + big.Width && small.Y + small.Height <= big.Y + big.Height; public virtual bool IntersectsWith(Rectangle2D area) { for (var i = 0; i < Area.Length; ++i) { if (CheckIntersection(area, Area[i])) { return true; } } return false; } public virtual void Reposition() { if (Area.Length == 0) { return; } var width = Area[0].Width; var height = Area[0].Height; var area = new Rectangle2D(); var isValid = false; for (var j = 0; j < 10; ++j) { area = new Rectangle2D( Bounds.X + Utility.Random(Bounds.Width - width), Bounds.Y + Utility.Random(Bounds.Height - height), width, height ); if (!CheckWeatherConflict(Facet, this, area)) { isValid = true; } if (isValid) { break; } } if (!isValid) { return; } Area[0] = area; } public virtual void RecalculateMovementAngle() { var angle = Utility.RandomDouble() * Math.PI * 2.0; var cos = Math.Cos(angle); var sin = Math.Sin(angle); MoveAngleX = (int)(100 * cos); MoveAngleY = (int)(100 * sin); } public virtual void MoveForward() { if (Area.Length == 0) { return; } for (var i = 0; i < 5; ++i) // try 5 times to find a valid spot { var xOffset = MoveSpeed * MoveAngleX / 100; var yOffset = MoveSpeed * MoveAngleY / 100; var oldArea = Area[0]; var newArea = new Rectangle2D(oldArea.X + xOffset, oldArea.Y + yOffset, oldArea.Width, oldArea.Height); if (!CheckWeatherConflict(Facet, this, newArea) && CheckContains(Bounds, newArea)) { Area[0] = newArea; break; } RecalculateMovementAngle(); } } public virtual void OnTick() { if (m_Stage == 0) { m_Active = ChanceOfPercipitation > Utility.Random(100); m_ExtremeTemperature = ChanceOfExtremeTemperature > Utility.Random(100); if (MoveSpeed > 0) { Reposition(); RecalculateMovementAngle(); } } if (m_Active) { if (m_Stage > 0 && MoveSpeed > 0) { MoveForward(); } int type, density; var temperature = Temperature; if (m_ExtremeTemperature) { temperature *= -1; } if (m_Stage < 15) { density = m_Stage * 5; } else { density = Math.Clamp(150 - m_Stage * 5, 10, 70); } if (density == 0) { type = 0xFE; } else if (temperature > 0) { type = 0; } else { type = 2; } foreach (var ns in NetState.Instances) { var mob = ns.Mobile; if (mob == null || mob.Map != Facet) { continue; } var contains = Area.Length == 0; for (var j = 0; !contains && j < Area.Length; ++j) { contains = Area[j].Contains(mob.Location); } if (!contains) { continue; } ns.SendWeather((byte)type, (byte)density, (byte)temperature); } } m_Stage++; m_Stage %= 30; } } public class WeatherMap : MapItem { [Constructible] public WeatherMap() { SetDisplay(0, 0, 5119, 4095, 400, 400); } public WeatherMap(Serial serial) : base(serial) { } public override string DefaultName => "weather map"; public override void OnDoubleClick(Mobile from) { var facet = from.Map; if (facet == null) { return; } var list = Weather.GetWeatherList(facet); ClearPins(); for (var i = 0; i < list.Count; ++i) { var w = list[i]; for (var j = 0; j < w.Area.Length; ++j) { AddWorldPin(w.Area[j].X + w.Area[j].Width / 2, w.Area[j].Y + w.Area[j].Height / 2); } } base.OnDoubleClick(from); } public override void Serialize(IGenericWriter writer) { base.Serialize(writer); writer.Write(0); } public override void Deserialize(IGenericReader reader) { base.Deserialize(reader); var version = reader.ReadInt(); } } }