modernuo/Projects/Server/Regions/Region.cs
2024-02-18 12:01:16 -08:00

884 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
using Server.Collections;
using Server.Json;
using Server.Logging;
using Server.Network;
using Server.Targeting;
namespace Server;
public enum MusicName
{
Invalid = -1,
OldUlt01 = 0,
Create1,
DragFlit,
OldUlt02,
OldUlt03,
OldUlt04,
OldUlt05,
OldUlt06,
Stones2,
Britain1,
Britain2,
Bucsden,
Jhelom,
LBCastle,
Linelle,
Magincia,
Minoc,
Ocllo,
Samlethe,
Serpents,
Skarabra,
Trinsic,
Vesper,
Wind,
Yew,
Cave01,
Dungeon9,
Forest_a,
InTown01,
Jungle_a,
Mountn_a,
Plains_a,
Sailing,
Swamp_a,
Tavern01,
Tavern02,
Tavern03,
Tavern04,
Combat1,
Combat2,
Combat3,
Approach,
Death,
Victory,
BTCastle,
Nujelm,
Dungeon2,
Cove,
Moonglow,
Zento,
TokunoDungeon,
Taiko,
DreadHornArea,
ElfCity,
GrizzleDungeon,
MelisandesLair,
ParoxysmusLair,
GwennoConversation,
GoodEndGame,
GoodVsEvil,
GreatEarthSerpents,
Humanoids_U9,
MinocNegative,
Paws,
SelimsBar,
SerpentIsleCombat_U7,
ValoriaShips,
TheWanderer,
Castle,
Festival,
Honor,
Medieval,
BattleOnStones,
Docktown,
GargoyleQueen,
GenericCombat,
Holycity,
HumanLevel,
LoginLoop,
NorthernForestBattleonStones,
PrimevalLich,
QueenPalace,
RoyalCity,
SlasherVeil,
StygianAbyss,
StygianDragon,
Void,
CodexShrine,
AnvilStrikeInMinoc,
ASkaranLullaby,
BlackthornsMarch,
DupresNightInTrinsic,
FayaxionAndTheSix,
FlightOfTheNexus,
GalehavenJaunt,
JhelomToArms,
MidnightInYew,
MoonglowSonata,
NewMaginciaMarch,
NujelmWaltz,
SherrysSong,
StarlightInBritain,
TheVesperMist,
NoMusic = 0x1FFF
}
public class Region : IComparable<Region>, IValueLinkListNode<Region>
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Region));
public const int DefaultPriority = 50;
public const int MinZ = sbyte.MinValue;
public const int MaxZ = sbyte.MaxValue + 1;
public Region(string name, Map map, int priority, params Rectangle2D[] area) : this(
name,
map,
priority,
ConvertTo3D(area)
)
{
}
public Region(string name, Map map, params Rectangle3D[] area) : this(name, map, null, area)
{
}
[JsonConstructor] // Don't include parent, since it is special
public Region(string name, Map map, int priority, params Rectangle3D[] area) : this(name, map, null, area) =>
Priority = priority;
public Region(string name, Map map, Region parent, int priority, params Rectangle3D[] area) : this(name, map, parent, area) =>
Priority = priority;
public Region(string name, Map map, Region parent, params Rectangle2D[] area) : this(
name,
map,
parent,
ConvertTo3D(area)
)
{
}
public Region(string name, Map map, Region parent, params Rectangle3D[] area)
{
Name = name;
Map = map;
Parent = parent;
Area = area;
Dynamic = true;
Music = DefaultMusic;
if (Parent == null)
{
ChildLevel = 0;
Priority = DefaultPriority;
}
else
{
ChildLevel = Parent.ChildLevel + 1;
Priority = Parent.Priority;
}
}
// Sectors
[JsonIgnore]
public Region Next { get; set; }
[JsonIgnore]
public Region Previous { get; set; }
[JsonIgnore]
public bool OnLinkList { get; set; }
// Used during deserialization only
public Expansion MinExpansion { get; set; } = Expansion.None;
// Used during deserialization only
public Expansion MaxExpansion { get; set; } = Expansion.EJ;
public static List<Region> Regions { get; } = new();
public static TimeSpan StaffLogoutDelay { get; set; } = TimeSpan.Zero;
public static TimeSpan DefaultLogoutDelay { get; set; } = TimeSpan.FromMinutes(5.0);
public string Name { get; }
public Map Map { get; }
[JsonInclude]
[JsonConverter(typeof(RegionByNameConverter))]
public Region Parent { get; private set; }
public List<Region> Children { get; } = new();
public Rectangle3D[] Area { get; }
public Map.Sector[] Sectors { get; private set; }
public bool Dynamic { get; }
public int Priority { get; }
public int ChildLevel { get; internal set; }
public bool Registered { get; private set; }
public Point3D GoLocation { get; set; }
public MusicName Music { get; set; }
public bool IsDefault => Map.DefaultRegion == this;
public virtual MusicName DefaultMusic => Parent?.Music ?? MusicName.Invalid;
public int CompareTo(Region reg)
{
if (reg == null)
{
return 1;
}
// Dynamic regions go first
if (Dynamic)
{
if (!reg.Dynamic)
{
return -1;
}
}
else if (reg.Dynamic)
{
return 1;
}
var regPriority = reg.Priority;
return Priority != regPriority ? reg.Priority - Priority : reg.ChildLevel - ChildLevel;
}
// This is not optimized. Use sparingly
public static Region Find(string name, Map map, bool insensitive = false)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
if (insensitive)
{
name = name.ToLower();
}
for (var i = 0; i < Regions.Count; i++)
{
var region = Regions[i];
if (region.Map != map)
{
continue;
}
var rName = region.Name;
if (insensitive)
{
rName = rName.ToLower();
}
if (rName == name)
{
return region;
}
}
return null;
}
public static Region Find(Point3D p, Map map)
{
if (map == null)
{
return Map.Internal.DefaultRegion;
}
var sector = map.GetSector(p);
var list = sector.Regions;
for (var i = 0; i < list.Count; ++i)
{
var region = list[i];
if (region.Contains(p))
{
return region;
}
}
return map.DefaultRegion;
}
public static Rectangle3D ConvertTo3D(Rectangle2D rect) =>
new(new Point3D(rect.Start, MinZ), new Point3D(rect.End, MaxZ));
public static Rectangle3D[] ConvertTo3D(Rectangle2D[] rects)
{
var ret = new Rectangle3D[rects.Length];
for (var i = 0; i < ret.Length; i++)
{
ret[i] = ConvertTo3D(rects[i]);
}
return ret;
}
public void Register()
{
if (Registered)
{
return;
}
OnRegister();
Registered = true;
if (Parent != null)
{
Parent.Children.Add(this);
Parent.OnChildAdded(this);
}
Regions.Add(this);
Map.RegisterRegion(this);
var sectors = new List<Map.Sector>();
for (var i = 0; i < Area.Length; i++)
{
var rect = Area[i];
var start = Map.Bound(new Point2D(rect.Start));
var end = Map.Bound(new Point2D(rect.End));
var startSector = Map.GetSector(start);
var endSector = Map.GetSector(end);
for (var x = startSector.X; x <= endSector.X; x++)
{
for (var y = startSector.Y; y <= endSector.Y; y++)
{
var sector = Map.GetRealSector(x, y);
// Region Areas are approximate and will overlap
// Don't add them multiple times!
if (!sectors.Contains(sector))
{
sector.OnEnter(this, rect);
sectors.Add(sector);
}
}
}
}
Sectors = sectors.ToArray();
}
public void Unregister()
{
if (!Registered)
{
return;
}
OnUnregister();
Registered = false;
if (Children.Count > 0)
{
logger.Warning("Unregistering region '{Region}' with children", this);
}
if (Parent != null)
{
Parent.Children.Remove(this);
Parent.OnChildRemoved(this);
}
Regions.Remove(this);
Map.UnregisterRegion(this);
if (Sectors != null)
{
for (var i = 0; i < Sectors.Length; i++)
{
Sectors[i].OnLeave(this);
}
}
Sectors = null;
}
public bool Contains(Point3D p)
{
for (var i = 0; i < Area.Length; i++)
{
var rect = Area[i];
if (rect.Contains(p))
{
return true;
}
}
return false;
}
// TODO: Memoize this
public bool IsChildOf(Region region)
{
if (region == null)
{
return false;
}
var p = Parent;
while (p != null)
{
if (p == region)
{
return true;
}
p = p.Parent;
}
return false;
}
// TODO: Memoize this
public T GetRegion<T>() where T : Region
{
var r = this;
do
{
if (r is T tr)
{
return tr;
}
r = r.Parent;
} while (r != null);
return null;
}
// TODO: Memoize this
public bool IsPartOf<T1, T2>() where T1 : Region where T2 : Region
{
var r = this;
do
{
if (r is T1 or T2)
{
return true;
}
r = r.Parent;
} while (r != null);
return false;
}
public Region GetRegion(Type regionType)
{
if (regionType == null)
{
return null;
}
var r = this;
do
{
if (regionType.IsInstanceOfType(r))
{
return r;
}
r = r.Parent;
} while (r != null);
return null;
}
public Region GetRegion(string regionName, bool caseSensitive = true)
{
if (regionName == null)
{
return null;
}
var r = this;
var comparisonType = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
do
{
if (string.Equals(r.Name, regionName, comparisonType))
{
return r;
}
r = r.Parent;
} while (r != null);
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPartOf<T>() where T : Region => GetRegion<T>() != null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPartOf(Region region) => this == region || IsChildOf(region);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPartOf(string regionName, bool caseSensitive = false) => GetRegion(regionName, caseSensitive) != null;
public virtual bool AcceptsSpawnsFrom(Region region) =>
AllowSpawn() && (region == this || Parent?.AcceptsSpawnsFrom(region) == true);
public List<Mobile> GetPlayers()
{
var list = new List<Mobile>();
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var ns in sector.Clients)
{
var player = ns.Mobile;
if (player?.Deleted == false && player.Region.IsPartOf(this))
{
list.Add(ns.Mobile);
}
}
}
return list;
}
public int GetPlayerCount()
{
var count = 0;
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var ns in sector.Clients)
{
var player = ns.Mobile;
if (player?.Deleted == false && player.Region.IsPartOf(this))
{
count++;
}
}
}
return count;
}
public List<Mobile> GetMobiles()
{
var list = new List<Mobile>();
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var mobile in sector.Mobiles)
{
if (mobile.Region.IsPartOf(this))
{
list.Add(mobile);
}
}
}
return list;
}
public int GetMobileCount()
{
var count = 0;
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var mobile in sector.Mobiles)
{
if (mobile.Region.IsPartOf(this))
{
count++;
}
}
}
return count;
}
public List<Item> GetItems()
{
var list = new List<Item>();
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var item in sector.Items)
{
if (Find(item.Location, item.Map).IsPartOf(this))
{
list.Add(item);
}
}
}
return list;
}
public int GetItemCount()
{
var count = 0;
for (var i = 0; i < Sectors?.Length; i++)
{
var sector = Sectors[i];
foreach (var item in sector.Items)
{
if (Find(item.Location, item.Map).IsPartOf(this))
{
count++;
}
}
}
return count;
}
public override string ToString() => Name ?? GetType().Name;
public virtual void OnRegister()
{
}
public virtual void OnUnregister()
{
}
public virtual void OnChildAdded(Region child)
{
}
public virtual void OnChildRemoved(Region child)
{
}
public virtual bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation) =>
m.WalkRegion == null || AcceptsSpawnsFrom(m.WalkRegion);
public virtual void OnEnter(Mobile m)
{
}
public virtual void OnExit(Mobile m)
{
}
public virtual void MakeGuard(Mobile focus)
{
Parent?.MakeGuard(focus);
}
public virtual Type GetResource(Type type) => Parent?.GetResource(type) ?? type;
public virtual bool CanUseStuckMenu(Mobile m) => Parent?.CanUseStuckMenu(m) != false;
public virtual void OnAggressed(Mobile aggressor, Mobile aggressed, bool criminal)
{
Parent?.OnAggressed(aggressor, aggressed, criminal);
}
public virtual void OnDidHarmful(Mobile harmer, Mobile harmed)
{
Parent?.OnDidHarmful(harmer, harmed);
}
public virtual void OnGotHarmful(Mobile harmer, Mobile harmed)
{
Parent?.OnGotHarmful(harmer, harmed);
}
public virtual void OnLocationChanged(Mobile m, Point3D oldLocation)
{
Parent?.OnLocationChanged(m, oldLocation);
}
public virtual bool OnTarget(Mobile m, Target t, object o) => Parent?.OnTarget(m, t, o) != false;
public virtual bool OnCombatantChange(Mobile m, Mobile old, Mobile newMobile) =>
Parent?.OnCombatantChange(m, old, newMobile) != false;
public virtual bool AllowHousing(Mobile from, Point3D p) => Parent?.AllowHousing(from, p) != false;
public virtual bool SendInaccessibleMessage(Item item, Mobile from) =>
Parent?.SendInaccessibleMessage(item, from) == true;
public virtual bool CheckAccessibility(Item item, Mobile from) => Parent?.CheckAccessibility(item, from) != false;
public virtual bool OnDecay(Item item) => Parent?.OnDecay(item) != false;
public virtual bool AllowHarmful(Mobile from, Mobile target) =>
Parent?.AllowHarmful(from, target) ?? Mobile.AllowHarmfulHandler?.Invoke(from, target) ?? true;
public virtual void OnCriminalAction(Mobile m, bool message)
{
if (Parent != null)
{
Parent.OnCriminalAction(m, message);
}
else if (message)
{
m.SendLocalizedMessage(1005040); // You've committed a criminal act!!
}
}
public virtual bool AllowBeneficial(Mobile from, Mobile target) =>
Parent?.AllowBeneficial(from, target) ??
Mobile.AllowBeneficialHandler?.Invoke(from, target) ?? true;
public virtual void OnBeneficialAction(Mobile helper, Mobile target)
{
Parent?.OnBeneficialAction(helper, target);
}
public virtual void OnGotBeneficialAction(Mobile helper, Mobile target)
{
Parent?.OnGotBeneficialAction(helper, target);
}
public virtual void SpellDamageScalar(Mobile caster, Mobile target, ref double damage)
{
Parent?.SpellDamageScalar(caster, target, ref damage);
}
public virtual void OnSpeech(SpeechEventArgs args)
{
Parent?.OnSpeech(args);
}
public virtual bool AllowGain(Mobile m, Skill skill, object obj) => Parent?.AllowGain(m, skill, obj) ?? true;
public virtual bool OnSkillUse(Mobile m, int skill) => Parent?.OnSkillUse(m, skill) ?? true;
public virtual bool OnBeginSpellCast(Mobile m, ISpell s) => Parent?.OnBeginSpellCast(m, s) ?? true;
public virtual void OnSpellCast(Mobile m, ISpell s)
{
Parent?.OnSpellCast(m, s);
}
public virtual bool OnResurrect(Mobile m) => Parent?.OnResurrect(m) ?? true;
public virtual bool OnBeforeDeath(Mobile m) => Parent?.OnBeforeDeath(m) ?? true;
public virtual void OnDeath(Mobile m)
{
Parent?.OnDeath(m);
}
public virtual bool OnDamage(Mobile m, ref int damage) => Parent?.OnDamage(m, ref damage) ?? true;
public virtual bool OnHeal(Mobile m, ref int heal) => Parent?.OnHeal(m, ref heal) ?? true;
public virtual bool OnDoubleClick(Mobile m, object o) => Parent?.OnDoubleClick(m, o) ?? true;
public virtual bool OnSingleClick(Mobile m, object o) => Parent?.OnSingleClick(m, o) ?? true;
public virtual bool AllowSpawn() => Parent?.AllowSpawn() ?? true;
public virtual void AlterLightLevel(Mobile m, ref int global, ref int personal)
{
Parent?.AlterLightLevel(m, ref global, ref personal);
}
public virtual TimeSpan GetLogoutDelay(Mobile m)
{
if (Parent != null)
{
return Parent.GetLogoutDelay(m);
}
return m.AccessLevel > AccessLevel.Player ? StaffLogoutDelay : DefaultLogoutDelay;
}
internal static bool CanMove(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation, Map map)
{
var oldRegion = m.Region;
var newRegion = Find(newLocation, map);
while (oldRegion != newRegion)
{
if (!newRegion.OnMoveInto(m, d, newLocation, oldLocation))
{
return false;
}
if (newRegion.Parent == null)
{
return true;
}
newRegion = newRegion.Parent;
}
return true;
}
internal static void OnRegionChange(Mobile m, Region oldRegion, Region newRegion)
{
if (newRegion != null && m.NetState != null)
{
m.CheckLightLevels(false);
if (oldRegion == null || oldRegion.Music != newRegion.Music)
{
m.NetState.SendPlayMusic(newRegion.Music);
}
}
var oldR = oldRegion;
var newR = newRegion;
while (oldR != newR)
{
var oldRChild = oldR?.ChildLevel ?? -1;
var newRChild = newR?.ChildLevel ?? -1;
if (oldRChild >= newRChild)
{
oldR?.OnExit(m);
oldR = oldR?.Parent;
}
if (newRChild >= oldRChild)
{
newR?.OnEnter(m);
newR = newR?.Parent;
}
}
}
}