diff --git a/Server.MirForms/Server.csproj b/Server.MirForms/Server.csproj index 939b7e78c..fe7fb60b8 100644 --- a/Server.MirForms/Server.csproj +++ b/Server.MirForms/Server.csproj @@ -29,7 +29,7 @@ - + diff --git a/Server/MirEnvir/Map.cs b/Server/MirEnvir/Map.cs index 9e4f2dcfb..87846cae6 100644 --- a/Server/MirEnvir/Map.cs +++ b/Server/MirEnvir/Map.cs @@ -1,5 +1,5 @@ using System.Drawing; -using Server.MirDatabase; +using Server.MirDatabase; using Server.MirObjects; using Shared; using S = ServerPackets; @@ -41,6 +41,8 @@ protected static MessageQueue MessageQueue public List Conquest = new List(); public ConquestObject tempConquest; + public readonly MapGrid mapGrid = new(); + public Map(MapInfo info) { Info = info; @@ -57,7 +59,7 @@ public Door AddDoor(byte DoorIndex, Point location) Doors.Add(DoorInfo); return DoorInfo; } - + public bool OpenDoor(byte DoorIndex) { for (int i = 0; i < Doors.Count; i++) @@ -150,7 +152,7 @@ private void LoadMapCellsv0(byte[] fileBytes) Cells[x, y].FishingAttribute = (sbyte)(light - 100); } } - + private void LoadMapCellsv1(byte[] fileBytes) { int offSet = 21; @@ -426,7 +428,7 @@ private void LoadMapCellsV100(byte[] Bytes) if (light >= 100 && light <= 119) Cells[x, y].FishingAttribute = (sbyte)(light - 100); } - + } public bool Load() @@ -437,7 +439,7 @@ public bool Load() if (File.Exists(fileName)) { byte[] fileBytes = File.ReadAllBytes(fileName); - switch(FindType(fileBytes)) + switch (FindType(fileBytes)) { case 0: LoadMapCellsv0(fileBytes); @@ -469,7 +471,7 @@ public bool Load() } GetWalkableCells(); - + for (int i = 0; i < Info.Respawns.Count; i++) { MapRespawn info = new MapRespawn(Info.Respawns[i]); @@ -570,14 +572,14 @@ private void CreateSafeZone(SafeZoneInfo info) if (!Cells[x, y].Valid) continue; SpellObject spell = new SpellObject - { - ExpireTime = long.MaxValue, - Value = 25, - TickSpeed = 2000, - Spell = Spell.Healing, - CurrentLocation = new Point(x, y), - CurrentMap = this - }; + { + ExpireTime = long.MaxValue, + Value = 25, + TickSpeed = 2000, + Spell = Spell.Healing, + CurrentLocation = new Point(x, y), + CurrentMap = this + }; Cells[x, y].Add(spell); @@ -601,7 +603,7 @@ private void CreateMine() Settings.MineSetList[Info.MineIndex - 1].SetDrops(Envir.ItemInfoList); for (int i = 0; i < Width; i++) for (int j = 0; j < Height; j++) - Mine[i,j].Mine = Settings.MineSetList[Info.MineIndex - 1]; + Mine[i, j].Mine = Settings.MineSetList[Info.MineIndex - 1]; } if (Info.MineZones.Count > 0) { @@ -611,7 +613,7 @@ private void CreateMine() if (Zone.Mine != 0) Settings.MineSetList[Zone.Mine - 1].SetDrops(Envir.ItemInfoList); if (Settings.MineSetList.Count < Zone.Mine) continue; - for (int x = Zone.Location.X - Zone.Size; x < Zone.Location.X + Zone.Size; x++) + for (int x = Zone.Location.X - Zone.Size; x < Zone.Location.X + Zone.Size; x++) for (int y = Zone.Location.Y - Zone.Size; y < Zone.Location.Y + Zone.Size; y++) { if ((x < 0) || (x >= Width) || (y < 0) || (y >= Height)) continue; @@ -673,7 +675,7 @@ public void Process() Point location; if (Envir.Random.Next(4) == 0) { - location = player.CurrentLocation; + location = player.CurrentLocation; } else location = new Point(player.CurrentLocation.X - 10 + Envir.Random.Next(20), player.CurrentLocation.Y - 10 + Envir.Random.Next(20)); @@ -799,7 +801,7 @@ public void Process(DelayedAction action) case DelayedType.Spawn: MapObject obj = (MapObject)action.Params[0]; - switch(obj.Race) + switch (obj.Race) { case ObjectType.Monster: { @@ -820,11 +822,11 @@ public void Process(DelayedAction action) } } - /** - * return the coordinates of effect coordinates within an n x n square (n should be odd number. i.e. 3x3, 5x5, 7x7) - * then use GetCell() in Map.cs to retrive real objects - * default 3x3 - */ + /** + * return the coordinates of effect coordinates within an n x n square (n should be odd number. i.e. 3x3, 5x5, 7x7) + * then use GetCell() in Map.cs to retrive real objects + * default 3x3 + */ public static List GetPointsInEffectiveSquare(Point location, int mapWidth, int mapHeight, int squareEdgeLength = 3) { var pointsWithinTheMap = new List(); @@ -833,7 +835,8 @@ public static List GetPointsInEffectiveSquare(Point location, int mapWidt if (squareEdgeLength > 1) { spread = (int)((squareEdgeLength - 1) / 2); - } else + } + else { spread = fallBackSpread; // 3x3 } @@ -1468,25 +1471,25 @@ private void CompleteMagic(IList data) if (!cast) continue; SpellObject ob = new SpellObject - { - Spell = Spell.PoisonCloud, - Value = value, - BonusDmg = bonusdmg, - ExpireTime = Envir.Time + 6000, - TickSpeed = 1000, - Caster = player, - CurrentLocation = new Point(x, y), - CastLocation = location, - Show = show, - CurrentMap = this, - }; + { + Spell = Spell.PoisonCloud, + Value = value, + BonusDmg = bonusdmg, + ExpireTime = Envir.Time + 6000, + TickSpeed = 1000, + Caster = player, + CurrentLocation = new Point(x, y), + CastLocation = location, + Show = show, + CurrentMap = this, + }; show = false; AddObject(ob); ob.Spawned(); } - } + } break; @@ -1617,7 +1620,8 @@ private void CompleteMagic(IList data) { monster.Die(); return; - }; + } + ; if (ValidPoint(front)) monster.Spawn(this, front); @@ -1655,7 +1659,7 @@ private void CompleteMagic(IList data) for (int o = 0; o < cell.Objects.Count; o++) { MapObject target = cell.Objects[o]; - if (target.Race != ObjectType.Spell || ((SpellObject) target).Spell != Spell.Blizzard) continue; + if (target.Race != ObjectType.Spell || ((SpellObject)target).Spell != Spell.Blizzard) continue; cast = false; break; @@ -1664,25 +1668,25 @@ private void CompleteMagic(IList data) if (!cast) continue; SpellObject ob = new SpellObject - { - Spell = Spell.Blizzard, - Value = value, - ExpireTime = Envir.Time + 3000, - TickSpeed = 440, - Caster = player, - CurrentLocation = new Point(x, y), - CastLocation = location, - Show = show, - CurrentMap = this, - StartTime = Envir.Time + 800, - }; + { + Spell = Spell.Blizzard, + Value = value, + ExpireTime = Envir.Time + 3000, + TickSpeed = 440, + Caster = player, + CurrentLocation = new Point(x, y), + CastLocation = location, + Show = show, + CurrentMap = this, + StartTime = Envir.Time + 800, + }; show = false; AddObject(ob); ob.Spawned(); } - } + } break; @@ -1779,7 +1783,7 @@ private void CompleteMagic(IList data) { centerTarget = (MonsterObject)target; } - + switch (target.Race) { case ObjectType.Monster: @@ -1978,7 +1982,7 @@ private void CompleteMagic(IList data) location = (Point)data[3]; // the skill affect a 3x3 square - var points= GetPointsInEffectiveSquare(location, Width, Height, 3); + var points = GetPointsInEffectiveSquare(location, Width, Height, 3); foreach (var point in points) { cell = GetCell(point.X, point.Y); @@ -2178,7 +2182,7 @@ private void CompleteMagic(IList data) #region Portal - case Spell.Portal: + case Spell.Portal: value = (int)data[2]; location = (Point)data[3]; value2 = (int)data[4]; @@ -2351,7 +2355,7 @@ private void CompleteMagic(IList data) break; #endregion - } + } if (train) player.LevelMagic(magic); @@ -2370,6 +2374,8 @@ public void AddObject(MapObject ob) if (ob.Race == ObjectType.Hero) Heroes.Add((HeroObject)ob); GetCell(ob.CurrentLocation).Add(ob); + + mapGrid.UpdateObject(ob); } public void RemoveObject(MapObject ob) @@ -2380,6 +2386,8 @@ public void RemoveObject(MapObject ob) if (ob.Race == ObjectType.Hero) Heroes.Remove((HeroObject)ob); GetCell(ob.CurrentLocation).Remove(ob); + + mapGrid.RemoveObject(ob); } @@ -2394,7 +2402,7 @@ public SafeZoneInfo GetSafeZone(Point location) return null; } - public List GetSpellObjects(Spell spell ,MapObject caster) + public List GetSpellObjects(Spell spell, MapObject caster) { List spellObjects = new List(); @@ -2443,7 +2451,7 @@ public void Broadcast(Packet p, Point location) PlayerObject player = Players[i]; if (Functions.InRange(location, player.CurrentLocation, Globals.DataRange)) - player.Enqueue(p); + player.Enqueue(p); } } @@ -2469,7 +2477,7 @@ public void Broadcast(Packet p, Point location, PlayerObject Player) if (Functions.InRange(location, Player.CurrentLocation, Globals.DataRange)) { Player.Enqueue(p); - } + } } } public class Cell @@ -2529,6 +2537,106 @@ private static void ReportCellIssue(string message) } } } + + public class MapGrid + { + private const int GridSize = 16; + private readonly Dictionary> _grids = new(); + + public MapGrid() + { + } + + private Point GetGridPoint(Point location) + { + return new Point(location.X / GridSize, location.Y / GridSize); + } + + public void UpdateObject(MapObject obj) + { + if (obj == null) return; + + Point newGrid = GetGridPoint(obj.CurrentLocation); + if (obj.LastGrid == newGrid) + { + return; + } + + if (obj.LastGrid != Point.Empty) + { + if (_grids.TryGetValue(obj.LastGrid, out var oldGridObjList)) + { + oldGridObjList.Remove(obj); + if (oldGridObjList.Count == 0) + { + _grids.Remove(obj.LastGrid); + } + } + } + + + obj.LastGrid = newGrid; + + if (!_grids.ContainsKey(newGrid)) + { + _grids[newGrid] = new List(); + } + _grids[newGrid].Add(obj); + } + + public void RemoveObject(MapObject obj) + { + if (obj == null || obj.LastGrid == Point.Empty) return; + + if (_grids.TryGetValue(obj.LastGrid, out var grid)) + { + grid.Remove(obj); + if (grid.Count == 0) + { + _grids.Remove(obj.LastGrid); + } + } + + obj.LastGrid = Point.Empty; + } + + public List GetObjectsInRange(Point center, int range) + { + List result = new List(); + + int minX = center.X - range; + int maxX = center.X + range; + int minY = center.Y - range; + int maxY = center.Y + range; + + Point minGrid = GetGridPoint(new Point(minX, minY)); + Point maxGrid = GetGridPoint(new Point(maxX, maxY)); + + Point gridPoint = new Point(); + + for (int gx = minGrid.X; gx <= maxGrid.X; gx++) + { + gridPoint.X = gx; + for (int gy = minGrid.Y; gy <= maxGrid.Y; gy++) + { + gridPoint.Y = gy; + if (_grids.TryGetValue(gridPoint, out var objects)) + { + result.AddRange(objects); + } + } + } + + result = result.Where(obj => + obj.CurrentLocation.X >= minX && + obj.CurrentLocation.X <= maxX && + obj.CurrentLocation.Y >= minY && + obj.CurrentLocation.Y <= maxY + ).ToList(); + + return result; + } + } public class MapRespawn { protected static Envir Envir diff --git a/Server/MirObjects/HeroObject.cs b/Server/MirObjects/HeroObject.cs index b94abc1c2..91972cd4c 100644 --- a/Server/MirObjects/HeroObject.cs +++ b/Server/MirObjects/HeroObject.cs @@ -681,6 +681,9 @@ public override void Revive(int hp, bool effect) ActionTime = Envir.Time + RevivalDelay; CurrentMap.AddObject(this); + + CurrentMap.mapGrid.UpdateObject(this); + BroadcastInfo(); Broadcast(new S.ObjectRevived { ObjectID = ObjectID, Effect = effect }); } @@ -1053,6 +1056,9 @@ public void OwnerRecall() if (!Teleport(Owner.CurrentMap, Owner.Back)) Teleport(Owner.CurrentMap, Owner.CurrentLocation); + if (CurrentMap != null) + CurrentMap.mapGrid.UpdateObject(this); + if (!Dead) { BroadcastManaChange(); diff --git a/Server/MirObjects/HumanObject.cs b/Server/MirObjects/HumanObject.cs index 93962660d..ac1e252cc 100644 --- a/Server/MirObjects/HumanObject.cs +++ b/Server/MirObjects/HumanObject.cs @@ -2509,6 +2509,8 @@ public bool Walk(MirDirection dir) } } + CurrentMap.mapGrid.UpdateObject(this); + return true; } public bool Run(MirDirection dir) @@ -2641,6 +2643,8 @@ public bool Run(MirDirection dir) } } + CurrentMap.mapGrid.UpdateObject(this); + return true; } protected virtual void Moved() @@ -2721,6 +2725,9 @@ public override int Pushed(MapObject pusher, MirDirection dir, int distance) } ActionTime = Envir.Time + 500; + + CurrentMap.mapGrid.UpdateObject(this); + return result; } diff --git a/Server/MirObjects/ItemObject.cs b/Server/MirObjects/ItemObject.cs index 62a1f30d0..3c9f28656 100644 --- a/Server/MirObjects/ItemObject.cs +++ b/Server/MirObjects/ItemObject.cs @@ -271,6 +271,7 @@ public bool Drop(int distance) CurrentLocation = bestLocation; CurrentMap.AddObject(this); Spawned(); + return true; } diff --git a/Server/MirObjects/MapObject.cs b/Server/MirObjects/MapObject.cs index 04e8164e5..f10f3a8b3 100644 --- a/Server/MirObjects/MapObject.cs +++ b/Server/MirObjects/MapObject.cs @@ -42,6 +42,8 @@ public Map CurrentMap public abstract int CurrentMapIndex { get; set; } public abstract Point CurrentLocation { get; set; } + + public Point LastGrid = Point.Empty; public abstract MirDirection Direction { get; set; } public abstract ushort Level { get; set; } diff --git a/Server/MirObjects/MonsterObject.cs b/Server/MirObjects/MonsterObject.cs index c1d557e20..c48f30d62 100644 --- a/Server/MirObjects/MonsterObject.cs +++ b/Server/MirObjects/MonsterObject.cs @@ -959,6 +959,8 @@ public override bool Teleport(Map temp, Point location, bool effects = true, byt BroadcastHealthChange(); + CurrentMap.mapGrid.RemoveObject(this); + return true; } @@ -2145,6 +2147,8 @@ public virtual bool Walk(MirDirection dir) //break; } + CurrentMap.mapGrid.UpdateObject(this); + return true; } protected virtual void Attack() diff --git a/Server/MirObjects/NPCObject.cs b/Server/MirObjects/NPCObject.cs index 2a4885bc7..cd9e7bb73 100644 --- a/Server/MirObjects/NPCObject.cs +++ b/Server/MirObjects/NPCObject.cs @@ -307,11 +307,17 @@ public void Hide() { CurrentMap.Broadcast(new S.ObjectRemove { ObjectID = ObjectID }, CurrentLocation); Visible = false; + + CurrentMap.mapGrid.RemoveObject(this); + } public void Show() { Visible = true; + + CurrentMap.mapGrid.UpdateObject(this); + for (int i = CurrentMap.Players.Count - 1; i >= 0; i--) { PlayerObject player = CurrentMap.Players[i]; diff --git a/Server/MirObjects/PlayerObject.cs b/Server/MirObjects/PlayerObject.cs index 5e344e4c9..e8153341b 100644 --- a/Server/MirObjects/PlayerObject.cs +++ b/Server/MirObjects/PlayerObject.cs @@ -1447,6 +1447,8 @@ public void TownRevive() Enqueue(GetFishInfo()); GroupMemberMapNameChanged(); GetPlayerLocation(); + + CurrentMap.mapGrid.UpdateObject(this); } public override bool Teleport(Map temp, Point location, bool effects = true, byte effectnumber = 0) { @@ -1496,6 +1498,8 @@ public override bool Teleport(Map temp, Point location, bool effects = true, byt ForceLeaveGroupRequiredMap(); } + CurrentMap.mapGrid.UpdateObject(this); + return true; } // Run after a successful map change (movement or teleport) @@ -1740,84 +1744,44 @@ private void GetRecipeInfo() } private void GetObjects() { - for (int y = CurrentLocation.Y - Globals.DataRange; y <= CurrentLocation.Y + Globals.DataRange; y++) + foreach (MapObject ob in CurrentMap.mapGrid.GetObjectsInRange(CurrentLocation, Globals.DataRange)) { - if (y < 0) continue; - if (y >= CurrentMap.Height) break; - - for (int x = CurrentLocation.X - Globals.DataRange; x <= CurrentLocation.X + Globals.DataRange; x++) - { - if (x < 0) continue; - if (x >= CurrentMap.Width) break; - if (x < 0 || x >= CurrentMap.Width) continue; - - Cell cell = CurrentMap.GetCell(x, y); - - if (!cell.Valid || cell.Objects == null) continue; - - for (int i = 0; i < cell.Objects.Count; i++) - { - MapObject ob = cell.Objects[i]; - - //if (ob.Race == ObjectType.Player && ob.Observer) continue; - - ob.Add(this); - } - } + ob.Add(this); } } private void GetObjectsPassive(MirConnection c = null) { - for (int y = CurrentLocation.Y - Globals.DataRange; y <= CurrentLocation.Y + Globals.DataRange; y++) + + foreach (MapObject ob in CurrentMap.mapGrid.GetObjectsInRange(CurrentLocation, Globals.DataRange)) { - if (y < 0) continue; - if (y >= CurrentMap.Height) break; + if (ob == this) continue; - for (int x = CurrentLocation.X - Globals.DataRange; x <= CurrentLocation.X + Globals.DataRange; x++) + if (ob.Race == ObjectType.Player) { - if (x < 0) continue; - if (x >= CurrentMap.Width) break; - if (x < 0 || x >= CurrentMap.Width) continue; - - Cell cell = CurrentMap.GetCell(x, y); - - if (!cell.Valid || cell.Objects == null) continue; - - for (int i = 0; i < cell.Objects.Count; i++) - { - MapObject ob = cell.Objects[i]; - if (ob == this) continue; - - if (ob.Race == ObjectType.Player) - { - PlayerObject Player = (PlayerObject)ob; - Enqueue(Player.GetInfoEx(this), c); - } - else if (ob.Race == ObjectType.Spell) - { - SpellObject obSpell = (SpellObject)ob; - - if ((obSpell.Spell != Spell.ExplosiveTrap) || (obSpell.Caster != null && IsFriendlyTarget(obSpell.Caster))) - Enqueue(ob.GetInfo(), c); - } - else if (ob.Race == ObjectType.Merchant) - { - NPCObject NPC = (NPCObject)ob; - - NPC.CheckVisible(this); - - if (NPC.VisibleLog[Info.Index] && NPC.Visible) Enqueue(ob.GetInfo(), c); - } - else - { - Enqueue(ob.GetInfo(), c); - } + PlayerObject Player = (PlayerObject)ob; + Enqueue(Player.GetInfoEx(this), c); + } + else if (ob.Race == ObjectType.Spell) + { + SpellObject obSpell = (SpellObject)ob; + if ((obSpell.Spell != Spell.ExplosiveTrap) || (obSpell.Caster != null && IsFriendlyTarget(obSpell.Caster))) + Enqueue(ob.GetInfo(), c); + } + else if (ob.Race == ObjectType.Merchant) + { + NPCObject NPC = (NPCObject)ob; + NPC.CheckVisible(this); + if (NPC.VisibleLog[Info.Index] && NPC.Visible) + Enqueue(ob.GetInfo(), c); + } + else + { + Enqueue(ob.GetInfo(), c); + } - if (ob.Race == ObjectType.Player || ob.Race == ObjectType.Monster || ob.Race == ObjectType.Hero) - { - ob.SendHealth(this); - } - } + if (ob.Race == ObjectType.Player || ob.Race == ObjectType.Monster || ob.Race == ObjectType.Hero) + { + ob.SendHealth(this); } } }