Convert Regions/Spawns to JSON (#138)

This commit is contained in:
Kamron Batman 2020-05-26 00:34:29 -07:00 committed by GitHub
parent b8316e9202
commit 390f30e706
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 97306 additions and 4848 deletions

View file

@ -1,5 +1,4 @@
using System;
using Server.Items;
using Server.Misc;
namespace Server.Tests

View file

@ -58,7 +58,8 @@ namespace Server
public static TypeCache GetTypeCache(Assembly asm)
{
if (asm == null) return m_NullCache ??= new TypeCache(null);
if (asm == null)
return m_NullCache ??= new TypeCache(null);
if (m_TypeCaches.TryGetValue(asm, out var c))
return c;
@ -68,6 +69,8 @@ namespace Server
public static Type FindFirstTypeForName(string name, bool ignoreCase = false, Func<Type, bool> predicate = null)
{
if (string.IsNullOrWhiteSpace(name)) return null;
var types = FindTypesByName(name, ignoreCase).ToList();
if (types.Count == 0)
return null;
@ -87,14 +90,19 @@ namespace Server
types[0];
}
public static IEnumerable<Type> FindTypesByName(string name, bool ignoreCase = false)
public static List<Type> FindTypesByName(string name, bool ignoreCase = false)
{
var types = new List<Type>();
if (ignoreCase)
name = name.ToLower();
for (var i = 0; i < Assemblies.Length; i++) types.AddRange(GetTypeCache(Assemblies[i])[name]);
for (var i = 0; i < Assemblies.Length; i++)
types.AddRange(GetTypeCache(Assemblies[i])[name]);
if (types.Count == 0)
types.AddRange(GetTypeCache(Core.Assembly)[name]);
return types;
}
@ -122,6 +130,7 @@ namespace Server
public TypeCache(Assembly asm)
{
m_Types = asm?.GetTypes() ?? Type.EmptyTypes;
var nameMap = new Dictionary<string, HashSet<int>>();
HashSet<int> refs;
Action<int, string> addToRefs = (index, key) =>
@ -136,6 +145,7 @@ namespace Server
nameMap.Add(key, refs);
}
};
var aliasType = typeof(TypeAliasAttribute);
for (var i = 0; i < m_Types.Length; i++)
{

View file

@ -3,7 +3,6 @@
// See LICENSE file in the project root for full license information.
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View file

@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace System.Buffers

View file

@ -2,7 +2,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
namespace System.Buffers

View file

@ -71,14 +71,18 @@ namespace Server
void OnStatsQuery(Mobile beholder, Mobile beheld);
}
public interface ISpawner
// TODO: Add SpawnMap and change Spawner.Map to use it
public interface ISpawner : IEntity
{
bool UnlinkOnTaming { get; }
Point3D HomeLocation { get; }
int HomeRange { get; }
Region Region { get; }
bool ReturnOnDeactivate { get; }
void Remove(ISpawnable spawn);
Point3D GetSpawnPosition(ISpawnable spawned, Map map);
void Respawn();
}
public interface ISpawnable : IEntity

View file

@ -0,0 +1,34 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: MapConverterFactory.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class MapConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Map);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new MapConverter();
}
}

View file

@ -0,0 +1,105 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Point2DConverter.cs *
* Created: 2020/04/12 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class Point2DConverter : JsonConverter<Point2D>
{
private Point2D DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[2];
var count = 0;
while (true)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndArray)
break;
if (reader.TokenType == JsonTokenType.Number)
{
if (count < 2)
data[count] = reader.GetInt32();
count++;
}
}
if (count > 2)
throw new JsonException("Point2D must be an array of x, y");
return new Point2D(data[0], data[1]);
}
private Point2D DeserializeObj(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[2];
while (true)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndObject)
break;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Invalid Json structure for Point2D object");
var key = reader.GetString();
var i = key switch
{
"x" => 0,
"y" => 1,
_ => throw new JsonException($"Invalid property {key} for Point2D")
};
reader.Read();
if (reader.TokenType != JsonTokenType.Number)
throw new JsonException($"Value for {key} must be a number");
data[i] = reader.GetInt32();
}
return new Point2D(data[0], data[1]);
}
public override Point2D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.StartArray => DeserializeArray(ref reader),
JsonTokenType.StartObject => DeserializeObj(ref reader),
_ => throw new JsonException("Invalid Json for Point3D")
};
public override void Write(Utf8JsonWriter writer, Point2D value, JsonSerializerOptions options)
{
writer.WriteStartArray();
writer.WriteNumberValue(value.X);
writer.WriteNumberValue(value.Y);
writer.WriteEndArray();
}
}
}

View file

@ -0,0 +1,34 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Point2DConverterFactory.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class Point2DConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Point2D) || typeToConvert == typeof(IPoint2D);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point2DConverter();
}
}

View file

@ -2,8 +2,8 @@
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Point3dConverter.cs *
* Created: 2020/04/12 - Updated: 2020/05/02 *
* File: Point3DConverter.cs *
* Created: 2020/04/12 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
@ -25,14 +25,11 @@ using System.Text.Json.Serialization;
namespace Server.Json
{
public class Point3dConverter : JsonConverter<Point3D>
public class Point3DConverter : JsonConverter<Point3D>
{
public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
private Point3D DeserializeArray(ref Utf8JsonReader reader)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException("Point3d must be an array of x, y, z");
var data = new int[3];
Span<int> data = stackalloc int[3];
var count = 0;
while (true)
@ -50,12 +47,54 @@ namespace Server.Json
}
}
if (count < 2 || count > 3)
throw new JsonException("Point3d must be an array of x, y, z");
if (count > 3)
throw new JsonException("Point3D must be an array of x, y, z");
return new Point3D(data[0], data[1], data[2]);
}
private Point3D DeserializeObj(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[3];
while (true)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndObject)
break;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Invalid Json structure for Point3D object");
var key = reader.GetString();
var i = key switch
{
"x" => 0,
"y" => 1,
"z" => 2,
_ => throw new JsonException($"Invalid property {key} for Point3D")
};
reader.Read();
if (reader.TokenType != JsonTokenType.Number)
throw new JsonException($"Value for {key} must be a number");
data[i] = reader.GetInt32();
}
return new Point3D(data[0], data[1], data[2]);
}
public override Point3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.StartArray => DeserializeArray(ref reader),
JsonTokenType.StartObject => DeserializeObj(ref reader),
_ => throw new JsonException("Invalid Json for Point3D")
};
public override void Write(Utf8JsonWriter writer, Point3D value, JsonSerializerOptions options)
{
writer.WriteStartArray();

View file

@ -0,0 +1,34 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Point3DConverterFactory.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class Point3DConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Point3D) || typeToConvert == typeof(IPoint3D);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new Point3DConverter();
}
}

View file

@ -0,0 +1,156 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Rectangle3DConverter.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class Rectangle3DConverter : JsonConverter<Rectangle3D>
{
private Rectangle3D DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[6];
var count = 0;
while (true)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndArray)
break;
if (reader.TokenType == JsonTokenType.Number)
{
if (count < 6)
data[count] = reader.GetInt32();
count++;
}
}
if (count > 6)
throw new JsonException("Rectangle3D must be an array of x, y, z, h, w, d");
return new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]);
}
private Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
Span<int> data = stackalloc int[6];
// 0 - xyzwhd, 1 - x1y1z1x2y2z2, 2 - start/end
int objType = -1;
while (true)
{
reader.Read();
if (reader.TokenType == JsonTokenType.EndObject)
break;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Invalid Json structure for Rectangle3D object");
var key = reader.GetString();
reader.Read();
if (key == "start" || key == "end")
{
if (objType > -1 && objType != 2)
throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both.");
objType = 2;
var point3D = reader.ToObject<Point3D>(options);
var offset = key == "end" ? 3 : 0;
data[0 + offset] = point3D.X;
data[1 + offset] = point3D.Y;
data[1 + offset] = point3D.Z;
continue;
}
var i = key switch
{
"x" => 0,
"y" => 1,
"z" => 2,
"w" => 3,
"width" => 3,
"h" => 4,
"height" => 4,
"d" => 5,
"depth" => 5,
"x1" => 10,
"y1" => 11,
"z1" => 12,
"x2" => 13,
"y2" => 14,
"z2" => 15,
_ => throw new JsonException($"Invalid property {key} for Rectangle3D")
};
if (i < 10)
{
if (objType > -1 && objType != 0)
throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both.");
objType = 0;
data[i] = reader.GetInt32();
continue;
}
if (objType > -1 && objType != 1)
throw new JsonException("Rectangle3D must have a start/end, or x/y/z/w/h/d, but not both.");
objType = 1;
data[i - 10] = reader.GetInt32();
}
return objType == 0
? new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5])
: new Rectangle3D(
new Point3D(data[0], data[1], data[2]),
new Point3D(data[3], data[4], data[5])
);
}
public override Rectangle3D Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.StartArray => DeserializeArray(ref reader),
JsonTokenType.StartObject => DeserializeObj(ref reader, options),
_ => throw new JsonException("Invalid Json for Point3D")
};
public override void Write(Utf8JsonWriter writer, Rectangle3D value, JsonSerializerOptions options)
{
writer.WriteStartArray();
writer.WriteNumberValue(value.Start.X);
writer.WriteNumberValue(value.Start.Y);
writer.WriteNumberValue(value.Start.Z);
writer.WriteNumberValue(value.Width);
writer.WriteNumberValue(value.Height);
writer.WriteNumberValue(value.Depth);
writer.WriteEndArray();
}
}
}

View file

@ -0,0 +1,35 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Rectangle3DConverterFactory.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class Rectangle3DConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Rectangle3D);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
new Rectangle3DConverter();
}
}

View file

@ -0,0 +1,36 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimeSpanConverter.cs *
* Created: 2020/04/12 - Updated: 2020/05/02 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> TimeSpan.Parse(reader.GetString());
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString());
}
}

View file

@ -0,0 +1,34 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimeSpanConverterFactory.cs *
* Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class TimeSpanConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TimeSpan);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => new TimeSpanConverter();
}
}

View file

@ -0,0 +1,57 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DynamicJson.cs - Created: 2020/05/23 - Updated: 2020/05/23 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class DynamicJson
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> data { get; set; }
public bool GetProperty<T>(string key, JsonSerializerOptions options, out T t)
{
if (data.TryGetValue(key, out var el))
{
t = el.ToObject<T>(options);
return true;
}
t = default;
return false;
}
public bool GetEnumProperty<T>(string key, JsonSerializerOptions options, out T t) where T : struct, Enum
{
if (data.TryGetValue(key, out var el))
return Enum.TryParse(el.ToObject<string>(options), out t);
t = default;
return false;
}
}
}

View file

@ -18,6 +18,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.IO;
using System.Text.Json;
@ -46,5 +48,23 @@ namespace Server.Json
File.WriteAllText(filePath, JsonSerializer.Serialize(value, options ?? Options));
}
public static T ToObject<T>(this ref Utf8JsonReader reader, JsonSerializerOptions options = null) =>
JsonSerializer.Deserialize<T>(ref reader, options);
public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
{
var bufferWriter = new ArrayBufferWriter<byte>();
using (var writer = new Utf8JsonWriter(bufferWriter))
element.WriteTo(writer);
return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
}
public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
return document.RootElement.ToObject<T>(options);
}
}
}

View file

@ -376,7 +376,7 @@ namespace Server
AssemblyHandler.Invoke("Configure");
Region.Load();
RegionLoader.LoadRegions();
World.Load();
AssemblyHandler.Invoke("Initialize");

View file

@ -374,15 +374,9 @@ namespace Server
public int CompareTo(Map other) => other == null ? -1 : MapID.CompareTo(other.MapID);
public static string[] GetMapNames()
{
return Maps.Where(m => m != null).Select(m => m.Name).ToArray();
}
public static string[] GetMapNames() => Maps.Where(m => m != null).Select(m => m.Name).ToArray();
public static Map[] GetMapValues()
{
return Maps.Where(m => m != null).ToArray();
}
public static Map[] GetMapValues() => Maps.Where(m => m != null).ToArray();
public static Map Parse(string value)
{

View file

@ -20,7 +20,6 @@
*************************************************************************/
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Connections;
using Microsoft.Extensions.Logging;

View file

@ -20,11 +20,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Text.Json;
using Server.Json;
using Server.Network;
using Server.Targeting;
using Server.Utilities;
namespace Server
{
@ -146,10 +145,10 @@ namespace Server
}
}
public Region(XmlElement xml, Map map, Region parent)
public Region(DynamicJson json, JsonSerializerOptions options)
{
Map = map;
Parent = parent;
Map = json.GetProperty("map", options, out Map map) ? map : null;
Parent = json.GetProperty("parent", options, out string parent) ? Find(parent, Map) : null;
Dynamic = false;
if (Parent == null)
@ -163,31 +162,21 @@ namespace Server
m_Priority = Parent.Priority;
}
ReadString(xml, "name", ref m_Name, false);
m_Name = json.GetProperty("name", options, out string name) ? name : null;
if (parent == null)
ReadInt32(xml, "priority", ref m_Priority, false);
m_Priority = json.GetProperty("priority", options, out int priority) ? priority : 0;
var minZ = MinZ;
var maxZ = MaxZ;
var zrange = xml["zrange"];
ReadInt32(zrange, "min", ref minZ, false);
ReadInt32(zrange, "max", ref maxZ, false);
var area = new List<Rectangle3D>();
foreach (XmlElement xmlRect in xml.SelectNodes("rect"))
{
if (ReadRectangle3D(xmlRect, minZ, maxZ, out var rect))
area.Add(rect);
}
Area = area.ToArray();
Area = json.GetProperty("rects", options, out List<Rectangle3D> rects) ?
rects.ToArray() : Array.Empty<Rectangle3D>();
if (Area.Length == 0)
Console.WriteLine("Empty area for region '{0}'", this);
if (!ReadPoint3D(xml["go"], map, ref m_GoLocation, false) && Area.Length > 0)
if (json.GetProperty("go", options, out Point3D go))
{
m_GoLocation = go;
}
else if (Area.Length > 0)
{
var start = Area[0].Start;
var end = Area[0].End;
@ -195,14 +184,10 @@ namespace Server
var x = start.X + (end.X - start.X) / 2;
var y = start.Y + (end.Y - start.Y) / 2;
m_GoLocation = new Point3D(x, y, Map.GetAverageZ(x, y));
m_GoLocation = new Point3D(x, y, Map?.GetAverageZ(x, y) ?? start.Z + (end.Z - start.Z) / 2);
}
var music = DefaultMusic;
ReadEnum(xml["music"], "name", ref music, false);
Music = music;
Music = json.GetEnumProperty("music", options, out MusicName music) ? music : DefaultMusic;
}
public static List<Region> Regions { get; } = new List<Region>();
@ -267,6 +252,29 @@ namespace Server
return reg.ChildLevel - ChildLevel;
}
// This is not optimized. Use sparingly
public static Region Find(string name, Map map, bool insensitive = false)
{
if (insensitive)
name = name.ToLower();
for (int i = 0; i < Regions.Count; i++)
{
var region = Regions[i];
if (region.Map != map)
continue;
string 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)
@ -727,338 +735,5 @@ namespace Server
}
}
}
internal static void Load()
{
if (!File.Exists("Data/Regions.xml"))
{
Console.WriteLine("Error: Data/Regions.xml does not exist");
return;
}
Console.Write("Regions: Loading...");
var doc = new XmlDocument();
doc.Load(Path.Combine(Core.BaseDirectory, "Data/Regions.xml"));
var root = doc["ServerRegions"];
if (root == null)
{
Console.WriteLine("Could not find root element 'ServerRegions' in Regions.xml");
return;
}
foreach (XmlElement facet in root.SelectNodes("Facet"))
{
Map map = null;
if (ReadMap(facet, "name", ref map))
{
if (map == Map.Internal)
Console.WriteLine("Invalid internal map in a facet element");
else
LoadRegions(facet, map, null);
}
}
Console.WriteLine("done");
}
private static void LoadRegions(XmlElement xml, Map map, Region parent)
{
foreach (XmlElement xmlReg in xml.SelectNodes("region"))
{
var type = DefaultRegionType;
ReadType(xmlReg, "type", ref type, false);
if (!typeof(Region).IsAssignableFrom(type))
{
Console.WriteLine("Invalid region type '{0}' in regions.xml", type.FullName);
continue;
}
Region region;
try
{
region = (Region)ActivatorUtil.CreateInstance(type, xmlReg, map, parent);
}
catch (Exception ex)
{
Console.WriteLine("Error during the creation of region type '{0}': {1}", type.FullName, ex);
continue;
}
region.Register();
LoadRegions(xmlReg, map, region);
}
}
protected static string GetAttribute(XmlElement xml, string attribute, bool mandatory)
{
if (xml == null)
{
if (mandatory)
Console.WriteLine("Missing element for attribute '{0}'", attribute);
return null;
}
if (xml.HasAttribute(attribute)) return xml.GetAttribute(attribute);
if (mandatory)
Console.WriteLine("Missing attribute '{0}' in element '{1}'", attribute, xml.Name);
return null;
}
public static bool ReadString(XmlElement xml, string attribute, ref string value) =>
ReadString(xml, attribute, ref value, true);
public static bool ReadString(XmlElement xml, string attribute, ref string value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
value = s;
return true;
}
public static bool ReadInt32(XmlElement xml, string attribute, ref int value) =>
ReadInt32(xml, attribute, ref value, true);
public static bool ReadInt32(XmlElement xml, string attribute, ref int value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
try
{
value = XmlConvert.ToInt32(s);
}
catch
{
Console.WriteLine("Could not parse integer attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
return true;
}
public static bool ReadBoolean(XmlElement xml, string attribute, ref bool value) =>
ReadBoolean(xml, attribute, ref value, true);
public static bool ReadBoolean(XmlElement xml, string attribute, ref bool value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
try
{
value = XmlConvert.ToBoolean(s);
}
catch
{
Console.WriteLine("Could not parse boolean attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
return true;
}
public static bool ReadDateTime(XmlElement xml, string attribute, ref DateTime value) =>
ReadDateTime(xml, attribute, ref value, true);
public static bool ReadDateTime(XmlElement xml, string attribute, ref DateTime value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
try
{
value = XmlConvert.ToDateTime(s, XmlDateTimeSerializationMode.Utc);
}
catch
{
Console.WriteLine("Could not parse DateTime attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
return true;
}
public static bool ReadTimeSpan(XmlElement xml, string attribute, ref TimeSpan value) =>
ReadTimeSpan(xml, attribute, ref value, true);
public static bool ReadTimeSpan(XmlElement xml, string attribute, ref TimeSpan value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
try
{
value = XmlConvert.ToTimeSpan(s);
}
catch
{
Console.WriteLine("Could not parse TimeSpan attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
return true;
}
public static bool ReadEnum<T>(XmlElement xml, string attribute, ref T value) where T : struct =>
ReadEnum(xml, attribute, ref value, true);
public static bool ReadEnum<T>(XmlElement xml, string attribute, ref T value, bool mandatory)
where T : struct // We can't limit the where clause to Enums only
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
var type = typeof(T);
if (type.IsEnum && Enum.TryParse(s, true, out T tempVal))
{
value = tempVal;
return true;
}
Console.WriteLine("Could not parse {0} enum attribute '{1}' in element '{2}'", type, attribute, xml.Name);
return false;
}
public static bool ReadMap(XmlElement xml, string attribute, ref Map value) => ReadMap(xml, attribute, ref value, true);
public static bool ReadMap(XmlElement xml, string attribute, ref Map value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
try
{
value = Map.Parse(s);
}
catch
{
Console.WriteLine("Could not parse Map attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
return true;
}
public static bool ReadType(XmlElement xml, string attribute, ref Type value) =>
ReadType(xml, attribute, ref value, true);
public static bool ReadType(XmlElement xml, string attribute, ref Type value, bool mandatory)
{
var s = GetAttribute(xml, attribute, mandatory);
if (s == null)
return false;
Type type;
try
{
type = AssemblyHandler.FindFirstTypeForName(s);
}
catch
{
Console.WriteLine("Could not parse Type attribute '{0}' in element '{1}'", attribute, xml.Name);
return false;
}
if (type == null)
{
Console.WriteLine("Could not find Type '{0}'", s);
return false;
}
value = type;
return true;
}
public static bool ReadPoint3D(XmlElement xml, Map map, ref Point3D value) => ReadPoint3D(xml, map, ref value, true);
public static bool ReadPoint3D(XmlElement xml, Map map, ref Point3D value, bool mandatory)
{
int x = 0, y = 0, z = 0;
var xyOk = ReadInt32(xml, "x", ref x, mandatory) & ReadInt32(xml, "y", ref y, mandatory);
var zOk = ReadInt32(xml, "z", ref z, mandatory && map == null);
if (xyOk && (zOk || map != null))
{
if (!zOk)
z = map.GetAverageZ(x, y);
value = new Point3D(x, y, z);
return true;
}
return false;
}
public static bool ReadRectangle3D(XmlElement xml, int defaultMinZ, int defaultMaxZ, out Rectangle3D value) =>
ReadRectangle3D(xml, defaultMinZ, defaultMaxZ, out value, true);
public static bool ReadRectangle3D(XmlElement xml, int defaultMinZ, int defaultMaxZ, out Rectangle3D value,
bool mandatory)
{
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
var z1 = defaultMinZ;
var z2 = defaultMaxZ;
if (xml.HasAttribute("x"))
{
if (ReadInt32(xml, "x", ref x1, mandatory)
& ReadInt32(xml, "y", ref y1, mandatory)
& ReadInt32(xml, "width", ref x2, mandatory)
& ReadInt32(xml, "height", ref y2, mandatory))
{
x2 += x1;
y2 += y1;
}
else
{
value = new Rectangle3D(new Point3D(x1, y1, z1), new Point3D(x2, y2, z2));
return false;
}
}
else
{
if (!ReadInt32(xml, "x1", ref x1, mandatory)
| !ReadInt32(xml, "y1", ref y1, mandatory)
| !ReadInt32(xml, "x2", ref x2, mandatory)
| !ReadInt32(xml, "y2", ref y2, mandatory))
{
value = new Rectangle3D(new Point3D(x1, y1, z1), new Point3D(x2, y2, z2));
return false;
}
}
ReadInt32(xml, "zmin", ref z1, false);
ReadInt32(xml, "zmax", ref z2, false);
value = new Rectangle3D(new Point3D(x1, y1, z1), new Point3D(x2, y2, z2));
return true;
}
}
}

View file

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using Server.Json;
using Server.Utilities;
namespace Server
{
public static class RegionLoader
{
public static void LoadRegions()
{
var path = Path.Join(Core.BaseDirectory, "Data/regions.json");
// Json Deserialization options for custom objects
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new MapConverterFactory());
options.Converters.Add(new Point3DConverterFactory());
options.Converters.Add(new Rectangle3DConverterFactory());
List<string> failures = new List<string>();
int count = 0;
Console.Write("Regions: Loading...");
var stopwatch = Stopwatch.StartNew();
List<DynamicJson> regions = JsonConfig.Deserialize<List<DynamicJson>>(path);
foreach (var json in regions)
{
Type type = AssemblyHandler.FindFirstTypeForName(json.Type);
if (type == null || !typeof(Region).IsAssignableFrom(type))
{
failures.Add($"\tInvalid region type {json.Type}");
continue;
}
var region = ActivatorUtil.CreateInstance(type, json, options) as Region;
region?.Register();
count++;
}
stopwatch.Stop();
Console.ForegroundColor = failures.Count > 0 ? ConsoleColor.Yellow : ConsoleColor.Green;
Console.Write("done{0}. ", failures.Count > 0 ? " with failures" : "");
Console.ResetColor();
Console.WriteLine("({0} regions, {1} failures) ({2:F2} seconds)", count, failures.Count, stopwatch.Elapsed.TotalSeconds);
if (failures.Count > 0)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(string.Join("\n", failures));
Console.ResetColor();
}
}
}
}

View file

@ -75,10 +75,10 @@
<CodeAnalysisRuleSet>..\..\Rules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
<PackageReference Include="ZLib.Bindings" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
@ -87,7 +87,7 @@
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers">
<Version>2.9.8</Version>
<Version>3.0.0</Version>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View file

@ -19,7 +19,6 @@
***************************************************************************/
using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;

View file

@ -47,7 +47,7 @@ namespace Server.Utilities
// check all given args map to params.
for (var i = 0; i < args.Length; i++)
// if a null reference is passed, but the type is not nullable
if ((args[i] == null && paramList[i].ParameterType.IsValueType)
if (args[i] == null && paramList[i].ParameterType.IsValueType
// or if an arg is not null and is not assignable to the parameter type, skip.
|| !(args[i] == null || paramList[i].ParameterType.IsAssignableFrom(args[i])))
return false;
@ -65,7 +65,8 @@ namespace Server.Utilities
}
catch (Exception e)
{
throw new TypeInitializationException(type.ToString(), e);
Console.WriteLine(e);
throw;
}
}

View file

@ -24,7 +24,8 @@ namespace Server
{
public interface IRandomProvider
{
public uint Next(uint c);
public ulong Next(ulong max);
public uint Next(uint max);
public bool NextBool();
public void GetBytes(Span<byte> b);
public double NextDouble();

View file

@ -31,6 +31,8 @@ namespace Server
public override void GetBytes(Span<byte> data) => m_Random.GetBytes(data);
public ulong Next(ulong c) => throw new NotImplementedException();
public uint Next(uint c) => throw new NotImplementedException();
public bool NextBool() => throw new NotImplementedException();

View file

@ -219,7 +219,7 @@ namespace Server.Misc
state.Send(new CharacterListUpdate(acct));
}
else if (m.AccessLevel == AccessLevel.Player &&
Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf<Jail>()) // Don't need to check current location, if netstate is null, they're logged out
Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf<JailRegion>()) // Don't need to check current location, if netstate is null, they're logged out
{
state.Send(new DeleteResult(DeleteResultType.BadRequest));
state.Send(new CharacterListUpdate(acct));

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Server.Items;
@ -190,13 +191,9 @@ namespace Server.Commands
if (!IsConstructible(ctor, from.AccessLevel))
continue;
int totalParams = 0;
// Handle optional constructors
ParameterInfo[] paramList = ctor.GetParameters();
for (int j = 0; j < paramList.Length; j++)
if (!paramList[j].HasDefaultValue)
totalParams += 1;
int totalParams = paramList.Count(t => !t.HasDefaultValue);
if (args.Length >= totalParams && args.Length <= paramList.Length)
{

View file

@ -4,8 +4,8 @@ using System.IO;
using System.Linq;
using Server.Engines.Quests.Haven;
using Server.Engines.Quests.Necro;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;
using Server.Utilities;
namespace Server.Commands

View file

@ -5,8 +5,8 @@ using System.IO;
using System.Linq;
using Server.Engines.Quests.Haven;
using Server.Engines.Quests.Necro;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;
using Server.Utilities;
namespace Server.Commands

View file

@ -11,7 +11,7 @@ namespace Server.Commands
{
public struct Location
{
[JsonPropertyName("point"), JsonConverter(typeof(Point3dConverter))]
[JsonPropertyName("point"), JsonConverter(typeof(Point3DConverter))]
public Point3D Pos { get; set; }
[JsonPropertyName("map"), JsonConverter(typeof(MapConverter))]
public Map Map { get; set; }

View file

@ -340,7 +340,7 @@ namespace Server.Commands
int v = -aCount.CompareTo(bCount);
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
return v != 0 ? v : x.Key.FullName?.CompareTo(y.Key.FullName) ?? -1;
}
}
@ -353,7 +353,7 @@ namespace Server.Commands
int v = -aCount.CompareTo(bCount);
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
return v != 0 ? v : x.Key.FullName?.CompareTo(y.Key.FullName) ?? 1;
}
}
}

View file

@ -1,4 +1,3 @@
using System;
using Server.Accounting;
using Server.Items;
using Server.Network;

View file

@ -1148,7 +1148,7 @@ namespace Server.Engines.ConPVP
if (!pm.CheckAlive())
{
}
else if (pm.Region.IsPartOf<Jail>())
else if (pm.Region.IsPartOf<JailRegion>())
{
}
else if (CheckCombat(pm))
@ -1942,7 +1942,7 @@ namespace Server.Engines.ConPVP
if (dp == null)
return "a slot is empty";
if (dp.Mobile.Region.IsPartOf<Jail>())
if (dp.Mobile.Region.IsPartOf<JailRegion>())
return $"{dp.Mobile.Name} is in jail";
if (Sigil.ExistsOn(dp.Mobile))

View file

@ -595,7 +595,7 @@ namespace Server.Engines.ConPVP
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
Sigil.ExistsOn(check) || check.Region.IsPartOf<JailRegion>())
{
bad = true;
break;
@ -776,7 +776,7 @@ namespace Server.Engines.ConPVP
Mobile check = part.Players[j];
if (check.Deleted || check.Map == null || check.Map == Map.Internal || !check.Alive ||
Sigil.ExistsOn(check) || check.Region.IsPartOf<Jail>())
Sigil.ExistsOn(check) || check.Region.IsPartOf<JailRegion>())
{
bad = true;
break;

View file

@ -254,7 +254,7 @@ namespace Server.Engines.Help
{
from.Location = house.BanLocation;
}
else if (from.Region.IsPartOf<Jail>())
else if (from.Region.IsPartOf<JailRegion>())
{
from.SendLocalizedMessage(1114345, "", 0x35); // You'll need a better jailbreak plan than that!
}
@ -313,7 +313,7 @@ namespace Server.Engines.Help
{
if (IsYoung(from))
{
if (from.Region.IsPartOf<Jail>())
if (from.Region.IsPartOf<JailRegion>())
from.SendLocalizedMessage(1114345, "", 0x35); // You'll need a better jailbreak plan than that!
else if (from.Region.IsPartOf("Haven Island"))
from.SendLocalizedMessage(1041529); // You're already in Haven

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Server.Configurations;
using Server.Misc;
using Server.Mobiles;
using Server.Network;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,5 +1,6 @@
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;
@ -275,4 +276,4 @@ namespace Server.Engines.MLQuests.Definitions
reader.ReadInt();
}
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,5 +1,6 @@
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,5 +1,6 @@
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;
@ -82,4 +83,4 @@ namespace Server.Engines.MLQuests.Definitions
int version = reader.ReadInt();
}
}
}
}

View file

@ -2,6 +2,7 @@ using System;
using Server.Engines.MLQuests.Items;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;
@ -2518,4 +2519,4 @@ namespace Server.Engines.MLQuests.Definitions
int version = reader.ReadInt();
}
}
}
}

View file

@ -1,5 +1,6 @@
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -1,6 +1,7 @@
using System;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Items;
using Server.Mobiles;

View file

@ -4,6 +4,7 @@ using System.Linq;
using Server.Engines.MLQuests.Gumps;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Engines.Spawners;
using Server.Mobiles;
namespace Server.Engines.MLQuests

View file

@ -162,7 +162,7 @@ namespace Server.Engines.Quests
public override void UseGate(Mobile m)
{
if (m.Region.IsPartOf<Jail>())
if (m.Region.IsPartOf<JailRegion>())
{
m.SendLocalizedMessage(1114345); // You'll need a better jailbreak plan than that!
}

View file

@ -1,41 +0,0 @@
using System;
using System.Xml;
using Server.Mobiles;
using Server.Regions;
namespace Server.Engines.Quests
{
public class CancelQuestRegion : BaseRegion
{
private readonly Type m_Quest;
public CancelQuestRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
ReadType(xml["quest"], "type", ref m_Quest);
}
public Type Quest => m_Quest;
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
if (!base.OnMoveInto(m, d, newLocation, oldLocation))
return false;
if (m.AccessLevel > AccessLevel.Player)
return true;
if (m_Quest == null)
return true;
if (m is PlayerMobile player && player.Quest != null && player.Quest.GetType() == m_Quest)
{
if (!player.HasGump<QuestCancelGump>())
player.Quest.BeginCancelQuest();
return false;
}
return true;
}
}
}

View file

@ -1,38 +0,0 @@
using System;
using System.Xml;
using Server.Mobiles;
using Server.Regions;
namespace Server.Engines.Quests
{
public class QuestCompleteObjectiveRegion : BaseRegion
{
private readonly Type m_Objective;
private readonly Type m_Quest;
public QuestCompleteObjectiveRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
XmlElement questEl = xml["quest"];
ReadType(questEl, "type", ref m_Quest);
ReadType(questEl, "complete", ref m_Objective);
}
public Type Quest => m_Quest;
public Type Objective => m_Objective;
public override void OnEnter(Mobile m)
{
base.OnEnter(m);
if (m_Quest != null && m_Objective != null)
if (m is PlayerMobile player && player.Quest != null && player.Quest.GetType() == m_Quest)
{
QuestObjective obj = player.Quest.FindObjective(m_Objective);
if (obj?.Completed == false)
obj.Complete();
}
}
}
}

View file

@ -1,55 +0,0 @@
using System;
using System.Xml;
using Server.Mobiles;
using Server.Regions;
namespace Server.Engines.Quests
{
public class QuestNoEntryRegion : BaseRegion
{
private readonly Type m_MaxObjective;
private readonly int m_Message;
private readonly Type m_MinObjective;
private readonly Type m_Quest;
public QuestNoEntryRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
XmlElement questEl = xml["quest"];
ReadType(questEl, "type", ref m_Quest);
ReadType(questEl, "min", ref m_MinObjective, false);
ReadType(questEl, "max", ref m_MaxObjective, false);
ReadInt32(questEl, "message", ref m_Message, false);
}
public Type Quest => m_Quest;
public Type MinObjective => m_MinObjective;
public Type MaxObjective => m_MaxObjective;
public int Message => m_Message;
public override bool OnMoveInto(Mobile m, Direction d, Point3D newLocation, Point3D oldLocation)
{
if (!base.OnMoveInto(m, d, newLocation, oldLocation))
return false;
if (m.AccessLevel > AccessLevel.Player)
return true;
if (m is BaseCreature bc && !bc.Controlled && !bc.Summoned)
return true;
if (m_Quest == null)
return true;
if (m is PlayerMobile player && player.Quest != null && player.Quest.GetType() == m_Quest
&& (m_MinObjective == null || player.Quest.FindObjective(m_MinObjective) != null)
&& (m_MaxObjective == null || player.Quest.FindObjective(m_MaxObjective) == null))
return true;
if (m_Message != 0)
m.SendLocalizedMessage(m_Message);
return false;
}
}
}

View file

@ -1,39 +0,0 @@
using System;
using System.Xml;
using Server.Mobiles;
using Server.Regions;
using Server.Utilities;
namespace Server.Engines.Quests
{
public class QuestOfferRegion : BaseRegion
{
private readonly Type m_Quest;
public QuestOfferRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
ReadType(xml["quest"], "type", ref m_Quest);
}
public Type Quest => m_Quest;
public override void OnEnter(Mobile m)
{
base.OnEnter(m);
if (m_Quest == null)
return;
if (m is PlayerMobile player && player.Quest == null && QuestSystem.CanOfferQuest(m, m_Quest))
try
{
QuestSystem qs = (QuestSystem)ActivatorUtil.CreateInstance(m_Quest, player);
qs.SendOffer();
}
catch (Exception ex)
{
Console.WriteLine("Error creating quest {0}: {1}", m_Quest, ex);
}
}
}
}

View file

@ -1,9 +0,0 @@
using System;
namespace Server.Mobiles
{
public class SpawnerType
{
public static Type GetType(string name) => AssemblyHandler.FindFirstTypeForName(name);
}
}

View file

@ -1,49 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using Server.Commands;
using Server.Items;
using Server.Json;
using Server.Mobiles;
using Server.Utilities;
using CPA = Server.CommandPropertyAttribute;
namespace Server.Mobiles
namespace Server.Engines.Spawners
{
public class Spawner : Item, ISpawner
public abstract class BaseSpawner : Item, ISpawner
{
private static WarnTimer m_WarnTimer;
private int m_Count;
private bool m_Group;
private int m_HomeRange;
private int m_Team;
private TimeSpan m_MaxDelay;
private TimeSpan m_MinDelay;
private bool m_Running;
private int m_Team;
private InternalTimer m_Timer;
private int m_WalkingRange = -1;
// [Constructible]
public Spawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnedNames) : base(0x1f13)
public BaseSpawner() : this(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4)
{
InitSpawn(amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay), team, homeRange);
AddEntry(spawnedNames, 100, amount, false);
}
// [Constructible]
public Spawner(string spawnedName) : base(0x1f13)
public BaseSpawner(string spawnedName) : this(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4, spawnedName)
{
InitSpawn(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4);
AddEntry(spawnedName, 100, 1, false);
}
[Constructible(AccessLevel.Developer)]
public Spawner() : base(0x1f13)
public BaseSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange,
params string[] spawnedNames) : this(amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay),
team, homeRange, spawnedNames)
{
InitSpawn(1, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(10), 0, 4);
}
public Spawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange,
public BaseSpawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange,
params string[] spawnedNames) : base(0x1f13)
{
InitSpawn(amount, minDelay, maxDelay, team, homeRange);
@ -51,7 +49,25 @@ namespace Server.Mobiles
AddEntry(spawnedNames[i], 100, amount, false);
}
public Spawner(Serial serial) : base(serial)
public BaseSpawner(DynamicJson json, JsonSerializerOptions options) : base(0x1f13)
{
json.GetProperty("count", options, out int amount);
json.GetProperty("minDelay", options, out TimeSpan minDelay);
json.GetProperty("maxDelay", options, out TimeSpan maxDelay);
json.GetProperty("team", options, out int team);
json.GetProperty("homeRange", options, out int homeRange);
json.GetProperty("walkingRange", options, out int walkingRange);
m_WalkingRange = walkingRange;
InitSpawn(amount, minDelay, maxDelay, team, homeRange);
json.GetProperty("entries", options, out List<SpawnerEntry> entries);
foreach (var entry in entries)
AddEntry(entry.SpawnedName, entry.SpawnedProbability, entry.SpawnedMaxCount, false);
}
public BaseSpawner(Serial serial) : base(serial)
{
}
@ -64,6 +80,9 @@ namespace Server.Mobiles
public Dictionary<ISpawnable, SpawnerEntry> Spawned { get; private set; }
[CommandProperty(AccessLevel.Developer)]
public bool ReturnOnDeactivate { get; set; }
[CommandProperty(AccessLevel.Developer)]
public int Count
{
@ -72,9 +91,8 @@ namespace Server.Mobiles
{
m_Count = value;
if (m_Timer != null)
if ((!IsFull && !m_Timer.Running) || (IsFull && m_Timer.Running))
DoTimer();
if (m_Timer != null && (!IsFull && !m_Timer.Running || IsFull && m_Timer.Running))
DoTimer();
InvalidateProperties();
}
@ -164,7 +182,7 @@ namespace Server.Mobiles
}
}
public Point3D HomeLocation => Location;
public virtual Point3D HomeLocation => Location;
public bool UnlinkOnTaming => true;
[CommandProperty(AccessLevel.Developer)]
@ -199,7 +217,7 @@ namespace Server.Mobiles
public override void OnAfterDuped(Item newItem)
{
if (newItem is Spawner newSpawner)
if (newItem is BaseSpawner newSpawner)
for (int i = 0; i < Entries.Count; i++)
newSpawner.AddEntry(Entries[i].SpawnedName, Entries[i].SpawnedProbability, Entries[i].SpawnedMaxCount,
false);
@ -238,6 +256,10 @@ namespace Server.Mobiles
from.SendGump(new SpawnerGump(this));
}
public virtual void GetSpawnerProperties(ObjectPropertyList list)
{
}
public override void GetProperties(ObjectPropertyList list)
{
base.GetProperties(list);
@ -248,14 +270,16 @@ namespace Server.Mobiles
list.Add(1060656, m_Count.ToString()); // amount to make: ~1_val~
list.Add(1061169, m_HomeRange.ToString()); // range ~1_val~
list.Add(1060658, "walking range\t{0}", m_WalkingRange); // ~1_val~: ~2_val~
list.Add(1050039, "walking range:\t{0}", m_WalkingRange); // ~1_NUMBER~ ~2_ITEMNAME~
list.Add(1060658, "group\t{0}", m_Group); // ~1_val~: ~2_val~
list.Add(1060659, "team\t{0}", m_Team); // ~1_val~: ~2_val~
list.Add(1060660, "speed\t{0} to {1}", m_MinDelay, m_MaxDelay); // ~1_val~: ~2_val~
list.Add(1053099, "group:\t{0}", m_Group); // ~1_oretype~: ~2_armortype~
list.Add(1060847, "team:\t{0}", m_Team); // ~1_val~ ~2_val~
list.Add(1063483, "delay:\t{0} to {1}", m_MinDelay, m_MaxDelay); // ~1_MATERIAL~: ~2_ITEMNAME~
for (int i = 0; i < 3 && i < Entries.Count; ++i)
list.Add(1060661 + i, "{0}\t{1}", Entries[i].SpawnedName, CountSpawns(Entries[i]));
GetSpawnerProperties(list);
for (int i = 0; i < 6 && i < Entries.Count; ++i)
list.Add(1060658 + i, "\t{0}\t{1}", Entries[i].SpawnedName, CountSpawns(Entries[i]));
}
else
{
@ -300,6 +324,22 @@ namespace Server.Mobiles
Entries[i].Defrag(this);
}
public virtual bool OnDefragSpawn(ISpawnable spawned, bool remove)
{
if (!remove) // Override could have set it to true already
remove = spawned.Deleted || spawned.Spawner == null || spawned switch
{
Item item => item.RootParent is Mobile || item.IsLockedDown || item.IsSecure,
Mobile m => m is BaseCreature c && (c.Controlled || c.IsStabled),
_ => true
};
if (remove)
Spawned.Remove(spawned);
return remove;
}
public void OnTick()
{
if (m_Group)
@ -336,11 +376,7 @@ namespace Server.Mobiles
if (Entries.Count <= 0 || IsFull)
return;
int probsum = 0;
for (int i = 0; i < Entries.Count; i++)
if (!Entries[i].IsFull)
probsum += Entries[i].SpawnedProbability;
int probsum = Entries.Where(t => !t.IsFull).Sum(t => t.SpawnedProbability);
if (probsum <= 0)
return;
@ -443,7 +479,7 @@ namespace Server.Mobiles
// Defrag taken care of in Spawn(), beforehand
// Count check taken care of in Spawn(), beforehand
Type type = SpawnerType.GetType(entry.SpawnedName);
Type type = AssemblyHandler.FindFirstTypeForName(entry.SpawnedName);
if (type != null)
{
@ -453,10 +489,8 @@ namespace Server.Mobiles
string[] paramargs;
string[] propargs;
if (string.IsNullOrEmpty(entry.Properties))
propargs = Array.Empty<string>();
else
propargs = CommandSystem.Split(entry.Properties.Trim());
propargs = string.IsNullOrEmpty(entry.Properties) ?
Array.Empty<string>() : CommandSystem.Split(entry.Properties.Trim());
string[,] props = FormatProperties(propargs);
@ -468,10 +502,8 @@ namespace Server.Mobiles
return false;
}
if (string.IsNullOrEmpty(entry.Parameters))
paramargs = Array.Empty<string>();
else
paramargs = entry.Parameters.Trim().Split(' ');
paramargs = string.IsNullOrEmpty(entry.Parameters) ?
Array.Empty<string>() : entry.Parameters.Trim().Split(' ');
if (paramargs.Length == 0)
{
@ -504,7 +536,6 @@ namespace Server.Mobiles
}
for (int i = 0; i < realProps.Length; i++)
{
if (realProps[i] != null)
{
object toSet = null;
@ -523,7 +554,6 @@ namespace Server.Mobiles
return false;
}
}
}
if (o is Mobile m)
{
@ -591,75 +621,7 @@ namespace Server.Mobiles
public virtual int GetWalkingRange() => m_WalkingRange;
public virtual Point3D GetSpawnPosition(ISpawnable spawned, Map map)
{
if (map == null || map == Map.Internal)
return Location;
bool waterMob, waterOnlyMob;
if (spawned is Mobile mob)
{
waterMob = mob.CanSwim;
waterOnlyMob = mob.CanSwim && mob.CantWalk;
}
else
{
waterMob = false;
waterOnlyMob = false;
}
// Try 10 times to find a Spawnable location.
for (int i = 0; i < 10; i++)
{
int x = Location.X + (Utility.Random(m_HomeRange * 2 + 1) - m_HomeRange);
int y = Location.Y + (Utility.Random(m_HomeRange * 2 + 1) - m_HomeRange);
int mapZ = map.GetAverageZ(x, y);
if (waterMob)
{
if (IsValidWater(map, x, y, Z))
return new Point3D(x, y, Z);
if (IsValidWater(map, x, y, mapZ))
return new Point3D(x, y, mapZ);
}
if (!waterOnlyMob)
{
if (map.CanSpawnMobile(x, y, Z))
return new Point3D(x, y, Z);
if (map.CanSpawnMobile(x, y, mapZ))
return new Point3D(x, y, mapZ);
}
}
return Location;
}
public static bool IsValidWater(Map map, int x, int y, int z)
{
if (!Region.Find(new Point3D(x, y, z), map).AllowSpawn() || !map.CanFit(x, y, z, 16, false, true, false))
return false;
LandTile landTile = map.Tiles.GetLandTile(x, y);
if (landTile.Z == z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet) != 0)
return true;
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true);
for (int i = 0; i < staticTiles.Length; ++i)
{
StaticTile staticTile = staticTiles[i];
if (staticTile.Z == z &&
(TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Flags & TileFlag.Wet) != 0)
return true;
}
return false;
}
public abstract Point3D GetSpawnPosition(ISpawnable spawned, Map map);
public virtual Map GetSpawnMap() => Map;
@ -783,7 +745,9 @@ namespace Server.Mobiles
{
base.Serialize(writer);
writer.Write(7); // version
writer.Write(8); // version
writer.Write(ReturnOnDeactivate);
writer.Write(Entries.Count);
@ -820,6 +784,11 @@ namespace Server.Mobiles
switch (version)
{
case 8:
{
ReturnOnDeactivate = reader.ReadBool();
goto case 7;
}
case 7:
{
int size = reader.ReadInt();
@ -909,7 +878,7 @@ namespace Server.Mobiles
else
Entries[i].SpawnedName = typeName;
if (SpawnerType.GetType(typeName) == null)
if (AssemblyHandler.FindFirstTypeForName(typeName) == null)
{
m_WarnTimer ??= new WarnTimer();
@ -928,7 +897,7 @@ namespace Server.Mobiles
e.Spawner = this;
for (int j = 0; j < Entries.Count; j++)
if (SpawnerType.GetType(Entries[j].SpawnedName) == e.GetType())
if (AssemblyHandler.FindFirstTypeForName(Entries[j].SpawnedName) == e.GetType())
{
Entries[j].Spawned.Add(e);
Spawned.Add(e, Entries[j]);
@ -947,30 +916,11 @@ namespace Server.Mobiles
m_WalkingRange = m_HomeRange;
}
public static string ConvertTypes(string type)
{
type = type.ToLower();
return type switch
{
"wheat" => "WheatSheaf",
"noxxiousmage" => "NoxiousMage",
"noxxiousarcher" => "NoxiousArcher",
"noxxiouswarrior" => "NoxiousWarrior",
"noxxiouswarlord" => "NoxiousWarlord",
"obsidian" => "obsidianstatue",
"adeepwaterelemental" => "deepwaterelemental",
"noxskeleton" => "poisonskeleton",
"earthcaller" => "earthsummoner",
"bonedemon" => "bonedaemon",
_ => type
};
}
private class InternalTimer : Timer
{
private readonly Spawner m_Spawner;
private readonly BaseSpawner m_Spawner;
public InternalTimer(Spawner spawner, TimeSpan delay) : base(delay)
public InternalTimer(BaseSpawner spawner, TimeSpan delay) : base(delay)
{
if (spawner.IsFull)
Priority = TimerPriority.FiveSeconds;
@ -1052,142 +1002,4 @@ namespace Server.Mobiles
InvalidProps = 0x004,
InvalidEntry = 0x008
}
public class SpawnerEntry
{
public SpawnerEntry(string name, int probability, int maxcount)
{
SpawnedName = name;
SpawnedProbability = probability;
SpawnedMaxCount = maxcount;
Spawned = new List<ISpawnable>();
}
public SpawnerEntry(Spawner parent, IGenericReader reader)
{
int version = reader.ReadInt();
SpawnedName = reader.ReadString();
SpawnedProbability = reader.ReadInt();
SpawnedMaxCount = reader.ReadInt();
Properties = reader.ReadString();
Parameters = reader.ReadString();
int count = reader.ReadInt();
Spawned = new List<ISpawnable>(count);
for (int i = 0; i < count; ++i)
// IEntity e = World.FindEntity( reader.ReadInt() );
if (reader.ReadEntity() is ISpawnable e)
{
e.Spawner = parent;
if (e is BaseCreature creature)
creature.RemoveIfUntamed = true;
Spawned.Add(e);
if (!parent.Spawned.ContainsKey(e))
parent.Spawned.Add(e, this);
}
}
public int SpawnedProbability { get; set; }
public int SpawnedMaxCount { get; set; }
public string SpawnedName { get; set; }
public string Properties { get; set; }
public string Parameters { get; set; }
public EntryFlags Valid { get; set; }
public List<ISpawnable> Spawned { get; }
public bool IsFull => Spawned.Count >= SpawnedMaxCount;
public void Serialize(IGenericWriter writer)
{
writer.Write(0); // version
writer.Write(SpawnedName);
writer.Write(SpawnedProbability);
writer.Write(SpawnedMaxCount);
writer.Write(Properties);
writer.Write(Parameters);
writer.Write(Spawned.Count);
for (int i = 0; i < Spawned.Count; ++i)
{
object o = Spawned[i];
if (o is Item item)
writer.Write(item);
else if (o is Mobile mobile)
writer.Write(mobile);
else
writer.Write(Serial.MinusOne);
}
}
public void Defrag(Spawner parent)
{
for (int i = 0; i < Spawned.Count; ++i)
{
ISpawnable e = Spawned[i];
bool remove = false;
if (e is Item item)
{
if (item.Deleted || item.RootParent is Mobile || item.IsLockedDown || item.IsSecure ||
item.Spawner == null)
remove = true;
}
else if (e is Mobile m)
{
if (m.Deleted)
{
remove = true;
}
else if (m is BaseCreature c)
{
if (c.Controlled || c.IsStabled)
remove = true;
/*
else if (c.Combatant == null && ( c.GetDistanceToSqrt( Location ) > (c.RangeHome * 4) ))
{
//m_Spawned[i].Delete();
m_Spawned.RemoveAt( i );
--i;
c.Delete();
remove = true;
}
*/
}
else if (m.Spawner == null)
{
remove = true;
}
}
else
{
remove = true;
}
if (remove)
{
Spawned.RemoveAt(i--);
if (parent.Spawned.ContainsKey(e))
parent.Spawned.Remove(e);
}
}
}
}
}

View file

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using Server.Json;
using Server.Utilities;
namespace Server.Engines.Spawners
{
public static class GenerateSpawners
{
public static void Initialize()
{
CommandSystem.Register("GenerateSpawners", AccessLevel.Developer, GenerateSpawners_OnCommand);
}
private static void GenerateSpawners_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
if (e.Arguments.Length == 0)
{
from.SendMessage("Usage: [GenerateSpawners <path|search pattern>");
return;
}
var di = new DirectoryInfo(Core.BaseDirectory);
var files = di.GetFiles(e.Arguments[0], SearchOption.AllDirectories);
if (files.Length == 0)
{
from.SendMessage("GenerateSpawners: No files found matching the pattern");
return;
}
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new MapConverterFactory());
options.Converters.Add(new Point3DConverterFactory());
options.Converters.Add(new TimeSpanConverterFactory());
options.Converters.Add(new TextDefinitionConverterFactory());
for (int i = 0; i < files.Length; i++)
{
var file = files[i];
from.SendMessage("GenerateSpawners: Generating spawners for {0}...", file.Name);
List<DynamicJson> spawners = JsonConfig.Deserialize<List<DynamicJson>>(file.FullName);
ParseSpawnerList(from, spawners, options);
}
}
private static void ParseSpawnerList(Mobile from, List<DynamicJson> spawners, JsonSerializerOptions options)
{
Stopwatch watch = Stopwatch.StartNew();
List<string> failures = new List<string>();
int count = 0;
for (var i = 0; i < spawners.Count; i++)
{
var json = spawners[i];
Type type = AssemblyHandler.FindFirstTypeForName(json.Type);
if (type == null || !typeof(BaseSpawner).IsAssignableFrom(type))
{
string failure = $"GenerateSpawners: Invalid spawner type {json.Type ?? "(-null-)"} ({i})";
if (!failures.Contains(failure))
{
failures.Add(failure);
from.SendMessage(failure);
}
continue;
}
json.GetProperty("location", options, out Point3D location);
json.GetProperty("map", options, out Map map);
var eable = map.GetItemsInRange<BaseSpawner>(location, 0);
if (eable.Any(sp => sp.GetType() == type))
{
eable.Free();
continue;
}
eable.Free();
try
{
var spawner = ActivatorUtil.CreateInstance(type, json, options) as ISpawner;
spawner!.MoveToWorld(location, map);
spawner!.Respawn();
}
catch (Exception)
{
string failure = $"GenerateSpawners: Spawner {type} failed to construct";
if (!failures.Contains(failure))
{
failures.Add(failure);
from.SendMessage(failure);
}
continue;
}
count++;
}
watch.Stop();
from.SendMessage("GenerateSpawners: Generated {0} spawners ({1:F2} seconds, {2} failures)", count, watch.Elapsed.TotalSeconds, failures.Count);
}
}
}

View file

@ -1,27 +1,30 @@
using System;
using System.Text.Json;
using Server.Json;
using Server.Mobiles;
namespace Server.Mobiles
namespace Server.Engines.Spawners
{
public class ProximitySpawner : Spawner
{
[Constructible]
[Constructible(AccessLevel.Developer)]
public ProximitySpawner()
{
}
[Constructible]
[Constructible(AccessLevel.Developer)]
public ProximitySpawner(string spawnName)
: base(spawnName)
{
}
[Constructible]
[Constructible(AccessLevel.Developer)]
public ProximitySpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, string spawnName)
: base(amount, minDelay, maxDelay, team, homeRange, spawnName)
{
}
[Constructible]
[Constructible(AccessLevel.Developer)]
public ProximitySpawner(int amount, int minDelay, int maxDelay, int team, int homeRange, int triggerRange,
string spawnMessage, bool instantFlag, string spawnName)
: base(amount, minDelay, maxDelay, team, homeRange, spawnName)
@ -31,12 +34,14 @@ namespace Server.Mobiles
InstantFlag = instantFlag;
}
[Constructible(AccessLevel.Developer)]
public ProximitySpawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange,
params string[] spawnedNames)
: base(amount, minDelay, maxDelay, team, homeRange, spawnedNames)
{
}
[Constructible(AccessLevel.Developer)]
public ProximitySpawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange, int triggerRange,
TextDefinition spawnMessage, bool instantFlag, params string[] spawnedNames)
: base(amount, minDelay, maxDelay, team, homeRange, spawnedNames)
@ -46,6 +51,17 @@ namespace Server.Mobiles
InstantFlag = instantFlag;
}
public ProximitySpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
json.GetProperty("triggerRange", options, out int triggerRange);
json.GetProperty("spawnMessage", options, out TextDefinition spawnMessage);
json.GetProperty("instant", options, out bool instant);
TriggerRange = triggerRange;
SpawnMessage = spawnMessage;
InstantFlag = instant;
}
public ProximitySpawner(Serial serial)
: base(serial)
{
@ -129,4 +145,4 @@ namespace Server.Mobiles
InstantFlag = reader.ReadBool();
}
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using System.Text.Json;
using Server.Json;
using Server.Regions;
namespace Server.Engines.Spawners
{
public class RegionSpawner : Spawner
{
private BaseRegion m_SpawnRegion;
[CommandProperty(AccessLevel.Developer)]
public BaseRegion SpawnRegion
{
get => m_SpawnRegion;
set
{
m_SpawnRegion = value;
m_SpawnRegion?.InitRectangles();
InvalidateProperties();
}
}
[Constructible(AccessLevel.Developer)]
public RegionSpawner()
{
}
[Constructible(AccessLevel.Developer)]
public RegionSpawner(string spawnedName) : base(spawnedName)
{
}
[Constructible(AccessLevel.Developer)]
public RegionSpawner(int amount, int minDelay, int maxDelay, int team, int homeRange,
params string[] spawnedNames) : this(amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay),
team, homeRange, spawnedNames)
{
}
[Constructible(AccessLevel.Developer)]
public RegionSpawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange,
params string[] spawnedNames) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames)
{
}
public RegionSpawner(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
json.GetProperty("map", options, out Map map);
json.GetProperty("region", options, out string spawnRegion);
m_SpawnRegion = Region.Find(spawnRegion, map) as BaseRegion;
m_SpawnRegion?.InitRectangles();
}
public RegionSpawner(Serial serial) : base(serial)
{
}
public override void GetSpawnerProperties(ObjectPropertyList list)
{
base.GetSpawnerProperties(list);
if (Running && m_SpawnRegion != null) list.Add(1076228, "region:\t{0}", m_SpawnRegion.Name); // ~1_DUMMY~ ~2_DUMMY~
}
public override Point3D GetSpawnPosition(ISpawnable spawned, Map map)
{
if (m_SpawnRegion == null || map == null || map == Map.Internal || map != m_SpawnRegion.Map || m_SpawnRegion.TotalWeight <= 0)
return Location;
bool waterMob, waterOnlyMob;
if (spawned is Mobile mob)
{
waterMob = mob.CanSwim;
waterOnlyMob = mob.CanSwim && mob.CantWalk;
}
else
{
waterMob = false;
waterOnlyMob = false;
}
// Try 10 times to find a valid location.
for (int i = 0; i < 10; i++)
{
int rand = Utility.Random(m_SpawnRegion.TotalWeight);
int x = int.MinValue;
int y = int.MinValue;
for (int j = 0; j < m_SpawnRegion.RectangleWeights.Length; j++)
{
int curWeight = m_SpawnRegion.RectangleWeights[j];
if (rand < curWeight)
{
Rectangle3D rect = m_SpawnRegion.Rectangles[j];
x = rect.Start.X + rand % rect.Width;
y = rect.Start.Y + rand / rect.Width;
break;
}
rand -= curWeight;
}
int mapZ = map.GetAverageZ(x, y);
if (waterMob)
{
if (IsValidWater(map, x, y, Z))
return new Point3D(x, y, Z);
if (IsValidWater(map, x, y, mapZ))
return new Point3D(x, y, mapZ);
}
if (!waterOnlyMob)
{
if (map.CanSpawnMobile(x, y, Z))
return new Point3D(x, y, Z);
if (map.CanSpawnMobile(x, y, mapZ))
return new Point3D(x, y, mapZ);
}
}
return HomeLocation;
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
m_SpawnRegion = Region.Find(reader.ReadString(), Map) as BaseRegion;
m_SpawnRegion?.InitRectangles();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(m_SpawnRegion?.Name);
}
}
}

View file

@ -0,0 +1,139 @@
using System;
using System.Text.Json;
using Server.Json;
namespace Server.Engines.Spawners
{
public class Spawner : BaseSpawner
{
public static bool IsValidWater(Map map, int x, int y, int z)
{
if (!Region.Find(new Point3D(x, y, z), map).AllowSpawn() || !map.CanFit(x, y, z, 16, false, true, false))
return false;
LandTile landTile = map.Tiles.GetLandTile(x, y);
if (landTile.Z == z && (TileData.LandTable[landTile.ID & TileData.MaxLandValue].Flags & TileFlag.Wet) != 0)
return true;
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true);
for (int i = 0; i < staticTiles.Length; ++i)
{
StaticTile staticTile = staticTiles[i];
if (staticTile.Z == z &&
(TileData.ItemTable[staticTile.ID & TileData.MaxItemValue].Flags & TileFlag.Wet) != 0)
return true;
}
return false;
}
[Constructible(AccessLevel.Developer)]
public Spawner()
{
}
[Constructible(AccessLevel.Developer)]
public Spawner(string spawnedName) : base(spawnedName)
{
}
[Constructible(AccessLevel.Developer)]
public Spawner(int amount, int minDelay, int maxDelay, int team, int homeRange,
params string[] spawnedNames) : this(amount, TimeSpan.FromMinutes(minDelay), TimeSpan.FromMinutes(maxDelay),
team, homeRange, spawnedNames)
{
}
[Constructible(AccessLevel.Developer)]
public Spawner(int amount, TimeSpan minDelay, TimeSpan maxDelay, int team, int homeRange,
params string[] spawnedNames) : base(amount, minDelay, maxDelay, team, homeRange, spawnedNames)
{
}
public Spawner(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public Spawner(Serial serial) : base(serial)
{
}
/*
public override bool OnDefragSpawn(ISpawnable spawned, bool remove)
{
// To despawn a mob that was lured 4x away from its spawner
// TODO: Move this to a config
if (spawned is BaseCreature c && c.Combatant == null && c.GetDistanceToSqrt( Location ) > c.RangeHome * 4)
{
c.Delete();
remove = true;
}
return base.OnDefragSpawn(entry, spawned, remove);
}
*/
public override Point3D GetSpawnPosition(ISpawnable spawned, Map map)
{
if (map == null || map == Map.Internal)
return Location;
bool waterMob, waterOnlyMob;
if (spawned is Mobile mob)
{
waterMob = mob.CanSwim;
waterOnlyMob = mob.CanSwim && mob.CantWalk;
}
else
{
waterMob = false;
waterOnlyMob = false;
}
// Try 10 times to find a valid location.
for (int i = 0; i < 10; i++)
{
int x = Location.X + (Utility.Random(HomeRange * 2 + 1) - HomeRange);
int y = Location.Y + (Utility.Random(HomeRange * 2 + 1) - HomeRange);
int mapZ = map.GetAverageZ(x, y);
if (waterMob)
{
if (IsValidWater(map, x, y, Z))
return new Point3D(x, y, Z);
if (IsValidWater(map, x, y, mapZ))
return new Point3D(x, y, mapZ);
}
if (!waterOnlyMob)
{
if (map.CanSpawnMobile(x, y, Z))
return new Point3D(x, y, Z);
if (map.CanSpawnMobile(x, y, mapZ))
return new Point3D(x, y, mapZ);
}
}
return HomeLocation;
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
}
}
}

View file

@ -0,0 +1,113 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Server.Mobiles;
namespace Server.Engines.Spawners
{
public class SpawnerEntry
{
public SpawnerEntry() => Spawned = new List<ISpawnable>();
public SpawnerEntry(string name, int probability, int maxcount) : this()
{
SpawnedName = name;
SpawnedProbability = probability;
SpawnedMaxCount = maxcount;
}
public SpawnerEntry(BaseSpawner parent, IGenericReader reader)
{
int version = reader.ReadInt();
SpawnedName = reader.ReadString();
SpawnedProbability = reader.ReadInt();
SpawnedMaxCount = reader.ReadInt();
Properties = reader.ReadString();
Parameters = reader.ReadString();
int count = reader.ReadInt();
Spawned = new List<ISpawnable>(count);
for (int i = 0; i < count; ++i)
// IEntity e = World.FindEntity( reader.ReadInt() );
if (reader.ReadEntity() is ISpawnable e)
{
e.Spawner = parent;
if (e is BaseCreature creature)
creature.RemoveIfUntamed = true;
Spawned.Add(e);
if (!parent.Spawned.ContainsKey(e))
parent.Spawned.Add(e, this);
}
}
[JsonPropertyName("probability")]
public int SpawnedProbability { get; set; }
[JsonPropertyName("maxCount")]
public int SpawnedMaxCount { get; set; }
[JsonPropertyName("name")]
public string SpawnedName { get; set; }
[JsonPropertyName("properties")]
public string Properties { get; set; }
[JsonPropertyName("parameters")]
public string Parameters { get; set; }
[JsonIgnore]
public EntryFlags Valid { get; set; }
[JsonIgnore]
public List<ISpawnable> Spawned { get; }
public bool IsFull => Spawned.Count >= SpawnedMaxCount;
public void Serialize(IGenericWriter writer)
{
writer.Write(0); // version
writer.Write(SpawnedName);
writer.Write(SpawnedProbability);
writer.Write(SpawnedMaxCount);
writer.Write(Properties);
writer.Write(Parameters);
writer.Write(Spawned.Count);
for (int i = 0; i < Spawned.Count; ++i)
{
object o = Spawned[i];
if (o is Item item)
writer.Write(item);
else if (o is Mobile mobile)
writer.Write(mobile);
else
writer.Write(Serial.MinusOne);
}
}
public void Defrag(BaseSpawner parent)
{
for (int i = 0; i < Spawned.Count; ++i)
{
ISpawnable spawned = Spawned[i];
if (parent.OnDefragSpawn(spawned, false))
{
Spawned.RemoveAt(i--);
parent.Spawned.Remove(spawned);
}
}
}
}
}

View file

@ -3,15 +3,15 @@ using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Mobiles
namespace Server.Engines.Spawners
{
public class SpawnerGump : Gump
{
private SpawnerEntry m_Entry;
private int m_Page;
private readonly Spawner m_Spawner;
private readonly BaseSpawner m_Spawner;
public SpawnerGump(Spawner spawner, SpawnerEntry focusentry = null, int page = 0) : base(50, 50)
public SpawnerGump(BaseSpawner spawner, SpawnerEntry focusentry = null, int page = 0) : base(50, 50)
{
m_Spawner = spawner;
m_Entry = focusentry;
@ -124,7 +124,7 @@ namespace Server.Mobiles
public int GetButtonID(int type, int index) => 1 + index * 10 + type;
public void CreateArray(RelayInfo info, Mobile from, Spawner spawner)
public void CreateArray(RelayInfo info, Mobile from, BaseSpawner spawner)
{
int ocount = spawner.Entries.Count;
@ -148,47 +148,46 @@ namespace Server.Mobiles
if (str.Length > 0)
{
Type type = SpawnerType.GetType(str);
Type type = AssemblyHandler.FindFirstTypeForName(str);
if (type != null)
if (type == null)
{
SpawnerEntry entry;
from.SendMessage("{0} is not a valid type name for entry #{1}.", str, i);
return;
}
if (entryindex < ocount)
{
entry = spawner.Entries[entryindex];
entry.SpawnedName = str;
SpawnerEntry entry;
if (mte != null)
entry.SpawnedMaxCount = Utility.ToInt32(mte.Text.Trim());
if (entryindex < ocount)
{
entry = spawner.Entries[entryindex];
entry.SpawnedName = str;
if (poste != null)
entry.SpawnedProbability = Utility.ToInt32(poste.Text.Trim());
}
else
{
int maxcount = 1;
int probcount = 100;
if (mte != null)
entry.SpawnedMaxCount = Utility.ToInt32(mte.Text.Trim());
if (mte != null)
maxcount = Utility.ToInt32(mte.Text.Trim());
if (poste != null)
probcount = Utility.ToInt32(poste.Text.Trim());
entry = spawner.AddEntry(str, probcount, maxcount);
}
if (parmte != null)
entry.Parameters = parmte.Text.Trim();
if (propte != null)
entry.Properties = propte.Text.Trim();
if (poste != null)
entry.SpawnedProbability = Utility.ToInt32(poste.Text.Trim());
}
else
{
from.SendMessage("{0} is not a valid type name for entry #{1}.", str, i);
int maxcount = 1;
int probcount = 100;
if (mte != null)
maxcount = Utility.ToInt32(mte.Text.Trim());
if (poste != null)
probcount = Utility.ToInt32(poste.Text.Trim());
entry = spawner.AddEntry(str, probcount, maxcount);
}
if (parmte != null)
entry.Parameters = parmte.Text.Trim();
if (propte != null)
entry.Properties = propte.Text.Trim();
}
else if (entryindex < ocount && spawner.Entries[entryindex] != null)
{

View file

@ -120,7 +120,7 @@ namespace Server.Items
private void InternalCallback(Mobile from, object targeted)
{
if (Deleted || UsesRemaining <= 0 || !from.InRange(GetWorldLocation(), 3) ||
if (Deleted || UsesRemaining <= 0 || !from.InRange(GetWorldLocation(), 3) ||
!IsAccessibleTo(from))
return;

View file

@ -138,7 +138,7 @@ namespace Server.Items
public override void OnDoubleClick(Mobile from)
{
if (from.Region.IsPartOf<Jail>())
if (from.Region.IsPartOf<JailRegion>())
from.SendMessage("You may not do that in jail.");
else if (!IsChildOf(from.Backpack))
MessageHelper.SendLocalizedMessageTo(this, from, 1062334,
@ -218,7 +218,7 @@ namespace Server.Items
if (m_Bag.Deleted)
return;
if (from.Region.IsPartOf<Jail>())
if (from.Region.IsPartOf<JailRegion>())
{
from.SendMessage("You may not do that in jail.");
}

View file

@ -201,7 +201,7 @@ namespace Server.Items
0x22); // The Crystal Ball fills with a red mist. You appear to have let your bond to your pet deteriorate.
}
else if (from.Map == Map.Ilshenar || from.Region.IsPartOf<DungeonRegion>() ||
from.Region.IsPartOf<Jail>() || from.Region.IsPartOf<SafeZone>())
from.Region.IsPartOf<JailRegion>() || from.Region.IsPartOf<SafeZone>())
{
from.Send(new AsciiMessage(Serial, ItemID, MessageType.Regular, 0x22, 3, "",
"You cannot summon your pet to this location."));

View file

@ -255,13 +255,13 @@ namespace Server.Items
return false;
}
if (from.Region.IsPartOf<Jail>())
if (from.Region.IsPartOf<JailRegion>())
{
from.SendLocalizedMessage(1114345, "", 0x35); // You'll need a better jailbreak plan than that!
return false;
}
if (boundRoot.Region.IsPartOf<Jail>())
if (boundRoot.Region.IsPartOf<JailRegion>())
{
from.SendLocalizedMessage(1019004); // You are not allowed to travel there.
return false;

View file

@ -0,0 +1,46 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TextDefinitionConverter.cs *
* Created: 2020/05/25 - Updated: 2020/05/25 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class TextDefinitionConverter : JsonConverter<TextDefinition>
{
public override TextDefinition Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
reader.TokenType switch
{
JsonTokenType.String => new TextDefinition(reader.GetString()),
JsonTokenType.Number => new TextDefinition(reader.GetInt32()),
_ => throw new JsonException("TextDefinition value must be an integer or string")
};
public override void Write(Utf8JsonWriter writer, TextDefinition value, JsonSerializerOptions options)
{
if (value.Number > 0)
writer.WriteNumberValue(value.Number);
else
writer.WriteStringValue(value.String);
}
}
}

View file

@ -0,0 +1,35 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TextDefinitionConverterFactory.cs *
* Created: 2020/05/25 - Updated: 2020/05/25 *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Server.Json
{
public class TextDefinitionConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(TextDefinition);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
new TextDefinitionConverter();
}
}

View file

@ -206,7 +206,7 @@ namespace Server.Misc
public static void Gain(Mobile from, Skill skill)
{
if (from.Region.IsPartOf<Jail>())
if (from.Region.IsPartOf<JailRegion>())
return;
if (from is BaseCreature creature && creature.IsDeadPet)

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Server.Mobiles;
using Server.Engines.Spawners;
namespace Server
{

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using Server.Mobiles;
using Server.Engines.Spawners;
// Version 0.8

View file

@ -4,11 +4,11 @@ using System.IO;
using Server.ContextMenus;
using Server.Engines.Quests;
using Server.Engines.Quests.Necro;
using Server.Engines.Spawners;
using Server.Factions;
using Server.Gumps;
using Server.Items;
using Server.Network;
using Server.Regions;
using Server.Spells;
using Server.Spells.Spellweaving;
using Server.Targets;
@ -1461,10 +1461,12 @@ namespace Server.Mobiles
m_Mobile.OwnerAbandonTime = DateTime.MinValue;
m_Mobile.IsBonded = false;
if (m_Mobile.Spawner is SpawnEntry se && se.HomeLocation != Point3D.Zero)
var spawner = m_Mobile.Spawner;
if (spawner != null && spawner.HomeLocation != Point3D.Zero)
{
m_Mobile.Home = se.HomeLocation;
m_Mobile.RangeHome = se.HomeRange;
m_Mobile.Home = spawner.HomeLocation;
m_Mobile.RangeHome = spawner.HomeRange;
}
if (m_Mobile.DeleteOnRelease || m_Mobile.IsDeadPet)
@ -1923,9 +1925,9 @@ namespace Server.Mobiles
if (m_Mobile.Home == Point3D.Zero)
{
if (m_Mobile.Spawner is SpawnEntry entry)
if (m_Mobile.Spawner is RegionSpawner rs)
{
Region region = entry.Region;
Region region = rs.SpawnRegion;
if (m_Mobile.Region.AcceptsSpawnsFrom(region))
{
@ -2375,13 +2377,15 @@ namespace Server.Mobiles
{
m_Timer.Stop();
if (m_Mobile.Spawner is SpawnEntry se && se.ReturnOnDeactivate && !m_Mobile.Controlled)
var spawner = m_Mobile.Spawner;
if (spawner?.ReturnOnDeactivate == true && !m_Mobile.Controlled)
{
if (se.HomeLocation == Point3D.Zero)
if (spawner.HomeLocation == Point3D.Zero)
{
if (!m_Mobile.Region.AcceptsSpawnsFrom(se.Region)) Timer.DelayCall(TimeSpan.Zero, ReturnToHome);
if (!m_Mobile.Region.AcceptsSpawnsFrom(spawner.Region)) Timer.DelayCall(TimeSpan.Zero, ReturnToHome);
}
else if (!m_Mobile.InRange(se.HomeLocation, se.HomeRange))
else if (!m_Mobile.InRange(spawner.HomeLocation, spawner.HomeRange))
{
Timer.DelayCall(TimeSpan.Zero, ReturnToHome);
}
@ -2391,11 +2395,11 @@ namespace Server.Mobiles
private void ReturnToHome()
{
if (m_Mobile.Spawner is SpawnEntry se)
if (m_Mobile.Spawner != null)
{
Point3D loc = se.RandomSpawnLocation(16, !m_Mobile.CantWalk, m_Mobile.CanSwim);
Point3D loc = m_Mobile.Spawner.GetSpawnPosition(m_Mobile, m_Mobile.Spawner.Map);
if (loc != Point3D.Zero) m_Mobile.MoveToWorld(loc, se.Region.Map);
if (loc != Point3D.Zero) m_Mobile.MoveToWorld(loc, m_Mobile.Spawner.Map);
}
}

View file

@ -9,6 +9,7 @@ using Server.Engines.PartySystem;
using Server.Engines.Quests;
using Server.Engines.Quests.Doom;
using Server.Engines.Quests.Haven;
using Server.Engines.Spawners;
using Server.Ethics;
using Server.Factions;
using Server.Items;
@ -2688,7 +2689,7 @@ namespace Server.Mobiles
}
public override bool CanBeRenamedBy(Mobile from) =>
(Controlled && from == ControlMaster && !from.Region.IsPartOf<Jail>()) ||
(Controlled && from == ControlMaster && !from.Region.IsPartOf<JailRegion>()) ||
base.CanBeRenamedBy(from);
public bool SetControlMaster(Mobile m)
@ -2746,7 +2747,7 @@ namespace Server.Mobiles
{
base.OnRegionChange(Old, New);
if (Controlled && Spawner is SpawnEntry se && !se.UnlinkOnTaming && New?.AcceptsSpawnsFrom(se.Region) != true)
if (Controlled && Spawner?.UnlinkOnTaming == false && New?.AcceptsSpawnsFrom(Spawner.Region) != true)
{
Spawner.Remove(this);
Spawner = null;

View file

@ -4119,7 +4119,7 @@ namespace Server.Mobiles
public bool YoungDeathTeleport()
{
if (Region.IsPartOf<Jail>()
if (Region.IsPartOf<JailRegion>()
|| Region.IsPartOf("Samurai start location")
|| Region.IsPartOf("Ninja start location")
|| Region.IsPartOf("Ninja cave"))

View file

@ -1,35 +1,25 @@
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text.Json;
using Server.Gumps;
using Server.Items;
using Server.Json;
using Server.Mobiles;
using Server.Spells;
namespace Server.Regions
{
public enum SpawnZLevel
{
Lowest,
Highest,
Random
}
public class BaseRegion : Region
{
private static readonly List<Rectangle3D> m_RectBuffer1 = new List<Rectangle3D>();
private static readonly List<Rectangle3D> m_RectBuffer2 = new List<Rectangle3D>();
private static readonly List<int> m_SpawnBuffer1 = new List<int>();
private static readonly List<Item> m_SpawnBuffer2 = new List<Item>();
private bool m_ExcludeFromParentSpawns;
public bool ExcludeFromParentSpawns { get; set; }
private Rectangle3D[] m_Rectangles;
private int[] m_RectangleWeights;
public Rectangle3D[] Rectangles { get; private set; }
public int[] RectangleWeights { get; private set; }
private string m_RuneName;
private SpawnEntry[] m_Spawns;
private int m_TotalWeight;
public int TotalWeight { get; private set; }
public BaseRegion(string name, Map map, int priority, params Rectangle2D[] area) : base(name, map, priority, area)
{
@ -47,63 +37,12 @@ namespace Server.Regions
{
}
public BaseRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
public BaseRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
ReadString(xml["rune"], "name", ref m_RuneName, false);
if (json.data.TryGetValue("rune", out var runeName))
m_RuneName = runeName.GetString();
bool logoutDelayActive = true;
ReadBoolean(xml["logoutDelay"], "active", ref logoutDelayActive, false);
NoLogoutDelay = !logoutDelayActive;
XmlElement spawning = xml["spawning"];
if (spawning != null)
{
ReadBoolean(spawning, "excludeFromParent", ref m_ExcludeFromParentSpawns, false);
SpawnZLevel zLevel = SpawnZLevel.Lowest;
ReadEnum(spawning, "zLevel", ref zLevel, false);
SpawnZLevel = zLevel;
List<SpawnEntry> list = new List<SpawnEntry>();
foreach (XmlNode node in spawning.ChildNodes)
if (node is XmlElement el)
{
SpawnDefinition def = SpawnDefinition.GetSpawnDefinition(el);
if (def == null)
continue;
int id = 0;
if (!ReadInt32(el, "id", ref id, true))
continue;
int amount = 0;
if (!ReadInt32(el, "amount", ref amount, true))
continue;
TimeSpan minSpawnTime = SpawnEntry.DefaultMinSpawnTime;
ReadTimeSpan(el, "minSpawnTime", ref minSpawnTime, false);
TimeSpan maxSpawnTime = SpawnEntry.DefaultMaxSpawnTime;
ReadTimeSpan(el, "maxSpawnTime", ref maxSpawnTime, false);
Point3D home = Point3D.Zero;
int range = 0;
XmlElement homeEl = el["home"];
if (ReadPoint3D(homeEl, map, ref home, false))
ReadInt32(homeEl, "range", ref range, false);
Direction dir = SpawnEntry.InvalidDirection;
ReadEnum(el["direction"], "value", ref dir, false);
SpawnEntry entry = new SpawnEntry(id, this, home, range, dir, def, amount, minSpawnTime,
maxSpawnTime);
list.Add(entry);
}
if (list.Count > 0) m_Spawns = list.ToArray();
}
NoLogoutDelay = json.data.TryGetValue("logoutDelay", out var logoutDelay) && !logoutDelay.GetBoolean();
}
public virtual bool YoungProtected => true;
@ -121,39 +60,11 @@ namespace Server.Regions
public bool NoLogoutDelay { get; set; }
public SpawnEntry[] Spawns
{
get => m_Spawns;
set
{
if (m_Spawns != null)
for (int i = 0; i < m_Spawns.Length; i++)
m_Spawns[i].Delete();
m_Spawns = value;
}
}
public SpawnZLevel SpawnZLevel { get; set; }
public bool ExcludeFromParentSpawns
{
get => m_ExcludeFromParentSpawns;
set => m_ExcludeFromParentSpawns = value;
}
public static void Configure()
{
DefaultRegionType = typeof(BaseRegion);
}
public override void OnUnregister()
{
base.OnUnregister();
Spawns = null;
}
public static string GetRuneNameFor(Region region)
{
while (region != null)
@ -169,61 +80,24 @@ namespace Server.Regions
return null;
}
public override TimeSpan GetLogoutDelay(Mobile m)
{
if (NoLogoutDelay)
if (m.Aggressors.Count == 0 && m.Aggressed.Count == 0 && !m.Criminal)
return TimeSpan.Zero;
return base.GetLogoutDelay(m);
}
public static bool CanSpawn(Region region, params Type[] types)
{
while (region != null)
{
if (!region.AllowSpawn())
return false;
if (region is BaseRegion br)
{
if (br.Spawns != null)
for (int i = 0; i < br.Spawns.Length; i++)
{
SpawnEntry entry = br.Spawns[i];
if (entry.Definition.CanSpawn(types))
return true;
}
if (br.ExcludeFromParentSpawns)
return false;
}
region = region.Parent;
}
return false;
}
public override TimeSpan GetLogoutDelay(Mobile m) =>
NoLogoutDelay && m.Aggressors.Count == 0 && m.Aggressed.Count == 0 && !m.Criminal
? TimeSpan.Zero
: base.GetLogoutDelay(m);
public override void OnEnter(Mobile m)
{
if (m is PlayerMobile mobile && mobile.Young)
if (!YoungProtected)
mobile.SendGump(new YoungDungeonWarning());
if (m is PlayerMobile mobile && mobile.Young && !YoungProtected)
mobile.SendGump(new YoungDungeonWarning());
}
public override bool AcceptsSpawnsFrom(Region region)
{
if (region == this || !m_ExcludeFromParentSpawns)
return base.AcceptsSpawnsFrom(region);
public override bool AcceptsSpawnsFrom(Region region) =>
(region == this || !ExcludeFromParentSpawns) && base.AcceptsSpawnsFrom(region);
return false;
}
private void InitRectangles()
// TODO: Clean this up
public void InitRectangles()
{
if (m_Rectangles != null)
if (Rectangles != null)
return;
// Test if area rectangles are overlapping, and in that case break them into smaller non overlapping rectangles
@ -270,277 +144,22 @@ namespace Server.Regions
m_RectBuffer2.Clear();
}
m_Rectangles = m_RectBuffer1.ToArray();
Rectangles = m_RectBuffer1.ToArray();
m_RectBuffer1.Clear();
m_RectangleWeights = new int[m_Rectangles.Length];
for (int i = 0; i < m_Rectangles.Length; i++)
RectangleWeights = new int[Rectangles.Length];
for (int i = 0; i < Rectangles.Length; i++)
{
Rectangle3D rect = m_Rectangles[i];
Rectangle3D rect = Rectangles[i];
int weight = rect.Width * rect.Height;
m_RectangleWeights[i] = weight;
m_TotalWeight += weight;
RectangleWeights[i] = weight;
TotalWeight += weight;
}
}
public Point3D RandomSpawnLocation(int spawnHeight, bool land, bool water, Point3D home, int range)
{
Map map = Map;
public override string ToString() => Name ?? RuneName ?? GetType().Name;
if (map == Map.Internal)
return Point3D.Zero;
InitRectangles();
if (m_TotalWeight <= 0)
return Point3D.Zero;
for (int i = 0; i < 10; i++) // Try 10 times
{
int x, y, minZ, maxZ;
if (home == Point3D.Zero)
{
int rand = Utility.Random(m_TotalWeight);
x = int.MinValue;
y = int.MinValue;
minZ = int.MaxValue;
maxZ = int.MinValue;
for (int j = 0; j < m_RectangleWeights.Length; j++)
{
int curWeight = m_RectangleWeights[j];
if (rand < curWeight)
{
Rectangle3D rect = m_Rectangles[j];
x = rect.Start.X + rand % rect.Width;
y = rect.Start.Y + rand / rect.Width;
minZ = rect.Start.Z;
maxZ = rect.End.Z;
break;
}
rand -= curWeight;
}
}
else
{
x = Utility.RandomMinMax(home.X - range, home.X + range);
y = Utility.RandomMinMax(home.Y - range, home.Y + range);
minZ = int.MaxValue;
maxZ = int.MinValue;
for (int j = 0; j < Area.Length; j++)
{
Rectangle3D rect = Area[j];
if (x >= rect.Start.X && x < rect.End.X && y >= rect.Start.Y && y < rect.End.Y)
{
minZ = rect.Start.Z;
maxZ = rect.End.Z;
break;
}
}
if (minZ == int.MaxValue)
continue;
}
if (x < 0 || y < 0 || x >= map.Width || y >= map.Height)
continue;
LandTile lt = map.Tiles.GetLandTile(x, y);
int ltLowZ = 0, ltAvgZ = 0, ltTopZ = 0;
map.GetAverageZ(x, y, ref ltLowZ, ref ltAvgZ, ref ltTopZ);
TileFlag ltFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags;
bool ltImpassable = (ltFlags & TileFlag.Impassable) != 0;
if (!lt.Ignored && ltAvgZ >= minZ && ltAvgZ < maxZ)
if ((ltFlags & TileFlag.Wet) != 0)
{
if (water)
m_SpawnBuffer1.Add(ltAvgZ);
}
else if (land && !ltImpassable)
{
m_SpawnBuffer1.Add(ltAvgZ);
}
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y, true);
for (int j = 0; j < staticTiles.Length; j++)
{
StaticTile tile = staticTiles[j];
ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
int tileZ = tile.Z + id.CalcHeight;
if (tileZ >= minZ && tileZ < maxZ)
if ((id.Flags & TileFlag.Wet) != 0)
{
if (water)
m_SpawnBuffer1.Add(tileZ);
}
else if (land && id.Surface && !id.Impassable)
{
m_SpawnBuffer1.Add(tileZ);
}
}
Sector sector = map.GetSector(x, y);
for (int j = 0; j < sector.Items.Count; j++)
{
Item item = sector.Items[j];
if (!(item is BaseMulti) && item.ItemID <= TileData.MaxItemValue && item.AtWorldPoint(x, y))
{
m_SpawnBuffer2.Add(item);
if (!item.Movable)
{
ItemData id = item.ItemData;
int itemZ = item.Z + id.CalcHeight;
if (itemZ >= minZ && itemZ < maxZ)
if ((id.Flags & TileFlag.Wet) != 0)
{
if (water)
m_SpawnBuffer1.Add(itemZ);
}
else if (land && id.Surface && !id.Impassable)
{
m_SpawnBuffer1.Add(itemZ);
}
}
}
}
if (m_SpawnBuffer1.Count == 0)
{
m_SpawnBuffer1.Clear();
m_SpawnBuffer2.Clear();
continue;
}
int z;
switch (SpawnZLevel)
{
case SpawnZLevel.Lowest:
{
z = int.MaxValue;
for (int j = 0; j < m_SpawnBuffer1.Count; j++)
{
int l = m_SpawnBuffer1[j];
if (l < z)
z = l;
}
break;
}
case SpawnZLevel.Highest:
{
z = int.MinValue;
for (int j = 0; j < m_SpawnBuffer1.Count; j++)
{
int l = m_SpawnBuffer1[j];
if (l > z)
z = l;
}
break;
}
default: // SpawnZLevel.Random
{
int index = Utility.Random(m_SpawnBuffer1.Count);
z = m_SpawnBuffer1[index];
break;
}
}
m_SpawnBuffer1.Clear();
if (!Find(new Point3D(x, y, z), map).AcceptsSpawnsFrom(this))
{
m_SpawnBuffer2.Clear();
continue;
}
int top = z + spawnHeight;
bool ok = true;
for (int j = 0; j < m_SpawnBuffer2.Count; j++)
{
Item item = m_SpawnBuffer2[j];
ItemData id = item.ItemData;
if ((id.Surface || id.Impassable) && item.Z + id.CalcHeight > z && item.Z < top)
{
ok = false;
break;
}
}
m_SpawnBuffer2.Clear();
if (!ok)
continue;
if (ltImpassable && ltAvgZ > z && ltLowZ < top)
continue;
for (int j = 0; j < staticTiles.Length; j++)
{
StaticTile tile = staticTiles[j];
ItemData id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
if ((id.Surface || id.Impassable) && tile.Z + id.CalcHeight > z && tile.Z < top)
{
ok = false;
break;
}
}
if (!ok)
continue;
for (int j = 0; j < sector.Mobiles.Count; j++)
{
Mobile m = sector.Mobiles[j];
if (m.X == x && m.Y == y && (m.AccessLevel == AccessLevel.Player || !m.Hidden))
if (m.Z + 16 > z && m.Z < top)
{
ok = false;
break;
}
}
if (ok)
return new Point3D(x, y, z);
}
return Point3D.Zero;
}
public override string ToString()
{
if (Name != null)
return Name;
if (RuneName != null)
return RuneName;
return GetType().Name;
}
public virtual bool CheckTravel(Mobile m, Point3D newLocation, TravelCheckType travelType) => true;
}
}
}

View file

@ -1,29 +1,22 @@
using System.Xml;
using System.Text.Json;
using Server.Json;
namespace Server.Regions
{
public class DungeonRegion : BaseRegion
{
private Point3D m_EntranceLocation;
public DungeonRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
public DungeonRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
XmlElement entrEl = xml["entrance"];
if (json.GetProperty("map", options, out Map map))
EntranceMap = map;
Map entrMap = map;
ReadMap(entrEl, "map", ref entrMap, false);
if (ReadPoint3D(entrEl, entrMap, ref m_EntranceLocation, false))
EntranceMap = entrMap;
if (json.GetProperty("entrance", options, out Point3D entrance))
EntranceLocation = entrance;
}
public override bool YoungProtected => false;
public Point3D EntranceLocation
{
get => m_EntranceLocation;
set => m_EntranceLocation = value;
}
public Point3D EntranceLocation { get; set; }
public Map EntranceMap { get; set; }
@ -34,12 +27,6 @@ namespace Server.Regions
global = LightCycle.DungeonLevel;
}
public override bool CanUseStuckMenu(Mobile m)
{
if (Map == Map.Felucca)
return false;
return base.CanUseStuckMenu(m);
}
public override bool CanUseStuckMenu(Mobile m) => Map != Map.Felucca && base.CanUseStuckMenu(m);
}
}
}

View file

@ -1,34 +0,0 @@
using System.Xml;
using Server.Spells.Chivalry;
using Server.Spells.Fourth;
using Server.Spells.Seventh;
using Server.Spells.Sixth;
namespace Server.Regions
{
public class GreenAcres : BaseRegion
{
public GreenAcres(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
}
public override bool AllowHousing(Mobile from, Point3D p)
{
if (from.AccessLevel == AccessLevel.Player)
return false;
return base.AllowHousing(from, p);
}
public override bool OnBeginSpellCast(Mobile m, ISpell s)
{
if ((s is GateTravelSpell || s is RecallSpell || s is MarkSpell || s is SacredJourneySpell) &&
m.AccessLevel == AccessLevel.Player)
{
m.SendMessage("You cannot cast that spell here.");
return false;
}
return base.OnBeginSpellCast(m, s);
}
}
}

View file

@ -0,0 +1,31 @@
using System.Text.Json;
using Server.Json;
using Server.Spells;
using Server.Spells.Sixth;
namespace Server.Regions
{
public class GreenAcresRegion : BaseRegion
{
public GreenAcresRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public override bool AllowHousing(Mobile from, Point3D p) =>
from.AccessLevel != AccessLevel.Player && base.AllowHousing(from, p);
public override bool CheckTravel(Mobile m, Point3D newLocation, TravelCheckType travelType) =>
m.AccessLevel != AccessLevel.Player;
public override bool OnBeginSpellCast(Mobile m, ISpell s)
{
if (m.AccessLevel == AccessLevel.Player && s is MarkSpell)
{
m.SendLocalizedMessage(501802); // Thy spell doth not appear to work...
return false;
}
return base.OnBeginSpellCast(m, s);
}
}
}

View file

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Text.Json;
using Server.Json;
using Server.Mobiles;
using Server.Utilities;
@ -14,21 +15,23 @@ namespace Server.Regions
private readonly Dictionary<Mobile, GuardTimer> m_GuardCandidates = new Dictionary<Mobile, GuardTimer>();
private readonly Type m_GuardType;
public GuardedRegion(string name, Map map, int priority, params Rectangle3D[] area) : base(name, map, priority, area) => m_GuardType = DefaultGuardType;
public GuardedRegion(string name, Map map, int priority, params Rectangle2D[] area)
: base(name, map, priority, area) =>
public GuardedRegion(string name, Map map, int priority, params Rectangle3D[] area) : base(name, map, priority, area) =>
m_GuardType = DefaultGuardType;
public GuardedRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
XmlElement el = xml["guards"];
public GuardedRegion(string name, Map map, int priority, params Rectangle2D[] area) : base(name, map, priority, area) =>
m_GuardType = DefaultGuardType;
if (ReadType(el, "type", ref m_GuardType, false))
public GuardedRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
if (json.GetProperty("guardsType", options, out string guardType))
{
if (!typeof(Mobile).IsAssignableFrom(m_GuardType))
m_GuardType = AssemblyHandler.FindFirstTypeForName(guardType);
if (!typeof(BaseGuard).IsAssignableFrom(m_GuardType))
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Invalid guard type for region '{0}'", this);
Console.ResetColor();
m_GuardType = DefaultGuardType;
}
}
@ -37,9 +40,7 @@ namespace Server.Regions
m_GuardType = DefaultGuardType;
}
bool disabled = false;
if (ReadBoolean(el, "disabled", ref disabled, false))
Disabled = disabled;
Disabled = json.GetProperty("guardsDisabled", options, out bool disabled) && disabled;
}
public bool Disabled { get; set; }
@ -98,10 +99,9 @@ namespace Server.Regions
{
reg.Disabled = !e.GetBoolean(0);
if (reg.Disabled)
from.SendMessage("The guards in this region have been disabled.");
else
from.SendMessage("The guards in this region have been enabled.");
from.SendMessage(reg.Disabled
? "The guards in this region have been disabled."
: "The guards in this region have been enabled.");
}
}
else
@ -125,10 +125,9 @@ namespace Server.Regions
{
reg.Disabled = !reg.Disabled;
if (reg.Disabled)
from.SendMessage("The guards in this region have been disabled.");
else
from.SendMessage("The guards in this region have been enabled.");
from.SendMessage(reg.Disabled
? "The guards in this region have been disabled."
: "The guards in this region have been enabled.");
}
}
@ -138,13 +137,8 @@ namespace Server.Regions
return reg;
}
public virtual bool CheckVendorAccess(BaseVendor vendor, Mobile from)
{
if (from.AccessLevel >= AccessLevel.GameMaster || IsDisabled())
return true;
return from.Kills < 5;
}
public virtual bool CheckVendorAccess(BaseVendor vendor, Mobile from) =>
from.AccessLevel >= AccessLevel.GameMaster || IsDisabled() || from.Kills < 5;
public override bool OnBeginSpellCast(Mobile m, ISpell s)
{
@ -298,7 +292,7 @@ namespace Server.Regions
foreach (Mobile m in eable)
if (IsGuardCandidate(m) &&
((!AllowReds && m.Kills >= 5 && m.Region.IsPartOf(this)) || m_GuardCandidates.ContainsKey(m)))
(!AllowReds && m.Kills >= 5 && m.Region.IsPartOf(this) || m_GuardCandidates.ContainsKey(m)))
{
if (m_GuardCandidates.TryGetValue(m, out GuardTimer timer))
{
@ -314,14 +308,10 @@ namespace Server.Regions
eable.Free();
}
public bool IsGuardCandidate(Mobile m)
{
if (m is BaseGuard || !m.Alive || m.AccessLevel > AccessLevel.Player || m.Blessed ||
(m is BaseCreature creature && creature.IsInvulnerable) || IsDisabled())
return false;
return (!AllowReds && m.Kills >= 5) || m.Criminal;
}
public bool IsGuardCandidate(Mobile m) =>
!(m is BaseGuard) && m.Alive && m.AccessLevel <= AccessLevel.Player && !m.Blessed &&
(!(m is BaseCreature creature) || !creature.IsInvulnerable) && !IsDisabled() &&
(!AllowReds && m.Kills >= 5 || m.Criminal);
private class GuardTimer : Timer
{
@ -338,11 +328,8 @@ namespace Server.Regions
protected override void OnTick()
{
if (m_Table.ContainsKey(m_Mobile))
{
m_Table.Remove(m_Mobile);
if (m_Table.Remove(m_Mobile))
m_Mobile.SendLocalizedMessage(502276); // Guards can no longer be called on you.
}
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
@ -82,43 +83,37 @@ namespace Server.Regions
BaseCreature bc = m as BaseCreature;
if (bc?.NoHouseRestrictions == true)
if (bc?.NoHouseRestrictions != true &&
(bc?.IsHouseSummonable != true || BaseCreature.Summoning || House.IsInside(oldLocation, 16)))
{
}
else if (bc?.IsHouseSummonable == true &&
!(BaseCreature.Summoning || House.IsInside(oldLocation, 16)))
{
}
else if ((House.Public || !House.IsAosRules) && House.IsBanned(m) && House.IsInside(m))
{
m.Location = House.BanLocation;
if (!Core.SE)
m.SendLocalizedMessage(501284); // You may not enter.
}
else if (House.IsAosRules && !House.Public && !House.HasAccess(m) && House.IsInside(m))
{
m.Location = House.BanLocation;
if (!Core.SE)
m.SendLocalizedMessage(501284); // You may not enter.
}
else if (House.IsCombatRestricted(m) && House.IsInside(m) && !House.IsInside(oldLocation, 16))
{
m.Location = House.BanLocation;
m.SendLocalizedMessage(1061637); // You are not allowed to access this.
}
else
{
HouseFoundation foundation = House as HouseFoundation;
if (foundation?.Customizer != null && foundation.Customizer != m && House.IsInside(m))
if ((House.Public || !House.IsAosRules) && House.IsBanned(m) && House.IsInside(m))
{
m.Location = House.BanLocation;
if (!Core.SE)
m.SendLocalizedMessage(501284); // You may not enter.
}
else if (House.IsAosRules && !House.Public && !House.HasAccess(m) && House.IsInside(m))
{
m.Location = House.BanLocation;
if (!Core.SE)
m.SendLocalizedMessage(501284); // You may not enter.
}
else if (House.IsCombatRestricted(m) && House.IsInside(m) && !House.IsInside(oldLocation, 16))
{
m.Location = House.BanLocation;
m.SendLocalizedMessage(1061637); // You are not allowed to access this.
}
else if (House is HouseFoundation foundation && foundation?.Customizer != null && foundation.Customizer != m &&
House.IsInside(m))
{
m.Location = House.BanLocation;
}
}
if (House.InternalizedVendors.Count > 0 && House.IsInside(m) && !House.IsInside(oldLocation, 16) &&
House.IsOwner(m) && m.Alive &&
!m.HasGump<NoticeGump>())
House.IsOwner(m) && m.Alive && !m.HasGump<NoticeGump>())
m.SendGump(new NoticeGump(1060635, 30720, 1061826, 32512, 320, 180));
m_Recursion = false;
@ -131,48 +126,40 @@ namespace Server.Regions
BaseCreature bc = from as BaseCreature;
if (bc?.NoHouseRestrictions == true)
if (bc?.NoHouseRestrictions != true)
{
}
else if (bc?.Controlled == false) // Untamed creatures cannot enter public houses
{
return false;
}
else if (bc?.IsHouseSummonable == true &&
!(BaseCreature.Summoning || House.IsInside(oldLocation, 16)))
{
return false;
}
else if (bc?.Controlled == false && House.IsAosRules && !House.Public)
{
return false;
}
else if ((House.Public || !House.IsAosRules) && House.IsBanned(from) && House.IsInside(newLocation, 16))
{
from.Location = House.BanLocation;
if (bc?.Controlled == false) // Untamed creatures cannot enter public houses
return false;
if (!Core.SE)
from.SendLocalizedMessage(501284); // You may not enter.
if (bc?.IsHouseSummonable == true &&
!(BaseCreature.Summoning || House.IsInside(oldLocation, 16)))
return false;
if (bc?.Controlled == false && House.IsAosRules && !House.Public)
return false;
if ((House.Public || !House.IsAosRules) && House.IsBanned(from) && House.IsInside(newLocation, 16))
{
from.Location = House.BanLocation;
return false;
}
else if (House.IsAosRules && !House.Public && !House.HasAccess(from) && House.IsInside(newLocation, 16))
{
if (!Core.SE)
from.SendLocalizedMessage(501284); // You may not enter.
if (!Core.SE)
from.SendLocalizedMessage(501284); // You may not enter.
return false;
}
else if (House.IsCombatRestricted(from) && !House.IsInside(oldLocation, 16) && House.IsInside(newLocation, 16))
{
from.SendLocalizedMessage(1061637); // You are not allowed to access this.
return false;
}
else
{
HouseFoundation foundation = House as HouseFoundation;
return false;
}
if (House.IsAosRules && !House.Public && !House.HasAccess(from) && House.IsInside(newLocation, 16))
{
if (!Core.SE)
from.SendLocalizedMessage(501284); // You may not enter.
if (foundation?.Customizer != null && foundation.Customizer != from && House.IsInside(newLocation, 16))
return false;
}
if (House.IsCombatRestricted(from) && !House.IsInside(oldLocation, 16) && House.IsInside(newLocation, 16))
{
from.SendLocalizedMessage(1061637); // You are not allowed to access this.
return false;
}
if (House is HouseFoundation foundation && foundation.Customizer != null && foundation.Customizer != from &&
House.IsInside(newLocation, 16))
return false;
}
@ -184,30 +171,14 @@ namespace Server.Regions
return true;
}
public override bool OnDecay(Item item)
{
if ((House.HasLockedDownItem(item) || House.HasSecureItem(item)) && House.IsInside(item))
return false;
return base.OnDecay(item);
}
public override bool OnDecay(Item item) =>
(!House.HasLockedDownItem(item) && !House.HasSecureItem(item) || !House.IsInside(item)) && base.OnDecay(item);
public override TimeSpan GetLogoutDelay(Mobile m)
{
if (House.IsFriend(m) && House.IsInside(m))
{
for (int i = 0; i < m.Aggressed.Count; ++i)
{
AggressorInfo info = m.Aggressed[i];
if (info.Defender.Player && DateTime.UtcNow - info.LastCombatTime < CombatHeatDelay)
return base.GetLogoutDelay(m);
}
return TimeSpan.Zero;
}
return base.GetLogoutDelay(m);
}
public override TimeSpan GetLogoutDelay(Mobile m) =>
House.IsFriend(m) && House.IsInside(m)
? m.Aggressed.Any(info => info.Defender.Player && DateTime.UtcNow - info.LastCombatTime < CombatHeatDelay)
? base.GetLogoutDelay(m) : TimeSpan.Zero
: base.GetLogoutDelay(m);
public override void OnSpeech(SpeechEventArgs e)
{
@ -340,13 +311,13 @@ namespace Server.Regions
{
SecureAccessResult res = House.CheckSecureAccess(from, c);
switch (res)
if (res == SecureAccessResult.Accessible)
return true;
if (res == SecureAccessResult.Inaccessible)
{
case SecureAccessResult.Insecure: break;
case SecureAccessResult.Accessible: return true;
case SecureAccessResult.Inaccessible:
c.SendLocalizedMessageTo(from, 1010563);
return false;
c.SendLocalizedMessageTo(from, 1010563);
return false;
}
}

View file

@ -1,27 +1,35 @@
using System.Xml;
using System.Text.Json;
using Server.Json;
using Server.Spells;
namespace Server.Regions
{
public class Jail : BaseRegion
public class JailRegion : BaseRegion
{
public Jail(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
public JailRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public override bool AllowBeneficial(Mobile from, Mobile target)
{
if (from.AccessLevel == AccessLevel.Player)
{
from.SendMessage("You may not do that in jail.");
return false;
}
return from.AccessLevel > AccessLevel.Player;
return true;
}
public override bool AllowHarmful(Mobile from, Mobile target)
{
if (from.AccessLevel == AccessLevel.Player)
{
from.SendMessage("You may not do that in jail.");
return false;
}
return from.AccessLevel > AccessLevel.Player;
return true;
}
public override bool AllowHousing(Mobile from, Point3D p) => false;
@ -31,22 +39,39 @@ namespace Server.Regions
global = LightCycle.JailLevel;
}
public override bool CheckTravel(Mobile m, Point3D newLocation, TravelCheckType travelType)
{
if (m?.AccessLevel == AccessLevel.Player)
{
m.SendLocalizedMessage(1114345); // You'll need a better jailbreak plan than that!
return false;
}
return base.CheckTravel(m, newLocation, travelType);
}
public override bool OnBeginSpellCast(Mobile from, ISpell s)
{
if (from.AccessLevel == AccessLevel.Player)
{
from.SendLocalizedMessage(502629); // You cannot cast spells here.
return false;
}
return from.AccessLevel > AccessLevel.Player;
return true;
}
public override bool OnSkillUse(Mobile from, int Skill)
{
if (from.AccessLevel == AccessLevel.Player)
{
from.SendMessage("You may not use skills in jail.");
return false;
}
return from.AccessLevel > AccessLevel.Player;
return true;
}
public override bool OnCombatantChange(Mobile from, Mobile Old, Mobile New) => from.AccessLevel > AccessLevel.Player;
}
}
}

View file

@ -0,0 +1,24 @@
using System.Text.Json;
using Server.Json;
using Server.Spells.Sixth;
namespace Server.Regions
{
public class MondainRegion : NoTravelSpellsAllowedRegion
{
public MondainRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public override bool OnBeginSpellCast(Mobile m, ISpell s)
{
if (m.Player && s is MarkSpell)
{
m.SendLocalizedMessage(501802); // Thy spell doth not appear to work...
return false;
}
return base.OnBeginSpellCast(m, s);
}
}
}

View file

@ -0,0 +1,13 @@
using System.Text.Json;
using Server.Json;
using Server.Regions;
namespace Server.Engines.NewMagincia
{
public class NewMaginciaRegion : TownRegion
{
public NewMaginciaRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
}
}

View file

@ -1,21 +1,18 @@
using System.Xml;
using System.Text.Json;
using Server.Json;
namespace Server.Regions
{
public class NoHousingRegion : BaseRegion
{
/* - False: this uses 'stupid OSI' house placement checking: part of the house may be placed here provided that the center is not in the region
* - True: this uses 'smart RunUO' house placement checking: no part of the house may be in the region
/* False: this uses 'stupid OSI' house placement checking: part of the house may be placed here provided that the center is not in the region
* True: this uses 'smart RunUO' house placement checking: no part of the house may be in the region
*/
private readonly bool m_SmartChecking;
public bool SmartChecking { get; }
public NoHousingRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
{
ReadBoolean(xml["smartNoHousing"], "active", ref m_SmartChecking, false);
}
public NoHousingRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options) =>
SmartChecking = json.GetProperty("smartNoHousing", options, out bool smartNoHousing) && smartNoHousing;
public bool SmartChecking => m_SmartChecking;
public override bool AllowHousing(Mobile from, Point3D p) => m_SmartChecking;
public override bool AllowHousing(Mobile from, Point3D p) => SmartChecking;
}
}
}

View file

@ -0,0 +1,15 @@
using System.Text.Json;
using Server;
using Server.Json;
using Server.Regions;
using Server.Spells;
public class NoTravelSpellsAllowedRegion : DungeonRegion
{
public NoTravelSpellsAllowedRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public override bool CheckTravel(Mobile m, Point3D newLocation, TravelCheckType travelType) =>
m.AccessLevel == AccessLevel.Player;
}

View file

@ -1,380 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Server.Items;
using Server.Mobiles;
using Server.Utilities;
namespace Server.Regions
{
public abstract class SpawnDefinition
{
public abstract ISpawnable Spawn(SpawnEntry entry);
public abstract bool CanSpawn(params Type[] types);
public static SpawnDefinition GetSpawnDefinition(XmlElement xml)
{
switch (xml.Name)
{
case "object":
{
Type type = null;
if (!Region.ReadType(xml, "type", ref type))
return null;
if (typeof(Mobile).IsAssignableFrom(type)) return SpawnMobile.Get(type);
if (typeof(Item).IsAssignableFrom(type)) return SpawnItem.Get(type);
Console.WriteLine("Invalid type '{0}' in a SpawnDefinition", type.FullName);
return null;
}
case "group":
{
string group = null;
if (!Region.ReadString(xml, "name", ref group))
return null;
if (!SpawnGroup.Table.TryGetValue(group, out SpawnGroup def))
{
Console.WriteLine("Could not find group '{0}' in a SpawnDefinition", group);
return null;
}
return def;
}
case "treasureChest":
{
int itemID = 0xE43;
Region.ReadInt32(xml, "itemID", ref itemID, false);
BaseTreasureChest.TreasureLevel level = BaseTreasureChest.TreasureLevel.Level2;
Region.ReadEnum(xml, "level", ref level, false);
return new SpawnTreasureChest(itemID, level);
}
default:
{
return null;
}
}
}
}
public abstract class SpawnType : SpawnDefinition
{
private bool m_Init;
protected SpawnType(Type type)
{
Type = type;
m_Init = false;
}
public Type Type { get; }
public abstract int Height { get; }
public abstract bool Land { get; }
public abstract bool Water { get; }
protected void EnsureInit()
{
if (m_Init)
return;
Init();
m_Init = true;
}
protected virtual void Init()
{
}
public override ISpawnable Spawn(SpawnEntry entry)
{
Region region = entry.Region;
Map map = region.Map;
Point3D loc = entry.RandomSpawnLocation(Height, Land, Water);
if (loc == Point3D.Zero)
return null;
return Construct(entry, loc, map);
}
protected abstract ISpawnable Construct(SpawnEntry entry, Point3D loc, Map map);
public override bool CanSpawn(params Type[] types)
{
for (int i = 0; i < types.Length; i++)
if (types[i] == Type)
return true;
return false;
}
}
public class SpawnMobile : SpawnType
{
private static readonly Dictionary<Type, SpawnMobile> m_Table = new Dictionary<Type, SpawnMobile>();
private bool m_Land;
private bool m_Water;
public SpawnMobile(Type type) : base(type)
{
}
public override int Height => 16;
public override bool Land
{
get
{
EnsureInit();
return m_Land;
}
}
public override bool Water
{
get
{
EnsureInit();
return m_Water;
}
}
public static SpawnMobile Get(Type type)
{
if (!m_Table.TryGetValue(type, out SpawnMobile sm))
m_Table[type] = sm = new SpawnMobile(type);
return sm;
}
protected override void Init()
{
Mobile mob = (Mobile)ActivatorUtil.CreateInstance(Type);
m_Land = !mob.CantWalk;
m_Water = mob.CanSwim;
mob.Delete();
}
protected override ISpawnable Construct(SpawnEntry entry, Point3D loc, Map map)
{
Mobile mobile = CreateMobile();
if (mobile is BaseCreature creature)
{
creature.Home = entry.HomeLocation;
creature.HomeMap = map;
creature.RangeHome = entry.HomeRange;
}
if (entry.Direction != SpawnEntry.InvalidDirection)
mobile.Direction = entry.Direction;
mobile.OnBeforeSpawn(loc, map);
mobile.MoveToWorld(loc, map);
mobile.OnAfterSpawn();
return mobile;
}
protected virtual Mobile CreateMobile() => (Mobile)ActivatorUtil.CreateInstance(Type);
}
public class SpawnItem : SpawnType
{
private static readonly Dictionary<Type, SpawnItem> m_Table = new Dictionary<Type, SpawnItem>();
protected int m_Height;
protected SpawnItem(Type type) : base(type)
{
}
public override int Height
{
get
{
EnsureInit();
return m_Height;
}
}
public override bool Land => true;
public override bool Water => false;
public static SpawnItem Get(Type type)
{
if (!m_Table.TryGetValue(type, out SpawnItem si))
m_Table[type] = si = new SpawnItem(type);
return si;
}
protected override void Init()
{
Item item = (Item)ActivatorUtil.CreateInstance(Type);
m_Height = item.ItemData.Height;
item.Delete();
}
protected override ISpawnable Construct(SpawnEntry entry, Point3D loc, Map map)
{
Item item = CreateItem();
item.OnBeforeSpawn(loc, map);
item.MoveToWorld(loc, map);
item.OnAfterSpawn();
return item;
}
protected virtual Item CreateItem() => (Item)ActivatorUtil.CreateInstance(Type);
}
public class SpawnTreasureChest : SpawnItem
{
public SpawnTreasureChest(int itemID, BaseTreasureChest.TreasureLevel level) : base(typeof(BaseTreasureChest))
{
ItemID = itemID;
Level = level;
}
public int ItemID { get; }
public BaseTreasureChest.TreasureLevel Level { get; }
protected override void Init()
{
m_Height = TileData.ItemTable[ItemID & TileData.MaxItemValue].Height;
}
protected override Item CreateItem() => new BaseTreasureChest(ItemID, Level);
}
public class SpawnGroupElement
{
public SpawnGroupElement(SpawnDefinition spawnDefinition, int weight)
{
SpawnDefinition = spawnDefinition;
Weight = weight;
}
public SpawnDefinition SpawnDefinition { get; }
public int Weight { get; }
}
public class SpawnGroup : SpawnDefinition
{
private readonly int m_TotalWeight;
static SpawnGroup()
{
string path = Path.Combine(Core.BaseDirectory, "Data/SpawnDefinitions.xml");
if (!File.Exists(path))
return;
try
{
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlElement root = doc["spawnDefinitions"];
if (root == null)
return;
foreach (XmlElement xmlDef in root.SelectNodes("spawnGroup"))
{
string name = null;
if (!Region.ReadString(xmlDef, "name", ref name))
continue;
List<SpawnGroupElement> list = new List<SpawnGroupElement>();
foreach (XmlNode node in xmlDef.ChildNodes)
if (node is XmlElement el)
{
SpawnDefinition def = GetSpawnDefinition(el);
if (def == null)
continue;
int weight = 1;
Region.ReadInt32(el, "weight", ref weight, false);
SpawnGroupElement groupElement = new SpawnGroupElement(def, weight);
list.Add(groupElement);
}
SpawnGroupElement[] elements = list.ToArray();
SpawnGroup group = new SpawnGroup(name, elements);
Register(group);
}
}
catch (Exception ex)
{
Console.WriteLine($"Could not load SpawnDefinitions.xml: {ex.Message}");
}
}
public SpawnGroup(string name, SpawnGroupElement[] elements)
{
Name = name;
Elements = elements;
m_TotalWeight = 0;
for (int i = 0; i < elements.Length; i++)
m_TotalWeight += elements[i].Weight;
}
public static Dictionary<string, SpawnGroup> Table { get; } = new Dictionary<string, SpawnGroup>();
public string Name { get; }
public SpawnGroupElement[] Elements { get; }
public static void Register(SpawnGroup group)
{
if (Table.ContainsKey(group.Name))
Console.WriteLine("Warning: Double SpawnGroup name '{0}'", group.Name);
else
Table[group.Name] = group;
}
public override ISpawnable Spawn(SpawnEntry entry)
{
int index = Utility.Random(m_TotalWeight);
for (int i = 0; i < Elements.Length; i++)
{
SpawnGroupElement element = Elements[i];
if (index < element.Weight)
return element.SpawnDefinition.Spawn(entry);
index -= element.Weight;
}
return null;
}
public override bool CanSpawn(params Type[] types)
{
for (int i = 0; i < Elements.Length; i++)
if (Elements[i].SpawnDefinition.CanSpawn(types))
return true;
return false;
}
}
}

View file

@ -1,425 +0,0 @@
using System;
using System.Collections.Generic;
using Server.Mobiles;
namespace Server.Regions
{
public class SpawnEntry : ISpawner
{
public static readonly TimeSpan DefaultMinSpawnTime = TimeSpan.FromMinutes(2.0);
public static readonly TimeSpan DefaultMaxSpawnTime = TimeSpan.FromMinutes(5.0);
public static readonly Direction InvalidDirection = Direction.Running;
private static List<IEntity> m_RemoveList;
private DateTime m_NextSpawn;
private Timer m_SpawnTimer;
public SpawnEntry(int id, BaseRegion region, Point3D home, int range, Direction direction,
SpawnDefinition definition, int max, TimeSpan minSpawnTime, TimeSpan maxSpawnTime)
{
ID = id;
Region = region;
HomeLocation = home;
HomeRange = range;
Direction = direction;
Definition = definition;
SpawnedObjects = new List<ISpawnable>();
Max = max;
MinSpawnTime = minSpawnTime;
MaxSpawnTime = maxSpawnTime;
Running = false;
if (Table.ContainsKey(id))
Console.WriteLine("Warning: double SpawnEntry ID '{0}'", id);
else
Table[id] = this;
}
public static Dictionary<int, SpawnEntry> Table { get; } = new Dictionary<int, SpawnEntry>();
// When a creature's AI is deactivated (PlayerRangeSensitive optimization) does it return home?
public bool ReturnOnDeactivate => true;
// Are unlinked and untamed creatures removed after 20 hours?
public bool RemoveIfUntamed => true;
public int ID { get; }
public BaseRegion Region { get; }
public Direction Direction { get; }
public SpawnDefinition Definition { get; }
public List<ISpawnable> SpawnedObjects { get; }
public int Max { get; private set; }
public TimeSpan MinSpawnTime { get; }
public TimeSpan MaxSpawnTime { get; }
public bool Running { get; private set; }
public bool Complete => SpawnedObjects.Count >= Max;
public bool Spawning => Running && !Complete;
// Are creatures unlinked on taming (true) or should they also go out of the region (false)?
public bool UnlinkOnTaming => false;
Region ISpawner.Region => Region;
public Point3D HomeLocation { get; }
public int HomeRange { get; }
void ISpawner.Remove(ISpawnable spawn)
{
SpawnedObjects.Remove(spawn);
CheckTimer();
}
public Point3D RandomSpawnLocation(int spawnHeight, bool land, bool water) => Region.RandomSpawnLocation(spawnHeight, land, water, HomeLocation, HomeRange);
public void Start()
{
if (Running)
return;
Running = true;
CheckTimer();
}
public void Stop()
{
if (!Running)
return;
Running = false;
CheckTimer();
}
private void Spawn()
{
ISpawnable spawn = Definition.Spawn(this);
if (spawn != null)
Add(spawn);
}
private void Add(ISpawnable spawn)
{
SpawnedObjects.Add(spawn);
spawn.Spawner = this;
if (spawn is BaseCreature creature)
creature.RemoveIfUntamed = RemoveIfUntamed;
}
private TimeSpan RandomTime()
{
int min = (int)MinSpawnTime.TotalSeconds;
int max = (int)MaxSpawnTime.TotalSeconds;
int rand = Utility.RandomMinMax(min, max);
return TimeSpan.FromSeconds(rand);
}
private void CheckTimer()
{
if (Spawning)
{
if (m_SpawnTimer == null)
{
TimeSpan time = RandomTime();
m_SpawnTimer = Timer.DelayCall(time, TimerCallback);
m_NextSpawn = DateTime.UtcNow + time;
}
}
else if (m_SpawnTimer != null)
{
m_SpawnTimer.Stop();
m_SpawnTimer = null;
}
}
private void TimerCallback()
{
int amount = Math.Max((Max - SpawnedObjects.Count) / 3, 1);
for (int i = 0; i < amount; i++)
Spawn();
m_SpawnTimer = null;
CheckTimer();
}
public void DeleteSpawnedObjects()
{
InternalDeleteSpawnedObjects();
Running = false;
CheckTimer();
}
private void InternalDeleteSpawnedObjects()
{
foreach (ISpawnable spawnable in SpawnedObjects)
{
spawnable.Spawner = null;
bool uncontrolled = (spawnable as BaseCreature)?.Controlled != true;
if (uncontrolled)
spawnable.Delete();
}
SpawnedObjects.Clear();
}
public void Respawn()
{
InternalDeleteSpawnedObjects();
for (int i = 0; !Complete && i < Max; i++)
Spawn();
Running = true;
CheckTimer();
}
public void Delete()
{
Max = 0;
InternalDeleteSpawnedObjects();
if (m_SpawnTimer != null)
{
m_SpawnTimer.Stop();
m_SpawnTimer = null;
}
if (Table.TryGetValue(ID, out SpawnEntry entry) && entry == this)
Table.Remove(ID);
}
public void Serialize(IGenericWriter writer)
{
writer.Write(SpawnedObjects.Count);
for (int i = 0; i < SpawnedObjects.Count; i++)
writer.Write(SpawnedObjects[i].Serial);
writer.Write(Running);
if (m_SpawnTimer != null)
{
writer.Write(true);
writer.WriteDeltaTime(m_NextSpawn);
}
else
{
writer.Write(false);
}
}
public void Deserialize(IGenericReader reader, int version)
{
int count = reader.ReadInt();
for (int i = 0; i < count; i++)
if (World.FindEntity(reader.ReadUInt()) is ISpawnable spawnableEntity)
Add(spawnableEntity);
Running = reader.ReadBool();
if (reader.ReadBool())
{
m_NextSpawn = reader.ReadDeltaTime();
if (Spawning)
{
m_SpawnTimer?.Stop();
TimeSpan delay = m_NextSpawn - DateTime.UtcNow;
m_SpawnTimer = Timer.DelayCall(delay > TimeSpan.Zero ? delay : TimeSpan.Zero, TimerCallback);
}
}
CheckTimer();
}
public static void Remove(IGenericReader reader, int version)
{
int count = reader.ReadInt();
for (int i = 0; i < count; i++)
{
IEntity entity = World.FindEntity(reader.ReadUInt());
if (entity != null)
{
m_RemoveList ??= new List<IEntity>();
m_RemoveList.Add(entity);
}
}
reader.ReadBool(); // m_Running
if (reader.ReadBool())
reader.ReadDeltaTime(); // m_NextSpawn
}
public static void Initialize()
{
if (m_RemoveList != null)
{
foreach (IEntity ent in m_RemoveList) ent.Delete();
m_RemoveList = null;
}
SpawnPersistence.EnsureExistence();
CommandSystem.Register("RespawnAllRegions", AccessLevel.Administrator, RespawnAllRegions_OnCommand);
CommandSystem.Register("RespawnRegion", AccessLevel.GameMaster, RespawnRegion_OnCommand);
CommandSystem.Register("DelAllRegionSpawns", AccessLevel.Administrator, DelAllRegionSpawns_OnCommand);
CommandSystem.Register("DelRegionSpawns", AccessLevel.GameMaster, DelRegionSpawns_OnCommand);
CommandSystem.Register("StartAllRegionSpawns", AccessLevel.Administrator, StartAllRegionSpawns_OnCommand);
CommandSystem.Register("StartRegionSpawns", AccessLevel.GameMaster, StartRegionSpawns_OnCommand);
CommandSystem.Register("StopAllRegionSpawns", AccessLevel.Administrator, StopAllRegionSpawns_OnCommand);
CommandSystem.Register("StopRegionSpawns", AccessLevel.GameMaster, StopRegionSpawns_OnCommand);
}
private static BaseRegion GetCommandData(CommandEventArgs args)
{
Mobile from = args.Mobile;
Region reg;
if (args.Length == 0)
{
reg = from.Region;
}
else
{
string name = args.GetString(0);
if (!from.Map.Regions.TryGetValue(name, out reg))
{
from.SendMessage("Could not find region '{0}'.", name);
return null;
}
}
if (reg is BaseRegion br && br.Spawns != null)
return br;
from.SendMessage("There are no spawners in region '{0}'.", reg);
return null;
}
[Usage("RespawnAllRegions")]
[Description("Respawns all regions and sets the spawners as running.")]
private static void RespawnAllRegions_OnCommand(CommandEventArgs args)
{
foreach (SpawnEntry entry in Table.Values)
entry.Respawn();
args.Mobile.SendMessage("All regions have respawned.");
}
[Usage("RespawnRegion [<region name>]")]
[Description("Respawns the region in which you are (or that you provided) and sets the spawners as running.")]
private static void RespawnRegion_OnCommand(CommandEventArgs args)
{
BaseRegion region = GetCommandData(args);
if (region == null)
return;
for (int i = 0; i < region.Spawns.Length; i++)
region.Spawns[i].Respawn();
args.Mobile.SendMessage("Region '{0}' has respawned.", region);
}
[Usage("DelAllRegionSpawns")]
[Description("Deletes all spawned objects of every regions and sets the spawners as not running.")]
private static void DelAllRegionSpawns_OnCommand(CommandEventArgs args)
{
foreach (SpawnEntry entry in Table.Values)
entry.DeleteSpawnedObjects();
args.Mobile.SendMessage("All region spawned objects have been deleted.");
}
[Usage("DelRegionSpawns [<region name>]")]
[Description(
"Deletes all spawned objects of the region in which you are (or that you provided) and sets the spawners as not running.")]
private static void DelRegionSpawns_OnCommand(CommandEventArgs args)
{
BaseRegion region = GetCommandData(args);
if (region == null)
return;
for (int i = 0; i < region.Spawns.Length; i++)
region.Spawns[i].DeleteSpawnedObjects();
args.Mobile.SendMessage("Spawned objects of region '{0}' have been deleted.", region);
}
[Usage("StartAllRegionSpawns")]
[Description("Sets the region spawners of all regions as running.")]
private static void StartAllRegionSpawns_OnCommand(CommandEventArgs args)
{
foreach (SpawnEntry entry in Table.Values)
entry.Start();
args.Mobile.SendMessage("All region spawners have started.");
}
[Usage("StartRegionSpawns [<region name>]")]
[Description("Sets the region spawners of the region in which you are (or that you provided) as running.")]
private static void StartRegionSpawns_OnCommand(CommandEventArgs args)
{
BaseRegion region = GetCommandData(args);
if (region == null)
return;
for (int i = 0; i < region.Spawns.Length; i++)
region.Spawns[i].Start();
args.Mobile.SendMessage("Spawners of region '{0}' have started.", region);
}
[Usage("StopAllRegionSpawns")]
[Description("Sets the region spawners of all regions as not running.")]
private static void StopAllRegionSpawns_OnCommand(CommandEventArgs args)
{
foreach (SpawnEntry entry in Table.Values)
entry.Stop();
args.Mobile.SendMessage("All region spawners have stopped.");
}
[Usage("StopRegionSpawns [<region name>]")]
[Description("Sets the region spawners of the region in which you are (or that you provided) as not running.")]
private static void StopRegionSpawns_OnCommand(CommandEventArgs args)
{
BaseRegion region = GetCommandData(args);
if (region == null)
return;
for (int i = 0; i < region.Spawns.Length; i++)
region.Spawns[i].Stop();
args.Mobile.SendMessage("Spawners of region '{0}' have stopped.", region);
}
}
}

View file

@ -1,56 +0,0 @@
namespace Server.Regions
{
[TypeAlias("Server.Regions.SpawnPersistance")]
public class SpawnPersistence : Item
{
private static SpawnPersistence m_Instance;
private SpawnPersistence() : base(1) => Movable = false;
public SpawnPersistence(Serial serial) : base(serial) => m_Instance = this;
public SpawnPersistence Instance => m_Instance;
public override string DefaultName => "Region spawn persistence - Internal";
public static void EnsureExistence()
{
m_Instance ??= new SpawnPersistence();
}
public override void Serialize(IGenericWriter writer)
{
base.Serialize(writer);
writer.WriteEncodedInt(0); // version
writer.Write(SpawnEntry.Table.Values.Count);
foreach (SpawnEntry entry in SpawnEntry.Table.Values)
{
writer.Write(entry.ID);
entry.Serialize(writer);
}
}
public override void Deserialize(IGenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadEncodedInt();
int count = reader.ReadInt();
for (int i = 0; i < count; i++)
{
int id = reader.ReadInt();
SpawnEntry entry = SpawnEntry.Table[id];
if (entry != null)
entry.Deserialize(reader, version);
else
SpawnEntry.Remove(reader, version);
}
}
}
}

View file

@ -1,11 +1,12 @@
using System.Xml;
using System.Text.Json;
using Server.Json;
namespace Server.Regions
{
public class TownRegion : GuardedRegion
{
public TownRegion(XmlElement xml, Map map, Region parent) : base(xml, map, parent)
public TownRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
}
}
}

View file

@ -0,0 +1,41 @@
using System.Text.Json;
using Server.Network;
using Server.Spells;
using Server.Spells.Ninjitsu;
using Server.Json;
namespace Server.Regions
{
public class TwistedWealdDesertRegion : MondainRegion
{
public TwistedWealdDesertRegion(DynamicJson json, JsonSerializerOptions options) : base(json, options)
{
}
public static void Initialize()
{
EventSink.Login += Desert_OnLogin;
}
public override void OnEnter(Mobile m)
{
NetState ns = m.NetState;
if (ns != null && !TransformationSpellHelper.UnderTransformation(m, typeof(AnimalForm)) &&
m.AccessLevel == AccessLevel.Player)
ns.Send(SpeedControl.WalkSpeed);
}
public override void OnExit(Mobile m)
{
NetState ns = m.NetState;
if (ns != null && !TransformationSpellHelper.UnderTransformation(m, typeof(AnimalForm)))
ns.Send(SpeedControl.Disable);
}
private static void Desert_OnLogin(Mobile m)
{
if (m.Region.IsPartOf<TwistedWealdDesertRegion>() && m.AccessLevel == AccessLevel.Player)
m.NetState.Send(SpeedControl.WalkSpeed);
}
}
}

View file

@ -411,9 +411,9 @@ namespace Server.Spells
{
Caster.SendLocalizedMessage(502642); // You are already casting a spell.
}
else if ((BlockedByHorrificBeast &&
TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell))) ||
(BlockedByAnimalForm && AnimalForm.UnderTransformation(Caster)))
else if (BlockedByHorrificBeast &&
TransformationSpellHelper.UnderTransformation(Caster, typeof(HorrificBeastSpell)) ||
BlockedByAnimalForm && AnimalForm.UnderTransformation(Caster))
{
Caster.SendLocalizedMessage(1061091); // You cannot cast that spell in this form.
}

View file

@ -591,12 +591,6 @@ namespace Server.Spells
return false;
}
if (caster?.AccessLevel == AccessLevel.Player && caster.Region.IsPartOf<Jail>())
{
caster.SendLocalizedMessage(1114345); // You'll need a better jailbreak plan than that!
return false;
}
// Always allow monsters to teleport
if (caster is BaseCreature bc && !bc.Controlled && !bc.Summoned && (type == TravelCheckType.TeleportTo || type == TravelCheckType.TeleportFrom))
return true;
@ -607,6 +601,15 @@ namespace Server.Spells
int v = (int)type;
bool isValid = true;
if (caster != null)
{
BaseRegion destination = Region.Find(loc, map) as BaseRegion;
BaseRegion current = Region.Find(caster.Location, map) as BaseRegion;
if (destination?.CheckTravel(caster, loc, type) == false || current?.CheckTravel(caster, loc, type) == false)
isValid = false;
}
for (int i = 0; isValid && i < m_Validators.Length; ++i)
isValid = m_Rules[v, i] || !m_Validators[i](map, loc);
@ -675,15 +678,10 @@ namespace Server.Spells
|| (x >= 1188 && y >= 509 && x < 1201 && y < 513);
}
public static bool IsSafeZone(Map map, Point3D loc)
{
if (Region.Find(loc, map).IsPartOf<SafeZone>() &&
(m_TravelType == TravelCheckType.TeleportTo || m_TravelType == TravelCheckType.TeleportFrom)
&& (m_TravelCaster as PlayerMobile)?.DuelPlayer?.Eliminated == false)
return true;
return false;
}
public static bool IsSafeZone(Map map, Point3D loc) =>
Region.Find(loc, map).IsPartOf<SafeZone>() &&
(m_TravelType == TravelCheckType.TeleportTo || m_TravelType == TravelCheckType.TeleportFrom)
&& (m_TravelCaster as PlayerMobile)?.DuelPlayer?.Eliminated == false;
public static bool IsFactionStronghold(Map map, Point3D loc) => Region.Find(loc, map).IsPartOf<StrongholdRegion>();

View file

@ -132,10 +132,7 @@ namespace Server.Spells.Necromancy
Guild mGuild = m.Guild as Guild;
Guild cGuild = Caster.Guild as Guild;
if (mGuild.IsAlly(cGuild))
return false;
if (mGuild == cGuild)
if (mGuild?.IsAlly(cGuild) == true || mGuild == cGuild)
return false;
}

View file

@ -29,8 +29,8 @@ namespace Server.Spells.Ninjitsu
public override bool CheckCast()
{
PlayerMobile pm = Caster as PlayerMobile; // IsStealthing should be moved to Server.Mobiles
if (!pm.IsStealthing)
// IsStealthing should be moved to Server.Mobiles
if ((Caster as PlayerMobile)?.IsStealthing != true)
{
Caster.SendLocalizedMessage(1063087); // You must be in stealth mode to use this ability.
return false;

Some files were not shown because too many files have changed in this diff Show more