Formats UO Content (#201)
This commit is contained in:
parent
86b7b3aed1
commit
ad3775c4d7
3249 changed files with 431083 additions and 440981 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -3,37 +3,37 @@ using Server.Accounting;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AccountPrompt
|
||||
{
|
||||
public static void Initialize()
|
||||
public class AccountPrompt
|
||||
{
|
||||
if (Accounts.Count == 0)
|
||||
{
|
||||
Console.WriteLine("This server has no accounts.");
|
||||
Console.Write("Do you want to create the owner account now? (y/n)");
|
||||
|
||||
if (Console.ReadKey(true).Key == ConsoleKey.Y)
|
||||
public static void Initialize()
|
||||
{
|
||||
Console.WriteLine();
|
||||
if (Accounts.Count == 0)
|
||||
{
|
||||
Console.WriteLine("This server has no accounts.");
|
||||
Console.Write("Do you want to create the owner account now? (y/n)");
|
||||
|
||||
Console.Write("Username: ");
|
||||
string username = Console.ReadLine();
|
||||
if (Console.ReadKey(true).Key == ConsoleKey.Y)
|
||||
{
|
||||
Console.WriteLine();
|
||||
|
||||
Console.Write("Password: ");
|
||||
string password = Console.ReadLine();
|
||||
Console.Write("Username: ");
|
||||
var username = Console.ReadLine();
|
||||
|
||||
Account a = new Account(username, password);
|
||||
a.AccessLevel = AccessLevel.Owner;
|
||||
Console.Write("Password: ");
|
||||
var password = Console.ReadLine();
|
||||
|
||||
Console.WriteLine("Account created.");
|
||||
var a = new Account(username, password);
|
||||
a.AccessLevel = AccessLevel.Owner;
|
||||
|
||||
Console.WriteLine("Account created.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Account not created.");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine("Account not created.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public static class Animations
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class Animations
|
||||
{
|
||||
EventSink.AnimateRequest += EventSink_AnimateRequest;
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.AnimateRequest += EventSink_AnimateRequest;
|
||||
}
|
||||
|
||||
private static void EventSink_AnimateRequest(Mobile from, string actionName)
|
||||
{
|
||||
int action = actionName switch
|
||||
{
|
||||
"bow" => 32,
|
||||
"salute" => 33,
|
||||
_ => 0,
|
||||
};
|
||||
private static void EventSink_AnimateRequest(Mobile from, string actionName)
|
||||
{
|
||||
var action = actionName switch
|
||||
{
|
||||
"bow" => 32,
|
||||
"salute" => 33,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
if (action > 0 && from.Alive && !from.Mounted && from.Body.IsHuman)
|
||||
from.Animate(action, 5, 1, true, false, 0);
|
||||
if (action > 0 && from.Alive && !from.Mounted && from.Body.IsHuman)
|
||||
from.Animate(action, 5, 1, true, false, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AttackMessage
|
||||
{
|
||||
private const string AggressorFormat = "You are attacking {0}!";
|
||||
private const string AggressedFormat = "{0} is attacking you!";
|
||||
private const int Hue = 0x22;
|
||||
|
||||
private static readonly TimeSpan Delay = TimeSpan.FromMinutes(1.0);
|
||||
|
||||
public static void Initialize()
|
||||
public class AttackMessage
|
||||
{
|
||||
EventSink.AggressiveAction += EventSink_AggressiveAction;
|
||||
private const string AggressorFormat = "You are attacking {0}!";
|
||||
private const string AggressedFormat = "{0} is attacking you!";
|
||||
private const int Hue = 0x22;
|
||||
|
||||
private static readonly TimeSpan Delay = TimeSpan.FromMinutes(1.0);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.AggressiveAction += EventSink_AggressiveAction;
|
||||
}
|
||||
|
||||
public static void EventSink_AggressiveAction(AggressiveActionEventArgs e)
|
||||
{
|
||||
var aggressor = e.Aggressor;
|
||||
var aggressed = e.Aggressed;
|
||||
|
||||
if (!aggressor.Player || !aggressed.Player)
|
||||
return;
|
||||
|
||||
if (!CheckAggressions(aggressor, aggressed))
|
||||
{
|
||||
aggressor.LocalOverheadMessage(
|
||||
MessageType.Regular,
|
||||
Hue,
|
||||
true,
|
||||
string.Format(AggressorFormat, aggressed.Name)
|
||||
);
|
||||
aggressed.LocalOverheadMessage(
|
||||
MessageType.Regular,
|
||||
Hue,
|
||||
true,
|
||||
string.Format(AggressedFormat, aggressor.Name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckAggressions(Mobile m1, Mobile m2)
|
||||
{
|
||||
var list = m1.Aggressors;
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var info = list[i];
|
||||
|
||||
if (info.Attacker == m2 && DateTime.UtcNow < info.LastCombatTime + Delay)
|
||||
return true;
|
||||
}
|
||||
|
||||
list = m2.Aggressors;
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var info = list[i];
|
||||
|
||||
if (info.Attacker == m1 && DateTime.UtcNow < info.LastCombatTime + Delay)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_AggressiveAction(AggressiveActionEventArgs e)
|
||||
{
|
||||
Mobile aggressor = e.Aggressor;
|
||||
Mobile aggressed = e.Aggressed;
|
||||
|
||||
if (!aggressor.Player || !aggressed.Player)
|
||||
return;
|
||||
|
||||
if (!CheckAggressions(aggressor, aggressed))
|
||||
{
|
||||
aggressor.LocalOverheadMessage(MessageType.Regular, Hue, true,
|
||||
string.Format(AggressorFormat, aggressed.Name));
|
||||
aggressed.LocalOverheadMessage(MessageType.Regular, Hue, true,
|
||||
string.Format(AggressedFormat, aggressor.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckAggressions(Mobile m1, Mobile m2)
|
||||
{
|
||||
List<AggressorInfo> list = m1.Aggressors;
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
AggressorInfo info = list[i];
|
||||
|
||||
if (info.Attacker == m2 && DateTime.UtcNow < info.LastCombatTime + Delay)
|
||||
return true;
|
||||
}
|
||||
|
||||
list = m2.Aggressors;
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
AggressorInfo info = list[i];
|
||||
|
||||
if (info.Attacker == m1 && DateTime.UtcNow < info.LastCombatTime + Delay)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,82 +2,82 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AutoRestart : Timer
|
||||
{
|
||||
public static bool Enabled; // is the script enabled?
|
||||
|
||||
private static readonly TimeSpan RestartTime = TimeSpan.FromHours(2.0); // time of day at which to restart
|
||||
|
||||
private static readonly TimeSpan
|
||||
RestartDelay =
|
||||
TimeSpan.Zero; // how long the server should remain active before restart (period of 'server wars')
|
||||
|
||||
private static readonly TimeSpan
|
||||
WarningDelay = TimeSpan.FromMinutes(1.0); // at what interval should the shutdown message be displayed?
|
||||
|
||||
private static DateTime m_RestartTime;
|
||||
|
||||
public AutoRestart() : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
|
||||
public class AutoRestart : Timer
|
||||
{
|
||||
Priority = TimerPriority.FiveSeconds;
|
||||
public static bool Enabled; // is the script enabled?
|
||||
|
||||
m_RestartTime = DateTime.UtcNow.Date + RestartTime;
|
||||
private static readonly TimeSpan RestartTime = TimeSpan.FromHours(2.0); // time of day at which to restart
|
||||
|
||||
if (m_RestartTime < DateTime.UtcNow)
|
||||
m_RestartTime += TimeSpan.FromDays(1.0);
|
||||
private static readonly TimeSpan
|
||||
RestartDelay =
|
||||
TimeSpan.Zero; // how long the server should remain active before restart (period of 'server wars')
|
||||
|
||||
private static readonly TimeSpan
|
||||
WarningDelay = TimeSpan.FromMinutes(1.0); // at what interval should the shutdown message be displayed?
|
||||
|
||||
private static DateTime m_RestartTime;
|
||||
|
||||
public AutoRestart() : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0))
|
||||
{
|
||||
Priority = TimerPriority.FiveSeconds;
|
||||
|
||||
m_RestartTime = DateTime.UtcNow.Date + RestartTime;
|
||||
|
||||
if (m_RestartTime < DateTime.UtcNow)
|
||||
m_RestartTime += TimeSpan.FromDays(1.0);
|
||||
}
|
||||
|
||||
public static bool Restarting { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Restart", AccessLevel.Administrator, Restart_OnCommand);
|
||||
new AutoRestart().Start();
|
||||
}
|
||||
|
||||
public static void Restart_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (Restarting)
|
||||
{
|
||||
e.Mobile.SendMessage("The server is already restarting.");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("You have initiated server shutdown.");
|
||||
Enabled = true;
|
||||
m_RestartTime = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
private void Warning_Callback()
|
||||
{
|
||||
World.Broadcast(0x22, true, "The server is going down shortly.");
|
||||
}
|
||||
|
||||
private void Restart_Callback()
|
||||
{
|
||||
Core.Kill(true);
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (Restarting || !Enabled)
|
||||
return;
|
||||
|
||||
if (DateTime.UtcNow < m_RestartTime)
|
||||
return;
|
||||
|
||||
if (WarningDelay > TimeSpan.Zero)
|
||||
{
|
||||
Warning_Callback();
|
||||
DelayCall(WarningDelay, WarningDelay, Warning_Callback);
|
||||
}
|
||||
|
||||
AutoSave.Save();
|
||||
|
||||
Restarting = true;
|
||||
|
||||
DelayCall(RestartDelay, Restart_Callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Restarting { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Restart", AccessLevel.Administrator, Restart_OnCommand);
|
||||
new AutoRestart().Start();
|
||||
}
|
||||
|
||||
public static void Restart_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (Restarting)
|
||||
{
|
||||
e.Mobile.SendMessage("The server is already restarting.");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("You have initiated server shutdown.");
|
||||
Enabled = true;
|
||||
m_RestartTime = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
private void Warning_Callback()
|
||||
{
|
||||
World.Broadcast(0x22, true, "The server is going down shortly.");
|
||||
}
|
||||
|
||||
private void Restart_Callback()
|
||||
{
|
||||
Core.Kill(true);
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (Restarting || !Enabled)
|
||||
return;
|
||||
|
||||
if (DateTime.UtcNow < m_RestartTime)
|
||||
return;
|
||||
|
||||
if (WarningDelay > TimeSpan.Zero)
|
||||
{
|
||||
Warning_Callback();
|
||||
DelayCall(WarningDelay, WarningDelay, Warning_Callback);
|
||||
}
|
||||
|
||||
AutoSave.Save();
|
||||
|
||||
Restarting = true;
|
||||
|
||||
DelayCall(RestartDelay, Restart_Callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,182 +3,190 @@ using System.IO;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AutoSave : Timer
|
||||
{
|
||||
private static readonly TimeSpan m_Delay = TimeSpan.FromMinutes(5.0);
|
||||
private static readonly TimeSpan m_Warning = TimeSpan.Zero;
|
||||
|
||||
private static readonly string[] m_Backups =
|
||||
public class AutoSave : Timer
|
||||
{
|
||||
"Third Backup",
|
||||
"Second Backup",
|
||||
"Most Recent"
|
||||
};
|
||||
private static readonly TimeSpan m_Delay = TimeSpan.FromMinutes(5.0);
|
||||
private static readonly TimeSpan m_Warning = TimeSpan.Zero;
|
||||
|
||||
public AutoSave() : base(m_Delay - m_Warning, m_Delay) => Priority = TimerPriority.OneMinute;
|
||||
|
||||
public static bool SavesEnabled { get; set; } = true;
|
||||
// private static TimeSpan m_Warning = TimeSpan.FromSeconds( 15.0 );
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
new AutoSave().Start();
|
||||
CommandSystem.Register("SetSaves", AccessLevel.Administrator, SetSaves_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("SetSaves <true | false>")]
|
||||
[Description("Enables or disables automatic shard saving.")]
|
||||
public static void SetSaves_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
{
|
||||
SavesEnabled = e.GetBoolean(0);
|
||||
e.Mobile.SendMessage("Saves have been {0}.", SavesEnabled ? "enabled" : "disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("Format: SetSaves <true | false>");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!SavesEnabled || AutoRestart.Restarting)
|
||||
return;
|
||||
|
||||
if (m_Warning == TimeSpan.Zero)
|
||||
{
|
||||
Save(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
int s = (int)m_Warning.TotalSeconds;
|
||||
int m = s / 60;
|
||||
s %= 60;
|
||||
|
||||
if (m > 0 && s > 0)
|
||||
World.Broadcast(0x35, true, "The world will save in {0} minute{1} and {2} second{3}.", m,
|
||||
m != 1 ? "s" : "", s, s != 1 ? "s" : "");
|
||||
else if (m > 0)
|
||||
World.Broadcast(0x35, true, "The world will save in {0} minute{1}.", m, m != 1 ? "s" : "");
|
||||
else
|
||||
World.Broadcast(0x35, true, "The world will save in {0} second{1}.", s, s != 1 ? "s" : "");
|
||||
|
||||
DelayCall(m_Warning, Save);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
Save(false);
|
||||
}
|
||||
|
||||
public static void Save(bool permitBackgroundWrite)
|
||||
{
|
||||
if (AutoRestart.Restarting)
|
||||
return;
|
||||
|
||||
World.WaitForWriteCompletion();
|
||||
|
||||
try
|
||||
{
|
||||
Backup();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("WARNING: Automatic backup FAILED: {0}", e);
|
||||
}
|
||||
|
||||
World.Save(true, permitBackgroundWrite);
|
||||
}
|
||||
|
||||
private static void Backup()
|
||||
{
|
||||
if (m_Backups.Length == 0)
|
||||
return;
|
||||
|
||||
string root = Path.Combine(Core.BaseDirectory, "Backups/Automatic");
|
||||
|
||||
if (!Directory.Exists(root))
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
string[] existing = Directory.GetDirectories(root);
|
||||
|
||||
for (int i = 0; i < m_Backups.Length; ++i)
|
||||
{
|
||||
DirectoryInfo dir = Match(existing, m_Backups[i]);
|
||||
|
||||
if (dir == null)
|
||||
continue;
|
||||
|
||||
if (i > 0)
|
||||
private static readonly string[] m_Backups =
|
||||
{
|
||||
string timeStamp = FindTimeStamp(dir.Name);
|
||||
"Third Backup",
|
||||
"Second Backup",
|
||||
"Most Recent"
|
||||
};
|
||||
|
||||
public AutoSave() : base(m_Delay - m_Warning, m_Delay) => Priority = TimerPriority.OneMinute;
|
||||
|
||||
public static bool SavesEnabled { get; set; } = true;
|
||||
// private static TimeSpan m_Warning = TimeSpan.FromSeconds( 15.0 );
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
new AutoSave().Start();
|
||||
CommandSystem.Register("SetSaves", AccessLevel.Administrator, SetSaves_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("SetSaves <true | false>")]
|
||||
[Description("Enables or disables automatic shard saving.")]
|
||||
public static void SetSaves_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
{
|
||||
SavesEnabled = e.GetBoolean(0);
|
||||
e.Mobile.SendMessage("Saves have been {0}.", SavesEnabled ? "enabled" : "disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("Format: SetSaves <true | false>");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!SavesEnabled || AutoRestart.Restarting)
|
||||
return;
|
||||
|
||||
if (m_Warning == TimeSpan.Zero)
|
||||
{
|
||||
Save(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var s = (int)m_Warning.TotalSeconds;
|
||||
var m = s / 60;
|
||||
s %= 60;
|
||||
|
||||
if (m > 0 && s > 0)
|
||||
World.Broadcast(
|
||||
0x35,
|
||||
true,
|
||||
"The world will save in {0} minute{1} and {2} second{3}.",
|
||||
m,
|
||||
m != 1 ? "s" : "",
|
||||
s,
|
||||
s != 1 ? "s" : ""
|
||||
);
|
||||
else if (m > 0)
|
||||
World.Broadcast(0x35, true, "The world will save in {0} minute{1}.", m, m != 1 ? "s" : "");
|
||||
else
|
||||
World.Broadcast(0x35, true, "The world will save in {0} second{1}.", s, s != 1 ? "s" : "");
|
||||
|
||||
DelayCall(m_Warning, Save);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
Save(false);
|
||||
}
|
||||
|
||||
public static void Save(bool permitBackgroundWrite)
|
||||
{
|
||||
if (AutoRestart.Restarting)
|
||||
return;
|
||||
|
||||
World.WaitForWriteCompletion();
|
||||
|
||||
if (timeStamp != null)
|
||||
try
|
||||
{
|
||||
dir.MoveTo(FormatDirectory(root, m_Backups[i - 1], timeStamp));
|
||||
Backup();
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
// ignored
|
||||
Console.WriteLine("WARNING: Automatic backup FAILED: {0}", e);
|
||||
}
|
||||
|
||||
World.Save(true, permitBackgroundWrite);
|
||||
}
|
||||
else
|
||||
|
||||
private static void Backup()
|
||||
{
|
||||
try
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
if (m_Backups.Length == 0)
|
||||
return;
|
||||
|
||||
var root = Path.Combine(Core.BaseDirectory, "Backups/Automatic");
|
||||
|
||||
if (!Directory.Exists(root))
|
||||
Directory.CreateDirectory(root);
|
||||
|
||||
var existing = Directory.GetDirectories(root);
|
||||
|
||||
for (var i = 0; i < m_Backups.Length; ++i)
|
||||
{
|
||||
var dir = Match(existing, m_Backups[i]);
|
||||
|
||||
if (dir == null)
|
||||
continue;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
var timeStamp = FindTimeStamp(dir.Name);
|
||||
|
||||
if (timeStamp != null)
|
||||
try
|
||||
{
|
||||
dir.MoveTo(FormatDirectory(root, m_Backups[i - 1], timeStamp));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var saves = Path.Combine(Core.BaseDirectory, "Saves");
|
||||
|
||||
if (Directory.Exists(saves))
|
||||
Directory.Move(saves, FormatDirectory(root, m_Backups[^1], GetTimeStamp()));
|
||||
}
|
||||
}
|
||||
|
||||
string saves = Path.Combine(Core.BaseDirectory, "Saves");
|
||||
private static DirectoryInfo Match(string[] paths, string match)
|
||||
{
|
||||
for (var i = 0; i < paths.Length; ++i)
|
||||
{
|
||||
var info = new DirectoryInfo(paths[i]);
|
||||
|
||||
if (Directory.Exists(saves))
|
||||
Directory.Move(saves, FormatDirectory(root, m_Backups[^1], GetTimeStamp()));
|
||||
if (info.Name.StartsWith(match))
|
||||
return info;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string FormatDirectory(string root, string name, string timeStamp) =>
|
||||
Path.Combine(root, $"{name} ({timeStamp})");
|
||||
|
||||
private static string FindTimeStamp(string input)
|
||||
{
|
||||
var start = input.IndexOf('(');
|
||||
|
||||
if (start >= 0)
|
||||
{
|
||||
var end = input.IndexOf(')', ++start);
|
||||
|
||||
if (end >= start)
|
||||
return input.Substring(start, end - start);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetTimeStamp()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return $"{now.Day}-{now.Month}-{now.Year} {now.Hour}-{now.Minute:D2}-{now.Second:D2}";
|
||||
}
|
||||
}
|
||||
|
||||
private static DirectoryInfo Match(string[] paths, string match)
|
||||
{
|
||||
for (int i = 0; i < paths.Length; ++i)
|
||||
{
|
||||
DirectoryInfo info = new DirectoryInfo(paths[i]);
|
||||
|
||||
if (info.Name.StartsWith(match))
|
||||
return info;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string FormatDirectory(string root, string name, string timeStamp) => Path.Combine(root, $"{name} ({timeStamp})");
|
||||
|
||||
private static string FindTimeStamp(string input)
|
||||
{
|
||||
int start = input.IndexOf('(');
|
||||
|
||||
if (start >= 0)
|
||||
{
|
||||
int end = input.IndexOf(')', ++start);
|
||||
|
||||
if (end >= start)
|
||||
return input.Substring(start, end - start);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetTimeStamp()
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
return $"{now.Day}-{now.Month}-{now.Year} {now.Hour}-{now.Minute:D2}-{now.Second:D2}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public static class Broadcasts
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class Broadcasts
|
||||
{
|
||||
EventSink.ServerCrashed += EventSink_Crashed;
|
||||
EventSink.Shutdown += EventSink_Shutdown;
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.ServerCrashed += EventSink_Crashed;
|
||||
EventSink.Shutdown += EventSink_Shutdown;
|
||||
}
|
||||
|
||||
public static void EventSink_Crashed(ServerCrashedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
World.Broadcast(0x35, true, "The server has crashed.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_Shutdown()
|
||||
{
|
||||
/* try
|
||||
public static void EventSink_Crashed(ServerCrashedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
World.Broadcast(0x35, true, "The server has shut down.");
|
||||
World.Broadcast(0x35, true, "The server has crashed.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}*/
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_Shutdown()
|
||||
{
|
||||
/* try
|
||||
{
|
||||
World.Broadcast(0x35, true, "The server has shut down.");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,268 +4,282 @@ using Server.Network;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class BuffInfo
|
||||
{
|
||||
public static bool Enabled { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
public class BuffInfo
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("buffIcons.enable", Core.ML);
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1)
|
||||
{
|
||||
}
|
||||
|
||||
if (Enabled)
|
||||
EventSink.ClientVersionReceived += ResendBuffsOnClientVersionReceived;
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc)
|
||||
{
|
||||
ID = iconID;
|
||||
TitleCliloc = titleCliloc;
|
||||
SecondaryCliloc = secondaryCliloc;
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m)
|
||||
{
|
||||
}
|
||||
|
||||
// Only the timed one needs to Mobile to know when to automagically remove it.
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m)
|
||||
: this(iconID, titleCliloc, secondaryCliloc)
|
||||
{
|
||||
TimeLength = length;
|
||||
TimeStart = DateTime.UtcNow;
|
||||
|
||||
Timer = Timer.DelayCall(length, RemoveBuff, m, this);
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TextDefinition args)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, args)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args)
|
||||
: this(iconID, titleCliloc, secondaryCliloc) =>
|
||||
Args = args;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, secondaryCliloc) =>
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TextDefinition args, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, args, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, args) =>
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m, TextDefinition args)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m, args)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(
|
||||
BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m,
|
||||
TextDefinition args
|
||||
)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, length, m) =>
|
||||
Args = args;
|
||||
|
||||
public BuffInfo(
|
||||
BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m, TextDefinition args,
|
||||
bool retainThroughDeath
|
||||
)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m, args, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(
|
||||
BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m,
|
||||
TextDefinition args, bool retainThroughDeath
|
||||
)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, length, m)
|
||||
{
|
||||
Args = args;
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
}
|
||||
|
||||
public static bool Enabled { get; private set; }
|
||||
|
||||
public BuffIcon ID { get; }
|
||||
|
||||
public int TitleCliloc { get; }
|
||||
|
||||
public int SecondaryCliloc { get; }
|
||||
|
||||
public TimeSpan TimeLength { get; }
|
||||
|
||||
public DateTime TimeStart { get; }
|
||||
|
||||
public Timer Timer { get; }
|
||||
|
||||
public bool RetainThroughDeath { get; }
|
||||
|
||||
public TextDefinition Args { get; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("buffIcons.enable", Core.ML);
|
||||
|
||||
if (Enabled)
|
||||
EventSink.ClientVersionReceived += ResendBuffsOnClientVersionReceived;
|
||||
}
|
||||
|
||||
public static void ResendBuffsOnClientVersionReceived(NetState ns, ClientVersion cv)
|
||||
{
|
||||
if (ns.Mobile is PlayerMobile pm)
|
||||
Timer.DelayCall(pm.ResendBuffs);
|
||||
}
|
||||
|
||||
public static void AddBuff(Mobile m, BuffInfo b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.AddBuff(b);
|
||||
}
|
||||
|
||||
public static void RemoveBuff(Mobile m, BuffInfo b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.RemoveBuff(b);
|
||||
}
|
||||
|
||||
public static void RemoveBuff(Mobile m, BuffIcon b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.RemoveBuff(b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ResendBuffsOnClientVersionReceived(NetState ns, ClientVersion cv)
|
||||
public enum BuffIcon : short
|
||||
{
|
||||
if (ns.Mobile is PlayerMobile pm)
|
||||
Timer.DelayCall(pm.ResendBuffs);
|
||||
DismountPrevention = 0x3E9,
|
||||
NoRearm = 0x3EA,
|
||||
|
||||
// Currently, no 0x3EB or 0x3EC
|
||||
NightSight = 0x3ED, // *
|
||||
DeathStrike,
|
||||
EvilOmen,
|
||||
UnknownStandingSwirl, // Which is healing throttle & Stamina throttle?
|
||||
UnknownKneelingSword,
|
||||
DivineFury, // *
|
||||
EnemyOfOne, // *
|
||||
HidingAndOrStealth, // *
|
||||
ActiveMeditation, // *
|
||||
BloodOathCaster, // *
|
||||
BloodOathCurse, // *
|
||||
CorpseSkin, // *
|
||||
Mindrot, // *
|
||||
PainSpike, // *
|
||||
Strangle,
|
||||
GiftOfRenewal, // *
|
||||
AttuneWeapon, // *
|
||||
Thunderstorm, // *
|
||||
EssenceOfWind, // *
|
||||
EtherealVoyage, // *
|
||||
GiftOfLife, // *
|
||||
ArcaneEmpowerment, // *
|
||||
MortalStrike,
|
||||
ReactiveArmor, // *
|
||||
Protection, // *
|
||||
ArchProtection,
|
||||
MagicReflection, // *
|
||||
Incognito, // *
|
||||
Disguised,
|
||||
AnimalForm,
|
||||
Polymorph,
|
||||
Invisibility, // *
|
||||
Paralyze, // *
|
||||
Poison,
|
||||
Bleed,
|
||||
Clumsy, // *
|
||||
FeebleMind, // *
|
||||
Weaken, // *
|
||||
Curse, // *
|
||||
MassCurse,
|
||||
Agility, // *
|
||||
Cunning, // *
|
||||
Strength, // *
|
||||
Bless, // *
|
||||
Sleep,
|
||||
StoneForm,
|
||||
SpellPlague,
|
||||
SpellTrigger,
|
||||
NetherBolt,
|
||||
Fly
|
||||
}
|
||||
|
||||
public BuffIcon ID { get; }
|
||||
|
||||
public int TitleCliloc { get; }
|
||||
|
||||
public int SecondaryCliloc { get; }
|
||||
|
||||
public TimeSpan TimeLength { get; }
|
||||
|
||||
public DateTime TimeStart { get; }
|
||||
|
||||
public Timer Timer { get; }
|
||||
|
||||
public bool RetainThroughDeath { get; }
|
||||
|
||||
public TextDefinition Args { get; }
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1)
|
||||
public sealed class AddBuffPacket : Packet
|
||||
{
|
||||
public AddBuffPacket(Mobile m, BuffInfo info)
|
||||
: this(
|
||||
m,
|
||||
info.ID,
|
||||
info.TitleCliloc,
|
||||
info.SecondaryCliloc,
|
||||
info.Args,
|
||||
info.TimeStart != DateTime.MinValue ? info.TimeStart + info.TimeLength - DateTime.UtcNow : TimeSpan.Zero
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public AddBuffPacket(
|
||||
Mobile mob, BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args,
|
||||
TimeSpan length
|
||||
)
|
||||
: base(0xDF)
|
||||
{
|
||||
var hasArgs = args != null;
|
||||
|
||||
EnsureCapacity(hasArgs ? 48 + args.ToString().Length * 2 : 44);
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x1); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x01); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
|
||||
if (length < TimeSpan.Zero)
|
||||
length = TimeSpan.Zero;
|
||||
|
||||
Stream.Write((short)length.TotalSeconds); // Time in seconds
|
||||
|
||||
Stream.Fill(3);
|
||||
Stream.Write(titleCliloc);
|
||||
Stream.Write(secondaryCliloc);
|
||||
|
||||
if (!hasArgs)
|
||||
{
|
||||
// m_Stream.Fill( 2 );
|
||||
Stream.Fill(10);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream.Fill(4);
|
||||
Stream.Write((short)0x1); // Unknown -> Possibly something saying 'hey, I have more data!'?
|
||||
Stream.Fill(2);
|
||||
|
||||
// m_Stream.WriteLittleUniNull( "\t#1018280" );
|
||||
Stream.WriteLittleUniNull($"\t{args}");
|
||||
|
||||
Stream.Write((short)0x1); // Even more Unknown -> Possibly something saying 'hey, I have more data!'?
|
||||
Stream.Fill(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc)
|
||||
public sealed class RemoveBuffPacket : Packet
|
||||
{
|
||||
ID = iconID;
|
||||
TitleCliloc = titleCliloc;
|
||||
SecondaryCliloc = secondaryCliloc;
|
||||
public RemoveBuffPacket(Mobile mob, BuffInfo info)
|
||||
: this(mob, info.ID)
|
||||
{
|
||||
}
|
||||
|
||||
public RemoveBuffPacket(Mobile mob, BuffIcon iconID)
|
||||
: base(0xDF)
|
||||
{
|
||||
EnsureCapacity(13);
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x0); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
}
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m)
|
||||
{
|
||||
}
|
||||
|
||||
// Only the timed one needs to Mobile to know when to automagically remove it.
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m)
|
||||
: this(iconID, titleCliloc, secondaryCliloc)
|
||||
{
|
||||
TimeLength = length;
|
||||
TimeStart = DateTime.UtcNow;
|
||||
|
||||
Timer = Timer.DelayCall(length, RemoveBuff, m, this);
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TextDefinition args)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, args)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args)
|
||||
: this(iconID, titleCliloc, secondaryCliloc) =>
|
||||
Args = args;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, secondaryCliloc) =>
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TextDefinition args, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, args, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, args) =>
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m, TextDefinition args)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m, args)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m,
|
||||
TextDefinition args)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, length, m) =>
|
||||
Args = args;
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, TimeSpan length, Mobile m, TextDefinition args,
|
||||
bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, titleCliloc + 1, length, m, args, retainThroughDeath)
|
||||
{
|
||||
}
|
||||
|
||||
public BuffInfo(BuffIcon iconID, int titleCliloc, int secondaryCliloc, TimeSpan length, Mobile m,
|
||||
TextDefinition args, bool retainThroughDeath)
|
||||
: this(iconID, titleCliloc, secondaryCliloc, length, m)
|
||||
{
|
||||
Args = args;
|
||||
RetainThroughDeath = retainThroughDeath;
|
||||
}
|
||||
|
||||
public static void AddBuff(Mobile m, BuffInfo b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.AddBuff(b);
|
||||
}
|
||||
|
||||
public static void RemoveBuff(Mobile m, BuffInfo b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.RemoveBuff(b);
|
||||
}
|
||||
|
||||
public static void RemoveBuff(Mobile m, BuffIcon b)
|
||||
{
|
||||
if (m is PlayerMobile pm)
|
||||
pm.RemoveBuff(b);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BuffIcon : short
|
||||
{
|
||||
DismountPrevention = 0x3E9,
|
||||
NoRearm = 0x3EA,
|
||||
|
||||
// Currently, no 0x3EB or 0x3EC
|
||||
NightSight = 0x3ED, // *
|
||||
DeathStrike,
|
||||
EvilOmen,
|
||||
UnknownStandingSwirl, // Which is healing throttle & Stamina throttle?
|
||||
UnknownKneelingSword,
|
||||
DivineFury, // *
|
||||
EnemyOfOne, // *
|
||||
HidingAndOrStealth, // *
|
||||
ActiveMeditation, // *
|
||||
BloodOathCaster, // *
|
||||
BloodOathCurse, // *
|
||||
CorpseSkin, // *
|
||||
Mindrot, // *
|
||||
PainSpike, // *
|
||||
Strangle,
|
||||
GiftOfRenewal, // *
|
||||
AttuneWeapon, // *
|
||||
Thunderstorm, // *
|
||||
EssenceOfWind, // *
|
||||
EtherealVoyage, // *
|
||||
GiftOfLife, // *
|
||||
ArcaneEmpowerment, // *
|
||||
MortalStrike,
|
||||
ReactiveArmor, // *
|
||||
Protection, // *
|
||||
ArchProtection,
|
||||
MagicReflection, // *
|
||||
Incognito, // *
|
||||
Disguised,
|
||||
AnimalForm,
|
||||
Polymorph,
|
||||
Invisibility, // *
|
||||
Paralyze, // *
|
||||
Poison,
|
||||
Bleed,
|
||||
Clumsy, // *
|
||||
FeebleMind, // *
|
||||
Weaken, // *
|
||||
Curse, // *
|
||||
MassCurse,
|
||||
Agility, // *
|
||||
Cunning, // *
|
||||
Strength, // *
|
||||
Bless, // *
|
||||
Sleep,
|
||||
StoneForm,
|
||||
SpellPlague,
|
||||
SpellTrigger,
|
||||
NetherBolt,
|
||||
Fly
|
||||
}
|
||||
|
||||
public sealed class AddBuffPacket : Packet
|
||||
{
|
||||
public AddBuffPacket(Mobile m, BuffInfo info)
|
||||
: this(m, info.ID, info.TitleCliloc, info.SecondaryCliloc, info.Args,
|
||||
info.TimeStart != DateTime.MinValue ? info.TimeStart + info.TimeLength - DateTime.UtcNow : TimeSpan.Zero)
|
||||
{
|
||||
}
|
||||
|
||||
public AddBuffPacket(Mobile mob, BuffIcon iconID, int titleCliloc, int secondaryCliloc, TextDefinition args,
|
||||
TimeSpan length)
|
||||
: base(0xDF)
|
||||
{
|
||||
bool hasArgs = args != null;
|
||||
|
||||
EnsureCapacity(hasArgs ? 48 + args.ToString().Length * 2 : 44);
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x1); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x01); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
|
||||
if (length < TimeSpan.Zero)
|
||||
length = TimeSpan.Zero;
|
||||
|
||||
Stream.Write((short)length.TotalSeconds); // Time in seconds
|
||||
|
||||
Stream.Fill(3);
|
||||
Stream.Write(titleCliloc);
|
||||
Stream.Write(secondaryCliloc);
|
||||
|
||||
if (!hasArgs)
|
||||
{
|
||||
// m_Stream.Fill( 2 );
|
||||
Stream.Fill(10);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream.Fill(4);
|
||||
Stream.Write((short)0x1); // Unknown -> Possibly something saying 'hey, I have more data!'?
|
||||
Stream.Fill(2);
|
||||
|
||||
// m_Stream.WriteLittleUniNull( "\t#1018280" );
|
||||
Stream.WriteLittleUniNull($"\t{args}");
|
||||
|
||||
Stream.Write((short)0x1); // Even more Unknown -> Possibly something saying 'hey, I have more data!'?
|
||||
Stream.Fill(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RemoveBuffPacket : Packet
|
||||
{
|
||||
public RemoveBuffPacket(Mobile mob, BuffInfo info)
|
||||
: this(mob, info.ID)
|
||||
{
|
||||
}
|
||||
|
||||
public RemoveBuffPacket(Mobile mob, BuffIcon iconID)
|
||||
: base(0xDF)
|
||||
{
|
||||
EnsureCapacity(13);
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
Stream.Write((short)iconID); // ID
|
||||
Stream.Write((short)0x0); // Type 0 for removal. 1 for add 2 for Data
|
||||
|
||||
Stream.Fill(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,158 +1,161 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Multis;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class Cleanup
|
||||
{
|
||||
public static void Initialize()
|
||||
public class Cleanup
|
||||
{
|
||||
Timer.DelayCall(TimeSpan.FromSeconds(2.5), Run);
|
||||
}
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
List<Item> items = new List<Item>();
|
||||
List<Item> validItems = new List<Item>();
|
||||
List<Mobile> hairCleanup = new List<Mobile>();
|
||||
|
||||
int boxes = 0;
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
if (item.Map == null)
|
||||
public static void Initialize()
|
||||
{
|
||||
items.Add(item);
|
||||
continue;
|
||||
Timer.DelayCall(TimeSpan.FromSeconds(2.5), Run);
|
||||
}
|
||||
|
||||
if (item is CommodityDeed deed)
|
||||
public static void Run()
|
||||
{
|
||||
if (deed.Commodity != null)
|
||||
validItems.Add(deed.Commodity);
|
||||
var items = new List<Item>();
|
||||
var validItems = new List<Item>();
|
||||
var hairCleanup = new List<Mobile>();
|
||||
|
||||
continue;
|
||||
}
|
||||
var boxes = 0;
|
||||
|
||||
if (item is BaseHouse house)
|
||||
{
|
||||
foreach (RelocatedEntity relEntity in house.RelocatedEntities)
|
||||
if (relEntity.Entity is Item item1)
|
||||
validItems.Add(item1);
|
||||
|
||||
foreach (VendorInventory inventory in house.VendorInventories)
|
||||
foreach (Item subItem in inventory.Items)
|
||||
validItems.Add(subItem);
|
||||
}
|
||||
else if (item is BankBox box)
|
||||
{
|
||||
Mobile owner = box.Owner;
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
items.Add(box);
|
||||
++boxes;
|
||||
}
|
||||
else if (box.Items.Count == 0)
|
||||
{
|
||||
items.Add(box);
|
||||
++boxes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (item.Layer == Layer.Hair || item.Layer == Layer.FacialHair)
|
||||
{
|
||||
if (item.RootParent is Mobile rootMobile)
|
||||
{
|
||||
if (item.Parent != rootMobile && rootMobile.AccessLevel == AccessLevel.Player)
|
||||
foreach (var item in World.Items.Values)
|
||||
{
|
||||
items.Add(item);
|
||||
continue;
|
||||
if (item.Map == null)
|
||||
{
|
||||
items.Add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item is CommodityDeed deed)
|
||||
{
|
||||
if (deed.Commodity != null)
|
||||
validItems.Add(deed.Commodity);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item is BaseHouse house)
|
||||
{
|
||||
foreach (var relEntity in house.RelocatedEntities)
|
||||
if (relEntity.Entity is Item item1)
|
||||
validItems.Add(item1);
|
||||
|
||||
foreach (var inventory in house.VendorInventories)
|
||||
foreach (var subItem in inventory.Items)
|
||||
validItems.Add(subItem);
|
||||
}
|
||||
else if (item is BankBox box)
|
||||
{
|
||||
var owner = box.Owner;
|
||||
|
||||
if (owner == null)
|
||||
{
|
||||
items.Add(box);
|
||||
++boxes;
|
||||
}
|
||||
else if (box.Items.Count == 0)
|
||||
{
|
||||
items.Add(box);
|
||||
++boxes;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (item.Layer == Layer.Hair || item.Layer == Layer.FacialHair)
|
||||
{
|
||||
if (item.RootParent is Mobile rootMobile)
|
||||
{
|
||||
if (item.Parent != rootMobile && rootMobile.AccessLevel == AccessLevel.Player)
|
||||
{
|
||||
items.Add(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Parent == rootMobile)
|
||||
{
|
||||
hairCleanup.Add(rootMobile);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Parent != null || item.Map != Map.Internal || item.HeldBy != null)
|
||||
continue;
|
||||
|
||||
if (item.Location != Point3D.Zero)
|
||||
continue;
|
||||
|
||||
if (!IsBuggable(item))
|
||||
continue;
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
if (item.Parent == rootMobile)
|
||||
for (var i = 0; i < validItems.Count; ++i)
|
||||
items.Remove(validItems[i]);
|
||||
|
||||
if (items.Count > 0)
|
||||
{
|
||||
hairCleanup.Add(rootMobile);
|
||||
continue;
|
||||
if (boxes > 0)
|
||||
Console.WriteLine(
|
||||
"Cleanup: Detected {0} inaccessible items, including {1} bank boxes, removing..",
|
||||
items.Count,
|
||||
boxes
|
||||
);
|
||||
else
|
||||
Console.WriteLine("Cleanup: Detected {0} inaccessible items, removing..", items.Count);
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
items[i].Delete();
|
||||
}
|
||||
|
||||
if (hairCleanup.Count > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Cleanup: Detected {0} hair and facial hair items being worn, converting to their virtual counterparts..",
|
||||
hairCleanup.Count
|
||||
);
|
||||
|
||||
for (var i = 0; i < hairCleanup.Count; i++)
|
||||
hairCleanup[i].ConvertHair();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Parent != null || item.Map != Map.Internal || item.HeldBy != null)
|
||||
continue;
|
||||
public static bool IsBuggable(Item item)
|
||||
{
|
||||
if (item is Fists)
|
||||
return false;
|
||||
|
||||
if (item.Location != Point3D.Zero)
|
||||
continue;
|
||||
if (item is ICommodity || item is BaseBoat
|
||||
|| item is Fish || item is BigFish || item is Food || item is CookableFood
|
||||
|| item is SpecialFishingNet || item is BaseMagicFish
|
||||
|| item is Shoes || item is Sandals
|
||||
|| item is Boots || item is ThighBoots
|
||||
|| item is TreasureMap || item is MessageInABottle
|
||||
|| item is BaseArmor || item is BaseWeapon
|
||||
|| item is BaseClothing
|
||||
|| item is BaseJewel && Core.AOS || item is SkullPole
|
||||
|| item is EvilIdolSkull
|
||||
|| item is MonsterStatuette
|
||||
|| item is Pier
|
||||
|| item is ArtifactLargeVase
|
||||
|| item is ArtifactVase
|
||||
|| item is MinotaurStatueDeed
|
||||
|| item is SwampTile
|
||||
|| item is WallBlood
|
||||
|| item is TatteredAncientMummyWrapping
|
||||
|| item is LavaTile
|
||||
|| item is DemonSkull
|
||||
|| item is Web
|
||||
|| item is WaterTile
|
||||
|| item is WindSpirit
|
||||
|| item is DirtPatch
|
||||
|| item is Futon)
|
||||
return true;
|
||||
|
||||
if (!IsBuggable(item))
|
||||
continue;
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
for (int i = 0; i < validItems.Count; ++i)
|
||||
items.Remove(validItems[i]);
|
||||
|
||||
if (items.Count > 0)
|
||||
{
|
||||
if (boxes > 0)
|
||||
Console.WriteLine("Cleanup: Detected {0} inaccessible items, including {1} bank boxes, removing..",
|
||||
items.Count, boxes);
|
||||
else
|
||||
Console.WriteLine("Cleanup: Detected {0} inaccessible items, removing..", items.Count);
|
||||
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
items[i].Delete();
|
||||
}
|
||||
|
||||
if (hairCleanup.Count > 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Cleanup: Detected {0} hair and facial hair items being worn, converting to their virtual counterparts..",
|
||||
hairCleanup.Count);
|
||||
|
||||
for (int i = 0; i < hairCleanup.Count; i++)
|
||||
hairCleanup[i].ConvertHair();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsBuggable(Item item)
|
||||
{
|
||||
if (item is Fists)
|
||||
return false;
|
||||
|
||||
if (item is ICommodity || item is BaseBoat
|
||||
|| item is Fish || item is BigFish || item is Food || item is CookableFood
|
||||
|| item is SpecialFishingNet || item is BaseMagicFish
|
||||
|| item is Shoes || item is Sandals
|
||||
|| item is Boots || item is ThighBoots
|
||||
|| item is TreasureMap || item is MessageInABottle
|
||||
|| item is BaseArmor || item is BaseWeapon
|
||||
|| item is BaseClothing
|
||||
|| (item is BaseJewel && Core.AOS) || item is SkullPole
|
||||
|| item is EvilIdolSkull
|
||||
|| item is MonsterStatuette
|
||||
|| item is Pier
|
||||
|| item is ArtifactLargeVase
|
||||
|| item is ArtifactVase
|
||||
|| item is MinotaurStatueDeed
|
||||
|| item is SwampTile
|
||||
|| item is WallBlood
|
||||
|| item is TatteredAncientMummyWrapping
|
||||
|| item is LavaTile
|
||||
|| item is DemonSkull
|
||||
|| item is Web
|
||||
|| item is WaterTile
|
||||
|| item is WindSpirit
|
||||
|| item is DirtPatch
|
||||
|| item is Futon)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,174 +7,200 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class ClientVerification
|
||||
{
|
||||
private static bool m_DetectClientRequirement;
|
||||
private static OldClientResponse m_OldClientResponse;
|
||||
|
||||
private static TimeSpan m_AgeLeniency;
|
||||
private static TimeSpan m_GameTimeLeniency;
|
||||
|
||||
public static ClientVersion Required { get; set; }
|
||||
|
||||
public static bool AllowRegular { get; set; } = true;
|
||||
|
||||
public static bool AllowUOTD { get; set; } = true;
|
||||
|
||||
public static bool AllowGod { get; set; } = true;
|
||||
|
||||
public static TimeSpan KickDelay { get; set; }
|
||||
|
||||
public static void Initialize()
|
||||
public class ClientVerification
|
||||
{
|
||||
m_DetectClientRequirement = ServerConfiguration.GetOrUpdateSetting("clientVerification.enable", true);
|
||||
m_OldClientResponse =
|
||||
ServerConfiguration.GetOrUpdateSetting("clientVerification.oldClientResponse", OldClientResponse.Kick);
|
||||
m_AgeLeniency = ServerConfiguration.GetOrUpdateSetting("clientVerification.ageLeniency", TimeSpan.FromDays(10));
|
||||
m_GameTimeLeniency = ServerConfiguration.GetOrUpdateSetting("clientVerification.gameTimeLeniency", TimeSpan.FromHours(25));
|
||||
KickDelay = ServerConfiguration.GetOrUpdateSetting("clientVerification.kickDelay", TimeSpan.FromSeconds(20.0));
|
||||
private static bool m_DetectClientRequirement;
|
||||
private static OldClientResponse m_OldClientResponse;
|
||||
|
||||
EventSink.ClientVersionReceived += EventSink_ClientVersionReceived;
|
||||
private static TimeSpan m_AgeLeniency;
|
||||
private static TimeSpan m_GameTimeLeniency;
|
||||
|
||||
if (m_DetectClientRequirement)
|
||||
{
|
||||
string path = Core.FindDataFile("client.exe", false);
|
||||
public static ClientVersion Required { get; set; }
|
||||
|
||||
if (File.Exists(path))
|
||||
public static bool AllowRegular { get; set; } = true;
|
||||
|
||||
public static bool AllowUOTD { get; set; } = true;
|
||||
|
||||
public static bool AllowGod { get; set; } = true;
|
||||
|
||||
public static TimeSpan KickDelay { get; set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
FileVersionInfo info = FileVersionInfo.GetVersionInfo(path);
|
||||
m_DetectClientRequirement = ServerConfiguration.GetOrUpdateSetting("clientVerification.enable", true);
|
||||
m_OldClientResponse =
|
||||
ServerConfiguration.GetOrUpdateSetting("clientVerification.oldClientResponse", OldClientResponse.Kick);
|
||||
m_AgeLeniency = ServerConfiguration.GetOrUpdateSetting("clientVerification.ageLeniency", TimeSpan.FromDays(10));
|
||||
m_GameTimeLeniency = ServerConfiguration.GetOrUpdateSetting(
|
||||
"clientVerification.gameTimeLeniency",
|
||||
TimeSpan.FromHours(25)
|
||||
);
|
||||
KickDelay = ServerConfiguration.GetOrUpdateSetting("clientVerification.kickDelay", TimeSpan.FromSeconds(20.0));
|
||||
|
||||
if (info.FileMajorPart != 0 || info.FileMinorPart != 0 || info.FileBuildPart != 0 ||
|
||||
info.FilePrivatePart != 0)
|
||||
Required = new ClientVersion(info.FileMajorPart, info.FileMinorPart, info.FileBuildPart,
|
||||
info.FilePrivatePart);
|
||||
}
|
||||
}
|
||||
EventSink.ClientVersionReceived += EventSink_ClientVersionReceived;
|
||||
|
||||
if (Required != null)
|
||||
{
|
||||
Utility.PushColor(ConsoleColor.White);
|
||||
Console.WriteLine("Restricting client version to {0}. Action to be taken: {1}", Required,
|
||||
m_OldClientResponse);
|
||||
Utility.PopColor();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EventSink_ClientVersionReceived(NetState state, ClientVersion version)
|
||||
{
|
||||
string kickMessage = null;
|
||||
|
||||
if (state.Mobile?.AccessLevel != AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (Required != null && version < Required && (m_OldClientResponse == OldClientResponse.Kick ||
|
||||
m_OldClientResponse == OldClientResponse.LenientKick &&
|
||||
DateTime.UtcNow - state.Mobile.CreationTime > m_AgeLeniency &&
|
||||
state.Mobile is PlayerMobile mobile &&
|
||||
mobile.GameTime > m_GameTimeLeniency))
|
||||
{
|
||||
kickMessage = $"This server requires your client version be at least {Required}.";
|
||||
}
|
||||
else if (!AllowGod || !AllowRegular || !AllowUOTD)
|
||||
{
|
||||
if (!AllowGod && version.Type == ClientType.God)
|
||||
kickMessage = "This server does not allow god clients to connect.";
|
||||
else if (!AllowRegular && version.Type == ClientType.Regular)
|
||||
kickMessage = "This server does not allow regular clients to connect.";
|
||||
else if (!AllowUOTD && state.IsUOTDClient)
|
||||
kickMessage = "This server does not allow UO:TD clients to connect.";
|
||||
|
||||
if (!AllowGod && !AllowRegular && !AllowUOTD)
|
||||
{
|
||||
kickMessage = "This server does not allow any clients to connect.";
|
||||
}
|
||||
else if (AllowGod && !AllowRegular && !AllowUOTD && version.Type != ClientType.God)
|
||||
{
|
||||
kickMessage = "This server requires you to use the god client.";
|
||||
}
|
||||
else if (kickMessage != null)
|
||||
{
|
||||
if (AllowRegular && AllowUOTD)
|
||||
kickMessage += " You can use regular or UO:TD clients.";
|
||||
else if (AllowRegular)
|
||||
kickMessage += " You can use regular clients.";
|
||||
else if (AllowUOTD)
|
||||
kickMessage += " You can use UO:TD clients.";
|
||||
}
|
||||
}
|
||||
|
||||
if (kickMessage != null)
|
||||
{
|
||||
state.Mobile.SendMessage(0x22, kickMessage);
|
||||
state.Mobile.SendMessage(0x22, "You will be disconnected in {0} seconds.", KickDelay.TotalSeconds);
|
||||
|
||||
Timer.DelayCall(KickDelay, OnKick, state);
|
||||
}
|
||||
else if (Required != null && version < Required)
|
||||
{
|
||||
switch (m_OldClientResponse)
|
||||
{
|
||||
case OldClientResponse.Warn:
|
||||
if (m_DetectClientRequirement)
|
||||
{
|
||||
state.Mobile.SendMessage(0x22, "Your client is out of date. Please update your client.", Required);
|
||||
state.Mobile.SendMessage(0x22, "This server recommends that your client version be at least {0}.",
|
||||
Required);
|
||||
break;
|
||||
var path = Core.FindDataFile("client.exe", false);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var info = FileVersionInfo.GetVersionInfo(path);
|
||||
|
||||
if (info.FileMajorPart != 0 || info.FileMinorPart != 0 || info.FileBuildPart != 0 ||
|
||||
info.FilePrivatePart != 0)
|
||||
Required = new ClientVersion(
|
||||
info.FileMajorPart,
|
||||
info.FileMinorPart,
|
||||
info.FileBuildPart,
|
||||
info.FilePrivatePart
|
||||
);
|
||||
}
|
||||
}
|
||||
case OldClientResponse.LenientKick:
|
||||
case OldClientResponse.Annoy:
|
||||
|
||||
if (Required != null)
|
||||
{
|
||||
SendAnnoyGump(state.Mobile);
|
||||
break;
|
||||
Utility.PushColor(ConsoleColor.White);
|
||||
Console.WriteLine(
|
||||
"Restricting client version to {0}. Action to be taken: {1}",
|
||||
Required,
|
||||
m_OldClientResponse
|
||||
);
|
||||
Utility.PopColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EventSink_ClientVersionReceived(NetState state, ClientVersion version)
|
||||
{
|
||||
string kickMessage = null;
|
||||
|
||||
if (state.Mobile?.AccessLevel != AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (Required != null && version < Required && (m_OldClientResponse == OldClientResponse.Kick ||
|
||||
m_OldClientResponse == OldClientResponse.LenientKick &&
|
||||
DateTime.UtcNow - state.Mobile.CreationTime > m_AgeLeniency &&
|
||||
state.Mobile is PlayerMobile mobile &&
|
||||
mobile.GameTime > m_GameTimeLeniency))
|
||||
{
|
||||
kickMessage = $"This server requires your client version be at least {Required}.";
|
||||
}
|
||||
else if (!AllowGod || !AllowRegular || !AllowUOTD)
|
||||
{
|
||||
if (!AllowGod && version.Type == ClientType.God)
|
||||
kickMessage = "This server does not allow god clients to connect.";
|
||||
else if (!AllowRegular && version.Type == ClientType.Regular)
|
||||
kickMessage = "This server does not allow regular clients to connect.";
|
||||
else if (!AllowUOTD && state.IsUOTDClient)
|
||||
kickMessage = "This server does not allow UO:TD clients to connect.";
|
||||
|
||||
if (!AllowGod && !AllowRegular && !AllowUOTD)
|
||||
{
|
||||
kickMessage = "This server does not allow any clients to connect.";
|
||||
}
|
||||
else if (AllowGod && !AllowRegular && !AllowUOTD && version.Type != ClientType.God)
|
||||
{
|
||||
kickMessage = "This server requires you to use the god client.";
|
||||
}
|
||||
else if (kickMessage != null)
|
||||
{
|
||||
if (AllowRegular && AllowUOTD)
|
||||
kickMessage += " You can use regular or UO:TD clients.";
|
||||
else if (AllowRegular)
|
||||
kickMessage += " You can use regular clients.";
|
||||
else if (AllowUOTD)
|
||||
kickMessage += " You can use UO:TD clients.";
|
||||
}
|
||||
}
|
||||
|
||||
if (kickMessage != null)
|
||||
{
|
||||
state.Mobile.SendMessage(0x22, kickMessage);
|
||||
state.Mobile.SendMessage(0x22, "You will be disconnected in {0} seconds.", KickDelay.TotalSeconds);
|
||||
|
||||
Timer.DelayCall(KickDelay, OnKick, state);
|
||||
}
|
||||
else if (Required != null && version < Required)
|
||||
{
|
||||
switch (m_OldClientResponse)
|
||||
{
|
||||
case OldClientResponse.Warn:
|
||||
{
|
||||
state.Mobile.SendMessage(
|
||||
0x22,
|
||||
"Your client is out of date. Please update your client.",
|
||||
Required
|
||||
);
|
||||
state.Mobile.SendMessage(
|
||||
0x22,
|
||||
"This server recommends that your client version be at least {0}.",
|
||||
Required
|
||||
);
|
||||
break;
|
||||
}
|
||||
case OldClientResponse.LenientKick:
|
||||
case OldClientResponse.Annoy:
|
||||
{
|
||||
SendAnnoyGump(state.Mobile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnKick(NetState ns)
|
||||
{
|
||||
if (ns.Connection != null)
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Disconnecting, bad version", ns);
|
||||
ns.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void KickMessage(Mobile from, bool okay)
|
||||
{
|
||||
from.SendMessage("You will be reminded of this again.");
|
||||
|
||||
if (m_OldClientResponse == OldClientResponse.LenientKick)
|
||||
from.SendMessage(
|
||||
"Old clients will be kicked after {0} days of character age and {1} hours of play time",
|
||||
m_AgeLeniency,
|
||||
m_GameTimeLeniency
|
||||
);
|
||||
|
||||
Timer.DelayCall(TimeSpan.FromMinutes(Utility.Random(5, 15)), SendAnnoyGump, from);
|
||||
}
|
||||
|
||||
private static void SendAnnoyGump(Mobile m)
|
||||
{
|
||||
if (m.NetState != null && m.NetState.Version < Required)
|
||||
{
|
||||
Gump g = new WarningGump(
|
||||
1060637,
|
||||
30720,
|
||||
$"Your client is out of date. Please update your client.<br>This server recommends that your client version be at least {Required}.<br> <br>You are currently using version {m.NetState.Version}.<br> <br>To patch, run UOPatch.exe inside your Ultima Online folder.",
|
||||
0xFFC000,
|
||||
480,
|
||||
360,
|
||||
okay => KickMessage(m, okay),
|
||||
false
|
||||
);
|
||||
|
||||
g.Draggable = false;
|
||||
g.Closable = false;
|
||||
g.Resizable = false;
|
||||
|
||||
m.SendGump(g);
|
||||
}
|
||||
}
|
||||
|
||||
private enum OldClientResponse
|
||||
{
|
||||
Ignore,
|
||||
Warn,
|
||||
Annoy,
|
||||
LenientKick,
|
||||
Kick
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnKick(NetState ns)
|
||||
{
|
||||
if (ns.Connection != null)
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Disconnecting, bad version", ns);
|
||||
ns.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static void KickMessage(Mobile from, bool okay)
|
||||
{
|
||||
from.SendMessage("You will be reminded of this again.");
|
||||
|
||||
if (m_OldClientResponse == OldClientResponse.LenientKick)
|
||||
from.SendMessage(
|
||||
"Old clients will be kicked after {0} days of character age and {1} hours of play time",
|
||||
m_AgeLeniency, m_GameTimeLeniency);
|
||||
|
||||
Timer.DelayCall(TimeSpan.FromMinutes(Utility.Random(5, 15)), SendAnnoyGump, from);
|
||||
}
|
||||
|
||||
private static void SendAnnoyGump(Mobile m)
|
||||
{
|
||||
if (m.NetState != null && m.NetState.Version < Required)
|
||||
{
|
||||
Gump g = new WarningGump(1060637, 30720,
|
||||
$"Your client is out of date. Please update your client.<br>This server recommends that your client version be at least {Required}.<br> <br>You are currently using version {m.NetState.Version}.<br> <br>To patch, run UOPatch.exe inside your Ultima Online folder.",
|
||||
0xFFC000, 480, 360, okay => KickMessage(m, okay), false);
|
||||
|
||||
g.Draggable = false;
|
||||
g.Closable = false;
|
||||
g.Resizable = false;
|
||||
|
||||
m.SendGump(g);
|
||||
}
|
||||
}
|
||||
|
||||
private enum OldClientResponse
|
||||
{
|
||||
Ignore,
|
||||
Warn,
|
||||
Annoy,
|
||||
LenientKick,
|
||||
Kick
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Server.Accounting;
|
||||
|
|
@ -7,240 +6,240 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class CrashGuard
|
||||
{
|
||||
private static readonly bool Enabled = true;
|
||||
private static readonly bool SaveBackup = true;
|
||||
private static readonly bool RestartServer = true;
|
||||
private static readonly bool GenerateReport = true;
|
||||
|
||||
public static void Initialize()
|
||||
public static class CrashGuard
|
||||
{
|
||||
if (Enabled) // If enabled, register our crash event handler
|
||||
EventSink.ServerCrashed += CrashGuard_OnCrash;
|
||||
}
|
||||
private static readonly bool Enabled = true;
|
||||
private static readonly bool SaveBackup = true;
|
||||
private static readonly bool RestartServer = true;
|
||||
private static readonly bool GenerateReport = true;
|
||||
|
||||
public static void CrashGuard_OnCrash(ServerCrashedEventArgs e)
|
||||
{
|
||||
if (GenerateReport)
|
||||
GenerateCrashReport(e);
|
||||
|
||||
World.WaitForWriteCompletion();
|
||||
|
||||
if (SaveBackup)
|
||||
Backup();
|
||||
|
||||
/*if (Core.Service)
|
||||
e.Close = true;
|
||||
else */
|
||||
if (RestartServer)
|
||||
Restart(e);
|
||||
}
|
||||
|
||||
private static void SendEmail(string filePath)
|
||||
{
|
||||
Console.Write("Crash: Sending email...");
|
||||
|
||||
Email.SendCrashEmail(filePath);
|
||||
}
|
||||
|
||||
private static string GetRoot()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static string Combine(string path1, string path2) => path1.Length == 0 ? path2 : Path.Combine(path1, path2);
|
||||
|
||||
private static void Restart(ServerCrashedEventArgs e)
|
||||
{
|
||||
string root = GetRoot();
|
||||
|
||||
Console.Write("Crash: Restarting...");
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(Core.ExePath, Core.Arguments);
|
||||
Console.WriteLine("done");
|
||||
|
||||
e.Close = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
private static void CreateDirectory(string path1, string path2)
|
||||
{
|
||||
CreateDirectory(Combine(path1, path2));
|
||||
}
|
||||
|
||||
private static void CopyFile(string rootOrigin, string rootBackup, string path)
|
||||
{
|
||||
string originPath = Combine(rootOrigin, path);
|
||||
string backupPath = Combine(rootBackup, path);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(originPath))
|
||||
File.Copy(originPath, backupPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static void Backup()
|
||||
{
|
||||
Console.Write("Crash: Backing up...");
|
||||
|
||||
try
|
||||
{
|
||||
string timeStamp = GetTimeStamp();
|
||||
|
||||
string root = GetRoot();
|
||||
string rootBackup = Combine(root, $"Backups/Crashed/{timeStamp}/");
|
||||
string rootOrigin = Combine(root, "Saves/");
|
||||
|
||||
// Create new directories
|
||||
CreateDirectory(rootBackup);
|
||||
CreateDirectory(rootBackup, "Accounts/");
|
||||
CreateDirectory(rootBackup, "Items/");
|
||||
CreateDirectory(rootBackup, "Mobiles/");
|
||||
CreateDirectory(rootBackup, "Guilds/");
|
||||
CreateDirectory(rootBackup, "Regions/");
|
||||
|
||||
// Copy files
|
||||
CopyFile(rootOrigin, rootBackup, "Accounts/Accounts.xml");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.idx");
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.tdb");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.idx");
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.tdb");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.idx");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Regions/Regions.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Regions/Regions.idx");
|
||||
|
||||
Console.WriteLine("done");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateCrashReport(ServerCrashedEventArgs e)
|
||||
{
|
||||
Console.Write("Crash: Generating report...");
|
||||
|
||||
try
|
||||
{
|
||||
string timeStamp = GetTimeStamp();
|
||||
string fileName = $"Crash {timeStamp}.log";
|
||||
|
||||
string root = GetRoot();
|
||||
string filePath = Combine(root, fileName);
|
||||
|
||||
using (StreamWriter op = new StreamWriter(filePath))
|
||||
public static void Initialize()
|
||||
{
|
||||
Version ver = Core.Assembly.GetName().Version ?? new Version("0.0.0.0");
|
||||
|
||||
op.WriteLine("Server Crash Report");
|
||||
op.WriteLine("===================");
|
||||
op.WriteLine();
|
||||
op.WriteLine($"ModernUO Version {ver.Major}.{ver.Minor}, Build {ver.Build}.{ver.Revision}");
|
||||
op.WriteLine("Operating System: {0}", Environment.OSVersion);
|
||||
op.WriteLine(".NET Framework: {0}", Environment.Version);
|
||||
op.WriteLine("Time: {0}", DateTime.UtcNow);
|
||||
|
||||
try
|
||||
{
|
||||
op.WriteLine("Mobiles: {0}", World.Mobiles.Count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
op.WriteLine("Items: {0}", World.Items.Count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
op.WriteLine("Exception:");
|
||||
op.WriteLine(e.Exception);
|
||||
op.WriteLine();
|
||||
|
||||
op.WriteLine("Clients:");
|
||||
|
||||
try
|
||||
{
|
||||
List<NetState> states = TcpServer.Instances;
|
||||
|
||||
op.WriteLine("- Count: {0}", states.Count);
|
||||
|
||||
for (int i = 0; i < states.Count; ++i)
|
||||
{
|
||||
NetState state = states[i];
|
||||
|
||||
op.Write("+ {0}:", state);
|
||||
|
||||
if (state.Account is Account a)
|
||||
op.Write(" (account = {0})", a.Username);
|
||||
|
||||
Mobile m = state.Mobile;
|
||||
|
||||
if (m != null)
|
||||
op.Write(" (mobile = 0x{0:X} '{1}')", m.Serial.Value, m.Name);
|
||||
|
||||
op.WriteLine();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
op.WriteLine("- Failed");
|
||||
}
|
||||
if (Enabled) // If enabled, register our crash event handler
|
||||
EventSink.ServerCrashed += CrashGuard_OnCrash;
|
||||
}
|
||||
|
||||
Console.WriteLine("done");
|
||||
public static void CrashGuard_OnCrash(ServerCrashedEventArgs e)
|
||||
{
|
||||
if (GenerateReport)
|
||||
GenerateCrashReport(e);
|
||||
|
||||
SendEmail(filePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
World.WaitForWriteCompletion();
|
||||
|
||||
if (SaveBackup)
|
||||
Backup();
|
||||
|
||||
/*if (Core.Service)
|
||||
e.Close = true;
|
||||
else */
|
||||
if (RestartServer)
|
||||
Restart(e);
|
||||
}
|
||||
|
||||
private static void SendEmail(string filePath)
|
||||
{
|
||||
Console.Write("Crash: Sending email...");
|
||||
|
||||
Email.SendCrashEmail(filePath);
|
||||
}
|
||||
|
||||
private static string GetRoot()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static string Combine(string path1, string path2) => path1.Length == 0 ? path2 : Path.Combine(path1, path2);
|
||||
|
||||
private static void Restart(ServerCrashedEventArgs e)
|
||||
{
|
||||
var root = GetRoot();
|
||||
|
||||
Console.Write("Crash: Restarting...");
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(Core.ExePath, Core.Arguments);
|
||||
Console.WriteLine("done");
|
||||
|
||||
e.Close = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
private static void CreateDirectory(string path1, string path2)
|
||||
{
|
||||
CreateDirectory(Combine(path1, path2));
|
||||
}
|
||||
|
||||
private static void CopyFile(string rootOrigin, string rootBackup, string path)
|
||||
{
|
||||
var originPath = Combine(rootOrigin, path);
|
||||
var backupPath = Combine(rootBackup, path);
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(originPath))
|
||||
File.Copy(originPath, backupPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static void Backup()
|
||||
{
|
||||
Console.Write("Crash: Backing up...");
|
||||
|
||||
try
|
||||
{
|
||||
var timeStamp = GetTimeStamp();
|
||||
|
||||
var root = GetRoot();
|
||||
var rootBackup = Combine(root, $"Backups/Crashed/{timeStamp}/");
|
||||
var rootOrigin = Combine(root, "Saves/");
|
||||
|
||||
// Create new directories
|
||||
CreateDirectory(rootBackup);
|
||||
CreateDirectory(rootBackup, "Accounts/");
|
||||
CreateDirectory(rootBackup, "Items/");
|
||||
CreateDirectory(rootBackup, "Mobiles/");
|
||||
CreateDirectory(rootBackup, "Guilds/");
|
||||
CreateDirectory(rootBackup, "Regions/");
|
||||
|
||||
// Copy files
|
||||
CopyFile(rootOrigin, rootBackup, "Accounts/Accounts.xml");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.idx");
|
||||
CopyFile(rootOrigin, rootBackup, "Items/Items.tdb");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.idx");
|
||||
CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.tdb");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.idx");
|
||||
|
||||
CopyFile(rootOrigin, rootBackup, "Regions/Regions.bin");
|
||||
CopyFile(rootOrigin, rootBackup, "Regions/Regions.idx");
|
||||
|
||||
Console.WriteLine("done");
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateCrashReport(ServerCrashedEventArgs e)
|
||||
{
|
||||
Console.Write("Crash: Generating report...");
|
||||
|
||||
try
|
||||
{
|
||||
var timeStamp = GetTimeStamp();
|
||||
var fileName = $"Crash {timeStamp}.log";
|
||||
|
||||
var root = GetRoot();
|
||||
var filePath = Combine(root, fileName);
|
||||
|
||||
using (var op = new StreamWriter(filePath))
|
||||
{
|
||||
var ver = Core.Assembly.GetName().Version ?? new Version("0.0.0.0");
|
||||
|
||||
op.WriteLine("Server Crash Report");
|
||||
op.WriteLine("===================");
|
||||
op.WriteLine();
|
||||
op.WriteLine($"ModernUO Version {ver.Major}.{ver.Minor}, Build {ver.Build}.{ver.Revision}");
|
||||
op.WriteLine("Operating System: {0}", Environment.OSVersion);
|
||||
op.WriteLine(".NET Framework: {0}", Environment.Version);
|
||||
op.WriteLine("Time: {0}", DateTime.UtcNow);
|
||||
|
||||
try
|
||||
{
|
||||
op.WriteLine("Mobiles: {0}", World.Mobiles.Count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
op.WriteLine("Items: {0}", World.Items.Count);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
op.WriteLine("Exception:");
|
||||
op.WriteLine(e.Exception);
|
||||
op.WriteLine();
|
||||
|
||||
op.WriteLine("Clients:");
|
||||
|
||||
try
|
||||
{
|
||||
var states = TcpServer.Instances;
|
||||
|
||||
op.WriteLine("- Count: {0}", states.Count);
|
||||
|
||||
for (var i = 0; i < states.Count; ++i)
|
||||
{
|
||||
var state = states[i];
|
||||
|
||||
op.Write("+ {0}:", state);
|
||||
|
||||
if (state.Account is Account a)
|
||||
op.Write(" (account = {0})", a.Username);
|
||||
|
||||
var m = state.Mobile;
|
||||
|
||||
if (m != null)
|
||||
op.Write(" (mobile = 0x{0:X} '{1}')", m.Serial.Value, m.Name);
|
||||
|
||||
op.WriteLine();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
op.WriteLine("- Failed");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("done");
|
||||
|
||||
SendEmail(filePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTimeStamp()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return $"{now.Day}-{now.Month}-{now.Year}-{now.Hour}-{now.Minute}-{now.Second}";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetTimeStamp()
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
return $"{now.Day}-{now.Month}-{now.Year}-{now.Hour}-{now.Minute}-{now.Second}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DispellableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DispellableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DispellableFieldAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DispellableFieldAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -9,28 +9,29 @@ using Server.Engines.Help;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class Email
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends Queue-Page request using Email
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="pageType"></param>
|
||||
public static void SendQueueEmail(PageEntry entry, string pageType)
|
||||
public static class Email
|
||||
{
|
||||
if (!EmailConfiguration.EmailEnabled) return;
|
||||
/// <summary>
|
||||
/// Sends Queue-Page request using Email
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="pageType"></param>
|
||||
public static void SendQueueEmail(PageEntry entry, string pageType)
|
||||
{
|
||||
if (!EmailConfiguration.EmailEnabled) return;
|
||||
|
||||
Mobile sender = entry.Sender;
|
||||
DateTime time = DateTime.UtcNow;
|
||||
var sender = entry.Sender;
|
||||
var time = DateTime.UtcNow;
|
||||
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
message.To.Add(EmailConfiguration.SpeechLogPageAddress);
|
||||
message.Subject = "ModernUO Speech Log Page Forwarding";
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
message.To.Add(EmailConfiguration.SpeechLogPageAddress);
|
||||
message.Subject = "ModernUO Speech Log Page Forwarding";
|
||||
|
||||
using (StringWriter writer = new StringWriter())
|
||||
{
|
||||
writer.WriteLine(@$"
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
writer.WriteLine(
|
||||
@$"
|
||||
ModernUO Speech Log Page - {pageType}
|
||||
|
||||
From: '{sender.RawName}', Account: '{(sender.Account is Account accSend ? accSend.Username : " ??? ")}'
|
||||
|
|
@ -43,85 +44,92 @@ namespace Server.Misc
|
|||
|
||||
Speech Log
|
||||
==========
|
||||
");
|
||||
"
|
||||
);
|
||||
|
||||
foreach (SpeechLogEntry logEntry in entry.SpeechLog)
|
||||
{
|
||||
Mobile from = logEntry.From;
|
||||
string fromName = from.RawName;
|
||||
string fromAccount = from.Account is Account accFrom ? accFrom.Username : "???";
|
||||
DateTime created = logEntry.Created;
|
||||
string speech = logEntry.Speech;
|
||||
writer.WriteLine(@$"{created.Hour}:{created.Minute:00}:{created.Second:00} - {fromName} ({fromAccount}): '{speech}'");
|
||||
foreach (var logEntry in entry.SpeechLog)
|
||||
{
|
||||
var from = logEntry.From;
|
||||
var fromName = from.RawName;
|
||||
var fromAccount = from.Account is Account accFrom ? accFrom.Username : "???";
|
||||
var created = logEntry.Created;
|
||||
var speech = logEntry.Speech;
|
||||
writer.WriteLine(
|
||||
@$"{created.Hour}:{created.Minute:00}:{created.Second:00} - {fromName} ({fromAccount}): '{speech}'"
|
||||
);
|
||||
}
|
||||
|
||||
message.Body = new BodyBuilder
|
||||
{
|
||||
TextBody = writer.ToString(),
|
||||
HtmlBody = null
|
||||
}.ToMessageBody();
|
||||
}
|
||||
|
||||
SendAsync(message);
|
||||
}
|
||||
|
||||
message.Body = new BodyBuilder
|
||||
/// <summary>
|
||||
/// Sends crash email
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public static void SendCrashEmail(string filePath)
|
||||
{
|
||||
TextBody = writer.ToString(),
|
||||
HtmlBody = null
|
||||
}.ToMessageBody();
|
||||
}
|
||||
SendAsync(message);
|
||||
}
|
||||
if (EmailConfiguration.EmailEnabled) return;
|
||||
|
||||
/// <summary>
|
||||
/// Sends crash email
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
public static void SendCrashEmail(string filePath)
|
||||
{
|
||||
if (EmailConfiguration.EmailEnabled) return;
|
||||
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
message.To.Add(EmailConfiguration.CrashAddress);
|
||||
message.Subject = "Automated ModernUO Crash Report";
|
||||
var builder = new BodyBuilder
|
||||
{
|
||||
TextBody = "Automated ModernUO Crash Report. See attachment for details.",
|
||||
HtmlBody = null
|
||||
};
|
||||
builder.Attachments.Add(filePath);
|
||||
message.Body = builder.ToMessageBody();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends emails async
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
private static async void SendAsync(MimeMessage message)
|
||||
{
|
||||
if (!EmailConfiguration.EmailEnabled) return;
|
||||
|
||||
DateTime now = DateTime.UtcNow;
|
||||
string messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EmailConfiguration.EmailServer}>";
|
||||
message.Headers.Add("Message-ID", messageID);
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
|
||||
int delay = EmailConfiguration.EmailSendRetryDelay;
|
||||
|
||||
for (int i = 0; i < EmailConfiguration.EmailSendRetryCount; i++)
|
||||
try
|
||||
{
|
||||
using SmtpClient client = new SmtpClient();
|
||||
await client.ConnectAsync(EmailConfiguration.EmailServer, EmailConfiguration.EmailPort, true);
|
||||
await client.AuthenticateAsync(EmailConfiguration.EmailServerUsername, EmailConfiguration.EmailServerPassword);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
return;
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
message.To.Add(EmailConfiguration.CrashAddress);
|
||||
message.Subject = "Automated ModernUO Crash Report";
|
||||
var builder = new BodyBuilder
|
||||
{
|
||||
TextBody = "Automated ModernUO Crash Report. See attachment for details.",
|
||||
HtmlBody = null
|
||||
};
|
||||
builder.Attachments.Add(filePath);
|
||||
message.Body = builder.ToMessageBody();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
/// <summary>
|
||||
/// Sends emails async
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
private static async void SendAsync(MimeMessage message)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
if (!EmailConfiguration.EmailEnabled) return;
|
||||
|
||||
delay *= delay;
|
||||
var now = DateTime.UtcNow;
|
||||
var messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EmailConfiguration.EmailServer}>";
|
||||
message.Headers.Add("Message-ID", messageID);
|
||||
message.From.Add(EmailConfiguration.FromAddress);
|
||||
|
||||
await Task.Delay(delay * 1000);
|
||||
var delay = EmailConfiguration.EmailSendRetryDelay;
|
||||
|
||||
for (var i = 0; i < EmailConfiguration.EmailSendRetryCount; i++)
|
||||
try
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
await client.ConnectAsync(EmailConfiguration.EmailServer, EmailConfiguration.EmailPort, true);
|
||||
await client.AuthenticateAsync(
|
||||
EmailConfiguration.EmailServerUsername,
|
||||
EmailConfiguration.EmailServerPassword
|
||||
);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
Console.WriteLine(ex.Message);
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
|
||||
delay *= delay;
|
||||
|
||||
await Task.Delay(delay * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,32 +2,32 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
// This fastwalk detection is no longer required
|
||||
// As of B36 PlayerMobile implements movement packet throttling which more reliably controls movement speeds
|
||||
public static class Fastwalk
|
||||
{
|
||||
private static readonly int MaxSteps = 4; // Maximum number of queued steps until fastwalk is detected
|
||||
private static readonly bool Enabled = false; // Is fastwalk detection enabled?
|
||||
private static readonly bool UOTDOverride = false; // Should UO:TD clients not be checked for fastwalk?
|
||||
|
||||
private static readonly AccessLevel
|
||||
AccessOverride = AccessLevel.GameMaster; // Anyone with this or higher access level is not checked for fastwalk
|
||||
|
||||
public static void Initialize()
|
||||
// This fastwalk detection is no longer required
|
||||
// As of B36 PlayerMobile implements movement packet throttling which more reliably controls movement speeds
|
||||
public static class Fastwalk
|
||||
{
|
||||
Mobile.FwdMaxSteps = MaxSteps;
|
||||
Mobile.FwdEnabled = Enabled;
|
||||
Mobile.FwdUOTDOverride = UOTDOverride;
|
||||
Mobile.FwdAccessOverride = AccessOverride;
|
||||
private static readonly int MaxSteps = 4; // Maximum number of queued steps until fastwalk is detected
|
||||
private static readonly bool Enabled = false; // Is fastwalk detection enabled?
|
||||
private static readonly bool UOTDOverride = false; // Should UO:TD clients not be checked for fastwalk?
|
||||
|
||||
if (Enabled)
|
||||
EventSink.FastWalk += OnFastWalk;
|
||||
}
|
||||
private static readonly AccessLevel
|
||||
AccessOverride = AccessLevel.GameMaster; // Anyone with this or higher access level is not checked for fastwalk
|
||||
|
||||
public static void OnFastWalk(FastWalkEventArgs e)
|
||||
{
|
||||
e.Blocked = true; // disallow this fastwalk
|
||||
Console.WriteLine("Client: {0}: Fast movement detected (name={1})", e.NetState, e.NetState.Mobile.Name);
|
||||
public static void Initialize()
|
||||
{
|
||||
Mobile.FwdMaxSteps = MaxSteps;
|
||||
Mobile.FwdEnabled = Enabled;
|
||||
Mobile.FwdUOTDOverride = UOTDOverride;
|
||||
Mobile.FwdAccessOverride = AccessOverride;
|
||||
|
||||
if (Enabled)
|
||||
EventSink.FastWalk += OnFastWalk;
|
||||
}
|
||||
|
||||
public static void OnFastWalk(FastWalkEventArgs e)
|
||||
{
|
||||
e.Blocked = true; // disallow this fastwalk
|
||||
Console.WriteLine("Client: {0}: Fast movement detected (name={1})", e.NetState, e.NetState.Mobile.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,39 +3,40 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class FoodDecayTimer : Timer
|
||||
{
|
||||
public FoodDecayTimer() : base(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)) => Priority = TimerPriority.OneMinute;
|
||||
|
||||
public static void Initialize()
|
||||
public class FoodDecayTimer : Timer
|
||||
{
|
||||
new FoodDecayTimer().Start();
|
||||
}
|
||||
public FoodDecayTimer() : base(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)) =>
|
||||
Priority = TimerPriority.OneMinute;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
FoodDecay();
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
new FoodDecayTimer().Start();
|
||||
}
|
||||
|
||||
public static void FoodDecay()
|
||||
{
|
||||
foreach (NetState state in TcpServer.Instances)
|
||||
{
|
||||
HungerDecay(state.Mobile);
|
||||
ThirstDecay(state.Mobile);
|
||||
}
|
||||
}
|
||||
protected override void OnTick()
|
||||
{
|
||||
FoodDecay();
|
||||
}
|
||||
|
||||
public static void HungerDecay(Mobile m)
|
||||
{
|
||||
if (m?.Hunger >= 1)
|
||||
m.Hunger -= 1;
|
||||
}
|
||||
public static void FoodDecay()
|
||||
{
|
||||
foreach (var state in TcpServer.Instances)
|
||||
{
|
||||
HungerDecay(state.Mobile);
|
||||
ThirstDecay(state.Mobile);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ThirstDecay(Mobile m)
|
||||
{
|
||||
if (m?.Thirst >= 1)
|
||||
m.Thirst -= 1;
|
||||
public static void HungerDecay(Mobile m)
|
||||
{
|
||||
if (m?.Hunger >= 1)
|
||||
m.Hunger -= 1;
|
||||
}
|
||||
|
||||
public static void ThirstDecay(Mobile m)
|
||||
{
|
||||
if (m?.Thirst >= 1)
|
||||
m.Thirst -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,216 +2,220 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public delegate void DoEffect_Callback(Point3D p, Map map);
|
||||
public delegate void DoEffect_Callback(Point3D p, Map map);
|
||||
|
||||
public static class Geometry
|
||||
{
|
||||
public static void Swap<T>(ref T a, ref T b)
|
||||
public static class Geometry
|
||||
{
|
||||
T temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
public static double RadiansToDegrees(double angle) => angle * (180.0 / Math.PI);
|
||||
|
||||
public static double DegreesToRadians(double angle) => angle * (Math.PI / 180.0);
|
||||
|
||||
public static Point2D ArcPoint(Point3D loc, int radius, int angle)
|
||||
{
|
||||
int sideA, sideB;
|
||||
|
||||
angle = Math.Clamp(angle, 0, 90);
|
||||
|
||||
sideA = (int)Math.Round(radius * Math.Sin(DegreesToRadians(angle)));
|
||||
sideB = (int)Math.Round(radius * Math.Cos(DegreesToRadians(angle)));
|
||||
|
||||
return new Point2D(loc.X - sideB, loc.Y - sideA);
|
||||
}
|
||||
|
||||
public static void Circle2D(Point3D loc, Map map, int radius, DoEffect_Callback effect)
|
||||
{
|
||||
Circle2D(loc, map, radius, effect, 0, 360);
|
||||
}
|
||||
|
||||
public static void Circle2D(Point3D loc, Map map, int radius, DoEffect_Callback effect, int angleStart, int angleEnd)
|
||||
{
|
||||
if (angleStart < 0 || angleStart > 360)
|
||||
angleStart = 0;
|
||||
|
||||
if (angleEnd > 360 || angleEnd < 0)
|
||||
angleEnd = 360;
|
||||
|
||||
if (angleStart == angleEnd)
|
||||
return;
|
||||
|
||||
bool opposite = angleStart > angleEnd;
|
||||
|
||||
int startQuadrant = angleStart / 90;
|
||||
int endQuadrant = angleEnd / 90;
|
||||
|
||||
Point2D start = ArcPoint(loc, radius, angleStart % 90);
|
||||
Point2D end = ArcPoint(loc, radius, angleEnd % 90);
|
||||
|
||||
if (opposite)
|
||||
{
|
||||
Swap(ref start, ref end);
|
||||
Swap(ref startQuadrant, ref endQuadrant);
|
||||
}
|
||||
|
||||
CirclePoint startPoint = new CirclePoint(start, angleStart, startQuadrant);
|
||||
CirclePoint endPoint = new CirclePoint(end, angleEnd, endQuadrant);
|
||||
|
||||
int error = -radius;
|
||||
int x = radius;
|
||||
int y = 0;
|
||||
|
||||
while (x > y)
|
||||
{
|
||||
plot4points(loc, map, x, y, startPoint, endPoint, effect, opposite);
|
||||
plot4points(loc, map, y, x, startPoint, endPoint, effect, opposite);
|
||||
|
||||
error += y * 2 + 1;
|
||||
++y;
|
||||
|
||||
if (error >= 0)
|
||||
public static void Swap<T>(ref T a, ref T b)
|
||||
{
|
||||
--x;
|
||||
error -= x * 2;
|
||||
var temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
}
|
||||
|
||||
plot4points(loc, map, x, y, startPoint, endPoint, effect, opposite);
|
||||
}
|
||||
public static double RadiansToDegrees(double angle) => angle * (180.0 / Math.PI);
|
||||
|
||||
public static void plot4points(Point3D loc, Map map, int x, int y, CirclePoint start, CirclePoint end,
|
||||
DoEffect_Callback effect, bool opposite)
|
||||
{
|
||||
Point2D pointA = new Point2D(loc.X - x, loc.Y - y);
|
||||
Point2D pointB = new Point2D(loc.X - y, loc.Y - x);
|
||||
public static double DegreesToRadians(double angle) => angle * (Math.PI / 180.0);
|
||||
|
||||
int quadrant = 2;
|
||||
|
||||
if (x == 0 && start.Quadrant == 3)
|
||||
quadrant = 3;
|
||||
|
||||
if (WithinCircleBounds(quadrant == 3 ? pointB : pointA, quadrant, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X + x, loc.Y + y, loc.Z), map);
|
||||
|
||||
quadrant = 3;
|
||||
|
||||
if (y == 0 && start.Quadrant == 0)
|
||||
quadrant = 0;
|
||||
|
||||
if (x != 0 && WithinCircleBounds(quadrant == 0 ? pointA : pointB, quadrant, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X - x, loc.Y + y, loc.Z), map);
|
||||
if (y != 0 && WithinCircleBounds(pointB, 1, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X + x, loc.Y - y, loc.Z), map);
|
||||
if (x != 0 && y != 0 && WithinCircleBounds(pointA, 0, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X - x, loc.Y - y, loc.Z), map);
|
||||
}
|
||||
|
||||
public static bool WithinCircleBounds(Point2D pointLoc, int pointQuadrant, Point3D center, CirclePoint start,
|
||||
CirclePoint end, bool opposite)
|
||||
{
|
||||
if (start.Angle == 0 && end.Angle == 360)
|
||||
return true;
|
||||
|
||||
int startX = start.Point.X;
|
||||
int startY = start.Point.Y;
|
||||
int endX = end.Point.X;
|
||||
int endY = end.Point.Y;
|
||||
|
||||
int x = pointLoc.X;
|
||||
int y = pointLoc.Y;
|
||||
|
||||
if (pointQuadrant < start.Quadrant || pointQuadrant > end.Quadrant)
|
||||
return opposite;
|
||||
|
||||
if (pointQuadrant > start.Quadrant && pointQuadrant < end.Quadrant)
|
||||
return !opposite;
|
||||
|
||||
bool withinBounds = true;
|
||||
|
||||
if (start.Quadrant == end.Quadrant)
|
||||
{
|
||||
if (startX == endX && (x > startX || y > startY || y < endY))
|
||||
withinBounds = false;
|
||||
else if (startY == endY && (y < startY || x < startX || x > endX))
|
||||
withinBounds = false;
|
||||
else if (x < startX || x > endX || y > startY || y < endY)
|
||||
withinBounds = false;
|
||||
}
|
||||
else if (pointQuadrant == start.Quadrant && (x < startX || y > startY))
|
||||
{
|
||||
withinBounds = false;
|
||||
}
|
||||
else if (pointQuadrant == end.Quadrant && (x > endX || y < endY))
|
||||
{
|
||||
withinBounds = false;
|
||||
}
|
||||
|
||||
return opposite ? !withinBounds : withinBounds;
|
||||
}
|
||||
|
||||
public static void Line2D(Point3D start, Point3D end, Map map, DoEffect_Callback effect)
|
||||
{
|
||||
bool steep = Math.Abs(end.Y - start.Y) > Math.Abs(end.X - start.X);
|
||||
|
||||
int x0 = start.X;
|
||||
int x1 = end.X;
|
||||
int y0 = start.Y;
|
||||
int y1 = end.Y;
|
||||
|
||||
if (steep)
|
||||
{
|
||||
Swap(ref x0, ref y0);
|
||||
Swap(ref x1, ref y1);
|
||||
}
|
||||
|
||||
if (x0 > x1)
|
||||
{
|
||||
Swap(ref x0, ref x1);
|
||||
Swap(ref y0, ref y1);
|
||||
}
|
||||
|
||||
int deltax = x1 - x0;
|
||||
int deltay = Math.Abs(y1 - y0);
|
||||
int error = deltax / 2;
|
||||
int ystep = y0 < y1 ? 1 : -1;
|
||||
int y = y0;
|
||||
|
||||
for (int x = x0; x <= x1; x++)
|
||||
{
|
||||
if (steep)
|
||||
effect(new Point3D(y, x, start.Z), map);
|
||||
else
|
||||
effect(new Point3D(x, y, start.Z), map);
|
||||
|
||||
error -= deltay;
|
||||
|
||||
if (error < 0)
|
||||
public static Point2D ArcPoint(Point3D loc, int radius, int angle)
|
||||
{
|
||||
y += ystep;
|
||||
error += deltax;
|
||||
int sideA, sideB;
|
||||
|
||||
angle = Math.Clamp(angle, 0, 90);
|
||||
|
||||
sideA = (int)Math.Round(radius * Math.Sin(DegreesToRadians(angle)));
|
||||
sideB = (int)Math.Round(radius * Math.Cos(DegreesToRadians(angle)));
|
||||
|
||||
return new Point2D(loc.X - sideB, loc.Y - sideA);
|
||||
}
|
||||
|
||||
public static void Circle2D(Point3D loc, Map map, int radius, DoEffect_Callback effect)
|
||||
{
|
||||
Circle2D(loc, map, radius, effect, 0, 360);
|
||||
}
|
||||
|
||||
public static void Circle2D(Point3D loc, Map map, int radius, DoEffect_Callback effect, int angleStart, int angleEnd)
|
||||
{
|
||||
if (angleStart < 0 || angleStart > 360)
|
||||
angleStart = 0;
|
||||
|
||||
if (angleEnd > 360 || angleEnd < 0)
|
||||
angleEnd = 360;
|
||||
|
||||
if (angleStart == angleEnd)
|
||||
return;
|
||||
|
||||
var opposite = angleStart > angleEnd;
|
||||
|
||||
var startQuadrant = angleStart / 90;
|
||||
var endQuadrant = angleEnd / 90;
|
||||
|
||||
var start = ArcPoint(loc, radius, angleStart % 90);
|
||||
var end = ArcPoint(loc, radius, angleEnd % 90);
|
||||
|
||||
if (opposite)
|
||||
{
|
||||
Swap(ref start, ref end);
|
||||
Swap(ref startQuadrant, ref endQuadrant);
|
||||
}
|
||||
|
||||
var startPoint = new CirclePoint(start, angleStart, startQuadrant);
|
||||
var endPoint = new CirclePoint(end, angleEnd, endQuadrant);
|
||||
|
||||
var error = -radius;
|
||||
var x = radius;
|
||||
var y = 0;
|
||||
|
||||
while (x > y)
|
||||
{
|
||||
plot4points(loc, map, x, y, startPoint, endPoint, effect, opposite);
|
||||
plot4points(loc, map, y, x, startPoint, endPoint, effect, opposite);
|
||||
|
||||
error += y * 2 + 1;
|
||||
++y;
|
||||
|
||||
if (error >= 0)
|
||||
{
|
||||
--x;
|
||||
error -= x * 2;
|
||||
}
|
||||
}
|
||||
|
||||
plot4points(loc, map, x, y, startPoint, endPoint, effect, opposite);
|
||||
}
|
||||
|
||||
public static void plot4points(
|
||||
Point3D loc, Map map, int x, int y, CirclePoint start, CirclePoint end,
|
||||
DoEffect_Callback effect, bool opposite
|
||||
)
|
||||
{
|
||||
var pointA = new Point2D(loc.X - x, loc.Y - y);
|
||||
var pointB = new Point2D(loc.X - y, loc.Y - x);
|
||||
|
||||
var quadrant = 2;
|
||||
|
||||
if (x == 0 && start.Quadrant == 3)
|
||||
quadrant = 3;
|
||||
|
||||
if (WithinCircleBounds(quadrant == 3 ? pointB : pointA, quadrant, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X + x, loc.Y + y, loc.Z), map);
|
||||
|
||||
quadrant = 3;
|
||||
|
||||
if (y == 0 && start.Quadrant == 0)
|
||||
quadrant = 0;
|
||||
|
||||
if (x != 0 && WithinCircleBounds(quadrant == 0 ? pointA : pointB, quadrant, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X - x, loc.Y + y, loc.Z), map);
|
||||
if (y != 0 && WithinCircleBounds(pointB, 1, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X + x, loc.Y - y, loc.Z), map);
|
||||
if (x != 0 && y != 0 && WithinCircleBounds(pointA, 0, loc, start, end, opposite))
|
||||
effect(new Point3D(loc.X - x, loc.Y - y, loc.Z), map);
|
||||
}
|
||||
|
||||
public static bool WithinCircleBounds(
|
||||
Point2D pointLoc, int pointQuadrant, Point3D center, CirclePoint start,
|
||||
CirclePoint end, bool opposite
|
||||
)
|
||||
{
|
||||
if (start.Angle == 0 && end.Angle == 360)
|
||||
return true;
|
||||
|
||||
var startX = start.Point.X;
|
||||
var startY = start.Point.Y;
|
||||
var endX = end.Point.X;
|
||||
var endY = end.Point.Y;
|
||||
|
||||
var x = pointLoc.X;
|
||||
var y = pointLoc.Y;
|
||||
|
||||
if (pointQuadrant < start.Quadrant || pointQuadrant > end.Quadrant)
|
||||
return opposite;
|
||||
|
||||
if (pointQuadrant > start.Quadrant && pointQuadrant < end.Quadrant)
|
||||
return !opposite;
|
||||
|
||||
var withinBounds = true;
|
||||
|
||||
if (start.Quadrant == end.Quadrant)
|
||||
{
|
||||
if (startX == endX && (x > startX || y > startY || y < endY))
|
||||
withinBounds = false;
|
||||
else if (startY == endY && (y < startY || x < startX || x > endX))
|
||||
withinBounds = false;
|
||||
else if (x < startX || x > endX || y > startY || y < endY)
|
||||
withinBounds = false;
|
||||
}
|
||||
else if (pointQuadrant == start.Quadrant && (x < startX || y > startY))
|
||||
{
|
||||
withinBounds = false;
|
||||
}
|
||||
else if (pointQuadrant == end.Quadrant && (x > endX || y < endY))
|
||||
{
|
||||
withinBounds = false;
|
||||
}
|
||||
|
||||
return opposite ? !withinBounds : withinBounds;
|
||||
}
|
||||
|
||||
public static void Line2D(Point3D start, Point3D end, Map map, DoEffect_Callback effect)
|
||||
{
|
||||
var steep = Math.Abs(end.Y - start.Y) > Math.Abs(end.X - start.X);
|
||||
|
||||
var x0 = start.X;
|
||||
var x1 = end.X;
|
||||
var y0 = start.Y;
|
||||
var y1 = end.Y;
|
||||
|
||||
if (steep)
|
||||
{
|
||||
Swap(ref x0, ref y0);
|
||||
Swap(ref x1, ref y1);
|
||||
}
|
||||
|
||||
if (x0 > x1)
|
||||
{
|
||||
Swap(ref x0, ref x1);
|
||||
Swap(ref y0, ref y1);
|
||||
}
|
||||
|
||||
var deltax = x1 - x0;
|
||||
var deltay = Math.Abs(y1 - y0);
|
||||
var error = deltax / 2;
|
||||
var ystep = y0 < y1 ? 1 : -1;
|
||||
var y = y0;
|
||||
|
||||
for (var x = x0; x <= x1; x++)
|
||||
{
|
||||
if (steep)
|
||||
effect(new Point3D(y, x, start.Z), map);
|
||||
else
|
||||
effect(new Point3D(x, y, start.Z), map);
|
||||
|
||||
error -= deltay;
|
||||
|
||||
if (error < 0)
|
||||
{
|
||||
y += ystep;
|
||||
error += deltax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CirclePoint
|
||||
{
|
||||
public CirclePoint(Point2D point, int angle, int quadrant)
|
||||
{
|
||||
Point = point;
|
||||
Angle = angle;
|
||||
Quadrant = quadrant;
|
||||
}
|
||||
|
||||
public Point2D Point { get; }
|
||||
|
||||
public int Angle { get; }
|
||||
|
||||
public int Quadrant { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CirclePoint
|
||||
{
|
||||
public CirclePoint(Point2D point, int angle, int quadrant)
|
||||
{
|
||||
Point = point;
|
||||
Angle = angle;
|
||||
Quadrant = quadrant;
|
||||
}
|
||||
|
||||
public Point2D Point { get; }
|
||||
|
||||
public int Angle { get; }
|
||||
|
||||
public int Quadrant { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
public class DecorativeTopiary : Item
|
||||
{
|
||||
[Constructible]
|
||||
public DecorativeTopiary() : base(0x2378)
|
||||
public class DecorativeTopiary : Item
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
[Constructible]
|
||||
public DecorativeTopiary() : base(0x2378)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public DecorativeTopiary(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
|
||||
public DecorativeTopiary(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
public class FestiveCactus : Item
|
||||
{
|
||||
[Constructible]
|
||||
public FestiveCactus() : base(0x2376)
|
||||
public class FestiveCactus : Item
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
[Constructible]
|
||||
public FestiveCactus() : base(0x2376)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public FestiveCactus(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
|
||||
public FestiveCactus(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,93 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
[Flippable(0x236E, 0x2371)]
|
||||
public class LightOfTheWinterSolstice : Item
|
||||
{
|
||||
private static readonly string[] m_StaffNames =
|
||||
[Flippable(0x236E, 0x2371)]
|
||||
public class LightOfTheWinterSolstice : Item
|
||||
{
|
||||
"Aenima",
|
||||
"Alkiser",
|
||||
"ASayre",
|
||||
"David",
|
||||
"Krrios",
|
||||
"Mark",
|
||||
"Merlin",
|
||||
"Merlix", // LordMerlix
|
||||
"Phantom",
|
||||
"Phenos",
|
||||
"psz",
|
||||
"Ryan",
|
||||
"Quantos",
|
||||
"Outkast", // TheOutkastDev
|
||||
"V", // Admin_V
|
||||
"Zippy"
|
||||
};
|
||||
private static readonly string[] m_StaffNames =
|
||||
{
|
||||
"Aenima",
|
||||
"Alkiser",
|
||||
"ASayre",
|
||||
"David",
|
||||
"Krrios",
|
||||
"Mark",
|
||||
"Merlin",
|
||||
"Merlix", // LordMerlix
|
||||
"Phantom",
|
||||
"Phenos",
|
||||
"psz",
|
||||
"Ryan",
|
||||
"Quantos",
|
||||
"Outkast", // TheOutkastDev
|
||||
"V", // Admin_V
|
||||
"Zippy"
|
||||
};
|
||||
|
||||
[Constructible]
|
||||
public LightOfTheWinterSolstice(string dipper = null) : base(0x236E)
|
||||
{
|
||||
Dipper = dipper ?? m_StaffNames.RandomElement();
|
||||
[Constructible]
|
||||
public LightOfTheWinterSolstice(string dipper = null) : base(0x236E)
|
||||
{
|
||||
Dipper = dipper ?? m_StaffNames.RandomElement();
|
||||
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
Light = LightType.Circle300;
|
||||
Hue = Utility.RandomDyedHue();
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
Light = LightType.Circle300;
|
||||
Hue = Utility.RandomDyedHue();
|
||||
}
|
||||
|
||||
public LightOfTheWinterSolstice(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Dipper { get; set; }
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070881, Dipper); // Hand Dipped by ~1_name~
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070881, Dipper); // Hand Dipped by ~1_name~
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
|
||||
writer.Write(Dipper);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
Dipper = reader.ReadString();
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
Dipper = m_StaffNames.RandomElement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Dipper != null)
|
||||
Dipper = string.Intern(Dipper);
|
||||
}
|
||||
}
|
||||
|
||||
public LightOfTheWinterSolstice(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Dipper { get; set; }
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070881, Dipper); // Hand Dipped by ~1_name~
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070881, Dipper); // Hand Dipped by ~1_name~
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
|
||||
writer.Write(Dipper);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
Dipper = reader.ReadString();
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
Dipper = m_StaffNames.RandomElement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Dipper != null)
|
||||
Dipper = string.Intern(Dipper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,317 +5,317 @@ using Server.Targeting;
|
|||
|
||||
namespace Server.Items
|
||||
{
|
||||
public class MistletoeAddon : Item, IDyable, IAddon
|
||||
{
|
||||
[Constructible]
|
||||
public MistletoeAddon() : this(Utility.RandomDyedHue())
|
||||
public class MistletoeAddon : Item, IDyable, IAddon
|
||||
{
|
||||
}
|
||||
|
||||
[Constructible]
|
||||
public MistletoeAddon(int hue) : base(0x2375)
|
||||
{
|
||||
Hue = hue;
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
public MistletoeAddon(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public bool CouldFit(IPoint3D p, Map map)
|
||||
{
|
||||
if (!map.CanFit(p.X, p.Y, p.Z, ItemData.Height))
|
||||
return false;
|
||||
|
||||
if (ItemID == 0x2375)
|
||||
return BaseAddon.IsWall(p.X, p.Y - 1, p.Z, map); // North wall
|
||||
return BaseAddon.IsWall(p.X - 1, p.Y, p.Z, map); // West wall
|
||||
}
|
||||
|
||||
public Item Deed => new MistletoeDeed(Hue);
|
||||
|
||||
public virtual bool Dye(Mobile from, DyeTub sender)
|
||||
{
|
||||
if (Deleted)
|
||||
return false;
|
||||
|
||||
BaseHouse house = BaseHouse.FindHouseAt(this);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 1))
|
||||
[Constructible]
|
||||
public MistletoeAddon() : this(Utility.RandomDyedHue())
|
||||
{
|
||||
Hue = sender.DyedHue;
|
||||
return true;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(500295); // You are too far away to do that.
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
Timer.DelayCall(FixMovingCrate);
|
||||
}
|
||||
|
||||
private void FixMovingCrate()
|
||||
{
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
if (Movable || IsLockedDown)
|
||||
{
|
||||
Item deed = Deed;
|
||||
|
||||
if (Parent is Item item)
|
||||
[Constructible]
|
||||
public MistletoeAddon(int hue) : base(0x2375)
|
||||
{
|
||||
item.AddItem(deed);
|
||||
deed.Location = Location;
|
||||
}
|
||||
else
|
||||
{
|
||||
deed.MoveToWorld(Location, Map);
|
||||
Hue = hue;
|
||||
Movable = false;
|
||||
}
|
||||
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
BaseHouse house = BaseHouse.FindHouseAt(this);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 3))
|
||||
public MistletoeAddon(Serial serial) : base(serial)
|
||||
{
|
||||
from.CloseGump<MistletoeAddonGump>();
|
||||
from.SendGump(new MistletoeAddonGump(from, this));
|
||||
}
|
||||
else
|
||||
|
||||
public bool CouldFit(IPoint3D p, Map map)
|
||||
{
|
||||
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that.
|
||||
if (!map.CanFit(p.X, p.Y, p.Z, ItemData.Height))
|
||||
return false;
|
||||
|
||||
if (ItemID == 0x2375)
|
||||
return BaseAddon.IsWall(p.X, p.Y - 1, p.Z, map); // North wall
|
||||
return BaseAddon.IsWall(p.X - 1, p.Y, p.Z, map); // West wall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MistletoeAddonGump : Gump
|
||||
{
|
||||
private readonly MistletoeAddon m_Addon;
|
||||
private readonly Mobile m_From;
|
||||
public Item Deed => new MistletoeDeed(Hue);
|
||||
|
||||
public MistletoeAddonGump(Mobile from, MistletoeAddon addon) : base(150, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Addon = addon;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, 220, 170, 0x13BE);
|
||||
AddBackground(10, 10, 200, 150, 0xBB8);
|
||||
AddHtmlLocalized(20, 30, 180, 60, 1062839); // Do you wish to re-deed this decoration?
|
||||
AddHtmlLocalized(55, 100, 160, 25, 1011011); // CONTINUE
|
||||
AddButton(20, 100, 0xFA5, 0xFA7, 1);
|
||||
AddHtmlLocalized(55, 125, 160, 25, 1011012); // CANCEL
|
||||
AddButton(20, 125, 0xFA5, 0xFA7, 0);
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Addon.Deleted || info.ButtonID != 1)
|
||||
return;
|
||||
|
||||
if (m_From.InRange(m_Addon.GetWorldLocation(), 3))
|
||||
public virtual bool Dye(Mobile from, DyeTub sender)
|
||||
{
|
||||
m_From.AddToBackpack(m_Addon.Deed);
|
||||
m_Addon.Delete();
|
||||
if (Deleted)
|
||||
return false;
|
||||
|
||||
var house = BaseHouse.FindHouseAt(this);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 1))
|
||||
{
|
||||
Hue = sender.DyedHue;
|
||||
return true;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(500295); // You are too far away to do that.
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
m_From.SendLocalizedMessage(500295); // You are too far away to do that.
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flippable(0x14F0, 0x14EF)]
|
||||
public class MistletoeDeed : Item
|
||||
{
|
||||
[Constructible]
|
||||
public MistletoeDeed(int hue = 0) : base(0x14F0)
|
||||
{
|
||||
Hue = hue;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public MistletoeDeed(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int LabelNumber => 1070882; // Mistletoe Deed
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (IsChildOf(from.Backpack))
|
||||
{
|
||||
BaseHouse house = BaseHouse.FindHouseAt(from);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
from.SendLocalizedMessage(1062838); // Where would you like to place this decoration?
|
||||
from.BeginTarget(-1, true, TargetFlags.None, Placement_OnTarget);
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
Timer.DelayCall(FixMovingCrate);
|
||||
}
|
||||
else
|
||||
|
||||
private void FixMovingCrate()
|
||||
{
|
||||
from.SendLocalizedMessage(502092); // You must be in your house to do this.
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
if (Movable || IsLockedDown)
|
||||
{
|
||||
var deed = Deed;
|
||||
|
||||
if (Parent is Item item)
|
||||
{
|
||||
item.AddItem(deed);
|
||||
deed.Location = Location;
|
||||
}
|
||||
else
|
||||
{
|
||||
deed.MoveToWorld(Location, Map);
|
||||
}
|
||||
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||
}
|
||||
}
|
||||
|
||||
public void Placement_OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (!(targeted is IPoint3D p))
|
||||
return;
|
||||
|
||||
Point3D loc = new Point3D(p);
|
||||
|
||||
BaseHouse house = BaseHouse.FindHouseAt(loc, from.Map, 16);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
bool northWall = BaseAddon.IsWall(loc.X, loc.Y - 1, loc.Z, from.Map);
|
||||
bool westWall = BaseAddon.IsWall(loc.X - 1, loc.Y, loc.Z, from.Map);
|
||||
|
||||
if (northWall && westWall)
|
||||
from.SendGump(new MistletoeDeedGump(from, loc, this));
|
||||
else
|
||||
PlaceAddon(from, loc, northWall, westWall);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1042036); // That location is not in your house.
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceAddon(Mobile from, Point3D loc, bool northWall, bool westWall)
|
||||
{
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
BaseHouse house = BaseHouse.FindHouseAt(loc, from.Map, 16);
|
||||
|
||||
if (house?.IsCoOwner(from) != true)
|
||||
{
|
||||
from.SendLocalizedMessage(1042036); // That location is not in your house.
|
||||
return;
|
||||
}
|
||||
|
||||
int itemID = 0;
|
||||
|
||||
if (northWall)
|
||||
itemID = 0x2374;
|
||||
else if (westWall)
|
||||
itemID = 0x2375;
|
||||
else
|
||||
from.SendLocalizedMessage(1070883); // The mistletoe must be placed next to a wall.
|
||||
|
||||
if (itemID > 0)
|
||||
{
|
||||
Item addon = new MistletoeAddon(Hue);
|
||||
|
||||
addon.ItemID = itemID;
|
||||
addon.MoveToWorld(loc, from.Map);
|
||||
|
||||
house.Addons.Add(addon);
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private class MistletoeDeedGump : Gump
|
||||
{
|
||||
private readonly MistletoeDeed m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
private readonly Point3D m_Loc;
|
||||
|
||||
public MistletoeDeedGump(Mobile from, Point3D loc, MistletoeDeed deed) : base(150, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Loc = loc;
|
||||
m_Deed = deed;
|
||||
|
||||
AddBackground(0, 0, 300, 150, 0xA28);
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddItem(90, 30, 0x2375);
|
||||
AddItem(180, 30, 0x2374);
|
||||
AddButton(50, 35, 0x868, 0x869, 1);
|
||||
AddButton(145, 35, 0x868, 0x869, 2);
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Deed.Deleted)
|
||||
return;
|
||||
|
||||
switch (info.ButtonID)
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
case 1:
|
||||
m_Deed.PlaceAddon(m_From, m_Loc, false, true);
|
||||
break;
|
||||
case 2:
|
||||
m_Deed.PlaceAddon(m_From, m_Loc, true, false);
|
||||
break;
|
||||
var house = BaseHouse.FindHouseAt(this);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 3))
|
||||
{
|
||||
from.CloseGump<MistletoeAddonGump>();
|
||||
from.SendGump(new MistletoeAddonGump(from, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
from.LocalOverheadMessage(MessageType.Regular, 0x3B2, 1019045); // I can't reach that.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MistletoeAddonGump : Gump
|
||||
{
|
||||
private readonly MistletoeAddon m_Addon;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public MistletoeAddonGump(Mobile from, MistletoeAddon addon) : base(150, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Addon = addon;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, 220, 170, 0x13BE);
|
||||
AddBackground(10, 10, 200, 150, 0xBB8);
|
||||
AddHtmlLocalized(20, 30, 180, 60, 1062839); // Do you wish to re-deed this decoration?
|
||||
AddHtmlLocalized(55, 100, 160, 25, 1011011); // CONTINUE
|
||||
AddButton(20, 100, 0xFA5, 0xFA7, 1);
|
||||
AddHtmlLocalized(55, 125, 160, 25, 1011012); // CANCEL
|
||||
AddButton(20, 125, 0xFA5, 0xFA7, 0);
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Addon.Deleted || info.ButtonID != 1)
|
||||
return;
|
||||
|
||||
if (m_From.InRange(m_Addon.GetWorldLocation(), 3))
|
||||
{
|
||||
m_From.AddToBackpack(m_Addon.Deed);
|
||||
m_Addon.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendLocalizedMessage(500295); // You are too far away to do that.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flippable(0x14F0, 0x14EF)]
|
||||
public class MistletoeDeed : Item
|
||||
{
|
||||
[Constructible]
|
||||
public MistletoeDeed(int hue = 0) : base(0x14F0)
|
||||
{
|
||||
Hue = hue;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public MistletoeDeed(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int LabelNumber => 1070882; // Mistletoe Deed
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (IsChildOf(from.Backpack))
|
||||
{
|
||||
var house = BaseHouse.FindHouseAt(from);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
from.SendLocalizedMessage(1062838); // Where would you like to place this decoration?
|
||||
from.BeginTarget(-1, true, TargetFlags.None, Placement_OnTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(502092); // You must be in your house to do this.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||
}
|
||||
}
|
||||
|
||||
public void Placement_OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (!(targeted is IPoint3D p))
|
||||
return;
|
||||
|
||||
var loc = new Point3D(p);
|
||||
|
||||
var house = BaseHouse.FindHouseAt(loc, from.Map, 16);
|
||||
|
||||
if (house?.IsCoOwner(from) == true)
|
||||
{
|
||||
var northWall = BaseAddon.IsWall(loc.X, loc.Y - 1, loc.Z, from.Map);
|
||||
var westWall = BaseAddon.IsWall(loc.X - 1, loc.Y, loc.Z, from.Map);
|
||||
|
||||
if (northWall && westWall)
|
||||
from.SendGump(new MistletoeDeedGump(from, loc, this));
|
||||
else
|
||||
PlaceAddon(from, loc, northWall, westWall);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1042036); // That location is not in your house.
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceAddon(Mobile from, Point3D loc, bool northWall, bool westWall)
|
||||
{
|
||||
if (Deleted)
|
||||
return;
|
||||
|
||||
var house = BaseHouse.FindHouseAt(loc, from.Map, 16);
|
||||
|
||||
if (house?.IsCoOwner(from) != true)
|
||||
{
|
||||
from.SendLocalizedMessage(1042036); // That location is not in your house.
|
||||
return;
|
||||
}
|
||||
|
||||
var itemID = 0;
|
||||
|
||||
if (northWall)
|
||||
itemID = 0x2374;
|
||||
else if (westWall)
|
||||
itemID = 0x2375;
|
||||
else
|
||||
from.SendLocalizedMessage(1070883); // The mistletoe must be placed next to a wall.
|
||||
|
||||
if (itemID > 0)
|
||||
{
|
||||
Item addon = new MistletoeAddon(Hue);
|
||||
|
||||
addon.ItemID = itemID;
|
||||
addon.MoveToWorld(loc, from.Map);
|
||||
|
||||
house.Addons.Add(addon);
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
|
||||
private class MistletoeDeedGump : Gump
|
||||
{
|
||||
private readonly MistletoeDeed m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
private readonly Point3D m_Loc;
|
||||
|
||||
public MistletoeDeedGump(Mobile from, Point3D loc, MistletoeDeed deed) : base(150, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Loc = loc;
|
||||
m_Deed = deed;
|
||||
|
||||
AddBackground(0, 0, 300, 150, 0xA28);
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddItem(90, 30, 0x2375);
|
||||
AddItem(180, 30, 0x2374);
|
||||
AddButton(50, 35, 0x868, 0x869, 1);
|
||||
AddButton(145, 35, 0x868, 0x869, 2);
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Deed.Deleted)
|
||||
return;
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 1:
|
||||
m_Deed.PlaceAddon(m_From, m_Loc, false, true);
|
||||
break;
|
||||
case 2:
|
||||
m_Deed.PlaceAddon(m_From, m_Loc, true, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,146 +4,148 @@ using Server.Targeting;
|
|||
|
||||
namespace Server.Items
|
||||
{
|
||||
public class PileOfGlacialSnow : Item
|
||||
{
|
||||
[Constructible]
|
||||
public PileOfGlacialSnow() : base(0x913)
|
||||
public class PileOfGlacialSnow : Item
|
||||
{
|
||||
Hue = 0x480;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public PileOfGlacialSnow(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int LabelNumber => 1070874; // a Pile of Glacial Snow
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
if (version == 0)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1042010); // You must have the object in your backpack to use it.
|
||||
}
|
||||
else if (from.Mounted)
|
||||
{
|
||||
from.SendLocalizedMessage(1010097); // You cannot use this while mounted.
|
||||
}
|
||||
else if (from.CanBeginAction<SnowPile>())
|
||||
{
|
||||
from.SendLocalizedMessage(1005575); // You carefully pack the snow into a ball...
|
||||
from.Target = new SnowTarget(from, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public InternalTimer(Mobile from) : base(TimeSpan.FromSeconds(5.0)) => m_From = from;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_From.EndAction<SnowPile>();
|
||||
}
|
||||
}
|
||||
|
||||
private class SnowTarget : Target
|
||||
{
|
||||
private Item m_Snow;
|
||||
private Mobile m_Thrower;
|
||||
|
||||
public SnowTarget(Mobile thrower, Item snow) : base(10, false, TargetFlags.None)
|
||||
{
|
||||
m_Thrower = thrower;
|
||||
m_Snow = snow;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
if (target == from)
|
||||
[Constructible]
|
||||
public PileOfGlacialSnow() : base(0x913)
|
||||
{
|
||||
from.SendLocalizedMessage(1005576); // You can't throw this at yourself.
|
||||
Hue = 0x480;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
else if (target is Mobile targ)
|
||||
|
||||
public PileOfGlacialSnow(Serial serial) : base(serial)
|
||||
{
|
||||
Container pack = targ.Backpack;
|
||||
}
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || targ.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not throw snow here.");
|
||||
}
|
||||
else if (pack?.FindItemByType(new[] { typeof(SnowPile), typeof(PileOfGlacialSnow) }) != null)
|
||||
{
|
||||
if (from.BeginAction<SnowPile>())
|
||||
public override int LabelNumber => 1070874; // a Pile of Glacial Snow
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
if (version == 0)
|
||||
{
|
||||
new InternalTimer(from).Start();
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
}
|
||||
|
||||
from.PlaySound(0x145);
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
from.Animate(9, 1, 1, true, false, 0);
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
targ.SendLocalizedMessage(1010572); // You have just been hit by a snowball!
|
||||
from.SendLocalizedMessage(1010573); // You throw the snowball and hit the target!
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
Effects.SendMovingEffect(from, targ, 0x36E4, 7, 0, false, true, 0x47F);
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1042010); // You must have the object in your backpack to use it.
|
||||
}
|
||||
else if (from.Mounted)
|
||||
{
|
||||
from.SendLocalizedMessage(1010097); // You cannot use this while mounted.
|
||||
}
|
||||
else if (from.CanBeginAction<SnowPile>())
|
||||
{
|
||||
from.SendLocalizedMessage(1005575); // You carefully pack the snow into a ball...
|
||||
from.Target = new SnowTarget(from, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577); // You can only throw a snowball at something that can throw one back.
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public InternalTimer(Mobile from) : base(TimeSpan.FromSeconds(5.0)) => m_From = from;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_From.EndAction<SnowPile>();
|
||||
}
|
||||
}
|
||||
|
||||
private class SnowTarget : Target
|
||||
{
|
||||
private Item m_Snow;
|
||||
private Mobile m_Thrower;
|
||||
|
||||
public SnowTarget(Mobile thrower, Item snow) : base(10, false, TargetFlags.None)
|
||||
{
|
||||
m_Thrower = thrower;
|
||||
m_Snow = snow;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
if (target == from)
|
||||
{
|
||||
from.SendLocalizedMessage(1005576); // You can't throw this at yourself.
|
||||
}
|
||||
else if (target is Mobile targ)
|
||||
{
|
||||
var pack = targ.Backpack;
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || targ.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not throw snow here.");
|
||||
}
|
||||
else if (pack?.FindItemByType(new[] { typeof(SnowPile), typeof(PileOfGlacialSnow) }) != null)
|
||||
{
|
||||
if (from.BeginAction<SnowPile>())
|
||||
{
|
||||
new InternalTimer(from).Start();
|
||||
|
||||
from.PlaySound(0x145);
|
||||
|
||||
from.Animate(9, 1, 1, true, false, 0);
|
||||
|
||||
targ.SendLocalizedMessage(1010572); // You have just been hit by a snowball!
|
||||
from.SendLocalizedMessage(1010573); // You throw the snowball and hit the target!
|
||||
|
||||
Effects.SendMovingEffect(from, targ, 0x36E4, 7, 0, false, true, 0x47F);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577
|
||||
); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577
|
||||
); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,132 +4,134 @@ using Server.Targeting;
|
|||
|
||||
namespace Server.Items
|
||||
{
|
||||
public class SnowPile : Item
|
||||
{
|
||||
[Constructible]
|
||||
public SnowPile() : base(0x913)
|
||||
public class SnowPile : Item
|
||||
{
|
||||
Hue = 0x481;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public SnowPile(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int LabelNumber => 1005578; // a pile of snow
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
if (version == 0)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1042010); // You must have the object in your backpack to use it.
|
||||
}
|
||||
else if (from.Mounted)
|
||||
{
|
||||
from.SendLocalizedMessage(1010097); // You cannot use this while mounted.
|
||||
}
|
||||
else if (from.CanBeginAction<SnowPile>())
|
||||
{
|
||||
from.SendLocalizedMessage(1005575); // You carefully pack the snow into a ball...
|
||||
from.Target = new SnowTarget(from, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public InternalTimer(Mobile from) : base(TimeSpan.FromSeconds(5.0)) => m_From = from;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_From.EndAction<SnowPile>();
|
||||
}
|
||||
}
|
||||
|
||||
private class SnowTarget : Target
|
||||
{
|
||||
private Item m_Snow;
|
||||
private Mobile m_Thrower;
|
||||
|
||||
public SnowTarget(Mobile thrower, Item snow) : base(10, false, TargetFlags.None)
|
||||
{
|
||||
m_Thrower = thrower;
|
||||
m_Snow = snow;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
if (target == from)
|
||||
[Constructible]
|
||||
public SnowPile() : base(0x913)
|
||||
{
|
||||
from.SendLocalizedMessage(1005576); // You can't throw this at yourself.
|
||||
Hue = 0x481;
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
else if (target is Mobile targ)
|
||||
|
||||
public SnowPile(Serial serial) : base(serial)
|
||||
{
|
||||
Container pack = targ.Backpack;
|
||||
}
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || targ.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not throw snow here.");
|
||||
}
|
||||
else if (pack?.FindItemByType(new[] { typeof(SnowPile), typeof(PileOfGlacialSnow) }) != null)
|
||||
{
|
||||
if (from.BeginAction<SnowPile>())
|
||||
public override int LabelNumber => 1005578; // a pile of snow
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(1); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
if (version == 0)
|
||||
{
|
||||
new InternalTimer(from).Start();
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
}
|
||||
|
||||
from.PlaySound(0x145);
|
||||
|
||||
from.Animate(9, 1, 1, true, false, 0);
|
||||
|
||||
targ.SendLocalizedMessage(1010572); // You have just been hit by a snowball!
|
||||
from.SendLocalizedMessage(1010573); // You throw the snowball and hit the target!
|
||||
|
||||
Effects.SendMovingEffect(from, targ, 0x36E4, 7, 0, false, true, 0x480);
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1042010); // You must have the object in your backpack to use it.
|
||||
}
|
||||
else if (from.Mounted)
|
||||
{
|
||||
from.SendLocalizedMessage(1010097); // You cannot use this while mounted.
|
||||
}
|
||||
else if (from.CanBeginAction<SnowPile>())
|
||||
{
|
||||
from.SendLocalizedMessage(1005575); // You carefully pack the snow into a ball...
|
||||
from.Target = new SnowTarget(from, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
private class InternalTimer : Timer
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577); // You can only throw a snowball at something that can throw one back.
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public InternalTimer(Mobile from) : base(TimeSpan.FromSeconds(5.0)) => m_From = from;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_From.EndAction<SnowPile>();
|
||||
}
|
||||
}
|
||||
|
||||
private class SnowTarget : Target
|
||||
{
|
||||
private Item m_Snow;
|
||||
private Mobile m_Thrower;
|
||||
|
||||
public SnowTarget(Mobile thrower, Item snow) : base(10, false, TargetFlags.None)
|
||||
{
|
||||
m_Thrower = thrower;
|
||||
m_Snow = snow;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object target)
|
||||
{
|
||||
if (target == from)
|
||||
{
|
||||
from.SendLocalizedMessage(1005576); // You can't throw this at yourself.
|
||||
}
|
||||
else if (target is Mobile targ)
|
||||
{
|
||||
var pack = targ.Backpack;
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || targ.Region.IsPartOf<SafeZone>())
|
||||
{
|
||||
from.SendMessage("You may not throw snow here.");
|
||||
}
|
||||
else if (pack?.FindItemByType(new[] { typeof(SnowPile), typeof(PileOfGlacialSnow) }) != null)
|
||||
{
|
||||
if (from.BeginAction<SnowPile>())
|
||||
{
|
||||
new InternalTimer(from).Start();
|
||||
|
||||
from.PlaySound(0x145);
|
||||
|
||||
from.Animate(9, 1, 1, true, false, 0);
|
||||
|
||||
targ.SendLocalizedMessage(1010572); // You have just been hit by a snowball!
|
||||
from.SendLocalizedMessage(1010573); // You throw the snowball and hit the target!
|
||||
|
||||
Effects.SendMovingEffect(from, targ, 0x36E4, 7, 0, false, true, 0x480);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(1005574); // The snow is not ready to be packed yet. Keep trying.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577
|
||||
); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1005577
|
||||
); // You can only throw a snowball at something that can throw one back.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,44 @@
|
|||
namespace Server.Items
|
||||
{
|
||||
public class SnowyTree : Item
|
||||
{
|
||||
[Constructible]
|
||||
public SnowyTree() : base(0x2377)
|
||||
public class SnowyTree : Item
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
[Constructible]
|
||||
public SnowyTree() : base(0x2377)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public SnowyTree(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
|
||||
public SnowyTree(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1070880); // Winter 2004
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,42 +3,42 @@ using Server.Items;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class WinterGiftGiver2004 : GiftGiver
|
||||
{
|
||||
public override DateTime Start => new DateTime(2004, 12, 24);
|
||||
public override DateTime Finish => new DateTime(2005, 1, 1);
|
||||
|
||||
public static void Initialize()
|
||||
public class WinterGiftGiver2004 : GiftGiver
|
||||
{
|
||||
GiftGiving.Register(new WinterGiftGiver2004());
|
||||
public override DateTime Start => new DateTime(2004, 12, 24);
|
||||
public override DateTime Finish => new DateTime(2005, 1, 1);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
GiftGiving.Register(new WinterGiftGiver2004());
|
||||
}
|
||||
|
||||
public override void GiveGift(Mobile mob)
|
||||
{
|
||||
var box = new GiftBox();
|
||||
|
||||
box.DropItem(new MistletoeDeed());
|
||||
box.DropItem(new PileOfGlacialSnow());
|
||||
box.DropItem(new LightOfTheWinterSolstice());
|
||||
|
||||
var random = Utility.Random(100);
|
||||
|
||||
if (random < 60)
|
||||
box.DropItem(new DecorativeTopiary());
|
||||
else if (random < 84)
|
||||
box.DropItem(new FestiveCactus());
|
||||
else
|
||||
box.DropItem(new SnowyTree());
|
||||
|
||||
switch (GiveGift(mob, box))
|
||||
{
|
||||
case GiftResult.Backpack:
|
||||
mob.SendMessage(0x482, "Happy Holidays from the team! Gift items have been placed in your backpack.");
|
||||
break;
|
||||
case GiftResult.BankBox:
|
||||
mob.SendMessage(0x482, "Happy Holidays from the team! Gift items have been placed in your bank box.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void GiveGift(Mobile mob)
|
||||
{
|
||||
GiftBox box = new GiftBox();
|
||||
|
||||
box.DropItem(new MistletoeDeed());
|
||||
box.DropItem(new PileOfGlacialSnow());
|
||||
box.DropItem(new LightOfTheWinterSolstice());
|
||||
|
||||
int random = Utility.Random(100);
|
||||
|
||||
if (random < 60)
|
||||
box.DropItem(new DecorativeTopiary());
|
||||
else if (random < 84)
|
||||
box.DropItem(new FestiveCactus());
|
||||
else
|
||||
box.DropItem(new SnowyTree());
|
||||
|
||||
switch (GiveGift(mob, box))
|
||||
{
|
||||
case GiftResult.Backpack:
|
||||
mob.SendMessage(0x482, "Happy Holidays from the team! Gift items have been placed in your backpack.");
|
||||
break;
|
||||
case GiftResult.BankBox:
|
||||
mob.SendMessage(0x482, "Happy Holidays from the team! Gift items have been placed in your bank box.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -7,166 +7,171 @@ using Server.Targeting;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class HardwareInfo
|
||||
{
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuModel { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuClockSpeed { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuQuantity { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSMajor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSMinor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSRevision { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int InstanceID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenWidth { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenHeight { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenDepth { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int PhysicalMemory { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuManufacturer { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuFamily { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCVendorID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCDeviceID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCMemory { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int DXMajor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int DXMinor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string VCDescription { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Language { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int Distribution { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ClientsRunning { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ClientsInstalled { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int PartialInstalled { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Unknown { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public DateTime TimeReceived { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
public class HardwareInfo
|
||||
{
|
||||
PacketHandlers.Register(0xD9, 0x10C, false, OnReceive);
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuModel { get; private set; }
|
||||
|
||||
CommandSystem.Register("HWInfo", AccessLevel.GameMaster, HWInfo_OnCommand);
|
||||
}
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuClockSpeed { get; private set; }
|
||||
|
||||
[Usage("HWInfo")]
|
||||
[Description("Displays information about a targeted player's hardware.")]
|
||||
public static void HWInfo_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.BeginTarget(-1, false, TargetFlags.None, HWInfo_OnTarget);
|
||||
e.Mobile.SendMessage("Target a player to view their hardware information.");
|
||||
}
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuQuantity { get; private set; }
|
||||
|
||||
public static void HWInfo_OnTarget(Mobile from, object obj)
|
||||
{
|
||||
if (obj is Mobile m && m.Player)
|
||||
{
|
||||
if (m.Account is Account acct)
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSMajor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSMinor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int OSRevision { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int InstanceID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenWidth { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenHeight { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ScreenDepth { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int PhysicalMemory { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuManufacturer { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int CpuFamily { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCVendorID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCDeviceID { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int VCMemory { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int DXMajor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int DXMinor { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string VCDescription { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Language { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int Distribution { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ClientsRunning { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ClientsInstalled { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int PartialInstalled { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string Unknown { get; private set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public DateTime TimeReceived { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
HardwareInfo hwInfo = acct.HardwareInfo;
|
||||
PacketHandlers.Register(0xD9, 0x10C, false, OnReceive);
|
||||
|
||||
if (hwInfo != null)
|
||||
CommandLogging.WriteLine(from, "{0} {1} viewing hardware info of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(m));
|
||||
|
||||
if (hwInfo != null)
|
||||
from.SendGump(new PropertiesGump(from, hwInfo));
|
||||
else
|
||||
from.SendMessage("No hardware information for that account was found.");
|
||||
CommandSystem.Register("HWInfo", AccessLevel.GameMaster, HWInfo_OnCommand);
|
||||
}
|
||||
else
|
||||
|
||||
[Usage("HWInfo")]
|
||||
[Description("Displays information about a targeted player's hardware.")]
|
||||
public static void HWInfo_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
from.SendMessage("No account has been attached to that player.");
|
||||
e.Mobile.BeginTarget(-1, false, TargetFlags.None, HWInfo_OnTarget);
|
||||
e.Mobile.SendMessage("Target a player to view their hardware information.");
|
||||
}
|
||||
|
||||
public static void HWInfo_OnTarget(Mobile from, object obj)
|
||||
{
|
||||
if (obj is Mobile m && m.Player)
|
||||
{
|
||||
if (m.Account is Account acct)
|
||||
{
|
||||
var hwInfo = acct.HardwareInfo;
|
||||
|
||||
if (hwInfo != null)
|
||||
CommandLogging.WriteLine(
|
||||
from,
|
||||
"{0} {1} viewing hardware info of {2}",
|
||||
from.AccessLevel,
|
||||
CommandLogging.Format(from),
|
||||
CommandLogging.Format(m)
|
||||
);
|
||||
|
||||
if (hwInfo != null)
|
||||
from.SendGump(new PropertiesGump(from, hwInfo));
|
||||
else
|
||||
from.SendMessage("No hardware information for that account was found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("No account has been attached to that player.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.BeginTarget(-1, false, TargetFlags.None, HWInfo_OnTarget);
|
||||
from.SendMessage("That is not a player. Try again.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnReceive(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
pvSrc.ReadByte(); // 1: <4.0.1a, 2>=4.0.1a
|
||||
|
||||
var info = new HardwareInfo();
|
||||
|
||||
info.InstanceID = pvSrc.ReadInt32();
|
||||
info.OSMajor = pvSrc.ReadInt32();
|
||||
info.OSMinor = pvSrc.ReadInt32();
|
||||
info.OSRevision = pvSrc.ReadInt32();
|
||||
info.CpuManufacturer = pvSrc.ReadByte();
|
||||
info.CpuFamily = pvSrc.ReadInt32();
|
||||
info.CpuModel = pvSrc.ReadInt32();
|
||||
info.CpuClockSpeed = pvSrc.ReadInt32();
|
||||
info.CpuQuantity = pvSrc.ReadByte();
|
||||
info.PhysicalMemory = pvSrc.ReadInt32();
|
||||
info.ScreenWidth = pvSrc.ReadInt32();
|
||||
info.ScreenHeight = pvSrc.ReadInt32();
|
||||
info.ScreenDepth = pvSrc.ReadInt32();
|
||||
info.DXMajor = pvSrc.ReadInt16();
|
||||
info.DXMinor = pvSrc.ReadInt16();
|
||||
info.VCDescription = pvSrc.ReadUnicodeStringLESafe(64);
|
||||
info.VCVendorID = pvSrc.ReadInt32();
|
||||
info.VCDeviceID = pvSrc.ReadInt32();
|
||||
info.VCMemory = pvSrc.ReadInt32();
|
||||
info.Distribution = pvSrc.ReadByte();
|
||||
info.ClientsRunning = pvSrc.ReadByte();
|
||||
info.ClientsInstalled = pvSrc.ReadByte();
|
||||
info.PartialInstalled = pvSrc.ReadByte();
|
||||
info.Language = pvSrc.ReadUnicodeStringLESafe(4);
|
||||
info.Unknown = pvSrc.ReadStringSafe(64);
|
||||
|
||||
info.TimeReceived = DateTime.UtcNow;
|
||||
|
||||
if (state.Account is Account acct)
|
||||
acct.HardwareInfo = info;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.BeginTarget(-1, false, TargetFlags.None, HWInfo_OnTarget);
|
||||
from.SendMessage("That is not a player. Try again.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnReceive(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
pvSrc.ReadByte(); // 1: <4.0.1a, 2>=4.0.1a
|
||||
|
||||
HardwareInfo info = new HardwareInfo();
|
||||
|
||||
info.InstanceID = pvSrc.ReadInt32();
|
||||
info.OSMajor = pvSrc.ReadInt32();
|
||||
info.OSMinor = pvSrc.ReadInt32();
|
||||
info.OSRevision = pvSrc.ReadInt32();
|
||||
info.CpuManufacturer = pvSrc.ReadByte();
|
||||
info.CpuFamily = pvSrc.ReadInt32();
|
||||
info.CpuModel = pvSrc.ReadInt32();
|
||||
info.CpuClockSpeed = pvSrc.ReadInt32();
|
||||
info.CpuQuantity = pvSrc.ReadByte();
|
||||
info.PhysicalMemory = pvSrc.ReadInt32();
|
||||
info.ScreenWidth = pvSrc.ReadInt32();
|
||||
info.ScreenHeight = pvSrc.ReadInt32();
|
||||
info.ScreenDepth = pvSrc.ReadInt32();
|
||||
info.DXMajor = pvSrc.ReadInt16();
|
||||
info.DXMinor = pvSrc.ReadInt16();
|
||||
info.VCDescription = pvSrc.ReadUnicodeStringLESafe(64);
|
||||
info.VCVendorID = pvSrc.ReadInt32();
|
||||
info.VCDeviceID = pvSrc.ReadInt32();
|
||||
info.VCMemory = pvSrc.ReadInt32();
|
||||
info.Distribution = pvSrc.ReadByte();
|
||||
info.ClientsRunning = pvSrc.ReadByte();
|
||||
info.ClientsInstalled = pvSrc.ReadByte();
|
||||
info.PartialInstalled = pvSrc.ReadByte();
|
||||
info.Language = pvSrc.ReadUnicodeStringLESafe(4);
|
||||
info.Unknown = pvSrc.ReadStringSafe(64);
|
||||
|
||||
info.TimeReceived = DateTime.UtcNow;
|
||||
|
||||
if (state.Account is Account acct)
|
||||
acct.HardwareInfo = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,53 +2,54 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class HexStringConverter
|
||||
{
|
||||
public static readonly uint[] m_Lookup32Chars = CreateLookup32Chars();
|
||||
|
||||
private static uint[] CreateLookup32Chars()
|
||||
public class HexStringConverter
|
||||
{
|
||||
var result = new uint[256];
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
string s = i.ToString("X2");
|
||||
if (BitConverter.IsLittleEndian)
|
||||
result[i] = s[0] + ((uint)s[1] << 16);
|
||||
else
|
||||
result[i] = s[1] + ((uint)s[0] << 16);
|
||||
}
|
||||
public static readonly uint[] m_Lookup32Chars = CreateLookup32Chars();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe string GetString(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var result = new string((char)0, bytes.Length * 2);
|
||||
fixed (char* resultP = result)
|
||||
{
|
||||
uint* resultP2 = (uint*)resultP;
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
resultP2[i] = m_Lookup32Chars[bytes[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe void GetBytes(string str, Span<byte> bytes)
|
||||
{
|
||||
fixed (char* strP = str)
|
||||
{
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (i < str.Length)
|
||||
private static uint[] CreateLookup32Chars()
|
||||
{
|
||||
int chr1 = strP[i++];
|
||||
int chr2 = strP[i++];
|
||||
if (BitConverter.IsLittleEndian)
|
||||
bytes[j++] = (byte)((chr1 - (chr1 >= 65 ? 55 : 48)) << 4 | (chr2 - (chr2 >= 65 ? 55 : 48)));
|
||||
else
|
||||
bytes[j++] = (byte)((chr1 - (chr1 >= 65 ? 55 : 48)) | ((chr2 - (chr2 >= 65 ? 55 : 48)) << 4));
|
||||
var result = new uint[256];
|
||||
for (var i = 0; i < 256; i++)
|
||||
{
|
||||
var s = i.ToString("X2");
|
||||
if (BitConverter.IsLittleEndian)
|
||||
result[i] = s[0] + ((uint)s[1] << 16);
|
||||
else
|
||||
result[i] = s[1] + ((uint)s[0] << 16);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe string GetString(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
var result = new string((char)0, bytes.Length * 2);
|
||||
fixed (char* resultP = result)
|
||||
{
|
||||
var resultP2 = (uint*)resultP;
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
resultP2[i] = m_Lookup32Chars[bytes[i]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static unsafe void GetBytes(string str, Span<byte> bytes)
|
||||
{
|
||||
fixed (char* strP = str)
|
||||
{
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
while (i < str.Length)
|
||||
{
|
||||
int chr1 = strP[i++];
|
||||
int chr2 = strP[i++];
|
||||
if (BitConverter.IsLittleEndian)
|
||||
bytes[j++] = (byte)(((chr1 - (chr1 >= 65 ? 55 : 48)) << 4) | (chr2 - (chr2 >= 65 ? 55 : 48)));
|
||||
else
|
||||
bytes[j++] = (byte)((chr1 - (chr1 >= 65 ? 55 : 48)) | ((chr2 - (chr2 >= 65 ? 55 : 48)) << 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,51 +4,55 @@ using Server.Mobiles;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class Keywords
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class Keywords
|
||||
{
|
||||
// Register our speech handler
|
||||
EventSink.Speech += EventSink_Speech;
|
||||
}
|
||||
|
||||
public static void EventSink_Speech(SpeechEventArgs args)
|
||||
{
|
||||
Mobile from = args.Mobile;
|
||||
int[] keywords = args.Keywords;
|
||||
|
||||
for (int i = 0; i < keywords.Length; ++i)
|
||||
switch (keywords[i])
|
||||
public static void Initialize()
|
||||
{
|
||||
case 0x002A: // *i resign from my guild*
|
||||
{
|
||||
((Guild)from.Guild)?.RemoveMember(from);
|
||||
// Register our speech handler
|
||||
EventSink.Speech += EventSink_Speech;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x0032: // *i must consider my sins*
|
||||
{
|
||||
if (!Core.SE)
|
||||
{
|
||||
from.SendMessage("Short Term Murders : {0}", from.ShortTermMurders);
|
||||
from.SendMessage("Long Term Murders : {0}", from.Kills);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage(0x3B2, "Short Term Murders: {0} Long Term Murders: {1}", from.ShortTermMurders,
|
||||
from.Kills);
|
||||
}
|
||||
public static void EventSink_Speech(SpeechEventArgs args)
|
||||
{
|
||||
var from = args.Mobile;
|
||||
var keywords = args.Keywords;
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x0035: // i renounce my young player status*
|
||||
{
|
||||
if (from is PlayerMobile mobile && mobile.Young && !mobile.HasGump<RenounceYoungGump>())
|
||||
mobile.SendGump(new RenounceYoungGump());
|
||||
for (var i = 0; i < keywords.Length; ++i)
|
||||
switch (keywords[i])
|
||||
{
|
||||
case 0x002A: // *i resign from my guild*
|
||||
{
|
||||
((Guild)from.Guild)?.RemoveMember(from);
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0032: // *i must consider my sins*
|
||||
{
|
||||
if (!Core.SE)
|
||||
{
|
||||
from.SendMessage("Short Term Murders : {0}", from.ShortTermMurders);
|
||||
from.SendMessage("Long Term Murders : {0}", from.Kills);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage(
|
||||
0x3B2,
|
||||
"Short Term Murders: {0} Long Term Murders: {1}",
|
||||
from.ShortTermMurders,
|
||||
from.Kills
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x0035: // i renounce my young player status*
|
||||
{
|
||||
if (from is PlayerMobile mobile && mobile.Young && !mobile.HasGump<RenounceYoungGump>())
|
||||
mobile.SendGump(new RenounceYoungGump());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Server.Misc
|
|||
**/
|
||||
public class LanguageStatistics
|
||||
{
|
||||
private static readonly InternationalCode[] InternationalCodes =
|
||||
private static readonly InternationalCode[] InternationalCodes =
|
||||
{
|
||||
new InternationalCode("ARA", "Arabic", "Saudi Arabia", "العربية", "السعودية"),
|
||||
new InternationalCode("ARI", "Arabic", "Iraq", "العربية", "العراق"),
|
||||
|
|
@ -160,48 +160,48 @@ namespace Server.Misc
|
|||
new InternationalCode("KOK", "Konkani", "India", "कोंकणी", "भारत")
|
||||
};
|
||||
|
||||
private static readonly bool DefaultLocalNames = false;
|
||||
private static readonly bool ShowAlternatives = true;
|
||||
private static readonly bool CountAccounts = true; // will consider only first character's valid language
|
||||
private static readonly bool DefaultLocalNames = false;
|
||||
private static readonly bool ShowAlternatives = true;
|
||||
private static readonly bool CountAccounts = true; // will consider only first character's valid language
|
||||
|
||||
private static string GetFormattedInfo(string code)
|
||||
private static string GetFormattedInfo(string code)
|
||||
{
|
||||
if (code == null || code.Length != 3)
|
||||
return $"Unknown code {code}";
|
||||
|
||||
for (int i = 0; i < InternationalCodes.Length; i++)
|
||||
for (var i = 0; i < InternationalCodes.Length; i++)
|
||||
if (code == InternationalCodes[i].Code)
|
||||
return $"{InternationalCodes[i].GetName()}";
|
||||
|
||||
return $"Unknown code {code}";
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("LanguageStatistics", AccessLevel.Administrator, LanguageStatistics_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("LanguageStatistics")]
|
||||
[Usage("LanguageStatistics")]
|
||||
[Description("Generate a file containing the list of languages for each PlayerMobile.")]
|
||||
public static void LanguageStatistics_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Dictionary<string, InternationalCodeCounter> ht = new Dictionary<string, InternationalCodeCounter>();
|
||||
var ht = new Dictionary<string, InternationalCodeCounter>();
|
||||
|
||||
using StreamWriter writer = new StreamWriter("languages.txt");
|
||||
using var writer = new StreamWriter("languages.txt");
|
||||
if (CountAccounts)
|
||||
foreach (Account acc in Accounts.GetAccounts())
|
||||
for (int i = 0; i < acc.Length; i++)
|
||||
for (var i = 0; i < acc.Length; i++)
|
||||
{
|
||||
Mobile mob = acc[i];
|
||||
var mob = acc[i];
|
||||
|
||||
string lang = mob?.Language;
|
||||
var lang = mob?.Language;
|
||||
|
||||
if (lang == null)
|
||||
continue;
|
||||
|
||||
lang = lang.ToUpper();
|
||||
|
||||
if (ht.TryGetValue(lang, out InternationalCodeCounter codes))
|
||||
if (ht.TryGetValue(lang, out var codes))
|
||||
codes.Increase();
|
||||
else
|
||||
ht[lang] = new InternationalCodeCounter(lang);
|
||||
|
|
@ -209,17 +209,17 @@ namespace Server.Misc
|
|||
break;
|
||||
}
|
||||
else
|
||||
foreach (Mobile mob in World.Mobiles.Values)
|
||||
foreach (var mob in World.Mobiles.Values)
|
||||
if (mob.Player)
|
||||
{
|
||||
string lang = mob.Language;
|
||||
var lang = mob.Language;
|
||||
|
||||
if (lang == null)
|
||||
continue;
|
||||
|
||||
lang = lang.ToUpper();
|
||||
|
||||
if (ht.TryGetValue(lang, out InternationalCodeCounter codes))
|
||||
if (ht.TryGetValue(lang, out var codes))
|
||||
codes.Increase();
|
||||
else
|
||||
ht[lang] = new InternationalCodeCounter(lang);
|
||||
|
|
@ -232,10 +232,10 @@ namespace Server.Misc
|
|||
writer.WriteLine();
|
||||
|
||||
// sort the list
|
||||
List<InternationalCodeCounter> list = new List<InternationalCodeCounter>(ht.Values);
|
||||
var list = new List<InternationalCodeCounter>(ht.Values);
|
||||
list.Sort(InternationalCodeComparer.Instance);
|
||||
|
||||
foreach (InternationalCodeCounter c in list)
|
||||
foreach (var c in list)
|
||||
writer.WriteLine($"{GetFormattedInfo(c.Code)} : {c.Count}");
|
||||
|
||||
e.Mobile.SendMessage("Languages list generated.");
|
||||
|
|
@ -294,17 +294,17 @@ namespace Server.Misc
|
|||
|
||||
private class InternationalCodeCounter
|
||||
{
|
||||
public InternationalCodeCounter(string code)
|
||||
public InternationalCodeCounter(string code)
|
||||
{
|
||||
Code = code;
|
||||
Count = 1;
|
||||
}
|
||||
|
||||
public string Code { get; }
|
||||
public string Code { get; }
|
||||
|
||||
public int Count { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
|
||||
public void Increase()
|
||||
public void Increase()
|
||||
{
|
||||
Count++;
|
||||
}
|
||||
|
|
@ -312,9 +312,9 @@ namespace Server.Misc
|
|||
|
||||
private class InternationalCodeComparer : IComparer<InternationalCodeCounter>
|
||||
{
|
||||
public static readonly InternationalCodeComparer Instance = new InternationalCodeComparer();
|
||||
public static readonly InternationalCodeComparer Instance = new InternationalCodeComparer();
|
||||
|
||||
public int Compare(InternationalCodeCounter x, InternationalCodeCounter y)
|
||||
public int Compare(InternationalCodeCounter x, InternationalCodeCounter y)
|
||||
{
|
||||
string a = null, b = null;
|
||||
int ca = 0, cb = 0;
|
||||
|
|
|
|||
|
|
@ -4,128 +4,129 @@ using Server.Network;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class LightCycle
|
||||
{
|
||||
public const int DayLevel = 0;
|
||||
public const int NightLevel = 12;
|
||||
public const int DungeonLevel = 26;
|
||||
public const int JailLevel = 9;
|
||||
|
||||
private static int m_LevelOverride = int.MinValue;
|
||||
|
||||
public static int LevelOverride
|
||||
public class LightCycle
|
||||
{
|
||||
get => m_LevelOverride;
|
||||
set
|
||||
{
|
||||
m_LevelOverride = value;
|
||||
public const int DayLevel = 0;
|
||||
public const int NightLevel = 12;
|
||||
public const int DungeonLevel = 26;
|
||||
public const int JailLevel = 9;
|
||||
|
||||
for (int i = 0; i < TcpServer.Instances.Count; ++i)
|
||||
private static int m_LevelOverride = int.MinValue;
|
||||
|
||||
public static int LevelOverride
|
||||
{
|
||||
NetState ns = TcpServer.Instances[i];
|
||||
Mobile m = ns.Mobile;
|
||||
get => m_LevelOverride;
|
||||
set
|
||||
{
|
||||
m_LevelOverride = value;
|
||||
|
||||
m?.CheckLightLevels(false);
|
||||
for (var i = 0; i < TcpServer.Instances.Count; ++i)
|
||||
{
|
||||
var ns = TcpServer.Instances[i];
|
||||
var m = ns.Mobile;
|
||||
|
||||
m?.CheckLightLevels(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
new LightCycleTimer().Start();
|
||||
EventSink.Login += OnLogin;
|
||||
|
||||
CommandSystem.Register("GlobalLight", AccessLevel.GameMaster, Light_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("GlobalLight <value>")]
|
||||
[Description("Sets the current global light level.")]
|
||||
private static void Light_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length >= 1)
|
||||
{
|
||||
LevelOverride = e.GetInt32(0);
|
||||
e.Mobile.SendMessage("Global light level override has been changed to {0}.", m_LevelOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
LevelOverride = int.MinValue;
|
||||
e.Mobile.SendMessage("Global light level override has been cleared.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnLogin(Mobile m)
|
||||
{
|
||||
m.CheckLightLevels(true);
|
||||
}
|
||||
|
||||
public static int ComputeLevelFor(Mobile from)
|
||||
{
|
||||
if (m_LevelOverride > int.MinValue)
|
||||
return m_LevelOverride;
|
||||
|
||||
Clock.GetTime(from.Map, from.X, from.Y, out int hours, out int minutes);
|
||||
|
||||
/* OSI times:
|
||||
*
|
||||
* Midnight -> 3:59 AM : Night
|
||||
* 4:00 AM -> 11:59 PM : Day
|
||||
*
|
||||
* RunUO times:
|
||||
*
|
||||
* 10:00 PM -> 11:59 PM : Scale to night
|
||||
* Midnight -> 3:59 AM : Night
|
||||
* 4:00 AM -> 5:59 AM : Scale to day
|
||||
* 6:00 AM -> 9:59 PM : Day
|
||||
*/
|
||||
|
||||
if (hours < 4)
|
||||
return NightLevel;
|
||||
|
||||
if (hours < 6)
|
||||
return NightLevel + ((hours - 4) * 60 + minutes) * (DayLevel - NightLevel) / 120;
|
||||
|
||||
if (hours < 22)
|
||||
return DayLevel;
|
||||
|
||||
if (hours < 24)
|
||||
return DayLevel + ((hours - 22) * 60 + minutes) * (NightLevel - DayLevel) / 120;
|
||||
|
||||
return NightLevel; // should never be
|
||||
}
|
||||
|
||||
private class LightCycleTimer : Timer
|
||||
{
|
||||
public LightCycleTimer() : base(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(5.0)) => Priority = TimerPriority.FiveSeconds;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
for (int i = 0; i < TcpServer.Instances.Count; ++i)
|
||||
public static void Initialize()
|
||||
{
|
||||
NetState ns = TcpServer.Instances[i];
|
||||
Mobile m = ns.Mobile;
|
||||
new LightCycleTimer().Start();
|
||||
EventSink.Login += OnLogin;
|
||||
|
||||
m?.CheckLightLevels(false);
|
||||
CommandSystem.Register("GlobalLight", AccessLevel.GameMaster, Light_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("GlobalLight <value>")]
|
||||
[Description("Sets the current global light level.")]
|
||||
private static void Light_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length >= 1)
|
||||
{
|
||||
LevelOverride = e.GetInt32(0);
|
||||
e.Mobile.SendMessage("Global light level override has been changed to {0}.", m_LevelOverride);
|
||||
}
|
||||
else
|
||||
{
|
||||
LevelOverride = int.MinValue;
|
||||
e.Mobile.SendMessage("Global light level override has been cleared.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnLogin(Mobile m)
|
||||
{
|
||||
m.CheckLightLevels(true);
|
||||
}
|
||||
|
||||
public static int ComputeLevelFor(Mobile from)
|
||||
{
|
||||
if (m_LevelOverride > int.MinValue)
|
||||
return m_LevelOverride;
|
||||
|
||||
Clock.GetTime(from.Map, from.X, from.Y, out var hours, out int minutes);
|
||||
|
||||
/* OSI times:
|
||||
*
|
||||
* Midnight -> 3:59 AM : Night
|
||||
* 4:00 AM -> 11:59 PM : Day
|
||||
*
|
||||
* RunUO times:
|
||||
*
|
||||
* 10:00 PM -> 11:59 PM : Scale to night
|
||||
* Midnight -> 3:59 AM : Night
|
||||
* 4:00 AM -> 5:59 AM : Scale to day
|
||||
* 6:00 AM -> 9:59 PM : Day
|
||||
*/
|
||||
|
||||
if (hours < 4)
|
||||
return NightLevel;
|
||||
|
||||
if (hours < 6)
|
||||
return NightLevel + ((hours - 4) * 60 + minutes) * (DayLevel - NightLevel) / 120;
|
||||
|
||||
if (hours < 22)
|
||||
return DayLevel;
|
||||
|
||||
if (hours < 24)
|
||||
return DayLevel + ((hours - 22) * 60 + minutes) * (NightLevel - DayLevel) / 120;
|
||||
|
||||
return NightLevel; // should never be
|
||||
}
|
||||
|
||||
private class LightCycleTimer : Timer
|
||||
{
|
||||
public LightCycleTimer() : base(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(5.0)) =>
|
||||
Priority = TimerPriority.FiveSeconds;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
for (var i = 0; i < TcpServer.Instances.Count; ++i)
|
||||
{
|
||||
var ns = TcpServer.Instances[i];
|
||||
var m = ns.Mobile;
|
||||
|
||||
m?.CheckLightLevels(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NightSightTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Owner;
|
||||
|
||||
public NightSightTimer(Mobile owner) : base(TimeSpan.FromMinutes(Utility.Random(15, 25)))
|
||||
{
|
||||
m_Owner = owner;
|
||||
Priority = TimerPriority.OneMinute;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_Owner.EndAction<LightCycle>();
|
||||
m_Owner.LightLevel = 0;
|
||||
BuffInfo.RemoveBuff(m_Owner, BuffIcon.NightSight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class NightSightTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Owner;
|
||||
|
||||
public NightSightTimer(Mobile owner) : base(TimeSpan.FromMinutes(Utility.Random(15, 25)))
|
||||
{
|
||||
m_Owner = owner;
|
||||
Priority = TimerPriority.OneMinute;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
m_Owner.EndAction<LightCycle>();
|
||||
m_Owner.LightLevel = 0;
|
||||
BuffInfo.RemoveBuff(m_Owner, BuffIcon.NightSight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,31 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class LoginStats
|
||||
{
|
||||
public static void Initialize()
|
||||
public class LoginStats
|
||||
{
|
||||
// Register our event handler
|
||||
EventSink.Login += EventSink_Login;
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
// Register our event handler
|
||||
EventSink.Login += EventSink_Login;
|
||||
}
|
||||
|
||||
private static void EventSink_Login(Mobile m)
|
||||
{
|
||||
int userCount = TcpServer.Instances.Count;
|
||||
int itemCount = World.Items.Count;
|
||||
int mobileCount = World.Mobiles.Count;
|
||||
private static void EventSink_Login(Mobile m)
|
||||
{
|
||||
var userCount = TcpServer.Instances.Count;
|
||||
var itemCount = World.Items.Count;
|
||||
var mobileCount = World.Mobiles.Count;
|
||||
|
||||
m.SendMessage(
|
||||
"Welcome, {0}! There {1} currently {2} user{3} online, with {4} item{5} and {6} mobile{7} in the world.",
|
||||
m.Name,
|
||||
userCount == 1 ? "is" : "are",
|
||||
userCount, userCount == 1 ? "" : "s",
|
||||
itemCount, itemCount == 1 ? "" : "s",
|
||||
mobileCount, mobileCount == 1 ? "" : "s");
|
||||
m.SendMessage(
|
||||
"Welcome, {0}! There {1} currently {2} user{3} online, with {4} item{5} and {6} mobile{7} in the world.",
|
||||
m.Name,
|
||||
userCount == 1 ? "is" : "are",
|
||||
userCount,
|
||||
userCount == 1 ? "" : "s",
|
||||
itemCount,
|
||||
itemCount == 1 ? "" : "s",
|
||||
mobileCount,
|
||||
mobileCount == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,53 +1,59 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public static class MapDefinitions
|
||||
{
|
||||
public static void Configure()
|
||||
public static class MapDefinitions
|
||||
{
|
||||
/* Here we configure all maps. Some notes:
|
||||
*
|
||||
* 1) The first 32 maps are reserved for core use.
|
||||
* 2) Map 0x7F is reserved for core use.
|
||||
* 3) Map 0xFF is reserved for core use.
|
||||
* 4) Changing or removing any predefined maps may cause server instability.
|
||||
*/
|
||||
public static void Configure()
|
||||
{
|
||||
/* Here we configure all maps. Some notes:
|
||||
*
|
||||
* 1) The first 32 maps are reserved for core use.
|
||||
* 2) Map 0x7F is reserved for core use.
|
||||
* 3) Map 0xFF is reserved for core use.
|
||||
* 4) Changing or removing any predefined maps may cause server instability.
|
||||
*/
|
||||
|
||||
RegisterMap(0, 0, 0, 7168, 4096, 4, "Felucca", MapRules.FeluccaRules);
|
||||
RegisterMap(1, 1, 1, 7168, 4096, 0, "Trammel", MapRules.TrammelRules);
|
||||
RegisterMap(2, 2, 2, 2304, 1600, 1, "Ilshenar", MapRules.TrammelRules);
|
||||
RegisterMap(3, 3, 3, 2560, 2048, 1, "Malas", MapRules.TrammelRules);
|
||||
RegisterMap(4, 4, 4, 1448, 1448, 1, "Tokuno", MapRules.TrammelRules);
|
||||
RegisterMap(5, 5, 5, 1280, 4096, 1, "TerMur", MapRules.TrammelRules);
|
||||
RegisterMap(0, 0, 0, 7168, 4096, 4, "Felucca", MapRules.FeluccaRules);
|
||||
RegisterMap(1, 1, 1, 7168, 4096, 0, "Trammel", MapRules.TrammelRules);
|
||||
RegisterMap(2, 2, 2, 2304, 1600, 1, "Ilshenar", MapRules.TrammelRules);
|
||||
RegisterMap(3, 3, 3, 2560, 2048, 1, "Malas", MapRules.TrammelRules);
|
||||
RegisterMap(4, 4, 4, 1448, 1448, 1, "Tokuno", MapRules.TrammelRules);
|
||||
RegisterMap(5, 5, 5, 1280, 4096, 1, "TerMur", MapRules.TrammelRules);
|
||||
|
||||
RegisterMap(0x7F, 0x7F, 0x7F, Map.SectorSize, Map.SectorSize, 1, "Internal", MapRules.Internal);
|
||||
RegisterMap(0x7F, 0x7F, 0x7F, Map.SectorSize, Map.SectorSize, 1, "Internal", MapRules.Internal);
|
||||
|
||||
/* Example of registering a custom map:
|
||||
* RegisterMap( 32, 0, 0, 6144, 4096, 3, "Iceland", MapRules.FeluccaRules );
|
||||
*
|
||||
* Defined:
|
||||
* RegisterMap( <index>, <mapID>, <fileIndex>, <width>, <height>, <season>, <name>, <rules> );
|
||||
* - <index> : An unreserved unique index for this map
|
||||
* - <mapID> : An identification number used in client communications. For any visible maps, this value must be from 0-5
|
||||
* - <fileIndex> : A file identification number. For any visible maps, this value must be from 0-5
|
||||
* - <width>, <height> : Size of the map (in tiles)
|
||||
* - <season> : Season of the map. 0 = Spring, 1 = Summer, 2 = Fall, 3 = Winter, 4 = Desolation
|
||||
* - <name> : Reference name for the map, used in props gump, get/set commands, region loading, etc
|
||||
* - <rules> : Rules and restrictions associated with the map. See documentation for details
|
||||
*/
|
||||
/* Example of registering a custom map:
|
||||
* RegisterMap( 32, 0, 0, 6144, 4096, 3, "Iceland", MapRules.FeluccaRules );
|
||||
*
|
||||
* Defined:
|
||||
* RegisterMap( <index>, <mapID>, <fileIndex>, <width>, <height>, <season>, <name>, <rules> );
|
||||
* - <index> : An unreserved unique index for this map
|
||||
* - <mapID> : An identification number used in client communications. For any visible maps, this value must be from 0-5
|
||||
* - <fileIndex> : A file identification number. For any visible maps, this value must be from 0-5
|
||||
* - <width>, <height> : Size of the map (in tiles)
|
||||
* - <season> : Season of the map. 0 = Spring, 1 = Summer, 2 = Fall, 3 = Winter, 4 = Desolation
|
||||
* - <name> : Reference name for the map, used in props gump, get/set commands, region loading, etc
|
||||
* - <rules> : Rules and restrictions associated with the map. See documentation for details
|
||||
*/
|
||||
|
||||
// Using this requires the old mapDif files to be present. Only needed to support Clients < 6.0.0.0
|
||||
TileMatrixPatch.Enabled = ServerConfiguration.GetOrUpdateSetting("maps.enableTileMatrixPatches", !Core.SE);
|
||||
// Using this requires the old mapDif files to be present. Only needed to support Clients < 6.0.0.0
|
||||
TileMatrixPatch.Enabled = ServerConfiguration.GetOrUpdateSetting("maps.enableTileMatrixPatches", !Core.SE);
|
||||
|
||||
MultiComponentList.PostHSFormat = ServerConfiguration.GetOrUpdateSetting("maps.enablePostHSMultiComponentFormat", true); // OSI Client Patch 7.0.9.0
|
||||
MultiComponentList.PostHSFormat =
|
||||
ServerConfiguration.GetOrUpdateSetting(
|
||||
"maps.enablePostHSMultiComponentFormat",
|
||||
true
|
||||
); // OSI Client Patch 7.0.9.0
|
||||
}
|
||||
|
||||
public static void RegisterMap(
|
||||
int mapIndex, int mapID, int fileIndex, int width, int height, int season,
|
||||
string name, MapRules rules
|
||||
)
|
||||
{
|
||||
var newMap = new Map(mapID, mapIndex, fileIndex, width, height, season, name, rules);
|
||||
|
||||
Map.Maps[mapIndex] = newMap;
|
||||
Map.AllMaps.Add(newMap);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterMap(int mapIndex, int mapID, int fileIndex, int width, int height, int season,
|
||||
string name, MapRules rules)
|
||||
{
|
||||
Map newMap = new Map(mapID, mapIndex, fileIndex, width, height, season, name, rules);
|
||||
|
||||
Map.Maps[mapIndex] = newMap;
|
||||
Map.AllMaps.Add(newMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,126 +5,128 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class MapUO
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class MapUO
|
||||
{
|
||||
if (Settings.PartyTrack)
|
||||
ProtocolExtensions.Register(0x00, true, OnPartyTrack);
|
||||
|
||||
if (Settings.GuildTrack)
|
||||
ProtocolExtensions.Register(0x01, true, OnGuildTrack);
|
||||
}
|
||||
|
||||
private static void OnPartyTrack(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
Mobile from = state.Mobile;
|
||||
Party party = Party.Get(from);
|
||||
|
||||
if (party != null)
|
||||
{
|
||||
Packets.PartyTrack packet = new Packets.PartyTrack(from, party);
|
||||
|
||||
if (packet.Stream.Length > 8)
|
||||
state.Send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnGuildTrack(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
Mobile from = state.Mobile;
|
||||
|
||||
if (from.Guild is Guild guild)
|
||||
{
|
||||
bool locations = pvSrc.ReadByte() != 0;
|
||||
|
||||
Packets.GuildTrack packet = new Packets.GuildTrack(from, guild, locations);
|
||||
|
||||
if (packet.Stream.Length > (locations ? 9 : 5))
|
||||
state.Send(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.Send(new Packets.GuildTrack());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Settings
|
||||
{
|
||||
public const bool PartyTrack = true;
|
||||
public const bool GuildTrack = true;
|
||||
public const bool GuildHitsPercent = true;
|
||||
}
|
||||
|
||||
private static class Packets
|
||||
{
|
||||
public sealed class PartyTrack : ProtocolExtension
|
||||
{
|
||||
public PartyTrack(Mobile from, Party party) : base(0x01, (party.Members.Count - 1) * 9 + 4)
|
||||
public static void Initialize()
|
||||
{
|
||||
for (int i = 0; i < party.Members.Count; ++i)
|
||||
{
|
||||
PartyMemberInfo pmi = party.Members[i];
|
||||
if (Settings.PartyTrack)
|
||||
ProtocolExtensions.Register(0x00, true, OnPartyTrack);
|
||||
|
||||
if (pmi == null || pmi.Mobile == from)
|
||||
continue;
|
||||
|
||||
Mobile mob = pmi.Mobile;
|
||||
|
||||
if (Utility.InUpdateRange(from, mob) && from.CanSee(mob))
|
||||
continue;
|
||||
|
||||
Stream.Write(mob.Serial);
|
||||
Stream.Write((short)mob.X);
|
||||
Stream.Write((short)mob.Y);
|
||||
Stream.Write((byte)(mob.Map?.MapID ?? 0));
|
||||
}
|
||||
|
||||
Stream.Write(0);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GuildTrack : ProtocolExtension
|
||||
{
|
||||
public GuildTrack() : base(0x02, 5)
|
||||
{
|
||||
Stream.Write((byte)0);
|
||||
Stream.Write(0);
|
||||
if (Settings.GuildTrack)
|
||||
ProtocolExtensions.Register(0x01, true, OnGuildTrack);
|
||||
}
|
||||
|
||||
public GuildTrack(Mobile from, Guild guild, bool locations) : base(0x02,
|
||||
(guild.Members.Count - 1) * (locations ? 10 : 4) + 5)
|
||||
private static void OnPartyTrack(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
Stream.Write((byte)(locations ? 1 : 0));
|
||||
var from = state.Mobile;
|
||||
var party = Party.Get(from);
|
||||
|
||||
for (int i = 0; i < guild.Members.Count; ++i)
|
||||
{
|
||||
Mobile mob = guild.Members[i];
|
||||
|
||||
if (mob == null || mob == from || mob.NetState == null)
|
||||
continue;
|
||||
|
||||
if (locations && Utility.InUpdateRange(from, mob) && from.CanSee(mob))
|
||||
continue;
|
||||
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
if (locations)
|
||||
if (party != null)
|
||||
{
|
||||
Stream.Write((short)mob.X);
|
||||
Stream.Write((short)mob.Y);
|
||||
Stream.Write((byte)(mob.Map?.MapID ?? 0));
|
||||
var packet = new Packets.PartyTrack(from, party);
|
||||
|
||||
if (Settings.GuildHitsPercent && mob.Alive)
|
||||
Stream.Write((byte)(mob.Hits / Math.Max(mob.HitsMax, 1.0) * 100));
|
||||
else
|
||||
Stream.Write((byte)0);
|
||||
if (packet.Stream.Length > 8)
|
||||
state.Send(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnGuildTrack(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
var from = state.Mobile;
|
||||
|
||||
if (from.Guild is Guild guild)
|
||||
{
|
||||
var locations = pvSrc.ReadByte() != 0;
|
||||
|
||||
var packet = new Packets.GuildTrack(from, guild, locations);
|
||||
|
||||
if (packet.Stream.Length > (locations ? 9 : 5))
|
||||
state.Send(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
state.Send(new Packets.GuildTrack());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Settings
|
||||
{
|
||||
public const bool PartyTrack = true;
|
||||
public const bool GuildTrack = true;
|
||||
public const bool GuildHitsPercent = true;
|
||||
}
|
||||
|
||||
private static class Packets
|
||||
{
|
||||
public sealed class PartyTrack : ProtocolExtension
|
||||
{
|
||||
public PartyTrack(Mobile from, Party party) : base(0x01, (party.Members.Count - 1) * 9 + 4)
|
||||
{
|
||||
for (var i = 0; i < party.Members.Count; ++i)
|
||||
{
|
||||
var pmi = party.Members[i];
|
||||
|
||||
if (pmi == null || pmi.Mobile == from)
|
||||
continue;
|
||||
|
||||
var mob = pmi.Mobile;
|
||||
|
||||
if (Utility.InUpdateRange(from, mob) && from.CanSee(mob))
|
||||
continue;
|
||||
|
||||
Stream.Write(mob.Serial);
|
||||
Stream.Write((short)mob.X);
|
||||
Stream.Write((short)mob.Y);
|
||||
Stream.Write((byte)(mob.Map?.MapID ?? 0));
|
||||
}
|
||||
|
||||
Stream.Write(0);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GuildTrack : ProtocolExtension
|
||||
{
|
||||
public GuildTrack() : base(0x02, 5)
|
||||
{
|
||||
Stream.Write((byte)0);
|
||||
Stream.Write(0);
|
||||
}
|
||||
|
||||
public GuildTrack(Mobile from, Guild guild, bool locations) : base(
|
||||
0x02,
|
||||
(guild.Members.Count - 1) * (locations ? 10 : 4) + 5
|
||||
)
|
||||
{
|
||||
Stream.Write((byte)(locations ? 1 : 0));
|
||||
|
||||
for (var i = 0; i < guild.Members.Count; ++i)
|
||||
{
|
||||
var mob = guild.Members[i];
|
||||
|
||||
if (mob == null || mob == from || mob.NetState == null)
|
||||
continue;
|
||||
|
||||
if (locations && Utility.InUpdateRange(from, mob) && from.CanSee(mob))
|
||||
continue;
|
||||
|
||||
Stream.Write(mob.Serial);
|
||||
|
||||
if (locations)
|
||||
{
|
||||
Stream.Write((short)mob.X);
|
||||
Stream.Write((short)mob.Y);
|
||||
Stream.Write((byte)(mob.Map?.MapID ?? 0));
|
||||
|
||||
if (Settings.GuildHitsPercent && mob.Alive)
|
||||
Stream.Write((byte)(mob.Hits / Math.Max(mob.HitsMax, 1.0) * 100));
|
||||
else
|
||||
Stream.Write((byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
Stream.Write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream.Write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,73 +5,75 @@ using Server.Utilities;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public static class MondainsLegacy
|
||||
{
|
||||
public static Type[] Artifacts { get; } =
|
||||
public static class MondainsLegacy
|
||||
{
|
||||
typeof(AegisOfGrace), typeof(BladeDance), typeof(BloodwoodSpirit), typeof(Bonesmasher),
|
||||
typeof(Boomstick), typeof(BrightsightLenses), typeof(FeyLeggings), typeof(FleshRipper),
|
||||
typeof(HelmOfSwiftness), typeof(PadsOfTheCuSidhe), typeof(QuiverOfRage), typeof(QuiverOfElements),
|
||||
typeof(RaedsGlory), typeof(RighteousAnger), typeof(RobeOfTheEclipse), typeof(RobeOfTheEquinox),
|
||||
typeof(SoulSeeker), typeof(TalonBite), typeof(TotemOfVoid), typeof(WildfireBow),
|
||||
typeof(Windsong)
|
||||
};
|
||||
public static Type[] Artifacts { get; } =
|
||||
{
|
||||
typeof(AegisOfGrace), typeof(BladeDance), typeof(BloodwoodSpirit), typeof(Bonesmasher),
|
||||
typeof(Boomstick), typeof(BrightsightLenses), typeof(FeyLeggings), typeof(FleshRipper),
|
||||
typeof(HelmOfSwiftness), typeof(PadsOfTheCuSidhe), typeof(QuiverOfRage), typeof(QuiverOfElements),
|
||||
typeof(RaedsGlory), typeof(RighteousAnger), typeof(RobeOfTheEclipse), typeof(RobeOfTheEquinox),
|
||||
typeof(SoulSeeker), typeof(TalonBite), typeof(TotemOfVoid), typeof(WildfireBow),
|
||||
typeof(Windsong)
|
||||
};
|
||||
|
||||
public static bool CheckArtifactChance(Mobile m, BaseCreature bc)
|
||||
{
|
||||
if (!Core.ML)
|
||||
return false;
|
||||
public static bool CheckArtifactChance(Mobile m, BaseCreature bc)
|
||||
{
|
||||
if (!Core.ML)
|
||||
return false;
|
||||
|
||||
return Paragon.CheckArtifactChance(m, bc);
|
||||
return Paragon.CheckArtifactChance(m, bc);
|
||||
}
|
||||
|
||||
public static void GiveArtifactTo(Mobile m)
|
||||
{
|
||||
if (!(ActivatorUtil.CreateInstance(Artifacts.RandomElement()) is Item item))
|
||||
return;
|
||||
|
||||
if (m.AddToBackpack(item))
|
||||
{
|
||||
m.SendLocalizedMessage(1072223); // An item has been placed in your backpack.
|
||||
m.SendLocalizedMessage(
|
||||
1062317
|
||||
); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
|
||||
}
|
||||
else if (m.BankBox.TryDropItem(m, item, false))
|
||||
{
|
||||
m.SendLocalizedMessage(1072224); // An item has been placed in your bank box.
|
||||
m.SendLocalizedMessage(
|
||||
1062317
|
||||
); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Item was placed at feet by m.AddToBackpack
|
||||
m.SendLocalizedMessage(1072523); // You find an artifact, but your backpack and bank are too full to hold it.
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckML(Mobile from, bool message = true)
|
||||
{
|
||||
if (from?.NetState == null)
|
||||
return false;
|
||||
|
||||
if (from.NetState.SupportsExpansion(Expansion.ML))
|
||||
return true;
|
||||
|
||||
if (message)
|
||||
from.SendLocalizedMessage(1072791); // You must upgrade to Mondain's Legacy in order to use that item.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMLRegion(Region region) =>
|
||||
region.IsPartOf("Twisted Weald")
|
||||
|| region.IsPartOf("Sanctuary")
|
||||
|| region.IsPartOf("The Prism of Light")
|
||||
|| region.IsPartOf("The Citadel")
|
||||
|| region.IsPartOf("Bedlam")
|
||||
|| region.IsPartOf("Blighted Grove")
|
||||
|| region.IsPartOf("The Painted Caves")
|
||||
|| region.IsPartOf("The Palace of Paroxysmus")
|
||||
|| region.IsPartOf("Labyrinth");
|
||||
}
|
||||
|
||||
public static void GiveArtifactTo(Mobile m)
|
||||
{
|
||||
if (!(ActivatorUtil.CreateInstance(Artifacts.RandomElement()) is Item item))
|
||||
return;
|
||||
|
||||
if (m.AddToBackpack(item))
|
||||
{
|
||||
m.SendLocalizedMessage(1072223); // An item has been placed in your backpack.
|
||||
m.SendLocalizedMessage(
|
||||
1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
|
||||
}
|
||||
else if (m.BankBox.TryDropItem(m, item, false))
|
||||
{
|
||||
m.SendLocalizedMessage(1072224); // An item has been placed in your bank box.
|
||||
m.SendLocalizedMessage(
|
||||
1062317); // For your valor in combating the fallen beast, a special artifact has been bestowed on you.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Item was placed at feet by m.AddToBackpack
|
||||
m.SendLocalizedMessage(1072523); // You find an artifact, but your backpack and bank are too full to hold it.
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckML(Mobile from, bool message = true)
|
||||
{
|
||||
if (from?.NetState == null)
|
||||
return false;
|
||||
|
||||
if (from.NetState.SupportsExpansion(Expansion.ML))
|
||||
return true;
|
||||
|
||||
if (message)
|
||||
from.SendLocalizedMessage(1072791); // You must upgrade to Mondain's Legacy in order to use that item.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsMLRegion(Region region) =>
|
||||
region.IsPartOf("Twisted Weald")
|
||||
|| region.IsPartOf("Sanctuary")
|
||||
|| region.IsPartOf("The Prism of Light")
|
||||
|| region.IsPartOf("The Citadel")
|
||||
|| region.IsPartOf("Bedlam")
|
||||
|| region.IsPartOf("Blighted Grove")
|
||||
|| region.IsPartOf("The Painted Caves")
|
||||
|| region.IsPartOf("The Palace of Paroxysmus")
|
||||
|| region.IsPartOf("Labyrinth");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,52 +6,51 @@ using Server.Json;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class NameList
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("names")]
|
||||
public string[] List { get; set; }
|
||||
|
||||
public bool ContainsName(string name)
|
||||
public class NameList
|
||||
{
|
||||
for (int i = 0; i < List.Length; i++)
|
||||
if (name == List[i])
|
||||
return true;
|
||||
private static readonly Dictionary<string, NameList> m_Table =
|
||||
new Dictionary<string, NameList>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return false;
|
||||
[JsonPropertyName("type")] public string Type { get; set; }
|
||||
|
||||
[JsonPropertyName("names")] public string[] List { get; set; }
|
||||
|
||||
public bool ContainsName(string name)
|
||||
{
|
||||
for (var i = 0; i < List.Length; i++)
|
||||
if (name == List[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetRandomName() => List.RandomElement() ?? "";
|
||||
|
||||
public static NameList GetNameList(string type)
|
||||
{
|
||||
m_Table.TryGetValue(type, out var n);
|
||||
return n;
|
||||
}
|
||||
|
||||
public static string RandomName(string type) => GetNameList(type)?.GetRandomName() ?? "";
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
// TODO: Turn this into a command so it can be updated in-game
|
||||
var filePath = Path.Combine(Core.BaseDirectory, "Data/names.json");
|
||||
|
||||
var nameLists = JsonConfig.Deserialize<List<NameList>>(filePath);
|
||||
foreach (var nameList in nameLists)
|
||||
{
|
||||
nameList.FixNames();
|
||||
m_Table.Add(nameList.Type, nameList);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixNames()
|
||||
{
|
||||
for (var i = 0; i < List.Length; i++)
|
||||
List[i] = Utility.Intern(List[i].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
public string GetRandomName() => List.RandomElement() ?? "";
|
||||
|
||||
public static NameList GetNameList(string type)
|
||||
{
|
||||
m_Table.TryGetValue(type, out NameList n);
|
||||
return n;
|
||||
}
|
||||
|
||||
public static string RandomName(string type) => GetNameList(type)?.GetRandomName() ?? "";
|
||||
|
||||
private static readonly Dictionary<string, NameList> m_Table = new Dictionary<string, NameList>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
// TODO: Turn this into a command so it can be updated in-game
|
||||
string filePath = Path.Combine(Core.BaseDirectory, "Data/names.json");
|
||||
|
||||
List<NameList> nameLists = JsonConfig.Deserialize<List<NameList>>(filePath);
|
||||
foreach (var nameList in nameLists)
|
||||
{
|
||||
nameList.FixNames();
|
||||
m_Table.Add(nameList.Type, nameList);
|
||||
}
|
||||
}
|
||||
|
||||
private void FixNames()
|
||||
{
|
||||
for (int i = 0; i < List.Length; i++)
|
||||
List[i] = Utility.Intern(List[i].Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,198 +1,214 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class NameVerification
|
||||
{
|
||||
public static readonly char[] SpaceDashPeriodQuote =
|
||||
public class NameVerification
|
||||
{
|
||||
' ', '-', '.', '\''
|
||||
};
|
||||
|
||||
public static readonly char[] Empty = System.Array.Empty<char>();
|
||||
|
||||
public static string[] StartDisallowed { get; } =
|
||||
{
|
||||
"seer",
|
||||
"counselor",
|
||||
"gm",
|
||||
"admin",
|
||||
"lady",
|
||||
"lord"
|
||||
};
|
||||
|
||||
public static string[] Disallowed { get; } =
|
||||
{
|
||||
"jigaboo",
|
||||
"chigaboo",
|
||||
"wop",
|
||||
"kyke",
|
||||
"kike",
|
||||
"tit",
|
||||
"spic",
|
||||
"prick",
|
||||
"piss",
|
||||
"lezbo",
|
||||
"lesbo",
|
||||
"felatio",
|
||||
"dyke",
|
||||
"dildo",
|
||||
"chinc",
|
||||
"chink",
|
||||
"cunnilingus",
|
||||
"cum",
|
||||
"cocksucker",
|
||||
"cock",
|
||||
"clitoris",
|
||||
"clit",
|
||||
"ass",
|
||||
"hitler",
|
||||
"penis",
|
||||
"nigga",
|
||||
"nigger",
|
||||
"klit",
|
||||
"kunt",
|
||||
"jiz",
|
||||
"jism",
|
||||
"jerkoff",
|
||||
"jackoff",
|
||||
"goddamn",
|
||||
"fag",
|
||||
"blowjob",
|
||||
"bitch",
|
||||
"asshole",
|
||||
"dick",
|
||||
"pussy",
|
||||
"snatch",
|
||||
"cunt",
|
||||
"twat",
|
||||
"shit",
|
||||
"fuck",
|
||||
"tailor",
|
||||
"smith",
|
||||
"scholar",
|
||||
"rogue",
|
||||
"novice",
|
||||
"neophyte",
|
||||
"merchant",
|
||||
"medium",
|
||||
"master",
|
||||
"mage",
|
||||
"lb",
|
||||
"journeyman",
|
||||
"grandmaster",
|
||||
"fisherman",
|
||||
"expert",
|
||||
"chef",
|
||||
"carpenter",
|
||||
"british",
|
||||
"blackthorne",
|
||||
"blackthorn",
|
||||
"beggar",
|
||||
"archer",
|
||||
"apprentice",
|
||||
"adept",
|
||||
"gamemaster",
|
||||
"frozen",
|
||||
"squelched",
|
||||
"invulnerable",
|
||||
"osi",
|
||||
"origin"
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("ValidateName", AccessLevel.Administrator, ValidateName_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("ValidateName")]
|
||||
[Description("Checks the result of NameValidation on the specified name.")]
|
||||
public static void ValidateName_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (Validate(e.ArgString, 2, 16, true, false, true, 1, SpaceDashPeriodQuote))
|
||||
e.Mobile.SendMessage(0x59, "That name is considered valid.");
|
||||
else
|
||||
e.Mobile.SendMessage(0x22, "That name is considered invalid.");
|
||||
}
|
||||
|
||||
public static bool Validate(string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
|
||||
bool noExceptionsAtStart, int maxExceptions, char[] exceptions) =>
|
||||
Validate(name, minLength, maxLength, allowLetters, allowDigits, noExceptionsAtStart, maxExceptions,
|
||||
exceptions, Disallowed, StartDisallowed);
|
||||
|
||||
public static bool Validate(string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
|
||||
bool noExceptionsAtStart, int maxExceptions, char[] exceptions, string[] disallowed, string[] startDisallowed)
|
||||
{
|
||||
if (name == null || name.Length < minLength || name.Length > maxLength)
|
||||
return false;
|
||||
|
||||
int exceptCount = 0;
|
||||
|
||||
name = name.ToLower();
|
||||
|
||||
if (!allowLetters || !allowDigits ||
|
||||
(exceptions.Length > 0 && (noExceptionsAtStart || maxExceptions < int.MaxValue)))
|
||||
for (int i = 0; i < name.Length; ++i)
|
||||
public static readonly char[] SpaceDashPeriodQuote =
|
||||
{
|
||||
char c = name[i];
|
||||
' ', '-', '.', '\''
|
||||
};
|
||||
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
if (!allowLetters)
|
||||
return false;
|
||||
public static readonly char[] Empty = Array.Empty<char>();
|
||||
|
||||
exceptCount = 0;
|
||||
}
|
||||
else if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (!allowDigits)
|
||||
return false;
|
||||
public static string[] StartDisallowed { get; } =
|
||||
{
|
||||
"seer",
|
||||
"counselor",
|
||||
"gm",
|
||||
"admin",
|
||||
"lady",
|
||||
"lord"
|
||||
};
|
||||
|
||||
exceptCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool except = false;
|
||||
public static string[] Disallowed { get; } =
|
||||
{
|
||||
"jigaboo",
|
||||
"chigaboo",
|
||||
"wop",
|
||||
"kyke",
|
||||
"kike",
|
||||
"tit",
|
||||
"spic",
|
||||
"prick",
|
||||
"piss",
|
||||
"lezbo",
|
||||
"lesbo",
|
||||
"felatio",
|
||||
"dyke",
|
||||
"dildo",
|
||||
"chinc",
|
||||
"chink",
|
||||
"cunnilingus",
|
||||
"cum",
|
||||
"cocksucker",
|
||||
"cock",
|
||||
"clitoris",
|
||||
"clit",
|
||||
"ass",
|
||||
"hitler",
|
||||
"penis",
|
||||
"nigga",
|
||||
"nigger",
|
||||
"klit",
|
||||
"kunt",
|
||||
"jiz",
|
||||
"jism",
|
||||
"jerkoff",
|
||||
"jackoff",
|
||||
"goddamn",
|
||||
"fag",
|
||||
"blowjob",
|
||||
"bitch",
|
||||
"asshole",
|
||||
"dick",
|
||||
"pussy",
|
||||
"snatch",
|
||||
"cunt",
|
||||
"twat",
|
||||
"shit",
|
||||
"fuck",
|
||||
"tailor",
|
||||
"smith",
|
||||
"scholar",
|
||||
"rogue",
|
||||
"novice",
|
||||
"neophyte",
|
||||
"merchant",
|
||||
"medium",
|
||||
"master",
|
||||
"mage",
|
||||
"lb",
|
||||
"journeyman",
|
||||
"grandmaster",
|
||||
"fisherman",
|
||||
"expert",
|
||||
"chef",
|
||||
"carpenter",
|
||||
"british",
|
||||
"blackthorne",
|
||||
"blackthorn",
|
||||
"beggar",
|
||||
"archer",
|
||||
"apprentice",
|
||||
"adept",
|
||||
"gamemaster",
|
||||
"frozen",
|
||||
"squelched",
|
||||
"invulnerable",
|
||||
"osi",
|
||||
"origin"
|
||||
};
|
||||
|
||||
for (int j = 0; !except && j < exceptions.Length; ++j)
|
||||
if (c == exceptions[j])
|
||||
except = true;
|
||||
|
||||
if (!except || (i == 0 && noExceptionsAtStart))
|
||||
return false;
|
||||
|
||||
if (exceptCount++ == maxExceptions)
|
||||
return false;
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("ValidateName", AccessLevel.Administrator, ValidateName_OnCommand);
|
||||
}
|
||||
|
||||
for (int i = 0; i < disallowed.Length; ++i)
|
||||
{
|
||||
int indexOf = name.IndexOf(disallowed[i]);
|
||||
[Usage("ValidateName")]
|
||||
[Description("Checks the result of NameValidation on the specified name.")]
|
||||
public static void ValidateName_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (Validate(e.ArgString, 2, 16, true, false, true, 1, SpaceDashPeriodQuote))
|
||||
e.Mobile.SendMessage(0x59, "That name is considered valid.");
|
||||
else
|
||||
e.Mobile.SendMessage(0x22, "That name is considered invalid.");
|
||||
}
|
||||
|
||||
if (indexOf == -1)
|
||||
continue;
|
||||
public static bool Validate(
|
||||
string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
|
||||
bool noExceptionsAtStart, int maxExceptions, char[] exceptions
|
||||
) =>
|
||||
Validate(
|
||||
name,
|
||||
minLength,
|
||||
maxLength,
|
||||
allowLetters,
|
||||
allowDigits,
|
||||
noExceptionsAtStart,
|
||||
maxExceptions,
|
||||
exceptions,
|
||||
Disallowed,
|
||||
StartDisallowed
|
||||
);
|
||||
|
||||
bool badPrefix = indexOf == 0;
|
||||
public static bool Validate(
|
||||
string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
|
||||
bool noExceptionsAtStart, int maxExceptions, char[] exceptions, string[] disallowed, string[] startDisallowed
|
||||
)
|
||||
{
|
||||
if (name == null || name.Length < minLength || name.Length > maxLength)
|
||||
return false;
|
||||
|
||||
for (int j = 0; !badPrefix && j < exceptions.Length; ++j)
|
||||
badPrefix = name[indexOf - 1] == exceptions[j];
|
||||
var exceptCount = 0;
|
||||
|
||||
if (!badPrefix)
|
||||
continue;
|
||||
name = name.ToLower();
|
||||
|
||||
bool badSuffix = indexOf + disallowed[i].Length >= name.Length;
|
||||
if (!allowLetters || !allowDigits ||
|
||||
exceptions.Length > 0 && (noExceptionsAtStart || maxExceptions < int.MaxValue))
|
||||
for (var i = 0; i < name.Length; ++i)
|
||||
{
|
||||
var c = name[i];
|
||||
|
||||
for (int j = 0; !badSuffix && j < exceptions.Length; ++j)
|
||||
badSuffix = name[indexOf + disallowed[i].Length] == exceptions[j];
|
||||
if (c >= 'a' && c <= 'z')
|
||||
{
|
||||
if (!allowLetters)
|
||||
return false;
|
||||
|
||||
if (badSuffix)
|
||||
return false;
|
||||
}
|
||||
exceptCount = 0;
|
||||
}
|
||||
else if (c >= '0' && c <= '9')
|
||||
{
|
||||
if (!allowDigits)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < startDisallowed.Length; ++i)
|
||||
if (name.StartsWith(startDisallowed[i]))
|
||||
return false;
|
||||
exceptCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var except = false;
|
||||
|
||||
return true;
|
||||
for (var j = 0; !except && j < exceptions.Length; ++j)
|
||||
if (c == exceptions[j])
|
||||
except = true;
|
||||
|
||||
if (!except || i == 0 && noExceptionsAtStart)
|
||||
return false;
|
||||
|
||||
if (exceptCount++ == maxExceptions)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < disallowed.Length; ++i)
|
||||
{
|
||||
var indexOf = name.IndexOf(disallowed[i]);
|
||||
|
||||
if (indexOf == -1)
|
||||
continue;
|
||||
|
||||
var badPrefix = indexOf == 0;
|
||||
|
||||
for (var j = 0; !badPrefix && j < exceptions.Length; ++j)
|
||||
badPrefix = name[indexOf - 1] == exceptions[j];
|
||||
|
||||
if (!badPrefix)
|
||||
continue;
|
||||
|
||||
var badSuffix = indexOf + disallowed[i].Length >= name.Length;
|
||||
|
||||
for (var j = 0; !badSuffix && j < exceptions.Length; ++j)
|
||||
badSuffix = name[indexOf + disallowed[i].Length] == exceptions[j];
|
||||
|
||||
if (badSuffix)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < startDisallowed.Length; ++i)
|
||||
if (name.StartsWith(startDisallowed[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,433 +12,434 @@ using Server.Spells.Seventh;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class NotorietyHandlers
|
||||
{
|
||||
public static void Initialize()
|
||||
public class NotorietyHandlers
|
||||
{
|
||||
Notoriety.Hues[Notoriety.Innocent] = 0x59;
|
||||
Notoriety.Hues[Notoriety.Ally] = 0x3F;
|
||||
Notoriety.Hues[Notoriety.CanBeAttacked] = 0x3B2;
|
||||
Notoriety.Hues[Notoriety.Criminal] = 0x3B2;
|
||||
Notoriety.Hues[Notoriety.Enemy] = 0x90;
|
||||
Notoriety.Hues[Notoriety.Murderer] = 0x22;
|
||||
Notoriety.Hues[Notoriety.Invulnerable] = 0x35;
|
||||
|
||||
Notoriety.Handler = MobileNotoriety;
|
||||
|
||||
Mobile.AllowBeneficialHandler = Mobile_AllowBeneficial;
|
||||
Mobile.AllowHarmfulHandler = Mobile_AllowHarmful;
|
||||
}
|
||||
|
||||
private static GuildStatus GetGuildStatus(Mobile m)
|
||||
{
|
||||
if (m.Guild == null)
|
||||
return GuildStatus.None;
|
||||
if (((Guild)m.Guild).Enemies.Count == 0 && m.Guild.Type == GuildType.Regular)
|
||||
return GuildStatus.Peaceful;
|
||||
|
||||
return GuildStatus.Waring;
|
||||
}
|
||||
|
||||
private static bool CheckBeneficialStatus(GuildStatus from, GuildStatus target)
|
||||
{
|
||||
if (from == GuildStatus.Waring || target == GuildStatus.Waring)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*private static bool CheckHarmfulStatus( GuildStatus from, GuildStatus target )
|
||||
{
|
||||
if (from == GuildStatus.Waring && target == GuildStatus.Waring)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
public static bool Mobile_AllowBeneficial(Mobile from, Mobile target)
|
||||
{
|
||||
if (from == null || target == null || from.AccessLevel > AccessLevel.Player ||
|
||||
target.AccessLevel > AccessLevel.Player)
|
||||
return true;
|
||||
|
||||
PlayerMobile pmFrom = from as PlayerMobile;
|
||||
PlayerMobile pmTarg = target as PlayerMobile;
|
||||
|
||||
if (pmFrom == null && from is BaseCreature bcFrom && bcFrom.Summoned)
|
||||
pmFrom = bcFrom.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmTarg == null && target is BaseCreature bcTarg && bcTarg.Summoned)
|
||||
pmTarg = bcTarg.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
{
|
||||
if (pmFrom.DuelContext != pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext?.Started == true || pmTarg.DuelContext?.Started == true))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
((pmFrom.DuelContext.StartedReadyCountdown && !pmFrom.DuelContext.Started) || pmFrom.DuelContext.Tied ||
|
||||
pmFrom.DuelPlayer.Eliminated || pmTarg.DuelPlayer.Eliminated))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelPlayer?.Eliminated == false && pmFrom.DuelContext?.IsSuddenDeath == true)
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
pmFrom.DuelContext.m_Tournament?.IsNotoRestricted == true &&
|
||||
pmFrom.DuelPlayer != null && pmTarg.DuelPlayer != null &&
|
||||
pmFrom.DuelPlayer.Participant != pmTarg.DuelPlayer.Participant)
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext?.Started == true && pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pmFrom?.DuelContext?.Started == true || pmTarg?.DuelContext?.Started == true)
|
||||
return false;
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || target.Region.IsPartOf<SafeZone>())
|
||||
return false;
|
||||
|
||||
Map map = from.Map;
|
||||
|
||||
Faction targetFaction = Faction.Find(target, true);
|
||||
|
||||
if ((!Core.ML || map == Faction.Facet) && targetFaction != null)
|
||||
if (Faction.Find(from, true) != targetFaction)
|
||||
return false;
|
||||
|
||||
if ((map?.Rules & MapRules.BeneficialRestrictions) == 0)
|
||||
return true; // In felucca, anything goes
|
||||
|
||||
if (!from.Player)
|
||||
return true; // NPCs have no restrictions
|
||||
|
||||
if (target is BaseCreature creature && !creature.Controlled)
|
||||
return false; // Players cannot heal uncontrolled mobiles
|
||||
|
||||
if (pmFrom?.Young == true || pmTarg?.Young == true)
|
||||
return false; // Young players cannot perform beneficial actions towards older players
|
||||
|
||||
if (from.Guild is Guild fromGuild && target.Guild is Guild targetGuild &&
|
||||
(targetGuild == fromGuild || fromGuild.IsAlly(targetGuild)))
|
||||
return true; // Guild members can be beneficial
|
||||
|
||||
return CheckBeneficialStatus(GetGuildStatus(from), GetGuildStatus(target));
|
||||
}
|
||||
|
||||
public static bool Mobile_AllowHarmful(Mobile from, Mobile target)
|
||||
{
|
||||
if (from == null || target == null || from.AccessLevel > AccessLevel.Player ||
|
||||
target.AccessLevel > AccessLevel.Player)
|
||||
return true;
|
||||
|
||||
PlayerMobile pmFrom = from as PlayerMobile;
|
||||
PlayerMobile pmTarg = target as PlayerMobile;
|
||||
BaseCreature bcTarg = target as BaseCreature;
|
||||
|
||||
if (pmFrom == null && from is BaseCreature bcFrom && bcFrom.Summoned)
|
||||
pmFrom = bcFrom.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmTarg == null && bcTarg?.Summoned == true)
|
||||
pmTarg = bcTarg.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
{
|
||||
if (pmFrom.DuelContext != pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext?.Started == true || pmTarg.DuelContext?.Started == true))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
((pmFrom.DuelContext.StartedReadyCountdown && !pmFrom.DuelContext.Started) || pmFrom.DuelContext.Tied ||
|
||||
pmFrom.DuelPlayer.Eliminated || pmTarg.DuelPlayer.Eliminated))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
pmFrom.DuelContext.m_Tournament?.IsNotoRestricted == true &&
|
||||
pmFrom.DuelPlayer != null && pmTarg.DuelPlayer != null &&
|
||||
pmFrom.DuelPlayer.Participant == pmTarg.DuelPlayer.Participant)
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext?.Started == true && pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pmFrom?.DuelContext?.Started == true || pmTarg?.DuelContext?.Started == true)
|
||||
return false;
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || target.Region.IsPartOf<SafeZone>())
|
||||
return false;
|
||||
|
||||
Map map = from.Map;
|
||||
|
||||
if ((map?.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
return true; // In felucca, anything goes
|
||||
|
||||
if (!from.Player && !(from is BaseCreature bc && bc.GetMaster() != null &&
|
||||
bc.GetMaster().AccessLevel == AccessLevel.Player))
|
||||
{
|
||||
if (!CheckAggressor(from.Aggressors, target) && !CheckAggressed(from.Aggressed, target) &&
|
||||
pmTarg?.CheckYoungProtection(from) == true)
|
||||
return false;
|
||||
|
||||
return true; // Uncontrolled NPCs are only restricted by the young system
|
||||
}
|
||||
|
||||
Guild fromGuild = GetGuildFor(from.Guild as Guild, from);
|
||||
Guild targetGuild = GetGuildFor(target.Guild as Guild, target);
|
||||
|
||||
if (fromGuild != null && targetGuild != null &&
|
||||
(fromGuild == targetGuild || fromGuild.IsAlly(targetGuild) || fromGuild.IsEnemy(targetGuild)))
|
||||
return true; // Guild allies or enemies can be harmful
|
||||
|
||||
if (bcTarg?.Controlled == true || (bcTarg?.Summoned == true && bcTarg?.SummonMaster != from))
|
||||
return false; // Cannot harm other controlled mobiles
|
||||
|
||||
if (target.Player)
|
||||
return false; // Cannot harm other players
|
||||
|
||||
return bcTarg?.InitialInnocent == true || Notoriety.Compute(from, target) != Notoriety.Innocent;
|
||||
}
|
||||
|
||||
public static Guild GetGuildFor(Guild def, Mobile m)
|
||||
{
|
||||
Guild g = def;
|
||||
|
||||
if (m is BaseCreature c && c.Controlled && c.ControlMaster != null)
|
||||
{
|
||||
c.DisplayGuildTitle = false;
|
||||
|
||||
if (c.Map != Map.Internal && (Core.AOS || Guild.NewGuildSystem || c.ControlOrder == OrderType.Attack ||
|
||||
c.ControlOrder == OrderType.Guard))
|
||||
g = (Guild)(c.Guild = c.ControlMaster.Guild);
|
||||
else if (c.Map == Map.Internal || c.ControlMaster.Guild == null)
|
||||
g = (Guild)(c.Guild = null);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
public static int CorpseNotoriety(Mobile source, Corpse target)
|
||||
{
|
||||
if (target.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
Body body = target.Amount;
|
||||
|
||||
Guild sourceGuild = GetGuildFor(source.Guild as Guild, source);
|
||||
Guild targetGuild = GetGuildFor(target.Guild, target.Owner);
|
||||
|
||||
Faction srcFaction = Faction.Find(source, true, true);
|
||||
Faction trgFaction = Faction.Find(target.Owner, true, true);
|
||||
List<Mobile> list = target.Aggressors;
|
||||
|
||||
if (sourceGuild != null && targetGuild != null)
|
||||
{
|
||||
if (sourceGuild == targetGuild || sourceGuild.IsAlly(targetGuild))
|
||||
return Notoriety.Ally;
|
||||
if (sourceGuild.IsEnemy(targetGuild))
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
|
||||
if (target.Owner is BaseCreature creature)
|
||||
{
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
return Notoriety.Enemy;
|
||||
|
||||
if (CheckHouseFlag(source, creature, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
int actual = Notoriety.CanBeAttacked;
|
||||
|
||||
if (target.Kills >= 5 || (body.IsMonster && IsSummoned(creature)) || creature.AlwaysMurderer ||
|
||||
creature.IsAnimatedDead)
|
||||
actual = Notoriety.Murderer;
|
||||
|
||||
if (DateTime.UtcNow >= target.TimeOfDeath + Corpse.MonsterLootRightSacrifice)
|
||||
return actual;
|
||||
|
||||
Party sourceParty = Party.Get(source);
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source || (sourceParty != null && Party.Get(list[i]) == sourceParty))
|
||||
return actual;
|
||||
|
||||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
if (target.Kills >= 5 || body.IsMonster)
|
||||
return Notoriety.Murderer;
|
||||
|
||||
if (target.Criminal && target.Map != null && (target.Map.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
return Notoriety.Criminal;
|
||||
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source || list[i] is BaseFactionGuard)
|
||||
return Notoriety.Enemy;
|
||||
|
||||
if (CheckHouseFlag(source, target.Owner, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (!(target.Owner is PlayerMobile))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
/* Must be thread-safe */
|
||||
public static int MobileNotoriety(Mobile source, Mobile target)
|
||||
{
|
||||
BaseCreature bcTarg = target as BaseCreature;
|
||||
|
||||
if (Core.AOS && (target.Blessed || bcTarg?.IsInvulnerable == true || target is PlayerVendor ||
|
||||
target is TownCrier))
|
||||
return Notoriety.Invulnerable;
|
||||
|
||||
PlayerMobile pmFrom = source as PlayerMobile;
|
||||
PlayerMobile pmTarg = target as PlayerMobile;
|
||||
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
if (pmFrom.DuelContext?.StartedBeginCountdown == true && !pmFrom.DuelContext.Finished && pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return pmFrom.DuelContext.IsAlly(pmFrom, pmTarg) ? Notoriety.Ally : Notoriety.Enemy;
|
||||
|
||||
if (target.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (source.Player && !target.Player && pmFrom != null && bcTarg != null)
|
||||
{
|
||||
Mobile master = bcTarg.GetMaster();
|
||||
|
||||
if (master?.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
master = bcTarg.ControlMaster;
|
||||
|
||||
if (Core.ML && master != null)
|
||||
public static void Initialize()
|
||||
{
|
||||
if ((source == master && CheckAggressor(bcTarg.Aggressors, source)) ||
|
||||
CheckAggressor(source.Aggressors, bcTarg))
|
||||
return Notoriety.CanBeAttacked;
|
||||
Notoriety.Hues[Notoriety.Innocent] = 0x59;
|
||||
Notoriety.Hues[Notoriety.Ally] = 0x3F;
|
||||
Notoriety.Hues[Notoriety.CanBeAttacked] = 0x3B2;
|
||||
Notoriety.Hues[Notoriety.Criminal] = 0x3B2;
|
||||
Notoriety.Hues[Notoriety.Enemy] = 0x90;
|
||||
Notoriety.Hues[Notoriety.Murderer] = 0x22;
|
||||
Notoriety.Hues[Notoriety.Invulnerable] = 0x35;
|
||||
|
||||
return MobileNotoriety(source, master);
|
||||
Notoriety.Handler = MobileNotoriety;
|
||||
|
||||
Mobile.AllowBeneficialHandler = Mobile_AllowBeneficial;
|
||||
Mobile.AllowHarmfulHandler = Mobile_AllowHarmful;
|
||||
}
|
||||
|
||||
if (!bcTarg.Summoned && !bcTarg.Controlled && pmFrom.EnemyOfOneType == bcTarg.GetType())
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
private static GuildStatus GetGuildStatus(Mobile m)
|
||||
{
|
||||
if (m.Guild == null)
|
||||
return GuildStatus.None;
|
||||
if (((Guild)m.Guild).Enemies.Count == 0 && m.Guild.Type == GuildType.Regular)
|
||||
return GuildStatus.Peaceful;
|
||||
|
||||
if (target.Kills >= 5 ||
|
||||
(target.Body.IsMonster && IsSummoned(bcTarg) && !(target is BaseFamiliar) && !(target is ArcaneFey) &&
|
||||
!(target is Golem)) || bcTarg?.AlwaysMurderer == true || bcTarg?.IsAnimatedDead == true)
|
||||
return Notoriety.Murderer;
|
||||
return GuildStatus.Waring;
|
||||
}
|
||||
|
||||
if (target.Criminal)
|
||||
return Notoriety.Criminal;
|
||||
private static bool CheckBeneficialStatus(GuildStatus from, GuildStatus target)
|
||||
{
|
||||
if (from == GuildStatus.Waring || target == GuildStatus.Waring)
|
||||
return false;
|
||||
|
||||
Guild sourceGuild = GetGuildFor(source.Guild as Guild, source);
|
||||
Guild targetGuild = GetGuildFor(target.Guild as Guild, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourceGuild != null && targetGuild != null)
|
||||
{
|
||||
if (sourceGuild == targetGuild || sourceGuild.IsAlly(targetGuild))
|
||||
return Notoriety.Ally;
|
||||
if (sourceGuild.IsEnemy(targetGuild))
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
/*private static bool CheckHarmfulStatus( GuildStatus from, GuildStatus target )
|
||||
{
|
||||
if (from == GuildStatus.Waring && target == GuildStatus.Waring)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
Faction srcFaction = Faction.Find(source, true, true);
|
||||
Faction trgFaction = Faction.Find(target, true, true);
|
||||
public static bool Mobile_AllowBeneficial(Mobile from, Mobile target)
|
||||
{
|
||||
if (from == null || target == null || from.AccessLevel > AccessLevel.Player ||
|
||||
target.AccessLevel > AccessLevel.Player)
|
||||
return true;
|
||||
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
return Notoriety.Enemy;
|
||||
var pmFrom = from as PlayerMobile;
|
||||
var pmTarg = target as PlayerMobile;
|
||||
|
||||
if (Stealing.ClassicMode && pmTarg?.PermaFlags.Contains(source) == true)
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom == null && from is BaseCreature bcFrom && bcFrom.Summoned)
|
||||
pmFrom = bcFrom.SummonMaster as PlayerMobile;
|
||||
|
||||
if (bcTarg?.AlwaysAttackable == true)
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmTarg == null && target is BaseCreature bcTarg && bcTarg.Summoned)
|
||||
pmTarg = bcTarg.SummonMaster as PlayerMobile;
|
||||
|
||||
if (CheckHouseFlag(source, target, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
{
|
||||
if (pmFrom.DuelContext != pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext?.Started == true || pmTarg.DuelContext?.Started == true))
|
||||
return false;
|
||||
|
||||
if (bcTarg?.InitialInnocent != true)
|
||||
if ((!target.Body.IsHuman && !target.Body.IsGhost && !IsPet(bcTarg) && pmTarg == null) ||
|
||||
(!Core.ML && !target.CanBeginAction<PolymorphSpell>()))
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext.StartedReadyCountdown && !pmFrom.DuelContext.Started || pmFrom.DuelContext.Tied ||
|
||||
pmFrom.DuelPlayer.Eliminated || pmTarg.DuelPlayer.Eliminated))
|
||||
return false;
|
||||
|
||||
if (CheckAggressor(source.Aggressors, target))
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom.DuelPlayer?.Eliminated == false && pmFrom.DuelContext?.IsSuddenDeath == true)
|
||||
return false;
|
||||
|
||||
if (CheckAggressed(source.Aggressed, target))
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
pmFrom.DuelContext.m_Tournament?.IsNotoRestricted == true &&
|
||||
pmFrom.DuelPlayer != null && pmTarg.DuelPlayer != null &&
|
||||
pmFrom.DuelPlayer.Participant != pmTarg.DuelPlayer.Participant)
|
||||
return false;
|
||||
|
||||
if (bcTarg?.Controlled == true && bcTarg.ControlOrder == OrderType.Guard &&
|
||||
bcTarg.ControlTarget == source)
|
||||
return Notoriety.CanBeAttacked;
|
||||
if (pmFrom.DuelContext?.Started == true && pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source is BaseCreature bc)
|
||||
{
|
||||
Mobile master = bc.GetMaster();
|
||||
if (pmFrom?.DuelContext?.Started == true || pmTarg?.DuelContext?.Started == true)
|
||||
return false;
|
||||
|
||||
if (master != null && (CheckAggressor(master.Aggressors, target) ||
|
||||
MobileNotoriety(master, target) == Notoriety.CanBeAttacked || bcTarg != null))
|
||||
return Notoriety.CanBeAttacked;
|
||||
}
|
||||
if (from.Region.IsPartOf<SafeZone>() || target.Region.IsPartOf<SafeZone>())
|
||||
return false;
|
||||
|
||||
return Notoriety.Innocent;
|
||||
var map = from.Map;
|
||||
|
||||
var targetFaction = Faction.Find(target, true);
|
||||
|
||||
if ((!Core.ML || map == Faction.Facet) && targetFaction != null)
|
||||
if (Faction.Find(from, true) != targetFaction)
|
||||
return false;
|
||||
|
||||
if ((map?.Rules & MapRules.BeneficialRestrictions) == 0)
|
||||
return true; // In felucca, anything goes
|
||||
|
||||
if (!from.Player)
|
||||
return true; // NPCs have no restrictions
|
||||
|
||||
if (target is BaseCreature creature && !creature.Controlled)
|
||||
return false; // Players cannot heal uncontrolled mobiles
|
||||
|
||||
if (pmFrom?.Young == true || pmTarg?.Young == true)
|
||||
return false; // Young players cannot perform beneficial actions towards older players
|
||||
|
||||
if (from.Guild is Guild fromGuild && target.Guild is Guild targetGuild &&
|
||||
(targetGuild == fromGuild || fromGuild.IsAlly(targetGuild)))
|
||||
return true; // Guild members can be beneficial
|
||||
|
||||
return CheckBeneficialStatus(GetGuildStatus(from), GetGuildStatus(target));
|
||||
}
|
||||
|
||||
public static bool Mobile_AllowHarmful(Mobile from, Mobile target)
|
||||
{
|
||||
if (from == null || target == null || from.AccessLevel > AccessLevel.Player ||
|
||||
target.AccessLevel > AccessLevel.Player)
|
||||
return true;
|
||||
|
||||
var pmFrom = from as PlayerMobile;
|
||||
var pmTarg = target as PlayerMobile;
|
||||
var bcTarg = target as BaseCreature;
|
||||
|
||||
if (pmFrom == null && from is BaseCreature bcFrom && bcFrom.Summoned)
|
||||
pmFrom = bcFrom.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmTarg == null && bcTarg?.Summoned == true)
|
||||
pmTarg = bcTarg.SummonMaster as PlayerMobile;
|
||||
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
{
|
||||
if (pmFrom.DuelContext != pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext?.Started == true || pmTarg.DuelContext?.Started == true))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
(pmFrom.DuelContext.StartedReadyCountdown && !pmFrom.DuelContext.Started || pmFrom.DuelContext.Tied ||
|
||||
pmFrom.DuelPlayer.Eliminated || pmTarg.DuelPlayer.Eliminated))
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext != null && pmFrom.DuelContext == pmTarg.DuelContext &&
|
||||
pmFrom.DuelContext.m_Tournament?.IsNotoRestricted == true &&
|
||||
pmFrom.DuelPlayer != null && pmTarg.DuelPlayer != null &&
|
||||
pmFrom.DuelPlayer.Participant == pmTarg.DuelPlayer.Participant)
|
||||
return false;
|
||||
|
||||
if (pmFrom.DuelContext?.Started == true && pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pmFrom?.DuelContext?.Started == true || pmTarg?.DuelContext?.Started == true)
|
||||
return false;
|
||||
|
||||
if (from.Region.IsPartOf<SafeZone>() || target.Region.IsPartOf<SafeZone>())
|
||||
return false;
|
||||
|
||||
var map = from.Map;
|
||||
|
||||
if ((map?.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
return true; // In felucca, anything goes
|
||||
|
||||
if (!from.Player && !(from is BaseCreature bc && bc.GetMaster() != null &&
|
||||
bc.GetMaster().AccessLevel == AccessLevel.Player))
|
||||
{
|
||||
if (!CheckAggressor(from.Aggressors, target) && !CheckAggressed(from.Aggressed, target) &&
|
||||
pmTarg?.CheckYoungProtection(from) == true)
|
||||
return false;
|
||||
|
||||
return true; // Uncontrolled NPCs are only restricted by the young system
|
||||
}
|
||||
|
||||
var fromGuild = GetGuildFor(from.Guild as Guild, from);
|
||||
var targetGuild = GetGuildFor(target.Guild as Guild, target);
|
||||
|
||||
if (fromGuild != null && targetGuild != null &&
|
||||
(fromGuild == targetGuild || fromGuild.IsAlly(targetGuild) || fromGuild.IsEnemy(targetGuild)))
|
||||
return true; // Guild allies or enemies can be harmful
|
||||
|
||||
if (bcTarg?.Controlled == true || bcTarg?.Summoned == true && bcTarg?.SummonMaster != @from)
|
||||
return false; // Cannot harm other controlled mobiles
|
||||
|
||||
if (target.Player)
|
||||
return false; // Cannot harm other players
|
||||
|
||||
return bcTarg?.InitialInnocent == true || Notoriety.Compute(from, target) != Notoriety.Innocent;
|
||||
}
|
||||
|
||||
public static Guild GetGuildFor(Guild def, Mobile m)
|
||||
{
|
||||
var g = def;
|
||||
|
||||
if (m is BaseCreature c && c.Controlled && c.ControlMaster != null)
|
||||
{
|
||||
c.DisplayGuildTitle = false;
|
||||
|
||||
if (c.Map != Map.Internal && (Core.AOS || Guild.NewGuildSystem || c.ControlOrder == OrderType.Attack ||
|
||||
c.ControlOrder == OrderType.Guard))
|
||||
g = (Guild)(c.Guild = c.ControlMaster.Guild);
|
||||
else if (c.Map == Map.Internal || c.ControlMaster.Guild == null)
|
||||
g = (Guild)(c.Guild = null);
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
public static int CorpseNotoriety(Mobile source, Corpse target)
|
||||
{
|
||||
if (target.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
Body body = target.Amount;
|
||||
|
||||
var sourceGuild = GetGuildFor(source.Guild as Guild, source);
|
||||
var targetGuild = GetGuildFor(target.Guild, target.Owner);
|
||||
|
||||
var srcFaction = Faction.Find(source, true, true);
|
||||
var trgFaction = Faction.Find(target.Owner, true, true);
|
||||
var list = target.Aggressors;
|
||||
|
||||
if (sourceGuild != null && targetGuild != null)
|
||||
{
|
||||
if (sourceGuild == targetGuild || sourceGuild.IsAlly(targetGuild))
|
||||
return Notoriety.Ally;
|
||||
if (sourceGuild.IsEnemy(targetGuild))
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
|
||||
if (target.Owner is BaseCreature creature)
|
||||
{
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
return Notoriety.Enemy;
|
||||
|
||||
if (CheckHouseFlag(source, creature, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
var actual = Notoriety.CanBeAttacked;
|
||||
|
||||
if (target.Kills >= 5 || body.IsMonster && IsSummoned(creature) || creature.AlwaysMurderer ||
|
||||
creature.IsAnimatedDead)
|
||||
actual = Notoriety.Murderer;
|
||||
|
||||
if (DateTime.UtcNow >= target.TimeOfDeath + Corpse.MonsterLootRightSacrifice)
|
||||
return actual;
|
||||
|
||||
var sourceParty = Party.Get(source);
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source || sourceParty != null && Party.Get(list[i]) == sourceParty)
|
||||
return actual;
|
||||
|
||||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
if (target.Kills >= 5 || body.IsMonster)
|
||||
return Notoriety.Murderer;
|
||||
|
||||
if (target.Criminal && target.Map != null && (target.Map.Rules & MapRules.HarmfulRestrictions) == 0)
|
||||
return Notoriety.Criminal;
|
||||
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source || list[i] is BaseFactionGuard)
|
||||
return Notoriety.Enemy;
|
||||
|
||||
if (CheckHouseFlag(source, target.Owner, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (!(target.Owner is PlayerMobile))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
if (list[i] == source)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
/* Must be thread-safe */
|
||||
public static int MobileNotoriety(Mobile source, Mobile target)
|
||||
{
|
||||
var bcTarg = target as BaseCreature;
|
||||
|
||||
if (Core.AOS && (target.Blessed || bcTarg?.IsInvulnerable == true || target is PlayerVendor ||
|
||||
target is TownCrier))
|
||||
return Notoriety.Invulnerable;
|
||||
|
||||
var pmFrom = source as PlayerMobile;
|
||||
var pmTarg = target as PlayerMobile;
|
||||
|
||||
if (pmFrom != null && pmTarg != null)
|
||||
if (pmFrom.DuelContext?.StartedBeginCountdown == true && !pmFrom.DuelContext.Finished &&
|
||||
pmFrom.DuelContext == pmTarg.DuelContext)
|
||||
return pmFrom.DuelContext.IsAlly(pmFrom, pmTarg) ? Notoriety.Ally : Notoriety.Enemy;
|
||||
|
||||
if (target.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (source.Player && !target.Player && pmFrom != null && bcTarg != null)
|
||||
{
|
||||
var master = bcTarg.GetMaster();
|
||||
|
||||
if (master?.AccessLevel > AccessLevel.Player)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
master = bcTarg.ControlMaster;
|
||||
|
||||
if (Core.ML && master != null)
|
||||
{
|
||||
if (source == master && CheckAggressor(bcTarg.Aggressors, source) ||
|
||||
CheckAggressor(source.Aggressors, bcTarg))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
return MobileNotoriety(source, master);
|
||||
}
|
||||
|
||||
if (!bcTarg.Summoned && !bcTarg.Controlled && pmFrom.EnemyOfOneType == bcTarg.GetType())
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
|
||||
if (target.Kills >= 5 ||
|
||||
target.Body.IsMonster && IsSummoned(bcTarg) && !(target is BaseFamiliar) && !(target is ArcaneFey) &&
|
||||
!(target is Golem) || bcTarg?.AlwaysMurderer == true || bcTarg?.IsAnimatedDead == true)
|
||||
return Notoriety.Murderer;
|
||||
|
||||
if (target.Criminal)
|
||||
return Notoriety.Criminal;
|
||||
|
||||
var sourceGuild = GetGuildFor(source.Guild as Guild, source);
|
||||
var targetGuild = GetGuildFor(target.Guild as Guild, target);
|
||||
|
||||
if (sourceGuild != null && targetGuild != null)
|
||||
{
|
||||
if (sourceGuild == targetGuild || sourceGuild.IsAlly(targetGuild))
|
||||
return Notoriety.Ally;
|
||||
if (sourceGuild.IsEnemy(targetGuild))
|
||||
return Notoriety.Enemy;
|
||||
}
|
||||
|
||||
var srcFaction = Faction.Find(source, true, true);
|
||||
var trgFaction = Faction.Find(target, true, true);
|
||||
|
||||
if (srcFaction != null && trgFaction != null && srcFaction != trgFaction && source.Map == Faction.Facet)
|
||||
return Notoriety.Enemy;
|
||||
|
||||
if (Stealing.ClassicMode && pmTarg?.PermaFlags.Contains(source) == true)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (bcTarg?.AlwaysAttackable == true)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (CheckHouseFlag(source, target, target.Location, target.Map))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (bcTarg?.InitialInnocent != true)
|
||||
if (!target.Body.IsHuman && !target.Body.IsGhost && !IsPet(bcTarg) && pmTarg == null ||
|
||||
!Core.ML && !target.CanBeginAction<PolymorphSpell>())
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (CheckAggressor(source.Aggressors, target))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (CheckAggressed(source.Aggressed, target))
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (bcTarg?.Controlled == true && bcTarg.ControlOrder == OrderType.Guard &&
|
||||
bcTarg.ControlTarget == source)
|
||||
return Notoriety.CanBeAttacked;
|
||||
|
||||
if (source is BaseCreature bc)
|
||||
{
|
||||
var master = bc.GetMaster();
|
||||
|
||||
if (master != null && (CheckAggressor(master.Aggressors, target) ||
|
||||
MobileNotoriety(master, target) == Notoriety.CanBeAttacked || bcTarg != null))
|
||||
return Notoriety.CanBeAttacked;
|
||||
}
|
||||
|
||||
return Notoriety.Innocent;
|
||||
}
|
||||
|
||||
public static bool CheckHouseFlag(Mobile from, Mobile m, Point3D p, Map map)
|
||||
{
|
||||
var house = BaseHouse.FindHouseAt(p, map, 16);
|
||||
|
||||
if (house?.Public != false || !house.IsFriend(from))
|
||||
return false;
|
||||
|
||||
if (m != null && house.IsFriend(m))
|
||||
return false;
|
||||
|
||||
return !(m is BaseCreature c) || c.Deleted || !c.Controlled || c.ControlMaster == null ||
|
||||
!house.IsFriend(c.ControlMaster);
|
||||
}
|
||||
|
||||
public static bool IsPet(BaseCreature c) => c?.Controlled == true;
|
||||
|
||||
public static bool IsSummoned(BaseCreature c) => c?.Summoned == true;
|
||||
|
||||
public static bool CheckAggressor(List<AggressorInfo> list, Mobile target)
|
||||
{
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
if (list[i].Attacker == target)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CheckAggressed(List<AggressorInfo> list, Mobile target)
|
||||
{
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var info = list[i];
|
||||
|
||||
if (!info.CriminalAggression && info.Defender == target)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum GuildStatus
|
||||
{
|
||||
None,
|
||||
Peaceful,
|
||||
Waring
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckHouseFlag(Mobile from, Mobile m, Point3D p, Map map)
|
||||
{
|
||||
BaseHouse house = BaseHouse.FindHouseAt(p, map, 16);
|
||||
|
||||
if (house?.Public != false || !house.IsFriend(from))
|
||||
return false;
|
||||
|
||||
if (m != null && house.IsFriend(m))
|
||||
return false;
|
||||
|
||||
return !(m is BaseCreature c) || c.Deleted || !c.Controlled || c.ControlMaster == null ||
|
||||
!house.IsFriend(c.ControlMaster);
|
||||
}
|
||||
|
||||
public static bool IsPet(BaseCreature c) => c?.Controlled == true;
|
||||
|
||||
public static bool IsSummoned(BaseCreature c) => c?.Summoned == true;
|
||||
|
||||
public static bool CheckAggressor(List<AggressorInfo> list, Mobile target)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
if (list[i].Attacker == target)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CheckAggressed(List<AggressorInfo> list, Mobile target)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
AggressorInfo info = list[i];
|
||||
|
||||
if (!info.CriminalAggression && info.Defender == target)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum GuildStatus
|
||||
{
|
||||
None,
|
||||
Peaceful,
|
||||
Waring
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class Paperdoll
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class Paperdoll
|
||||
{
|
||||
EventSink.PaperdollRequest += EventSink_PaperdollRequest;
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.PaperdollRequest += EventSink_PaperdollRequest;
|
||||
}
|
||||
|
||||
public static void EventSink_PaperdollRequest(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
beholder.Send(
|
||||
new DisplayPaperdoll(
|
||||
beheld.Serial,
|
||||
Titles.ComputeTitle(beholder, beheld),
|
||||
beheld.Warmode,
|
||||
beheld.AllowEquipFrom(beholder)
|
||||
)
|
||||
);
|
||||
|
||||
if (ObjectPropertyList.Enabled)
|
||||
{
|
||||
var items = beheld.Items;
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
beholder.Send(items[i].OPLPacket);
|
||||
|
||||
// NOTE: OSI sends MobileUpdate when opening your own paperdoll.
|
||||
// It has a very bad rubber-banding affect. What positive affects does it have?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_PaperdollRequest(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
beholder.Send(new DisplayPaperdoll(beheld.Serial, Titles.ComputeTitle(beholder, beheld),
|
||||
beheld.Warmode, beheld.AllowEquipFrom(beholder)));
|
||||
|
||||
if (ObjectPropertyList.Enabled)
|
||||
{
|
||||
List<Item> items = beheld.Items;
|
||||
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
beholder.Send(items[i].OPLPacket);
|
||||
|
||||
// NOTE: OSI sends MobileUpdate when opening your own paperdoll.
|
||||
// It has a very bad rubber-banding affect. What positive affects does it have?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,144 +8,155 @@ using Server.Spells.Ninjitsu;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class PoisonImpl : Poison
|
||||
{
|
||||
private readonly int m_Count;
|
||||
private readonly int m_MessageInterval;
|
||||
|
||||
// Timers
|
||||
private readonly TimeSpan m_Delay;
|
||||
private readonly TimeSpan m_Interval;
|
||||
|
||||
// Info
|
||||
|
||||
// Damage
|
||||
private readonly int m_Minimum;
|
||||
private readonly int m_Maximum;
|
||||
private readonly double m_Scalar;
|
||||
|
||||
public PoisonImpl(string name, int level, int min, int max, double percent, double delay, double interval, int count,
|
||||
int messageInterval)
|
||||
public class PoisonImpl : Poison
|
||||
{
|
||||
Name = name;
|
||||
Level = level;
|
||||
m_Minimum = min;
|
||||
m_Maximum = max;
|
||||
m_Scalar = percent * 0.01;
|
||||
m_Delay = TimeSpan.FromSeconds(delay);
|
||||
m_Interval = TimeSpan.FromSeconds(interval);
|
||||
m_Count = count;
|
||||
m_MessageInterval = messageInterval;
|
||||
}
|
||||
private readonly int m_Count;
|
||||
|
||||
public override string Name { get; }
|
||||
// Timers
|
||||
private readonly TimeSpan m_Delay;
|
||||
private readonly TimeSpan m_Interval;
|
||||
private readonly int m_Maximum;
|
||||
private readonly int m_MessageInterval;
|
||||
|
||||
public override int Level { get; }
|
||||
// Info
|
||||
|
||||
[CallPriority(10)]
|
||||
public static void Configure()
|
||||
{
|
||||
if (Core.AOS)
|
||||
{
|
||||
Register(new PoisonImpl("Lesser", 0, 4, 16, 7.5, 3.0, 2.25, 10, 4));
|
||||
Register(new PoisonImpl("Regular", 1, 8, 18, 10.0, 3.0, 3.25, 10, 3));
|
||||
Register(new PoisonImpl("Greater", 2, 12, 20, 15.0, 3.0, 4.25, 10, 2));
|
||||
Register(new PoisonImpl("Deadly", 3, 16, 30, 30.0, 3.0, 5.25, 15, 2));
|
||||
Register(new PoisonImpl("Lethal", 4, 20, 50, 35.0, 3.0, 5.25, 20, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
Register(new PoisonImpl("Lesser", 0, 4, 26, 2.500, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Regular", 1, 5, 26, 3.125, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Greater", 2, 6, 26, 6.250, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Deadly", 3, 7, 26, 12.500, 3.5, 4.0, 10, 2));
|
||||
Register(new PoisonImpl("Lethal", 4, 9, 26, 25.000, 3.5, 5.0, 10, 2));
|
||||
}
|
||||
}
|
||||
// Damage
|
||||
private readonly int m_Minimum;
|
||||
private readonly double m_Scalar;
|
||||
|
||||
public static Poison IncreaseLevel(Poison oldPoison)
|
||||
{
|
||||
Poison newPoison = oldPoison == null ? null : GetPoison(oldPoison.Level + 1);
|
||||
|
||||
return newPoison ?? oldPoison;
|
||||
}
|
||||
|
||||
public override Timer ConstructTimer(Mobile m) => new PoisonTimer(m, this);
|
||||
|
||||
public class PoisonTimer : Timer
|
||||
{
|
||||
private int m_Index;
|
||||
private int m_LastDamage;
|
||||
private readonly Mobile m_Mobile;
|
||||
private readonly PoisonImpl m_Poison;
|
||||
|
||||
public PoisonTimer(Mobile m, PoisonImpl p) : base(p.m_Delay, p.m_Interval)
|
||||
{
|
||||
From = m;
|
||||
m_Mobile = m;
|
||||
m_Poison = p;
|
||||
}
|
||||
|
||||
public Mobile From { get; set; }
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if ((Core.AOS && m_Poison.Level < 4 &&
|
||||
TransformationSpellHelper.UnderTransformation(m_Mobile, typeof(VampiricEmbraceSpell))) ||
|
||||
(m_Poison.Level < 3 && OrangePetals.UnderEffect(m_Mobile)) ||
|
||||
AnimalForm.UnderTransformation(m_Mobile, typeof(Unicorn)))
|
||||
if (m_Mobile.CurePoison(m_Mobile))
|
||||
{
|
||||
m_Mobile.LocalOverheadMessage(MessageType.Emote, 0x3F, true,
|
||||
"* You feel yourself resisting the effects of the poison *");
|
||||
|
||||
m_Mobile.NonlocalOverheadMessage(MessageType.Emote, 0x3F, true,
|
||||
$"* {m_Mobile.Name} seems resistant to the poison *");
|
||||
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Index++ == m_Poison.m_Count)
|
||||
public PoisonImpl(
|
||||
string name, int level, int min, int max, double percent, double delay, double interval, int count,
|
||||
int messageInterval
|
||||
)
|
||||
{
|
||||
m_Mobile.SendLocalizedMessage(502136); // The poison seems to have worn off.
|
||||
m_Mobile.Poison = null;
|
||||
|
||||
Stop();
|
||||
return;
|
||||
Name = name;
|
||||
Level = level;
|
||||
m_Minimum = min;
|
||||
m_Maximum = max;
|
||||
m_Scalar = percent * 0.01;
|
||||
m_Delay = TimeSpan.FromSeconds(delay);
|
||||
m_Interval = TimeSpan.FromSeconds(interval);
|
||||
m_Count = count;
|
||||
m_MessageInterval = messageInterval;
|
||||
}
|
||||
|
||||
int damage;
|
||||
public override string Name { get; }
|
||||
|
||||
if (!Core.AOS && m_LastDamage != 0 && Utility.RandomBool())
|
||||
public override int Level { get; }
|
||||
|
||||
[CallPriority(10)]
|
||||
public static void Configure()
|
||||
{
|
||||
damage = m_LastDamage;
|
||||
}
|
||||
else
|
||||
{
|
||||
damage = 1 + (int)(m_Mobile.Hits * m_Poison.m_Scalar);
|
||||
|
||||
if (damage < m_Poison.m_Minimum)
|
||||
damage = m_Poison.m_Minimum;
|
||||
else if (damage > m_Poison.m_Maximum)
|
||||
damage = m_Poison.m_Maximum;
|
||||
|
||||
m_LastDamage = damage;
|
||||
if (Core.AOS)
|
||||
{
|
||||
Register(new PoisonImpl("Lesser", 0, 4, 16, 7.5, 3.0, 2.25, 10, 4));
|
||||
Register(new PoisonImpl("Regular", 1, 8, 18, 10.0, 3.0, 3.25, 10, 3));
|
||||
Register(new PoisonImpl("Greater", 2, 12, 20, 15.0, 3.0, 4.25, 10, 2));
|
||||
Register(new PoisonImpl("Deadly", 3, 16, 30, 30.0, 3.0, 5.25, 15, 2));
|
||||
Register(new PoisonImpl("Lethal", 4, 20, 50, 35.0, 3.0, 5.25, 20, 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
Register(new PoisonImpl("Lesser", 0, 4, 26, 2.500, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Regular", 1, 5, 26, 3.125, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Greater", 2, 6, 26, 6.250, 3.5, 3.0, 10, 2));
|
||||
Register(new PoisonImpl("Deadly", 3, 7, 26, 12.500, 3.5, 4.0, 10, 2));
|
||||
Register(new PoisonImpl("Lethal", 4, 9, 26, 25.000, 3.5, 5.0, 10, 2));
|
||||
}
|
||||
}
|
||||
|
||||
From?.DoHarmful(m_Mobile, true);
|
||||
public static Poison IncreaseLevel(Poison oldPoison)
|
||||
{
|
||||
var newPoison = oldPoison == null ? null : GetPoison(oldPoison.Level + 1);
|
||||
|
||||
if (m_Mobile is IHonorTarget honorTarget)
|
||||
honorTarget.ReceivedHonorContext?.OnTargetPoisoned();
|
||||
return newPoison ?? oldPoison;
|
||||
}
|
||||
|
||||
AOS.Damage(m_Mobile, From, damage, 0, 0, 0, 100, 0);
|
||||
public override Timer ConstructTimer(Mobile m) => new PoisonTimer(m, this);
|
||||
|
||||
if (Utility.RandomDouble() >= 0.60) // OSI: randomly revealed between first and third damage tick, guessing 60% chance
|
||||
m_Mobile.RevealingAction();
|
||||
public class PoisonTimer : Timer
|
||||
{
|
||||
private readonly Mobile m_Mobile;
|
||||
private readonly PoisonImpl m_Poison;
|
||||
private int m_Index;
|
||||
private int m_LastDamage;
|
||||
|
||||
if (m_Index % m_Poison.m_MessageInterval == 0)
|
||||
m_Mobile.OnPoisoned(From, m_Poison, m_Poison);
|
||||
}
|
||||
public PoisonTimer(Mobile m, PoisonImpl p) : base(p.m_Delay, p.m_Interval)
|
||||
{
|
||||
From = m;
|
||||
m_Mobile = m;
|
||||
m_Poison = p;
|
||||
}
|
||||
|
||||
public Mobile From { get; set; }
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (Core.AOS && m_Poison.Level < 4 &&
|
||||
TransformationSpellHelper.UnderTransformation(m_Mobile, typeof(VampiricEmbraceSpell)) ||
|
||||
m_Poison.Level < 3 && OrangePetals.UnderEffect(m_Mobile) ||
|
||||
AnimalForm.UnderTransformation(m_Mobile, typeof(Unicorn)))
|
||||
if (m_Mobile.CurePoison(m_Mobile))
|
||||
{
|
||||
m_Mobile.LocalOverheadMessage(
|
||||
MessageType.Emote,
|
||||
0x3F,
|
||||
true,
|
||||
"* You feel yourself resisting the effects of the poison *"
|
||||
);
|
||||
|
||||
m_Mobile.NonlocalOverheadMessage(
|
||||
MessageType.Emote,
|
||||
0x3F,
|
||||
true,
|
||||
$"* {m_Mobile.Name} seems resistant to the poison *"
|
||||
);
|
||||
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Index++ == m_Poison.m_Count)
|
||||
{
|
||||
m_Mobile.SendLocalizedMessage(502136); // The poison seems to have worn off.
|
||||
m_Mobile.Poison = null;
|
||||
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
int damage;
|
||||
|
||||
if (!Core.AOS && m_LastDamage != 0 && Utility.RandomBool())
|
||||
{
|
||||
damage = m_LastDamage;
|
||||
}
|
||||
else
|
||||
{
|
||||
damage = 1 + (int)(m_Mobile.Hits * m_Poison.m_Scalar);
|
||||
|
||||
if (damage < m_Poison.m_Minimum)
|
||||
damage = m_Poison.m_Minimum;
|
||||
else if (damage > m_Poison.m_Maximum)
|
||||
damage = m_Poison.m_Maximum;
|
||||
|
||||
m_LastDamage = damage;
|
||||
}
|
||||
|
||||
From?.DoHarmful(m_Mobile, true);
|
||||
|
||||
if (m_Mobile is IHonorTarget honorTarget)
|
||||
honorTarget.ReceivedHonorContext?.OnTargetPoisoned();
|
||||
|
||||
AOS.Damage(m_Mobile, From, damage, 0, 0, 0, 100, 0);
|
||||
|
||||
if (Utility.RandomDouble() >= 0.60
|
||||
) // OSI: randomly revealed between first and third damage tick, guessing 60% chance
|
||||
m_Mobile.RevealingAction();
|
||||
|
||||
if (m_Index % m_Poison.m_MessageInterval == 0)
|
||||
m_Mobile.OnPoisoned(From, m_Poison, m_Poison);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,122 +1,132 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public enum ProfanityAction
|
||||
{
|
||||
None, // no action taken
|
||||
Disallow, // speech is not displayed
|
||||
Criminal, // makes the player criminal, not killable by guards
|
||||
CriminalAction, // makes the player criminal, can be killed by guards
|
||||
Disconnect, // player is kicked
|
||||
Other // some other implementation
|
||||
}
|
||||
|
||||
public static class ProfanityProtection
|
||||
{
|
||||
// TODO: Move this to configuration
|
||||
private static readonly bool Enabled = false;
|
||||
|
||||
private static readonly ProfanityAction
|
||||
Action = ProfanityAction.Disallow; // change here what to do when profanity is detected
|
||||
|
||||
public static char[] Exceptions { get; } =
|
||||
public enum ProfanityAction
|
||||
{
|
||||
' ', '-', '.', '\'', '"', ',', '_', '+', '=', '~', '`', '!', '^', '*', '\\', '/', ';', ':', '<', '>', '[', ']',
|
||||
'{', '}', '?', '|', '(', ')', '%', '$', '&', '#', '@'
|
||||
};
|
||||
|
||||
public static string[] StartDisallowed { get; } = { };
|
||||
|
||||
public static string[] Disallowed { get; } =
|
||||
{
|
||||
"jigaboo",
|
||||
"chigaboo",
|
||||
"wop",
|
||||
"kyke",
|
||||
"kike",
|
||||
"tit",
|
||||
"spic",
|
||||
"prick",
|
||||
"piss",
|
||||
"lezbo",
|
||||
"lesbo",
|
||||
"felatio",
|
||||
"dyke",
|
||||
"dildo",
|
||||
"chinc",
|
||||
"chink",
|
||||
"cunnilingus",
|
||||
"cum",
|
||||
"cocksucker",
|
||||
"cock",
|
||||
"clitoris",
|
||||
"clit",
|
||||
"ass",
|
||||
"hitler",
|
||||
"penis",
|
||||
"nigga",
|
||||
"nigger",
|
||||
"klit",
|
||||
"kunt",
|
||||
"jiz",
|
||||
"jism",
|
||||
"jerkoff",
|
||||
"jackoff",
|
||||
"goddamn",
|
||||
"fag",
|
||||
"blowjob",
|
||||
"bitch",
|
||||
"asshole",
|
||||
"dick",
|
||||
"pussy",
|
||||
"snatch",
|
||||
"cunt",
|
||||
"twat",
|
||||
"shit",
|
||||
"fuck"
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Enabled)
|
||||
EventSink.Speech += EventSink_Speech;
|
||||
None, // no action taken
|
||||
Disallow, // speech is not displayed
|
||||
Criminal, // makes the player criminal, not killable by guards
|
||||
CriminalAction, // makes the player criminal, can be killed by guards
|
||||
Disconnect, // player is kicked
|
||||
Other // some other implementation
|
||||
}
|
||||
|
||||
private static bool OnProfanityDetected(Mobile from, string speech)
|
||||
public static class ProfanityProtection
|
||||
{
|
||||
switch (Action)
|
||||
{
|
||||
case ProfanityAction.None: return true;
|
||||
case ProfanityAction.Disallow: return false;
|
||||
case ProfanityAction.Criminal:
|
||||
from.Criminal = true;
|
||||
return true;
|
||||
case ProfanityAction.CriminalAction:
|
||||
from.CriminalAction(false);
|
||||
return true;
|
||||
case ProfanityAction.Disconnect:
|
||||
{
|
||||
from.NetState?.Dispose();
|
||||
// TODO: Move this to configuration
|
||||
private static readonly bool Enabled = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
case ProfanityAction.Other: // TODO: Provide custom implementation if this is chosen
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private static readonly ProfanityAction
|
||||
Action = ProfanityAction.Disallow; // change here what to do when profanity is detected
|
||||
|
||||
public static char[] Exceptions { get; } =
|
||||
{
|
||||
' ', '-', '.', '\'', '"', ',', '_', '+', '=', '~', '`', '!', '^', '*', '\\', '/', ';', ':', '<', '>', '[', ']',
|
||||
'{', '}', '?', '|', '(', ')', '%', '$', '&', '#', '@'
|
||||
};
|
||||
|
||||
public static string[] StartDisallowed { get; } = { };
|
||||
|
||||
public static string[] Disallowed { get; } =
|
||||
{
|
||||
"jigaboo",
|
||||
"chigaboo",
|
||||
"wop",
|
||||
"kyke",
|
||||
"kike",
|
||||
"tit",
|
||||
"spic",
|
||||
"prick",
|
||||
"piss",
|
||||
"lezbo",
|
||||
"lesbo",
|
||||
"felatio",
|
||||
"dyke",
|
||||
"dildo",
|
||||
"chinc",
|
||||
"chink",
|
||||
"cunnilingus",
|
||||
"cum",
|
||||
"cocksucker",
|
||||
"cock",
|
||||
"clitoris",
|
||||
"clit",
|
||||
"ass",
|
||||
"hitler",
|
||||
"penis",
|
||||
"nigga",
|
||||
"nigger",
|
||||
"klit",
|
||||
"kunt",
|
||||
"jiz",
|
||||
"jism",
|
||||
"jerkoff",
|
||||
"jackoff",
|
||||
"goddamn",
|
||||
"fag",
|
||||
"blowjob",
|
||||
"bitch",
|
||||
"asshole",
|
||||
"dick",
|
||||
"pussy",
|
||||
"snatch",
|
||||
"cunt",
|
||||
"twat",
|
||||
"shit",
|
||||
"fuck"
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Enabled)
|
||||
EventSink.Speech += EventSink_Speech;
|
||||
}
|
||||
|
||||
private static bool OnProfanityDetected(Mobile from, string speech)
|
||||
{
|
||||
switch (Action)
|
||||
{
|
||||
case ProfanityAction.None: return true;
|
||||
case ProfanityAction.Disallow: return false;
|
||||
case ProfanityAction.Criminal:
|
||||
from.Criminal = true;
|
||||
return true;
|
||||
case ProfanityAction.CriminalAction:
|
||||
from.CriminalAction(false);
|
||||
return true;
|
||||
case ProfanityAction.Disconnect:
|
||||
{
|
||||
from.NetState?.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
case ProfanityAction.Other: // TODO: Provide custom implementation if this is chosen
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EventSink_Speech(SpeechEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
if (from.AccessLevel > AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (!NameVerification.Validate(
|
||||
e.Speech,
|
||||
0,
|
||||
int.MaxValue,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
int.MaxValue,
|
||||
Exceptions,
|
||||
Disallowed,
|
||||
StartDisallowed
|
||||
))
|
||||
e.Blocked = !OnProfanityDetected(from, e.Speech);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EventSink_Speech(SpeechEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (from.AccessLevel > AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (!NameVerification.Validate(e.Speech, 0, int.MaxValue, true, true, false, int.MaxValue, Exceptions,
|
||||
Disallowed, StartDisallowed))
|
||||
e.Blocked = !OnProfanityDetected(from, e.Speech);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,83 +4,83 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class Profile
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class Profile
|
||||
{
|
||||
EventSink.ProfileRequest += EventSink_ProfileRequest;
|
||||
EventSink.ChangeProfileRequest += EventSink_ChangeProfileRequest;
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.ProfileRequest += EventSink_ProfileRequest;
|
||||
EventSink.ChangeProfileRequest += EventSink_ChangeProfileRequest;
|
||||
}
|
||||
|
||||
public static void EventSink_ChangeProfileRequest(Mobile beholder, Mobile beheld, string text)
|
||||
{
|
||||
if (beholder.ProfileLocked)
|
||||
beholder.SendMessage("Your profile is locked. You may not change it.");
|
||||
else
|
||||
beholder.Profile = text;
|
||||
}
|
||||
|
||||
public static void EventSink_ProfileRequest(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
if (!beheld.Player)
|
||||
return;
|
||||
|
||||
if (beholder.Map != beheld.Map || !beholder.InRange(beheld, 12) || !beholder.CanSee(beheld))
|
||||
return;
|
||||
|
||||
var header = Titles.ComputeTitle(beholder, beheld);
|
||||
|
||||
var footer = "";
|
||||
|
||||
if (beheld.ProfileLocked)
|
||||
{
|
||||
if (beholder == beheld)
|
||||
footer = "Your profile has been locked.";
|
||||
else if (beholder.AccessLevel >= AccessLevel.Counselor)
|
||||
footer = "This profile has been locked.";
|
||||
}
|
||||
|
||||
if (footer.Length == 0 && beholder == beheld)
|
||||
footer = GetAccountDuration(beheld);
|
||||
|
||||
var body = beheld.Profile ?? "";
|
||||
var serial = beholder != beheld || !beheld.ProfileLocked ? beheld.Serial : Serial.Zero;
|
||||
|
||||
beholder.Send(new DisplayProfile(serial, header, body, footer));
|
||||
}
|
||||
|
||||
private static string GetAccountDuration(Mobile m)
|
||||
{
|
||||
if (!(m.Account is Account a))
|
||||
return "";
|
||||
|
||||
var ts = DateTime.UtcNow - a.Created;
|
||||
|
||||
if (Format(ts.TotalDays, "This account is {0} day{1} old.", out var v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalHours, "This account is {0} hour{1} old.", out v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalMinutes, "This account is {0} minute{1} old.", out v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalSeconds, "This account is {0} second{1} old.", out v))
|
||||
return v;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static bool Format(double value, string format, out string op)
|
||||
{
|
||||
if (value >= 1.0)
|
||||
{
|
||||
op = string.Format(format, (int)value, (int)value != 1 ? "s" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
op = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_ChangeProfileRequest(Mobile beholder, Mobile beheld, string text)
|
||||
{
|
||||
if (beholder.ProfileLocked)
|
||||
beholder.SendMessage("Your profile is locked. You may not change it.");
|
||||
else
|
||||
beholder.Profile = text;
|
||||
}
|
||||
|
||||
public static void EventSink_ProfileRequest(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
if (!beheld.Player)
|
||||
return;
|
||||
|
||||
if (beholder.Map != beheld.Map || !beholder.InRange(beheld, 12) || !beholder.CanSee(beheld))
|
||||
return;
|
||||
|
||||
string header = Titles.ComputeTitle(beholder, beheld);
|
||||
|
||||
string footer = "";
|
||||
|
||||
if (beheld.ProfileLocked)
|
||||
{
|
||||
if (beholder == beheld)
|
||||
footer = "Your profile has been locked.";
|
||||
else if (beholder.AccessLevel >= AccessLevel.Counselor)
|
||||
footer = "This profile has been locked.";
|
||||
}
|
||||
|
||||
if (footer.Length == 0 && beholder == beheld)
|
||||
footer = GetAccountDuration(beheld);
|
||||
|
||||
string body = beheld.Profile ?? "";
|
||||
Serial serial = beholder != beheld || !beheld.ProfileLocked ? beheld.Serial : Serial.Zero;
|
||||
|
||||
beholder.Send(new DisplayProfile(serial, header, body, footer));
|
||||
}
|
||||
|
||||
private static string GetAccountDuration(Mobile m)
|
||||
{
|
||||
if (!(m.Account is Account a))
|
||||
return "";
|
||||
|
||||
TimeSpan ts = DateTime.UtcNow - a.Created;
|
||||
|
||||
if (Format(ts.TotalDays, "This account is {0} day{1} old.", out string v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalHours, "This account is {0} hour{1} old.", out v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalMinutes, "This account is {0} minute{1} old.", out v))
|
||||
return v;
|
||||
|
||||
if (Format(ts.TotalSeconds, "This account is {0} second{1} old.", out v))
|
||||
return v;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static bool Format(double value, string format, out string op)
|
||||
{
|
||||
if (value >= 1.0)
|
||||
{
|
||||
op = string.Format(format, (int)value, (int)value != 1 ? "s" : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
op = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,62 +3,64 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class ProtocolExtensions
|
||||
{
|
||||
private static readonly PacketHandler[] m_Handlers = new PacketHandler[0x100];
|
||||
|
||||
public static void Initialize()
|
||||
public class ProtocolExtensions
|
||||
{
|
||||
PacketHandlers.Register(0xF0, 0, false, DecodeBundledPacket);
|
||||
}
|
||||
private static readonly PacketHandler[] m_Handlers = new PacketHandler[0x100];
|
||||
|
||||
public static void Register(int packetID, bool ingame, OnPacketReceive onReceive)
|
||||
{
|
||||
m_Handlers[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
|
||||
}
|
||||
|
||||
public static PacketHandler GetHandler(int packetID)
|
||||
{
|
||||
if (packetID >= 0 && packetID < m_Handlers.Length)
|
||||
return m_Handlers[packetID];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DecodeBundledPacket(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
int packetID = pvSrc.ReadByte();
|
||||
|
||||
PacketHandler ph = GetHandler(packetID);
|
||||
|
||||
if (ph != null)
|
||||
{
|
||||
if (ph.Ingame && state.Mobile == null)
|
||||
public static void Initialize()
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Client: {0}: Sent ingame packet (0xF0x{1:X2}) before having been attached to a mobile", state,
|
||||
packetID);
|
||||
state.Dispose();
|
||||
PacketHandlers.Register(0xF0, 0, false, DecodeBundledPacket);
|
||||
}
|
||||
else if (ph.Ingame && state.Mobile.Deleted)
|
||||
{
|
||||
state.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
ph.OnReceive(state, pvSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ProtocolExtension : Packet
|
||||
{
|
||||
public ProtocolExtension(int packetID, int capacity) : base(0xF0)
|
||||
public static void Register(int packetID, bool ingame, OnPacketReceive onReceive)
|
||||
{
|
||||
m_Handlers[packetID] = new PacketHandler(packetID, 0, ingame, onReceive);
|
||||
}
|
||||
|
||||
public static PacketHandler GetHandler(int packetID)
|
||||
{
|
||||
if (packetID >= 0 && packetID < m_Handlers.Length)
|
||||
return m_Handlers[packetID];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DecodeBundledPacket(NetState state, PacketReader pvSrc)
|
||||
{
|
||||
int packetID = pvSrc.ReadByte();
|
||||
|
||||
var ph = GetHandler(packetID);
|
||||
|
||||
if (ph != null)
|
||||
{
|
||||
if (ph.Ingame && state.Mobile == null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"Client: {0}: Sent ingame packet (0xF0x{1:X2}) before having been attached to a mobile",
|
||||
state,
|
||||
packetID
|
||||
);
|
||||
state.Dispose();
|
||||
}
|
||||
else if (ph.Ingame && state.Mobile.Deleted)
|
||||
{
|
||||
state.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
ph.OnReceive(state, pvSrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ProtocolExtension : Packet
|
||||
{
|
||||
EnsureCapacity(4 + capacity);
|
||||
public ProtocolExtension(int packetID, int capacity) : base(0xF0)
|
||||
{
|
||||
EnsureCapacity(4 + capacity);
|
||||
|
||||
Stream.Write((byte)packetID);
|
||||
Stream.Write((byte)packetID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,275 +1,277 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public class RaceDefinitions
|
||||
{
|
||||
public static void Configure()
|
||||
public class RaceDefinitions
|
||||
{
|
||||
/* Here we configure all races. Some notes:
|
||||
*
|
||||
* 1) The first 32 races are reserved for core use.
|
||||
* 2) Race 0x7F is reserved for core use.
|
||||
* 3) Race 0xFF is reserved for core use.
|
||||
* 4) Changing or removing any predefined races may cause server instability.
|
||||
*/
|
||||
|
||||
RegisterRace(new Human(0, 0));
|
||||
RegisterRace(new Elf(1, 1));
|
||||
RegisterRace(new Gargoyle(2, 2));
|
||||
}
|
||||
|
||||
public static void RegisterRace(Race race)
|
||||
{
|
||||
Race.Races[race.RaceIndex] = race;
|
||||
Race.AllRaces.Add(race);
|
||||
}
|
||||
|
||||
private class Human : Race
|
||||
{
|
||||
public Human(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Human", "Humans", 400, 401, 402, 403, Expansion.None)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
|
||||
if ((female && itemID == 0x2048) || (!female && itemID == 0x2046))
|
||||
return false; // Buns & Receding Hair
|
||||
|
||||
if (itemID >= 0x203B && itemID <= 0x203D)
|
||||
return true;
|
||||
|
||||
if (itemID >= 0x2044 && itemID <= 0x204A)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int RandomHair(bool female) // Random hair doesn't include baldness
|
||||
{
|
||||
return Utility.Random(9) switch
|
||||
public static void Configure()
|
||||
{
|
||||
0 => 0x203B, // Short
|
||||
1 => 0x203C, // Long
|
||||
2 => 0x203D, // Pony Tail
|
||||
3 => 0x2044, // Mohawk
|
||||
4 => 0x2045, // Pageboy
|
||||
5 => 0x2047, // Afro
|
||||
6 => 0x2049, // Pig tails
|
||||
7 => 0x204A, // Krisna
|
||||
_ => female ? 0x2046 : 0x2048
|
||||
};
|
||||
}
|
||||
/* Here we configure all races. Some notes:
|
||||
*
|
||||
* 1) The first 32 races are reserved for core use.
|
||||
* 2) Race 0x7F is reserved for core use.
|
||||
* 3) Race 0xFF is reserved for core use.
|
||||
* 4) Changing or removing any predefined races may cause server instability.
|
||||
*/
|
||||
|
||||
public override bool ValidateFacialHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
RegisterRace(new Human(0, 0));
|
||||
RegisterRace(new Elf(1, 1));
|
||||
RegisterRace(new Gargoyle(2, 2));
|
||||
}
|
||||
|
||||
if (female)
|
||||
return false;
|
||||
|
||||
if (itemID >= 0x203E && itemID <= 0x2041)
|
||||
return true;
|
||||
|
||||
if (itemID >= 0x204B && itemID <= 0x204D)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int RandomFacialHair(bool female)
|
||||
{
|
||||
if (female)
|
||||
return 0;
|
||||
|
||||
int rand = Utility.Random(7);
|
||||
|
||||
return (rand < 4 ? 0x203E : 0x2047) + rand;
|
||||
}
|
||||
|
||||
public override int ClipSkinHue(int hue)
|
||||
{
|
||||
if (hue < 1002)
|
||||
return 1002;
|
||||
if (hue > 1058)
|
||||
return 1058;
|
||||
return hue;
|
||||
}
|
||||
|
||||
public override int RandomSkinHue() => Utility.Random(1002, 57) | 0x8000;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
if (hue < 1102)
|
||||
return 1102;
|
||||
if (hue > 1149)
|
||||
return 1149;
|
||||
return hue;
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => Utility.Random(1102, 48);
|
||||
}
|
||||
|
||||
private class Elf : Race
|
||||
{
|
||||
private static readonly int[] m_SkinHues =
|
||||
{
|
||||
0x0BF, 0x24D, 0x24E, 0x24F, 0x353, 0x361, 0x367, 0x374,
|
||||
0x375, 0x376, 0x381, 0x382, 0x383, 0x384, 0x385, 0x389,
|
||||
0x3DE, 0x3E5, 0x3E6, 0x3E8, 0x3E9, 0x430, 0x4A7, 0x4DE,
|
||||
0x51D, 0x53F, 0x579, 0x76B, 0x76C, 0x76D, 0x835, 0x903
|
||||
};
|
||||
|
||||
private static readonly int[] m_HairHues =
|
||||
{
|
||||
0x034, 0x035, 0x036, 0x037, 0x038, 0x039, 0x058, 0x08E,
|
||||
0x08F, 0x090, 0x091, 0x092, 0x101, 0x159, 0x15A, 0x15B,
|
||||
0x15C, 0x15D, 0x15E, 0x128, 0x12F, 0x1BD, 0x1E4, 0x1F3,
|
||||
0x207, 0x211, 0x239, 0x251, 0x26C, 0x2C3, 0x2C9, 0x31D,
|
||||
0x31E, 0x31F, 0x320, 0x321, 0x322, 0x323, 0x324, 0x325,
|
||||
0x326, 0x369, 0x386, 0x387, 0x388, 0x389, 0x38A, 0x59D,
|
||||
0x6B8, 0x725, 0x853
|
||||
};
|
||||
|
||||
public Elf(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Elf", "Elves", 605, 606, 607, 608, Expansion.ML)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
|
||||
if ((female && (itemID == 0x2FCD || itemID == 0x2FBF)) || (!female && (itemID == 0x2FCC || itemID == 0x2FD0)))
|
||||
return false;
|
||||
|
||||
if (itemID >= 0x2FBF && itemID <= 0x2FC2)
|
||||
return true;
|
||||
|
||||
if (itemID >= 0x2FCC && itemID <= 0x2FD1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int RandomHair(bool female) // Random hair doesn't include baldness
|
||||
{
|
||||
return Utility.Random(8) switch
|
||||
public static void RegisterRace(Race race)
|
||||
{
|
||||
0 => 0x2FC0, // Long Feather
|
||||
1 => 0x2FC1, // Short
|
||||
2 => 0x2FC2, // Mullet
|
||||
3 => 0x2FCE, // Knob
|
||||
4 => 0x2FCF, // Braided
|
||||
5 => 0x2FD1, // Spiked
|
||||
6 => female ? 0x2FCC : 0x2FBF, // Flower or Mid-long
|
||||
_ => female ? 0x2FD0 : 0x2FCD
|
||||
};
|
||||
}
|
||||
Race.Races[race.RaceIndex] = race;
|
||||
Race.AllRaces.Add(race);
|
||||
}
|
||||
|
||||
public override bool ValidateFacialHair(bool female, int itemID) => itemID == 0;
|
||||
|
||||
public override int RandomFacialHair(bool female) => 0;
|
||||
|
||||
public override int ClipSkinHue(int hue)
|
||||
{
|
||||
for (int i = 0; i < m_SkinHues.Length; i++)
|
||||
if (m_SkinHues[i] == hue)
|
||||
return hue;
|
||||
|
||||
return m_SkinHues[0];
|
||||
}
|
||||
|
||||
public override int RandomSkinHue() => m_SkinHues.RandomElement() | 0x8000;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
for (int i = 0; i < m_HairHues.Length; i++)
|
||||
if (m_HairHues[i] == hue)
|
||||
return hue;
|
||||
|
||||
return m_HairHues[0];
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => m_HairHues.RandomElement();
|
||||
}
|
||||
|
||||
private class Gargoyle : Race
|
||||
{
|
||||
// Todo Finish body hues
|
||||
private static readonly int[] m_BodyHues =
|
||||
{
|
||||
0x86DB, 0x86DC, 0x86DD, 0x86DE,
|
||||
0x86DF, 0x86E0, 0x86E1, 0x86E2,
|
||||
0x86E3, 0x86E4, 0x86E5, 0x86E6
|
||||
// 0x, 0x, 0x, 0x, // 86E7/86E8/86E9/86EA?
|
||||
// 0x, 0x, 0x, 0x, // 86EB/86EC/86ED/86EE?
|
||||
// 0x86F3, 0x86DB, 0x86DC, 0x86DD
|
||||
};
|
||||
|
||||
private static readonly int[] m_HornHues =
|
||||
{
|
||||
0x709, 0x70B, 0x70D, 0x70F, 0x711, 0x763,
|
||||
0x765, 0x768, 0x76B, 0x6F3, 0x6F1, 0x6EF,
|
||||
0x6E4, 0x6E2, 0x6E0, 0x709, 0x70B, 0x70D
|
||||
};
|
||||
|
||||
public Gargoyle(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Gargoyle", "Gargoyles", 666, 667, 402, 403, Expansion.SA)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (female == false) return itemID >= 0x4258 && itemID <= 0x425F;
|
||||
|
||||
return itemID == 0x4261 || itemID == 0x4262 || (itemID >= 0x4273 && itemID <= 0x4275) || itemID == 0x42B0 ||
|
||||
itemID == 0x42B1 || itemID == 0x42AA || itemID == 0x42AB;
|
||||
}
|
||||
|
||||
public override int RandomHair(bool female)
|
||||
{
|
||||
if (Utility.Random(9) == 0)
|
||||
return 0;
|
||||
if (!female)
|
||||
return 0x4258 + Utility.Random(8);
|
||||
return Utility.Random(9) switch
|
||||
private class Human : Race
|
||||
{
|
||||
0 => 0x4261,
|
||||
1 => 0x4262,
|
||||
2 => 0x4273,
|
||||
3 => 0x4274,
|
||||
4 => 0x4275,
|
||||
5 => 0x42B0,
|
||||
6 => 0x42B1,
|
||||
7 => 0x42AA,
|
||||
8 => 0x42AB,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
public Human(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Human", "Humans", 400, 401, 402, 403, Expansion.None)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateFacialHair(bool female, int itemID) => !female && itemID >= 0x42AD && itemID <= 0x42B0;
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
|
||||
public override int RandomFacialHair(bool female) => female ? 0 : Utility.RandomList(0, 0x42AD, 0x42AE, 0x42AF, 0x42B0);
|
||||
if (female && itemID == 0x2048 || !female && itemID == 0x2046)
|
||||
return false; // Buns & Receding Hair
|
||||
|
||||
public override int ClipSkinHue(int hue) => hue;
|
||||
if (itemID >= 0x203B && itemID <= 0x203D)
|
||||
return true;
|
||||
|
||||
public override int RandomSkinHue() => m_BodyHues.RandomElement() | 0x8000;
|
||||
if (itemID >= 0x2044 && itemID <= 0x204A)
|
||||
return true;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
for (int i = 0; i < m_HornHues.Length; i++)
|
||||
if (m_HornHues[i] == hue)
|
||||
return hue;
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_HornHues[0];
|
||||
}
|
||||
public override int RandomHair(bool female) // Random hair doesn't include baldness
|
||||
{
|
||||
return Utility.Random(9) switch
|
||||
{
|
||||
0 => 0x203B, // Short
|
||||
1 => 0x203C, // Long
|
||||
2 => 0x203D, // Pony Tail
|
||||
3 => 0x2044, // Mohawk
|
||||
4 => 0x2045, // Pageboy
|
||||
5 => 0x2047, // Afro
|
||||
6 => 0x2049, // Pig tails
|
||||
7 => 0x204A, // Krisna
|
||||
_ => female ? 0x2046 : 0x2048
|
||||
};
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => m_HornHues.RandomElement();
|
||||
public override bool ValidateFacialHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
|
||||
if (female)
|
||||
return false;
|
||||
|
||||
if (itemID >= 0x203E && itemID <= 0x2041)
|
||||
return true;
|
||||
|
||||
if (itemID >= 0x204B && itemID <= 0x204D)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int RandomFacialHair(bool female)
|
||||
{
|
||||
if (female)
|
||||
return 0;
|
||||
|
||||
var rand = Utility.Random(7);
|
||||
|
||||
return (rand < 4 ? 0x203E : 0x2047) + rand;
|
||||
}
|
||||
|
||||
public override int ClipSkinHue(int hue)
|
||||
{
|
||||
if (hue < 1002)
|
||||
return 1002;
|
||||
if (hue > 1058)
|
||||
return 1058;
|
||||
return hue;
|
||||
}
|
||||
|
||||
public override int RandomSkinHue() => Utility.Random(1002, 57) | 0x8000;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
if (hue < 1102)
|
||||
return 1102;
|
||||
if (hue > 1149)
|
||||
return 1149;
|
||||
return hue;
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => Utility.Random(1102, 48);
|
||||
}
|
||||
|
||||
private class Elf : Race
|
||||
{
|
||||
private static readonly int[] m_SkinHues =
|
||||
{
|
||||
0x0BF, 0x24D, 0x24E, 0x24F, 0x353, 0x361, 0x367, 0x374,
|
||||
0x375, 0x376, 0x381, 0x382, 0x383, 0x384, 0x385, 0x389,
|
||||
0x3DE, 0x3E5, 0x3E6, 0x3E8, 0x3E9, 0x430, 0x4A7, 0x4DE,
|
||||
0x51D, 0x53F, 0x579, 0x76B, 0x76C, 0x76D, 0x835, 0x903
|
||||
};
|
||||
|
||||
private static readonly int[] m_HairHues =
|
||||
{
|
||||
0x034, 0x035, 0x036, 0x037, 0x038, 0x039, 0x058, 0x08E,
|
||||
0x08F, 0x090, 0x091, 0x092, 0x101, 0x159, 0x15A, 0x15B,
|
||||
0x15C, 0x15D, 0x15E, 0x128, 0x12F, 0x1BD, 0x1E4, 0x1F3,
|
||||
0x207, 0x211, 0x239, 0x251, 0x26C, 0x2C3, 0x2C9, 0x31D,
|
||||
0x31E, 0x31F, 0x320, 0x321, 0x322, 0x323, 0x324, 0x325,
|
||||
0x326, 0x369, 0x386, 0x387, 0x388, 0x389, 0x38A, 0x59D,
|
||||
0x6B8, 0x725, 0x853
|
||||
};
|
||||
|
||||
public Elf(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Elf", "Elves", 605, 606, 607, 608, Expansion.ML)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (itemID == 0)
|
||||
return true;
|
||||
|
||||
if (female && (itemID == 0x2FCD || itemID == 0x2FBF) || !female && (itemID == 0x2FCC || itemID == 0x2FD0))
|
||||
return false;
|
||||
|
||||
if (itemID >= 0x2FBF && itemID <= 0x2FC2)
|
||||
return true;
|
||||
|
||||
if (itemID >= 0x2FCC && itemID <= 0x2FD1)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int RandomHair(bool female) // Random hair doesn't include baldness
|
||||
{
|
||||
return Utility.Random(8) switch
|
||||
{
|
||||
0 => 0x2FC0, // Long Feather
|
||||
1 => 0x2FC1, // Short
|
||||
2 => 0x2FC2, // Mullet
|
||||
3 => 0x2FCE, // Knob
|
||||
4 => 0x2FCF, // Braided
|
||||
5 => 0x2FD1, // Spiked
|
||||
6 => female ? 0x2FCC : 0x2FBF, // Flower or Mid-long
|
||||
_ => female ? 0x2FD0 : 0x2FCD
|
||||
};
|
||||
}
|
||||
|
||||
public override bool ValidateFacialHair(bool female, int itemID) => itemID == 0;
|
||||
|
||||
public override int RandomFacialHair(bool female) => 0;
|
||||
|
||||
public override int ClipSkinHue(int hue)
|
||||
{
|
||||
for (var i = 0; i < m_SkinHues.Length; i++)
|
||||
if (m_SkinHues[i] == hue)
|
||||
return hue;
|
||||
|
||||
return m_SkinHues[0];
|
||||
}
|
||||
|
||||
public override int RandomSkinHue() => m_SkinHues.RandomElement() | 0x8000;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
for (var i = 0; i < m_HairHues.Length; i++)
|
||||
if (m_HairHues[i] == hue)
|
||||
return hue;
|
||||
|
||||
return m_HairHues[0];
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => m_HairHues.RandomElement();
|
||||
}
|
||||
|
||||
private class Gargoyle : Race
|
||||
{
|
||||
// Todo Finish body hues
|
||||
private static readonly int[] m_BodyHues =
|
||||
{
|
||||
0x86DB, 0x86DC, 0x86DD, 0x86DE,
|
||||
0x86DF, 0x86E0, 0x86E1, 0x86E2,
|
||||
0x86E3, 0x86E4, 0x86E5, 0x86E6
|
||||
// 0x, 0x, 0x, 0x, // 86E7/86E8/86E9/86EA?
|
||||
// 0x, 0x, 0x, 0x, // 86EB/86EC/86ED/86EE?
|
||||
// 0x86F3, 0x86DB, 0x86DC, 0x86DD
|
||||
};
|
||||
|
||||
private static readonly int[] m_HornHues =
|
||||
{
|
||||
0x709, 0x70B, 0x70D, 0x70F, 0x711, 0x763,
|
||||
0x765, 0x768, 0x76B, 0x6F3, 0x6F1, 0x6EF,
|
||||
0x6E4, 0x6E2, 0x6E0, 0x709, 0x70B, 0x70D
|
||||
};
|
||||
|
||||
public Gargoyle(int raceID, int raceIndex)
|
||||
: base(raceID, raceIndex, "Gargoyle", "Gargoyles", 666, 667, 402, 403, Expansion.SA)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool ValidateHair(bool female, int itemID)
|
||||
{
|
||||
if (female == false) return itemID >= 0x4258 && itemID <= 0x425F;
|
||||
|
||||
return itemID == 0x4261 || itemID == 0x4262 || itemID >= 0x4273 && itemID <= 0x4275 || itemID == 0x42B0 ||
|
||||
itemID == 0x42B1 || itemID == 0x42AA || itemID == 0x42AB;
|
||||
}
|
||||
|
||||
public override int RandomHair(bool female)
|
||||
{
|
||||
if (Utility.Random(9) == 0)
|
||||
return 0;
|
||||
if (!female)
|
||||
return 0x4258 + Utility.Random(8);
|
||||
return Utility.Random(9) switch
|
||||
{
|
||||
0 => 0x4261,
|
||||
1 => 0x4262,
|
||||
2 => 0x4273,
|
||||
3 => 0x4274,
|
||||
4 => 0x4275,
|
||||
5 => 0x42B0,
|
||||
6 => 0x42B1,
|
||||
7 => 0x42AA,
|
||||
8 => 0x42AB,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
public override bool ValidateFacialHair(bool female, int itemID) =>
|
||||
!female && itemID >= 0x42AD && itemID <= 0x42B0;
|
||||
|
||||
public override int RandomFacialHair(bool female) =>
|
||||
female ? 0 : Utility.RandomList(0, 0x42AD, 0x42AE, 0x42AF, 0x42B0);
|
||||
|
||||
public override int ClipSkinHue(int hue) => hue;
|
||||
|
||||
public override int RandomSkinHue() => m_BodyHues.RandomElement() | 0x8000;
|
||||
|
||||
public override int ClipHairHue(int hue)
|
||||
{
|
||||
for (var i = 0; i < m_HornHues.Length; i++)
|
||||
if (m_HornHues[i] == hue)
|
||||
return hue;
|
||||
|
||||
return m_HornHues[0];
|
||||
}
|
||||
|
||||
public override int RandomHairHue() => m_HornHues.RandomElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,205 +7,205 @@ using Server.Spells.Ninjitsu;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class RegenRates
|
||||
{
|
||||
[CallPriority(10)]
|
||||
public static void Configure()
|
||||
public class RegenRates
|
||||
{
|
||||
Mobile.DefaultHitsRate = TimeSpan.FromSeconds(11.0);
|
||||
Mobile.DefaultStamRate = TimeSpan.FromSeconds(7.0);
|
||||
Mobile.DefaultManaRate = TimeSpan.FromSeconds(7.0);
|
||||
[CallPriority(10)]
|
||||
public static void Configure()
|
||||
{
|
||||
Mobile.DefaultHitsRate = TimeSpan.FromSeconds(11.0);
|
||||
Mobile.DefaultStamRate = TimeSpan.FromSeconds(7.0);
|
||||
Mobile.DefaultManaRate = TimeSpan.FromSeconds(7.0);
|
||||
|
||||
Mobile.ManaRegenRateHandler = Mobile_ManaRegenRate;
|
||||
Mobile.ManaRegenRateHandler = Mobile_ManaRegenRate;
|
||||
|
||||
if (Core.AOS)
|
||||
{
|
||||
Mobile.StamRegenRateHandler = Mobile_StamRegenRate;
|
||||
Mobile.HitsRegenRateHandler = Mobile_HitsRegenRate;
|
||||
}
|
||||
if (Core.AOS)
|
||||
{
|
||||
Mobile.StamRegenRateHandler = Mobile_StamRegenRate;
|
||||
Mobile.HitsRegenRateHandler = Mobile_HitsRegenRate;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckBonusSkill(Mobile m, int cur, int max, SkillName skill)
|
||||
{
|
||||
if (!m.Alive)
|
||||
return;
|
||||
|
||||
var n = (double)cur / max;
|
||||
var v = Math.Sqrt(m.Skills[skill].Value * 0.005);
|
||||
|
||||
n *= 1.0 - v;
|
||||
n += v;
|
||||
|
||||
m.CheckSkill(skill, n);
|
||||
}
|
||||
|
||||
private static bool CheckTransform(Mobile m, Type type) => TransformationSpellHelper.UnderTransformation(m, type);
|
||||
|
||||
private static bool CheckAnimal(Mobile m, Type type) => AnimalForm.UnderTransformation(m, type);
|
||||
|
||||
private static TimeSpan Mobile_HitsRegenRate(Mobile from)
|
||||
{
|
||||
var points = AosAttributes.GetValue(from, AosAttribute.RegenHits);
|
||||
|
||||
var bc = from as BaseCreature;
|
||||
|
||||
if (bc?.IsAnimatedDead == false)
|
||||
points += 4;
|
||||
|
||||
if (bc?.IsParagon == true || from is Leviathan)
|
||||
points += 40;
|
||||
|
||||
if (Core.ML && from.Race == Race.Human) // Is this affected by the cap?
|
||||
points += 2;
|
||||
|
||||
if (points < 0)
|
||||
points = 0;
|
||||
|
||||
if (Core.ML && from is PlayerMobile) // does racial bonus go before/after?
|
||||
points = Math.Min(points, 18);
|
||||
|
||||
if (CheckTransform(from, typeof(HorrificBeastSpell)))
|
||||
points += 20;
|
||||
|
||||
if (CheckAnimal(from, typeof(Dog)) || CheckAnimal(from, typeof(Cat)))
|
||||
points += from.Skills.Ninjitsu.Fixed / 30;
|
||||
|
||||
return TimeSpan.FromSeconds(1.0 / (0.1 * (1 + points)));
|
||||
}
|
||||
|
||||
private static TimeSpan Mobile_StamRegenRate(Mobile from)
|
||||
{
|
||||
if (from.Skills == null)
|
||||
return Mobile.DefaultStamRate;
|
||||
|
||||
CheckBonusSkill(from, from.Stam, from.StamMax, SkillName.Focus);
|
||||
|
||||
var points = (int)(from.Skills.Focus.Value * 0.1);
|
||||
|
||||
if (@from is BaseCreature creature && creature.IsParagon || from is Leviathan)
|
||||
points += 40;
|
||||
|
||||
var cappedPoints = AosAttributes.GetValue(from, AosAttribute.RegenStam);
|
||||
|
||||
if (CheckTransform(from, typeof(VampiricEmbraceSpell)))
|
||||
cappedPoints += 15;
|
||||
|
||||
if (CheckAnimal(from, typeof(Kirin)))
|
||||
cappedPoints += 20;
|
||||
|
||||
if (Core.ML && from is PlayerMobile)
|
||||
cappedPoints = Math.Min(cappedPoints, 24);
|
||||
|
||||
points += cappedPoints;
|
||||
|
||||
if (points < -1)
|
||||
points = -1;
|
||||
|
||||
return TimeSpan.FromSeconds(1.0 / (0.1 * (2 + points)));
|
||||
}
|
||||
|
||||
private static TimeSpan Mobile_ManaRegenRate(Mobile from)
|
||||
{
|
||||
if (from.Skills == null)
|
||||
return Mobile.DefaultManaRate;
|
||||
|
||||
if (!from.Meditating)
|
||||
CheckBonusSkill(from, from.Mana, from.ManaMax, SkillName.Meditation);
|
||||
|
||||
double rate;
|
||||
var armorPenalty = GetArmorOffset(from);
|
||||
|
||||
if (Core.AOS)
|
||||
{
|
||||
var medPoints = from.Int + from.Skills.Meditation.Value * 3;
|
||||
|
||||
medPoints *= from.Skills.Meditation.Value < 100.0 ? 0.025 : 0.0275;
|
||||
|
||||
CheckBonusSkill(from, from.Mana, from.ManaMax, SkillName.Focus);
|
||||
|
||||
var focusPoints = from.Skills.Focus.Value * 0.05;
|
||||
|
||||
if (armorPenalty > 0)
|
||||
medPoints = 0; // In AOS, wearing any meditation-blocking armor completely removes meditation bonus
|
||||
|
||||
var totalPoints = focusPoints + medPoints + (from.Meditating ? medPoints > 13.0 ? 13.0 : medPoints : 0.0);
|
||||
|
||||
if (@from is BaseCreature creature && creature.IsParagon || from is Leviathan)
|
||||
totalPoints += 40;
|
||||
|
||||
var cappedPoints = AosAttributes.GetValue(from, AosAttribute.RegenMana);
|
||||
|
||||
if (CheckTransform(from, typeof(VampiricEmbraceSpell)))
|
||||
cappedPoints += 3;
|
||||
else if (CheckTransform(from, typeof(LichFormSpell)))
|
||||
cappedPoints += 13;
|
||||
|
||||
if (Core.ML && from is PlayerMobile)
|
||||
cappedPoints = Math.Min(cappedPoints, 18);
|
||||
|
||||
totalPoints += cappedPoints;
|
||||
|
||||
if (totalPoints < -1)
|
||||
totalPoints = -1;
|
||||
|
||||
if (Core.ML)
|
||||
totalPoints = Math.Floor(totalPoints);
|
||||
|
||||
rate = 1.0 / (0.1 * (2 + totalPoints));
|
||||
}
|
||||
else
|
||||
{
|
||||
var medPoints = (from.Int + from.Skills.Meditation.Value) * 0.5;
|
||||
|
||||
if (medPoints <= 0)
|
||||
rate = 7.0;
|
||||
else if (medPoints <= 100)
|
||||
rate = 7.0 - 239 * medPoints / 2400 + 19 * medPoints * medPoints / 48000;
|
||||
else if (medPoints < 120)
|
||||
rate = 1.0;
|
||||
else
|
||||
rate = 0.75;
|
||||
|
||||
rate += armorPenalty;
|
||||
|
||||
if (from.Meditating)
|
||||
rate *= 0.5;
|
||||
|
||||
rate = Math.Clamp(rate, 0.5, 7.0);
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(rate);
|
||||
}
|
||||
|
||||
public static double GetArmorOffset(Mobile from)
|
||||
{
|
||||
var rating = 0.0;
|
||||
|
||||
if (!Core.AOS)
|
||||
rating += GetArmorMeditationValue(from.ShieldArmor as BaseArmor);
|
||||
|
||||
rating += GetArmorMeditationValue(from.NeckArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.HandArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.HeadArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.ArmsArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.LegsArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.ChestArmor as BaseArmor);
|
||||
|
||||
return rating / 4;
|
||||
}
|
||||
|
||||
private static double GetArmorMeditationValue(BaseArmor ar)
|
||||
{
|
||||
if (ar == null || ar.ArmorAttributes.MageArmor != 0 || ar.Attributes.SpellChanneling != 0)
|
||||
return 0.0;
|
||||
|
||||
return ar.MeditationAllowance switch
|
||||
{
|
||||
ArmorMeditationAllowance.None => ar.BaseArmorRatingScaled,
|
||||
ArmorMeditationAllowance.Half => ar.BaseArmorRatingScaled / 2.0,
|
||||
ArmorMeditationAllowance.All => 0.0,
|
||||
_ => ar.BaseArmorRatingScaled
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckBonusSkill(Mobile m, int cur, int max, SkillName skill)
|
||||
{
|
||||
if (!m.Alive)
|
||||
return;
|
||||
|
||||
double n = (double)cur / max;
|
||||
double v = Math.Sqrt(m.Skills[skill].Value * 0.005);
|
||||
|
||||
n *= 1.0 - v;
|
||||
n += v;
|
||||
|
||||
m.CheckSkill(skill, n);
|
||||
}
|
||||
|
||||
private static bool CheckTransform(Mobile m, Type type) => TransformationSpellHelper.UnderTransformation(m, type);
|
||||
|
||||
private static bool CheckAnimal(Mobile m, Type type) => AnimalForm.UnderTransformation(m, type);
|
||||
|
||||
private static TimeSpan Mobile_HitsRegenRate(Mobile from)
|
||||
{
|
||||
int points = AosAttributes.GetValue(from, AosAttribute.RegenHits);
|
||||
|
||||
BaseCreature bc = from as BaseCreature;
|
||||
|
||||
if (bc?.IsAnimatedDead == false)
|
||||
points += 4;
|
||||
|
||||
if (bc?.IsParagon == true || from is Leviathan)
|
||||
points += 40;
|
||||
|
||||
if (Core.ML && from.Race == Race.Human) // Is this affected by the cap?
|
||||
points += 2;
|
||||
|
||||
if (points < 0)
|
||||
points = 0;
|
||||
|
||||
if (Core.ML && from is PlayerMobile) // does racial bonus go before/after?
|
||||
points = Math.Min(points, 18);
|
||||
|
||||
if (CheckTransform(from, typeof(HorrificBeastSpell)))
|
||||
points += 20;
|
||||
|
||||
if (CheckAnimal(from, typeof(Dog)) || CheckAnimal(from, typeof(Cat)))
|
||||
points += from.Skills.Ninjitsu.Fixed / 30;
|
||||
|
||||
return TimeSpan.FromSeconds(1.0 / (0.1 * (1 + points)));
|
||||
}
|
||||
|
||||
private static TimeSpan Mobile_StamRegenRate(Mobile from)
|
||||
{
|
||||
if (from.Skills == null)
|
||||
return Mobile.DefaultStamRate;
|
||||
|
||||
CheckBonusSkill(from, from.Stam, from.StamMax, SkillName.Focus);
|
||||
|
||||
int points = (int)(from.Skills.Focus.Value * 0.1);
|
||||
|
||||
if ((from is BaseCreature creature && creature.IsParagon) || from is Leviathan)
|
||||
points += 40;
|
||||
|
||||
int cappedPoints = AosAttributes.GetValue(from, AosAttribute.RegenStam);
|
||||
|
||||
if (CheckTransform(from, typeof(VampiricEmbraceSpell)))
|
||||
cappedPoints += 15;
|
||||
|
||||
if (CheckAnimal(from, typeof(Kirin)))
|
||||
cappedPoints += 20;
|
||||
|
||||
if (Core.ML && from is PlayerMobile)
|
||||
cappedPoints = Math.Min(cappedPoints, 24);
|
||||
|
||||
points += cappedPoints;
|
||||
|
||||
if (points < -1)
|
||||
points = -1;
|
||||
|
||||
return TimeSpan.FromSeconds(1.0 / (0.1 * (2 + points)));
|
||||
}
|
||||
|
||||
private static TimeSpan Mobile_ManaRegenRate(Mobile from)
|
||||
{
|
||||
if (from.Skills == null)
|
||||
return Mobile.DefaultManaRate;
|
||||
|
||||
if (!from.Meditating)
|
||||
CheckBonusSkill(from, from.Mana, from.ManaMax, SkillName.Meditation);
|
||||
|
||||
double rate;
|
||||
double armorPenalty = GetArmorOffset(from);
|
||||
|
||||
if (Core.AOS)
|
||||
{
|
||||
double medPoints = from.Int + from.Skills.Meditation.Value * 3;
|
||||
|
||||
medPoints *= from.Skills.Meditation.Value < 100.0 ? 0.025 : 0.0275;
|
||||
|
||||
CheckBonusSkill(from, from.Mana, from.ManaMax, SkillName.Focus);
|
||||
|
||||
double focusPoints = from.Skills.Focus.Value * 0.05;
|
||||
|
||||
if (armorPenalty > 0)
|
||||
medPoints = 0; // In AOS, wearing any meditation-blocking armor completely removes meditation bonus
|
||||
|
||||
double totalPoints = focusPoints + medPoints + (from.Meditating ? medPoints > 13.0 ? 13.0 : medPoints : 0.0);
|
||||
|
||||
if ((from is BaseCreature creature && creature.IsParagon) || from is Leviathan)
|
||||
totalPoints += 40;
|
||||
|
||||
int cappedPoints = AosAttributes.GetValue(from, AosAttribute.RegenMana);
|
||||
|
||||
if (CheckTransform(from, typeof(VampiricEmbraceSpell)))
|
||||
cappedPoints += 3;
|
||||
else if (CheckTransform(from, typeof(LichFormSpell)))
|
||||
cappedPoints += 13;
|
||||
|
||||
if (Core.ML && from is PlayerMobile)
|
||||
cappedPoints = Math.Min(cappedPoints, 18);
|
||||
|
||||
totalPoints += cappedPoints;
|
||||
|
||||
if (totalPoints < -1)
|
||||
totalPoints = -1;
|
||||
|
||||
if (Core.ML)
|
||||
totalPoints = Math.Floor(totalPoints);
|
||||
|
||||
rate = 1.0 / (0.1 * (2 + totalPoints));
|
||||
}
|
||||
else
|
||||
{
|
||||
double medPoints = (from.Int + from.Skills.Meditation.Value) * 0.5;
|
||||
|
||||
if (medPoints <= 0)
|
||||
rate = 7.0;
|
||||
else if (medPoints <= 100)
|
||||
rate = 7.0 - 239 * medPoints / 2400 + 19 * medPoints * medPoints / 48000;
|
||||
else if (medPoints < 120)
|
||||
rate = 1.0;
|
||||
else
|
||||
rate = 0.75;
|
||||
|
||||
rate += armorPenalty;
|
||||
|
||||
if (from.Meditating)
|
||||
rate *= 0.5;
|
||||
|
||||
rate = Math.Clamp(rate, 0.5, 7.0);
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(rate);
|
||||
}
|
||||
|
||||
public static double GetArmorOffset(Mobile from)
|
||||
{
|
||||
double rating = 0.0;
|
||||
|
||||
if (!Core.AOS)
|
||||
rating += GetArmorMeditationValue(from.ShieldArmor as BaseArmor);
|
||||
|
||||
rating += GetArmorMeditationValue(from.NeckArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.HandArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.HeadArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.ArmsArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.LegsArmor as BaseArmor);
|
||||
rating += GetArmorMeditationValue(from.ChestArmor as BaseArmor);
|
||||
|
||||
return rating / 4;
|
||||
}
|
||||
|
||||
private static double GetArmorMeditationValue(BaseArmor ar)
|
||||
{
|
||||
if (ar == null || ar.ArmorAttributes.MageArmor != 0 || ar.Attributes.SpellChanneling != 0)
|
||||
return 0.0;
|
||||
|
||||
return ar.MeditationAllowance switch
|
||||
{
|
||||
ArmorMeditationAllowance.None => ar.BaseArmorRatingScaled,
|
||||
ArmorMeditationAllowance.Half => ar.BaseArmorRatingScaled / 2.0,
|
||||
ArmorMeditationAllowance.All => 0.0,
|
||||
_ => ar.BaseArmorRatingScaled
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,55 @@
|
|||
namespace Server.Misc
|
||||
{
|
||||
public static class RenameRequests
|
||||
{
|
||||
public static void Initialize()
|
||||
public static class RenameRequests
|
||||
{
|
||||
EventSink.RenameRequest += EventSink_RenameRequest;
|
||||
}
|
||||
|
||||
private static void EventSink_RenameRequest(Mobile from, Mobile targ, string name)
|
||||
{
|
||||
if (from.CanSee(targ) && from.InRange(targ, 12) && targ.CanBeRenamedBy(from))
|
||||
{
|
||||
name = name.Trim();
|
||||
|
||||
if (NameVerification.Validate(name, 1, 16, true, false, true, 0, NameVerification.Empty,
|
||||
NameVerification.StartDisallowed, Core.ML ? NameVerification.Disallowed : new string[] { }))
|
||||
public static void Initialize()
|
||||
{
|
||||
if (Core.ML)
|
||||
{
|
||||
string[] disallowed = ProfanityProtection.Disallowed;
|
||||
|
||||
for (int i = 0; i < disallowed.Length; i++)
|
||||
if (name.IndexOf(disallowed[i]) != -1)
|
||||
{
|
||||
from.SendLocalizedMessage(1072622); // That name isn't very polite.
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(1072623,
|
||||
$"{targ.Name}\t{name}"); // Pet ~1_OLDPETNAME~ renamed to ~2_NEWPETNAME~.
|
||||
}
|
||||
|
||||
targ.Name = name;
|
||||
EventSink.RenameRequest += EventSink_RenameRequest;
|
||||
}
|
||||
else
|
||||
|
||||
private static void EventSink_RenameRequest(Mobile from, Mobile targ, string name)
|
||||
{
|
||||
from.SendMessage("That name is unacceptable.");
|
||||
if (from.CanSee(targ) && from.InRange(targ, 12) && targ.CanBeRenamedBy(from))
|
||||
{
|
||||
name = name.Trim();
|
||||
|
||||
if (NameVerification.Validate(
|
||||
name,
|
||||
1,
|
||||
16,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
0,
|
||||
NameVerification.Empty,
|
||||
NameVerification.StartDisallowed,
|
||||
Core.ML ? NameVerification.Disallowed : new string[] { }
|
||||
))
|
||||
{
|
||||
if (Core.ML)
|
||||
{
|
||||
var disallowed = ProfanityProtection.Disallowed;
|
||||
|
||||
for (var i = 0; i < disallowed.Length; i++)
|
||||
if (name.IndexOf(disallowed[i]) != -1)
|
||||
{
|
||||
from.SendLocalizedMessage(1072622); // That name isn't very polite.
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(
|
||||
1072623,
|
||||
$"{targ.Name}\t{name}"
|
||||
); // Pet ~1_OLDPETNAME~ renamed to ~2_NEWPETNAME~.
|
||||
}
|
||||
|
||||
targ.Name = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("That name is unacceptable.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,169 +4,173 @@ using System.Linq;
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public static class ServerList
|
||||
{
|
||||
/*
|
||||
* The default setting for Address, a value of 'null', will use your local IP address. If all of your local IP addresses
|
||||
* are private network addresses and AutoDetect is 'true' then RunUO will attempt to discover your public IP address
|
||||
* for you automatically.
|
||||
*
|
||||
* If you do not plan on allowing clients outside of your LAN to connect, you can set AutoDetect to 'false' and leave
|
||||
* Address set to 'null'.
|
||||
*
|
||||
* If your public IP address cannot be determined, you must change the value of Address to your public IP address
|
||||
* manually to allow clients outside of your LAN to connect to your server. Address can be either an IP address or
|
||||
* a hostname that will be resolved when RunUO starts.
|
||||
*
|
||||
* If you want players outside your LAN to be able to connect to your server and you are behind a router, you must also
|
||||
* forward TCP port 2593 to your private IP address. The procedure for doing this varies by manufacturer but generally
|
||||
* involves configuration of the router through your web browser.
|
||||
*
|
||||
* ServerList will direct connecting clients depending on both the address they are connecting from and the address and
|
||||
* port they are connecting to. If it is determined that both ends of a connection are private IP addresses, ServerList
|
||||
* will direct the client to the local private IP address. If a client is connecting to a local public IP address, they
|
||||
* will be directed to whichever address and port they initially connected to. This allows multihomed servers to function
|
||||
* properly and fully supports listening on multiple ports. If a client with a public IP address is connecting to a
|
||||
* locally private address, the server will direct the client to either the AutoDetected IP address or the manually entered
|
||||
* IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback.
|
||||
*
|
||||
* If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress
|
||||
* firewalls) or specific IP addresses you can do so by modifying the file SocketOptions.cs found in this directory.
|
||||
*/
|
||||
|
||||
public static string Address { get; private set; }
|
||||
public static string ServerName { get; private set; }
|
||||
|
||||
public static bool AutoDetect { get; private set; }
|
||||
|
||||
private static IPAddress m_PublicAddress;
|
||||
|
||||
public static void Initialize()
|
||||
public static class ServerList
|
||||
{
|
||||
Address = ServerConfiguration.GetOrUpdateSetting("serverListing.address", "(-null-)");
|
||||
AutoDetect = ServerConfiguration.GetOrUpdateSetting("serverListing.autoDetect", true);
|
||||
ServerName = ServerConfiguration.GetOrUpdateSetting("serverListing.serverName", "ModernUO");
|
||||
private static IPAddress m_PublicAddress;
|
||||
/*
|
||||
* The default setting for Address, a value of 'null', will use your local IP address. If all of your local IP addresses
|
||||
* are private network addresses and AutoDetect is 'true' then RunUO will attempt to discover your public IP address
|
||||
* for you automatically.
|
||||
*
|
||||
* If you do not plan on allowing clients outside of your LAN to connect, you can set AutoDetect to 'false' and leave
|
||||
* Address set to 'null'.
|
||||
*
|
||||
* If your public IP address cannot be determined, you must change the value of Address to your public IP address
|
||||
* manually to allow clients outside of your LAN to connect to your server. Address can be either an IP address or
|
||||
* a hostname that will be resolved when RunUO starts.
|
||||
*
|
||||
* If you want players outside your LAN to be able to connect to your server and you are behind a router, you must also
|
||||
* forward TCP port 2593 to your private IP address. The procedure for doing this varies by manufacturer but generally
|
||||
* involves configuration of the router through your web browser.
|
||||
*
|
||||
* ServerList will direct connecting clients depending on both the address they are connecting from and the address and
|
||||
* port they are connecting to. If it is determined that both ends of a connection are private IP addresses, ServerList
|
||||
* will direct the client to the local private IP address. If a client is connecting to a local public IP address, they
|
||||
* will be directed to whichever address and port they initially connected to. This allows multihomed servers to function
|
||||
* properly and fully supports listening on multiple ports. If a client with a public IP address is connecting to a
|
||||
* locally private address, the server will direct the client to either the AutoDetected IP address or the manually entered
|
||||
* IP address or hostname, whichever is applicable. Loopback clients will be directed to loopback.
|
||||
*
|
||||
* If you would like to listen on additional ports (i.e. 22, 23, 80, for clients behind highly restrictive egress
|
||||
* firewalls) or specific IP addresses you can do so by modifying the file SocketOptions.cs found in this directory.
|
||||
*/
|
||||
|
||||
if (Address == null)
|
||||
{
|
||||
if (AutoDetect)
|
||||
AutoDetection();
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(Address, out m_PublicAddress);
|
||||
}
|
||||
public static string Address { get; private set; }
|
||||
public static string ServerName { get; private set; }
|
||||
|
||||
EventSink.ServerList += EventSink_ServerList;
|
||||
}
|
||||
public static bool AutoDetect { get; private set; }
|
||||
|
||||
private static void EventSink_ServerList(ServerListEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
NetState ns = e.State;
|
||||
|
||||
IPEndPoint ipep = (IPEndPoint)ns.Connection.LocalEndPoint;
|
||||
|
||||
IPAddress localAddress = ipep.Address;
|
||||
int localPort = ipep.Port;
|
||||
|
||||
if (IsPrivateNetwork(localAddress))
|
||||
public static void Initialize()
|
||||
{
|
||||
ipep = (IPEndPoint)ns.Connection.RemoteEndPoint;
|
||||
if (!IsPrivateNetwork(ipep.Address) && m_PublicAddress != null)
|
||||
localAddress = m_PublicAddress;
|
||||
Address = ServerConfiguration.GetOrUpdateSetting("serverListing.address", "(-null-)");
|
||||
AutoDetect = ServerConfiguration.GetOrUpdateSetting("serverListing.autoDetect", true);
|
||||
ServerName = ServerConfiguration.GetOrUpdateSetting("serverListing.serverName", "ModernUO");
|
||||
|
||||
if (Address == null)
|
||||
{
|
||||
if (AutoDetect)
|
||||
AutoDetection();
|
||||
}
|
||||
else
|
||||
{
|
||||
Resolve(Address, out m_PublicAddress);
|
||||
}
|
||||
|
||||
EventSink.ServerList += EventSink_ServerList;
|
||||
}
|
||||
|
||||
e.AddServer(ServerName, new IPEndPoint(localAddress, localPort));
|
||||
}
|
||||
catch (Exception er)
|
||||
{
|
||||
Console.WriteLine(er);
|
||||
e.Rejected = true;
|
||||
}
|
||||
private static void EventSink_ServerList(ServerListEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ns = e.State;
|
||||
|
||||
var ipep = (IPEndPoint)ns.Connection.LocalEndPoint;
|
||||
|
||||
var localAddress = ipep.Address;
|
||||
var localPort = ipep.Port;
|
||||
|
||||
if (IsPrivateNetwork(localAddress))
|
||||
{
|
||||
ipep = (IPEndPoint)ns.Connection.RemoteEndPoint;
|
||||
if (!IsPrivateNetwork(ipep.Address) && m_PublicAddress != null)
|
||||
localAddress = m_PublicAddress;
|
||||
}
|
||||
|
||||
e.AddServer(ServerName, new IPEndPoint(localAddress, localPort));
|
||||
}
|
||||
catch (Exception er)
|
||||
{
|
||||
Console.WriteLine(er);
|
||||
e.Rejected = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AutoDetection()
|
||||
{
|
||||
if (!HasPublicIPAddress())
|
||||
{
|
||||
Console.Write("ServerList: Auto-detecting public IP address...");
|
||||
m_PublicAddress = FindPublicAddress();
|
||||
|
||||
if (m_PublicAddress != null)
|
||||
Console.WriteLine("done ({0})", m_PublicAddress);
|
||||
else
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Resolve(string addr, out IPAddress outValue)
|
||||
{
|
||||
if (IPAddress.TryParse(addr, out outValue))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var iphe = Dns.GetHostEntry(addr);
|
||||
|
||||
if (iphe.AddressList.Length > 0)
|
||||
outValue = iphe.AddressList[^1];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasPublicIPAddress() =>
|
||||
NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Select(adapter => adapter.GetIPProperties())
|
||||
.Any(
|
||||
properties => properties.UnicastAddresses.Select(unicast => unicast.Address)
|
||||
.Any(
|
||||
ip => !IPAddress.IsLoopback(ip) && ip.AddressFamily != AddressFamily.InterNetworkV6 &&
|
||||
!IsPrivateNetwork(ip)
|
||||
)
|
||||
);
|
||||
|
||||
// 10.0.0.0/8
|
||||
// 172.16.0.0/12
|
||||
// 192.168.0.0/16
|
||||
// 169.254.0.0/16
|
||||
// 100.64.0.0/10 RFC 6598
|
||||
private static bool IsPrivateNetwork(IPAddress ip) =>
|
||||
ip.AddressFamily != AddressFamily.InterNetworkV6 &&
|
||||
(Utility.IPMatch("192.168.*", ip) ||
|
||||
Utility.IPMatch("10.*", ip) ||
|
||||
Utility.IPMatch("172.16-31.*", ip) ||
|
||||
Utility.IPMatch("169.254.*", ip) ||
|
||||
Utility.IPMatch("100.64-127.*", ip));
|
||||
|
||||
private static IPAddress FindPublicAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
var req = WebRequest.Create("https://api.ipify.org");
|
||||
|
||||
req.Timeout = 15000;
|
||||
|
||||
var res = req.GetResponse();
|
||||
|
||||
var s = res.GetResponseStream();
|
||||
|
||||
var sr = new StreamReader(s);
|
||||
|
||||
var ip = IPAddress.Parse(sr.ReadLine() ?? "");
|
||||
|
||||
sr.Close();
|
||||
s.Close();
|
||||
res.Close();
|
||||
|
||||
return ip;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AutoDetection()
|
||||
{
|
||||
if (!HasPublicIPAddress())
|
||||
{
|
||||
Console.Write("ServerList: Auto-detecting public IP address...");
|
||||
m_PublicAddress = FindPublicAddress();
|
||||
|
||||
if (m_PublicAddress != null)
|
||||
Console.WriteLine("done ({0})", m_PublicAddress);
|
||||
else
|
||||
Console.WriteLine("failed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Resolve(string addr, out IPAddress outValue)
|
||||
{
|
||||
if (IPAddress.TryParse(addr, out outValue))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IPHostEntry iphe = Dns.GetHostEntry(addr);
|
||||
|
||||
if (iphe.AddressList.Length > 0)
|
||||
outValue = iphe.AddressList[^1];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasPublicIPAddress() =>
|
||||
NetworkInterface.GetAllNetworkInterfaces().Select(adapter => adapter.GetIPProperties())
|
||||
.Any(properties => properties.UnicastAddresses.Select(unicast => unicast.Address)
|
||||
.Any(ip => !IPAddress.IsLoopback(ip) && ip.AddressFamily != AddressFamily.InterNetworkV6 && !IsPrivateNetwork(ip)));
|
||||
|
||||
// 10.0.0.0/8
|
||||
// 172.16.0.0/12
|
||||
// 192.168.0.0/16
|
||||
// 169.254.0.0/16
|
||||
// 100.64.0.0/10 RFC 6598
|
||||
private static bool IsPrivateNetwork(IPAddress ip) =>
|
||||
ip.AddressFamily != AddressFamily.InterNetworkV6 &&
|
||||
(Utility.IPMatch("192.168.*", ip) ||
|
||||
Utility.IPMatch("10.*", ip) ||
|
||||
Utility.IPMatch("172.16-31.*", ip) ||
|
||||
Utility.IPMatch("169.254.*", ip) ||
|
||||
Utility.IPMatch("100.64-127.*", ip));
|
||||
|
||||
private static IPAddress FindPublicAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
WebRequest req = WebRequest.Create("https://api.ipify.org");
|
||||
|
||||
req.Timeout = 15000;
|
||||
|
||||
WebResponse res = req.GetResponse();
|
||||
|
||||
Stream s = res.GetResponseStream();
|
||||
|
||||
StreamReader sr = new StreamReader(s);
|
||||
|
||||
IPAddress ip = IPAddress.Parse(sr.ReadLine() ?? "");
|
||||
|
||||
sr.Close();
|
||||
s.Close();
|
||||
res.Close();
|
||||
|
||||
return ip;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,75 +1,76 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class ShrinkTable
|
||||
{
|
||||
public const int DefaultItemID = 0x1870; // Yellow virtue stone
|
||||
|
||||
private static int[] m_Table;
|
||||
|
||||
public static int Lookup(Mobile m) => Lookup(m.Body.BodyID, DefaultItemID);
|
||||
|
||||
public static int Lookup(int body) => Lookup(body, DefaultItemID);
|
||||
|
||||
public static int Lookup(Mobile m, int defaultValue) => Lookup(m.Body.BodyID, defaultValue);
|
||||
|
||||
public static int Lookup(int body, int defaultValue)
|
||||
public class ShrinkTable
|
||||
{
|
||||
if (m_Table == null)
|
||||
Load();
|
||||
public const int DefaultItemID = 0x1870; // Yellow virtue stone
|
||||
|
||||
int val = 0;
|
||||
private static int[] m_Table;
|
||||
|
||||
if (body >= 0 && body < m_Table!.Length)
|
||||
val = m_Table[body];
|
||||
public static int Lookup(Mobile m) => Lookup(m.Body.BodyID, DefaultItemID);
|
||||
|
||||
if (val == 0)
|
||||
val = defaultValue;
|
||||
public static int Lookup(int body) => Lookup(body, DefaultItemID);
|
||||
|
||||
return val;
|
||||
}
|
||||
public static int Lookup(Mobile m, int defaultValue) => Lookup(m.Body.BodyID, defaultValue);
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
string path = Path.Combine(Core.BaseDirectory, "Data/shrink.cfg");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
m_Table = System.Array.Empty<int>();
|
||||
return;
|
||||
}
|
||||
|
||||
m_Table = new int[1000];
|
||||
|
||||
using StreamReader ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (line.Length == 0 || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
try
|
||||
public static int Lookup(int body, int defaultValue)
|
||||
{
|
||||
string[] split = line.Split('\t');
|
||||
if (m_Table == null)
|
||||
Load();
|
||||
|
||||
if (split.Length >= 2)
|
||||
{
|
||||
int body = Utility.ToInt32(split[0]);
|
||||
int item = Utility.ToInt32(split[1]);
|
||||
var val = 0;
|
||||
|
||||
if (body >= 0 && body < m_Table.Length)
|
||||
m_Table[body] = item;
|
||||
}
|
||||
if (body >= 0 && body < m_Table!.Length)
|
||||
val = m_Table[body];
|
||||
|
||||
if (val == 0)
|
||||
val = defaultValue;
|
||||
|
||||
return val;
|
||||
}
|
||||
catch
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
// ignored
|
||||
var path = Path.Combine(Core.BaseDirectory, "Data/shrink.cfg");
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
m_Table = Array.Empty<int>();
|
||||
return;
|
||||
}
|
||||
|
||||
m_Table = new int[1000];
|
||||
|
||||
using var ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (line.Length == 0 || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var split = line.Split('\t');
|
||||
|
||||
if (split.Length >= 2)
|
||||
{
|
||||
var body = Utility.ToInt32(split[0]);
|
||||
var item = Utility.ToInt32(split[1]);
|
||||
|
||||
if (body >= 0 && body < m_Table.Length)
|
||||
m_Table[body] = item;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,391 +5,394 @@ using Server.Regions;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class SkillCheck
|
||||
{
|
||||
public enum Stat
|
||||
public class SkillCheck
|
||||
{
|
||||
Str,
|
||||
Dex,
|
||||
Int
|
||||
}
|
||||
public enum Stat
|
||||
{
|
||||
Str,
|
||||
Dex,
|
||||
Int
|
||||
}
|
||||
|
||||
public const int Allowance = 3; // How many times may we use the same location/target for gain
|
||||
public const int Allowance = 3; // How many times may we use the same location/target for gain
|
||||
|
||||
private const int
|
||||
LocationSize = 5; // The size of eeach location, make this smaller so players dont have to move as far
|
||||
private const int
|
||||
LocationSize = 5; // The size of eeach location, make this smaller so players dont have to move as far
|
||||
|
||||
private static readonly bool AntiMacroCode = !Core.ML; // Change this to false to disable anti-macro code
|
||||
private static readonly bool AntiMacroCode = !Core.ML; // Change this to false to disable anti-macro code
|
||||
|
||||
public static TimeSpan AntiMacroExpire = TimeSpan.FromMinutes(5.0); // How long do we remember targets/locations?
|
||||
public static TimeSpan AntiMacroExpire = TimeSpan.FromMinutes(5.0); // How long do we remember targets/locations?
|
||||
|
||||
private static readonly bool[] UseAntiMacro =
|
||||
{
|
||||
// true if this skill uses the anti-macro code, false if it does not
|
||||
false, // Alchemy = 0,
|
||||
true, // Anatomy = 1,
|
||||
true, // AnimalLore = 2,
|
||||
true, // ItemID = 3,
|
||||
true, // ArmsLore = 4,
|
||||
false, // Parry = 5,
|
||||
true, // Begging = 6,
|
||||
false, // Blacksmith = 7,
|
||||
false, // Fletching = 8,
|
||||
true, // Peacemaking = 9,
|
||||
true, // Camping = 10,
|
||||
false, // Carpentry = 11,
|
||||
false, // Cartography = 12,
|
||||
false, // Cooking = 13,
|
||||
true, // DetectHidden = 14,
|
||||
true, // Discordance = 15,
|
||||
true, // EvalInt = 16,
|
||||
true, // Healing = 17,
|
||||
true, // Fishing = 18,
|
||||
true, // Forensics = 19,
|
||||
true, // Herding = 20,
|
||||
true, // Hiding = 21,
|
||||
true, // Provocation = 22,
|
||||
false, // Inscribe = 23,
|
||||
true, // Lockpicking = 24,
|
||||
true, // Magery = 25,
|
||||
true, // MagicResist = 26,
|
||||
false, // Tactics = 27,
|
||||
true, // Snooping = 28,
|
||||
true, // Musicianship = 29,
|
||||
true, // Poisoning = 30,
|
||||
false, // Archery = 31,
|
||||
true, // SpiritSpeak = 32,
|
||||
true, // Stealing = 33,
|
||||
false, // Tailoring = 34,
|
||||
true, // AnimalTaming = 35,
|
||||
true, // TasteID = 36,
|
||||
false, // Tinkering = 37,
|
||||
true, // Tracking = 38,
|
||||
true, // Veterinary = 39,
|
||||
false, // Swords = 40,
|
||||
false, // Macing = 41,
|
||||
false, // Fencing = 42,
|
||||
false, // Wrestling = 43,
|
||||
true, // Lumberjacking = 44,
|
||||
true, // Mining = 45,
|
||||
true, // Meditation = 46,
|
||||
true, // Stealth = 47,
|
||||
true, // RemoveTrap = 48,
|
||||
true, // Necromancy = 49,
|
||||
false, // Focus = 50,
|
||||
true, // Chivalry = 51
|
||||
true, // Bushido = 52
|
||||
true, // Ninjitsu = 53
|
||||
true // Spellweaving
|
||||
};
|
||||
private static readonly bool[] UseAntiMacro =
|
||||
{
|
||||
// true if this skill uses the anti-macro code, false if it does not
|
||||
false, // Alchemy = 0,
|
||||
true, // Anatomy = 1,
|
||||
true, // AnimalLore = 2,
|
||||
true, // ItemID = 3,
|
||||
true, // ArmsLore = 4,
|
||||
false, // Parry = 5,
|
||||
true, // Begging = 6,
|
||||
false, // Blacksmith = 7,
|
||||
false, // Fletching = 8,
|
||||
true, // Peacemaking = 9,
|
||||
true, // Camping = 10,
|
||||
false, // Carpentry = 11,
|
||||
false, // Cartography = 12,
|
||||
false, // Cooking = 13,
|
||||
true, // DetectHidden = 14,
|
||||
true, // Discordance = 15,
|
||||
true, // EvalInt = 16,
|
||||
true, // Healing = 17,
|
||||
true, // Fishing = 18,
|
||||
true, // Forensics = 19,
|
||||
true, // Herding = 20,
|
||||
true, // Hiding = 21,
|
||||
true, // Provocation = 22,
|
||||
false, // Inscribe = 23,
|
||||
true, // Lockpicking = 24,
|
||||
true, // Magery = 25,
|
||||
true, // MagicResist = 26,
|
||||
false, // Tactics = 27,
|
||||
true, // Snooping = 28,
|
||||
true, // Musicianship = 29,
|
||||
true, // Poisoning = 30,
|
||||
false, // Archery = 31,
|
||||
true, // SpiritSpeak = 32,
|
||||
true, // Stealing = 33,
|
||||
false, // Tailoring = 34,
|
||||
true, // AnimalTaming = 35,
|
||||
true, // TasteID = 36,
|
||||
false, // Tinkering = 37,
|
||||
true, // Tracking = 38,
|
||||
true, // Veterinary = 39,
|
||||
false, // Swords = 40,
|
||||
false, // Macing = 41,
|
||||
false, // Fencing = 42,
|
||||
false, // Wrestling = 43,
|
||||
true, // Lumberjacking = 44,
|
||||
true, // Mining = 45,
|
||||
true, // Meditation = 46,
|
||||
true, // Stealth = 47,
|
||||
true, // RemoveTrap = 48,
|
||||
true, // Necromancy = 49,
|
||||
false, // Focus = 50,
|
||||
true, // Chivalry = 51
|
||||
true, // Bushido = 52
|
||||
true, // Ninjitsu = 53
|
||||
true // Spellweaving
|
||||
};
|
||||
|
||||
private static readonly TimeSpan m_StatGainDelay = TimeSpan.FromMinutes(Core.ML ? 0.05 : 15);
|
||||
private static readonly TimeSpan m_PetStatGainDelay = TimeSpan.FromMinutes(5.0);
|
||||
private static readonly TimeSpan m_StatGainDelay = TimeSpan.FromMinutes(Core.ML ? 0.05 : 15);
|
||||
private static readonly TimeSpan m_PetStatGainDelay = TimeSpan.FromMinutes(5.0);
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Mobile.SkillCheckLocationHandler = Mobile_SkillCheckLocation;
|
||||
Mobile.SkillCheckDirectLocationHandler = Mobile_SkillCheckDirectLocation;
|
||||
public static void Initialize()
|
||||
{
|
||||
Mobile.SkillCheckLocationHandler = Mobile_SkillCheckLocation;
|
||||
Mobile.SkillCheckDirectLocationHandler = Mobile_SkillCheckDirectLocation;
|
||||
|
||||
Mobile.SkillCheckTargetHandler = Mobile_SkillCheckTarget;
|
||||
Mobile.SkillCheckDirectTargetHandler = Mobile_SkillCheckDirectTarget;
|
||||
}
|
||||
Mobile.SkillCheckTargetHandler = Mobile_SkillCheckTarget;
|
||||
Mobile.SkillCheckDirectTargetHandler = Mobile_SkillCheckDirectTarget;
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckLocation(Mobile from, SkillName skillName, double minSkill, double maxSkill)
|
||||
{
|
||||
Skill skill = from.Skills[skillName];
|
||||
public static bool Mobile_SkillCheckLocation(Mobile from, SkillName skillName, double minSkill, double maxSkill)
|
||||
{
|
||||
var skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
double value = skill.Value;
|
||||
var value = skill.Value;
|
||||
|
||||
if (value < minSkill)
|
||||
return false; // Too difficult
|
||||
if (value >= maxSkill)
|
||||
return true; // No challenge
|
||||
if (value < minSkill)
|
||||
return false; // Too difficult
|
||||
if (value >= maxSkill)
|
||||
return true; // No challenge
|
||||
|
||||
double chance = (value - minSkill) / (maxSkill - minSkill);
|
||||
var chance = (value - minSkill) / (maxSkill - minSkill);
|
||||
|
||||
Point2D loc = new Point2D(from.Location.X / LocationSize, from.Location.Y / LocationSize);
|
||||
return CheckSkill(from, skill, loc, chance);
|
||||
}
|
||||
var loc = new Point2D(from.Location.X / LocationSize, from.Location.Y / LocationSize);
|
||||
return CheckSkill(from, skill, loc, chance);
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckDirectLocation(Mobile from, SkillName skillName, double chance)
|
||||
{
|
||||
Skill skill = from.Skills[skillName];
|
||||
public static bool Mobile_SkillCheckDirectLocation(Mobile from, SkillName skillName, double chance)
|
||||
{
|
||||
var skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
if (chance < 0.0)
|
||||
return false; // Too difficult
|
||||
if (chance >= 1.0)
|
||||
return true; // No challenge
|
||||
if (chance < 0.0)
|
||||
return false; // Too difficult
|
||||
if (chance >= 1.0)
|
||||
return true; // No challenge
|
||||
|
||||
Point2D loc = new Point2D(from.Location.X / LocationSize, from.Location.Y / LocationSize);
|
||||
return CheckSkill(from, skill, loc, chance);
|
||||
}
|
||||
var loc = new Point2D(from.Location.X / LocationSize, from.Location.Y / LocationSize);
|
||||
return CheckSkill(from, skill, loc, chance);
|
||||
}
|
||||
|
||||
public static bool CheckSkill(Mobile from, Skill skill, object amObj, double chance)
|
||||
{
|
||||
if (from.Skills.Cap == 0)
|
||||
return false;
|
||||
public static bool CheckSkill(Mobile from, Skill skill, object amObj, double chance)
|
||||
{
|
||||
if (from.Skills.Cap == 0)
|
||||
return false;
|
||||
|
||||
bool success = chance >= Utility.RandomDouble();
|
||||
double gc = (double)(from.Skills.Cap - from.Skills.Total) / from.Skills.Cap;
|
||||
gc += (skill.Cap - skill.Base) / skill.Cap;
|
||||
gc /= 2;
|
||||
var success = chance >= Utility.RandomDouble();
|
||||
var gc = (double)(from.Skills.Cap - from.Skills.Total) / from.Skills.Cap;
|
||||
gc += (skill.Cap - skill.Base) / skill.Cap;
|
||||
gc /= 2;
|
||||
|
||||
gc += (1.0 - chance) * (success ? 0.5 : Core.AOS ? 0.0 : 0.2);
|
||||
gc /= 2;
|
||||
gc += (1.0 - chance) * (success ? 0.5 :
|
||||
Core.AOS ? 0.0 : 0.2);
|
||||
gc /= 2;
|
||||
|
||||
gc *= skill.Info.GainFactor;
|
||||
gc *= skill.Info.GainFactor;
|
||||
|
||||
if (gc < 0.01)
|
||||
gc = 0.01;
|
||||
if (gc < 0.01)
|
||||
gc = 0.01;
|
||||
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
gc *= 2;
|
||||
|
||||
if (from.Alive && ((gc >= Utility.RandomDouble() && AllowGain(from, skill, amObj)) || skill.Base < 10.0))
|
||||
Gain(from, skill);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckTarget(Mobile from, SkillName skillName, object target, double minSkill,
|
||||
double maxSkill)
|
||||
{
|
||||
Skill skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
double value = skill.Value;
|
||||
|
||||
if (value < minSkill)
|
||||
return false; // Too difficult
|
||||
if (value >= maxSkill)
|
||||
return true; // No challenge
|
||||
|
||||
double chance = (value - minSkill) / (maxSkill - minSkill);
|
||||
|
||||
return CheckSkill(from, skill, target, chance);
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckDirectTarget(Mobile from, SkillName skillName, object target, double chance)
|
||||
{
|
||||
Skill skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
if (chance < 0.0)
|
||||
return false; // Too difficult
|
||||
if (chance >= 1.0)
|
||||
return true; // No challenge
|
||||
|
||||
return CheckSkill(from, skill, target, chance);
|
||||
}
|
||||
|
||||
private static bool AllowGain(Mobile from, Skill skill, object obj)
|
||||
{
|
||||
if (Core.AOS && Faction.InSkillLoss(from)) // Changed some time between the introduction of AoS and SE.
|
||||
return false;
|
||||
|
||||
if (AntiMacroCode && from is PlayerMobile mobile && UseAntiMacro[skill.Info.SkillID])
|
||||
return mobile.AntiMacroCheck(skill, obj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Gain(Mobile from, Skill skill)
|
||||
{
|
||||
if (from.Region.IsPartOf<JailRegion>())
|
||||
return;
|
||||
|
||||
if (from is BaseCreature creature && creature.IsDeadPet)
|
||||
return;
|
||||
|
||||
if (skill.SkillName == SkillName.Focus && from is BaseCreature)
|
||||
return;
|
||||
|
||||
if (skill.Base < skill.Cap && skill.Lock == SkillLock.Up)
|
||||
{
|
||||
int toGain = 1;
|
||||
|
||||
if (skill.Base <= 10.0)
|
||||
toGain = Utility.Random(4) + 1;
|
||||
|
||||
Skills skills = from.Skills;
|
||||
|
||||
if (from.Player && skills.Total / skills.Cap >= Utility.RandomDouble())
|
||||
for (int i = 0; i < skills.Length; ++i)
|
||||
{
|
||||
Skill toLower = skills[i];
|
||||
|
||||
if (toLower != skill && toLower.Lock == SkillLock.Down && toLower.BaseFixedPoint >= toGain)
|
||||
{
|
||||
toLower.BaseFixedPoint -= toGain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (from is PlayerMobile pm && skill.SkillName == pm.AcceleratedSkill &&
|
||||
pm.AcceleratedStart > DateTime.UtcNow)
|
||||
toGain *= Utility.RandomMinMax(2, 5);
|
||||
|
||||
if (!from.Player || skills.Total + toGain <= skills.Cap) skill.BaseFixedPoint += toGain;
|
||||
}
|
||||
|
||||
if (skill.Lock == SkillLock.Up)
|
||||
{
|
||||
SkillInfo info = skill.Info;
|
||||
|
||||
if (from.StrLock == StatLockType.Up && info.StrGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Str);
|
||||
else if (from.DexLock == StatLockType.Up && info.DexGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Dex);
|
||||
else if (from.IntLock == StatLockType.Up && info.IntGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Int);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CanLower(Mobile from, Stat stat)
|
||||
{
|
||||
return stat switch
|
||||
{
|
||||
Stat.Str => from.StrLock == StatLockType.Down && from.RawStr > 10,
|
||||
Stat.Dex => from.DexLock == StatLockType.Down && from.RawDex > 10,
|
||||
Stat.Int => from.IntLock == StatLockType.Down && from.RawInt > 10,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static bool CanRaise(Mobile from, Stat stat)
|
||||
{
|
||||
if (!(from is BaseCreature creature && creature.Controlled))
|
||||
if (from.RawStatTotal >= from.StatCap)
|
||||
return false;
|
||||
|
||||
return stat switch
|
||||
{
|
||||
Stat.Str => from.StrLock == StatLockType.Up && from.RawStr < 125,
|
||||
Stat.Dex => from.DexLock == StatLockType.Up && from.RawDex < 125,
|
||||
Stat.Int => from.IntLock == StatLockType.Up && from.RawInt < 125,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static void IncreaseStat(Mobile from, Stat stat, bool atrophy)
|
||||
{
|
||||
atrophy = atrophy || from.RawStatTotal >= from.StatCap;
|
||||
|
||||
switch (stat)
|
||||
{
|
||||
case Stat.Str:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Dex) && (from.RawDex < from.RawInt || !CanLower(from, Stat.Int)))
|
||||
--from.RawDex;
|
||||
else if (CanLower(from, Stat.Int))
|
||||
--from.RawInt;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Str))
|
||||
++from.RawStr;
|
||||
|
||||
break;
|
||||
}
|
||||
case Stat.Dex:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Str) && (from.RawStr < from.RawInt || !CanLower(from, Stat.Int)))
|
||||
--from.RawStr;
|
||||
else if (CanLower(from, Stat.Int))
|
||||
--from.RawInt;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Dex))
|
||||
++from.RawDex;
|
||||
|
||||
break;
|
||||
}
|
||||
case Stat.Int:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Str) && (from.RawStr < from.RawDex || !CanLower(from, Stat.Dex)))
|
||||
--from.RawStr;
|
||||
else if (CanLower(from, Stat.Dex))
|
||||
--from.RawDex;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Int))
|
||||
++from.RawInt;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GainStat(Mobile from, Stat stat)
|
||||
{
|
||||
switch (stat)
|
||||
{
|
||||
case Stat.Str:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastStrGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
gc *= 2;
|
||||
|
||||
if (from.Alive && (gc >= Utility.RandomDouble() && AllowGain(@from, skill, amObj) || skill.Base < 10.0))
|
||||
Gain(from, skill);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckTarget(
|
||||
Mobile from, SkillName skillName, object target, double minSkill,
|
||||
double maxSkill
|
||||
)
|
||||
{
|
||||
var skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
var value = skill.Value;
|
||||
|
||||
if (value < minSkill)
|
||||
return false; // Too difficult
|
||||
if (value >= maxSkill)
|
||||
return true; // No challenge
|
||||
|
||||
var chance = (value - minSkill) / (maxSkill - minSkill);
|
||||
|
||||
return CheckSkill(from, skill, target, chance);
|
||||
}
|
||||
|
||||
public static bool Mobile_SkillCheckDirectTarget(Mobile from, SkillName skillName, object target, double chance)
|
||||
{
|
||||
var skill = from.Skills[skillName];
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
if (chance < 0.0)
|
||||
return false; // Too difficult
|
||||
if (chance >= 1.0)
|
||||
return true; // No challenge
|
||||
|
||||
return CheckSkill(from, skill, target, chance);
|
||||
}
|
||||
|
||||
private static bool AllowGain(Mobile from, Skill skill, object obj)
|
||||
{
|
||||
if (Core.AOS && Faction.InSkillLoss(from)) // Changed some time between the introduction of AoS and SE.
|
||||
return false;
|
||||
|
||||
if (AntiMacroCode && from is PlayerMobile mobile && UseAntiMacro[skill.Info.SkillID])
|
||||
return mobile.AntiMacroCheck(skill, obj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Gain(Mobile from, Skill skill)
|
||||
{
|
||||
if (from.Region.IsPartOf<JailRegion>())
|
||||
return;
|
||||
}
|
||||
else if (from.LastStrGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
from.LastStrGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
case Stat.Dex:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastDexGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
if (from is BaseCreature creature && creature.IsDeadPet)
|
||||
return;
|
||||
}
|
||||
else if (from.LastDexGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
from.LastDexGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
case Stat.Int:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastIntGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
if (skill.SkillName == SkillName.Focus && from is BaseCreature)
|
||||
return;
|
||||
}
|
||||
else if (from.LastIntGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
|
||||
if (skill.Base < skill.Cap && skill.Lock == SkillLock.Up)
|
||||
{
|
||||
return;
|
||||
var toGain = 1;
|
||||
|
||||
if (skill.Base <= 10.0)
|
||||
toGain = Utility.Random(4) + 1;
|
||||
|
||||
var skills = from.Skills;
|
||||
|
||||
if (from.Player && skills.Total / skills.Cap >= Utility.RandomDouble())
|
||||
for (var i = 0; i < skills.Length; ++i)
|
||||
{
|
||||
var toLower = skills[i];
|
||||
|
||||
if (toLower != skill && toLower.Lock == SkillLock.Down && toLower.BaseFixedPoint >= toGain)
|
||||
{
|
||||
toLower.BaseFixedPoint -= toGain;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (from is PlayerMobile pm && skill.SkillName == pm.AcceleratedSkill &&
|
||||
pm.AcceleratedStart > DateTime.UtcNow)
|
||||
toGain *= Utility.RandomMinMax(2, 5);
|
||||
|
||||
if (!from.Player || skills.Total + toGain <= skills.Cap) skill.BaseFixedPoint += toGain;
|
||||
}
|
||||
|
||||
from.LastIntGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skill.Lock == SkillLock.Up)
|
||||
{
|
||||
var info = skill.Info;
|
||||
|
||||
bool atrophy = from.RawStatTotal / (double)from.StatCap >= Utility.RandomDouble();
|
||||
if (from.StrLock == StatLockType.Up && info.StrGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Str);
|
||||
else if (from.DexLock == StatLockType.Up && info.DexGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Dex);
|
||||
else if (from.IntLock == StatLockType.Up && info.IntGain / 33.3 > Utility.RandomDouble())
|
||||
GainStat(from, Stat.Int);
|
||||
}
|
||||
}
|
||||
|
||||
IncreaseStat(from, stat, atrophy);
|
||||
public static bool CanLower(Mobile from, Stat stat)
|
||||
{
|
||||
return stat switch
|
||||
{
|
||||
Stat.Str => from.StrLock == StatLockType.Down && from.RawStr > 10,
|
||||
Stat.Dex => from.DexLock == StatLockType.Down && from.RawDex > 10,
|
||||
Stat.Int => from.IntLock == StatLockType.Down && from.RawInt > 10,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static bool CanRaise(Mobile from, Stat stat)
|
||||
{
|
||||
if (!(from is BaseCreature creature && creature.Controlled))
|
||||
if (from.RawStatTotal >= from.StatCap)
|
||||
return false;
|
||||
|
||||
return stat switch
|
||||
{
|
||||
Stat.Str => from.StrLock == StatLockType.Up && from.RawStr < 125,
|
||||
Stat.Dex => from.DexLock == StatLockType.Up && from.RawDex < 125,
|
||||
Stat.Int => from.IntLock == StatLockType.Up && from.RawInt < 125,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static void IncreaseStat(Mobile from, Stat stat, bool atrophy)
|
||||
{
|
||||
atrophy = atrophy || from.RawStatTotal >= from.StatCap;
|
||||
|
||||
switch (stat)
|
||||
{
|
||||
case Stat.Str:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Dex) && (from.RawDex < from.RawInt || !CanLower(from, Stat.Int)))
|
||||
--from.RawDex;
|
||||
else if (CanLower(from, Stat.Int))
|
||||
--from.RawInt;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Str))
|
||||
++from.RawStr;
|
||||
|
||||
break;
|
||||
}
|
||||
case Stat.Dex:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Str) && (from.RawStr < from.RawInt || !CanLower(from, Stat.Int)))
|
||||
--from.RawStr;
|
||||
else if (CanLower(from, Stat.Int))
|
||||
--from.RawInt;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Dex))
|
||||
++from.RawDex;
|
||||
|
||||
break;
|
||||
}
|
||||
case Stat.Int:
|
||||
{
|
||||
if (atrophy)
|
||||
{
|
||||
if (CanLower(from, Stat.Str) && (from.RawStr < from.RawDex || !CanLower(from, Stat.Dex)))
|
||||
--from.RawStr;
|
||||
else if (CanLower(from, Stat.Dex))
|
||||
--from.RawDex;
|
||||
}
|
||||
|
||||
if (CanRaise(from, Stat.Int))
|
||||
++from.RawInt;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GainStat(Mobile from, Stat stat)
|
||||
{
|
||||
switch (stat)
|
||||
{
|
||||
case Stat.Str:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastStrGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
return;
|
||||
}
|
||||
else if (from.LastStrGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
from.LastStrGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
case Stat.Dex:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastDexGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
return;
|
||||
}
|
||||
else if (from.LastDexGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
from.LastDexGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
case Stat.Int:
|
||||
{
|
||||
if (from is BaseCreature creature && creature.Controlled)
|
||||
{
|
||||
if (creature.LastIntGain + m_PetStatGainDelay >= DateTime.UtcNow)
|
||||
return;
|
||||
}
|
||||
else if (from.LastIntGain + m_StatGainDelay >= DateTime.UtcNow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
from.LastIntGain = DateTime.UtcNow;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var atrophy = from.RawStatTotal / (double)from.StatCap >= Utility.RandomDouble();
|
||||
|
||||
IncreaseStat(from, stat, atrophy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,164 +4,176 @@ using Server.Network;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
[Parsable]
|
||||
public class TextDefinition
|
||||
{
|
||||
public TextDefinition(string text) : this(0, text)
|
||||
[Parsable]
|
||||
public class TextDefinition
|
||||
{
|
||||
public TextDefinition(string text) : this(0, text)
|
||||
{
|
||||
}
|
||||
|
||||
public TextDefinition(int number = 0, string text = null)
|
||||
{
|
||||
Number = number;
|
||||
String = text;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public string String { get; }
|
||||
|
||||
public bool IsEmpty => Number <= 0 && String == null;
|
||||
|
||||
public override string ToString() => Number > 0 ? $"#{Number}" : String ?? "";
|
||||
|
||||
public string Format(bool propsGump) =>
|
||||
Number > 0 ? $"{Number} (0x{Number:X})" :
|
||||
String != null ? $"\"{String}\"" :
|
||||
propsGump ? "-empty-" : "empty";
|
||||
|
||||
public string GetValue() => Number > 0 ? Number.ToString() : String ?? "";
|
||||
|
||||
public static void Serialize(IGenericWriter writer, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
{
|
||||
writer.WriteEncodedInt(3);
|
||||
}
|
||||
else if (def.Number > 0)
|
||||
{
|
||||
writer.WriteEncodedInt(1);
|
||||
writer.WriteEncodedInt(def.Number);
|
||||
}
|
||||
else if (def.String != null)
|
||||
{
|
||||
writer.WriteEncodedInt(2);
|
||||
writer.Write(def.String);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteEncodedInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static TextDefinition Deserialize(IGenericReader reader)
|
||||
{
|
||||
var type = reader.ReadEncodedInt();
|
||||
|
||||
return type switch
|
||||
{
|
||||
0 => new TextDefinition(),
|
||||
1 => new TextDefinition(reader.ReadEncodedInt()),
|
||||
2 => new TextDefinition(reader.ReadString()),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static void AddTo(ObjectPropertyList list, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
list.Add(def.Number);
|
||||
else if (def.String != null)
|
||||
list.Add(def.String);
|
||||
}
|
||||
|
||||
public static implicit operator TextDefinition(int v) => new TextDefinition(v);
|
||||
|
||||
public static implicit operator TextDefinition(string s) => new TextDefinition(s);
|
||||
|
||||
public static implicit operator int(TextDefinition m) => m?.Number ?? 0;
|
||||
|
||||
public static implicit operator string(TextDefinition m) => m?.String;
|
||||
|
||||
public static void AddHtmlText(
|
||||
Gump g, int x, int y, int width, int height, TextDefinition def, bool back,
|
||||
bool scroll, int numberColor, int stringColor
|
||||
)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
{
|
||||
if (numberColor >= 0) // 5 bits per RGB component (15 bit RGB)
|
||||
g.AddHtmlLocalized(x, y, width, height, def.Number, numberColor, back, scroll);
|
||||
else
|
||||
g.AddHtmlLocalized(x, y, width, height, def.Number, back, scroll);
|
||||
}
|
||||
else if (def.String != null)
|
||||
{
|
||||
if (stringColor >= 0) // 8 bits per RGB component (24 bit RGB)
|
||||
g.AddHtml(
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
$"<BASEFONT COLOR=#{stringColor:X6}>{def.String}</BASEFONT>",
|
||||
back,
|
||||
scroll
|
||||
);
|
||||
else
|
||||
g.AddHtml(x, y, width, height, def.String, back, scroll);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddHtmlText(
|
||||
Gump g, int x, int y, int width, int height, TextDefinition def, bool back,
|
||||
bool scroll
|
||||
)
|
||||
{
|
||||
AddHtmlText(g, x, y, width, height, def, back, scroll, -1, -1);
|
||||
}
|
||||
|
||||
public static void SendMessageTo(Mobile m, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.SendLocalizedMessage(def.Number);
|
||||
else if (def.String != null)
|
||||
m.SendMessage(def.String);
|
||||
}
|
||||
|
||||
public static void SendMessageTo(Mobile m, TextDefinition def, int hue)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.SendLocalizedMessage(def.Number, "", hue);
|
||||
else if (def.String != null)
|
||||
m.SendMessage(hue, def.String);
|
||||
}
|
||||
|
||||
public static void PublicOverheadMessage(Mobile m, MessageType messageType, int hue, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.PublicOverheadMessage(messageType, hue, def.Number);
|
||||
else if (def.String != null)
|
||||
m.PublicOverheadMessage(messageType, hue, false, def.String);
|
||||
}
|
||||
|
||||
public static TextDefinition Parse(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
int i;
|
||||
bool isInteger;
|
||||
|
||||
isInteger = value.StartsWith("0x")
|
||||
? int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i)
|
||||
: int.TryParse(value, out i);
|
||||
|
||||
return isInteger ? new TextDefinition(i) : new TextDefinition(value);
|
||||
}
|
||||
|
||||
public static bool IsNullOrEmpty(TextDefinition def) => def?.IsEmpty != false;
|
||||
}
|
||||
|
||||
public TextDefinition(int number = 0, string text = null)
|
||||
{
|
||||
Number = number;
|
||||
String = text;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public string String { get; }
|
||||
|
||||
public bool IsEmpty => Number <= 0 && String == null;
|
||||
|
||||
public override string ToString() => Number > 0 ? $"#{Number}" : String ?? "";
|
||||
|
||||
public string Format(bool propsGump) =>
|
||||
Number > 0 ? $"{Number} (0x{Number:X})" :
|
||||
String != null ? $"\"{String}\"" : propsGump ? "-empty-" : "empty";
|
||||
|
||||
public string GetValue() => Number > 0 ? Number.ToString() : String ?? "";
|
||||
|
||||
public static void Serialize(IGenericWriter writer, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
{
|
||||
writer.WriteEncodedInt(3);
|
||||
}
|
||||
else if (def.Number > 0)
|
||||
{
|
||||
writer.WriteEncodedInt(1);
|
||||
writer.WriteEncodedInt(def.Number);
|
||||
}
|
||||
else if (def.String != null)
|
||||
{
|
||||
writer.WriteEncodedInt(2);
|
||||
writer.Write(def.String);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteEncodedInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static TextDefinition Deserialize(IGenericReader reader)
|
||||
{
|
||||
int type = reader.ReadEncodedInt();
|
||||
|
||||
return type switch
|
||||
{
|
||||
0 => new TextDefinition(),
|
||||
1 => new TextDefinition(reader.ReadEncodedInt()),
|
||||
2 => new TextDefinition(reader.ReadString()),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public static void AddTo(ObjectPropertyList list, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
list.Add(def.Number);
|
||||
else if (def.String != null)
|
||||
list.Add(def.String);
|
||||
}
|
||||
|
||||
public static implicit operator TextDefinition(int v) => new TextDefinition(v);
|
||||
|
||||
public static implicit operator TextDefinition(string s) => new TextDefinition(s);
|
||||
|
||||
public static implicit operator int(TextDefinition m) => m?.Number ?? 0;
|
||||
|
||||
public static implicit operator string(TextDefinition m) => m?.String;
|
||||
|
||||
public static void AddHtmlText(Gump g, int x, int y, int width, int height, TextDefinition def, bool back,
|
||||
bool scroll, int numberColor, int stringColor)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
{
|
||||
if (numberColor >= 0) // 5 bits per RGB component (15 bit RGB)
|
||||
g.AddHtmlLocalized(x, y, width, height, def.Number, numberColor, back, scroll);
|
||||
else
|
||||
g.AddHtmlLocalized(x, y, width, height, def.Number, back, scroll);
|
||||
}
|
||||
else if (def.String != null)
|
||||
{
|
||||
if (stringColor >= 0) // 8 bits per RGB component (24 bit RGB)
|
||||
g.AddHtml(x, y, width, height, $"<BASEFONT COLOR=#{stringColor:X6}>{def.String}</BASEFONT>", back,
|
||||
scroll);
|
||||
else
|
||||
g.AddHtml(x, y, width, height, def.String, back, scroll);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddHtmlText(Gump g, int x, int y, int width, int height, TextDefinition def, bool back,
|
||||
bool scroll)
|
||||
{
|
||||
AddHtmlText(g, x, y, width, height, def, back, scroll, -1, -1);
|
||||
}
|
||||
|
||||
public static void SendMessageTo(Mobile m, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.SendLocalizedMessage(def.Number);
|
||||
else if (def.String != null)
|
||||
m.SendMessage(def.String);
|
||||
}
|
||||
|
||||
public static void SendMessageTo(Mobile m, TextDefinition def, int hue)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.SendLocalizedMessage(def.Number, "", hue);
|
||||
else if (def.String != null)
|
||||
m.SendMessage(hue, def.String);
|
||||
}
|
||||
|
||||
public static void PublicOverheadMessage(Mobile m, MessageType messageType, int hue, TextDefinition def)
|
||||
{
|
||||
if (def == null)
|
||||
return;
|
||||
|
||||
if (def.Number > 0)
|
||||
m.PublicOverheadMessage(messageType, hue, def.Number);
|
||||
else if (def.String != null)
|
||||
m.PublicOverheadMessage(messageType, hue, false, def.String);
|
||||
}
|
||||
|
||||
public static TextDefinition Parse(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
int i;
|
||||
bool isInteger;
|
||||
|
||||
isInteger = value.StartsWith("0x") ?
|
||||
int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i) :
|
||||
int.TryParse(value, out i);
|
||||
|
||||
return isInteger ? new TextDefinition(i) : new TextDefinition(value);
|
||||
}
|
||||
|
||||
public static bool IsNullOrEmpty(TextDefinition def) => def?.IsEmpty != false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,387 +5,408 @@ using Server.Mobiles;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class Titles
|
||||
{
|
||||
public const int MinFame = 0;
|
||||
public const int MaxFame = 15000;
|
||||
|
||||
public const int MinKarma = -15000;
|
||||
public const int MaxKarma = 15000;
|
||||
|
||||
public static string[] HarrowerTitles =
|
||||
public class Titles
|
||||
{
|
||||
"Spite", "Opponent", "Hunter", "Venom", "Executioner", "Annihilator", "Champion", "Assailant", "Purifier",
|
||||
"Nullifier"
|
||||
};
|
||||
public const int MinFame = 0;
|
||||
public const int MaxFame = 15000;
|
||||
|
||||
private static readonly string[,] m_Levels =
|
||||
{
|
||||
{ "Neophyte", "Neophyte", "Neophyte" },
|
||||
{ "Novice", "Novice", "Novice" },
|
||||
{ "Apprentice", "Apprentice", "Apprentice" },
|
||||
{ "Journeyman", "Journeyman", "Journeyman" },
|
||||
{ "Expert", "Expert", "Expert" },
|
||||
{ "Adept", "Adept", "Adept" },
|
||||
{ "Master", "Master", "Master" },
|
||||
{ "Grandmaster", "Grandmaster", "Grandmaster" },
|
||||
{ "Elder", "Tatsujin", "Shinobi" },
|
||||
{ "Legendary", "Kengo", "Ka-ge" }
|
||||
};
|
||||
public const int MinKarma = -15000;
|
||||
public const int MaxKarma = 15000;
|
||||
|
||||
private static readonly FameEntry[] m_FameEntries =
|
||||
{
|
||||
new FameEntry(1249, new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Outcast {0}"),
|
||||
new KarmaEntry(-5000, "The Despicable {0}"),
|
||||
new KarmaEntry(-2500, "The Scoundrel {0}"),
|
||||
new KarmaEntry(-1250, "The Unsavory {0}"),
|
||||
new KarmaEntry(-625, "The Rude {0}"),
|
||||
new KarmaEntry(624, "{0}"),
|
||||
new KarmaEntry(1249, "The Fair {0}"),
|
||||
new KarmaEntry(2499, "The Kind {0}"),
|
||||
new KarmaEntry(4999, "The Good {0}"),
|
||||
new KarmaEntry(9999, "The Honest {0}"),
|
||||
new KarmaEntry(10000, "The Trustworthy {0}")
|
||||
}),
|
||||
new FameEntry(2499, new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Wretched {0}"),
|
||||
new KarmaEntry(-5000, "The Dastardly {0}"),
|
||||
new KarmaEntry(-2500, "The Malicious {0}"),
|
||||
new KarmaEntry(-1250, "The Dishonorable {0}"),
|
||||
new KarmaEntry(-625, "The Disreputable {0}"),
|
||||
new KarmaEntry(624, "The Notable {0}"),
|
||||
new KarmaEntry(1249, "The Upstanding {0}"),
|
||||
new KarmaEntry(2499, "The Respectable {0}"),
|
||||
new KarmaEntry(4999, "The Honorable {0}"),
|
||||
new KarmaEntry(9999, "The Commendable {0}"),
|
||||
new KarmaEntry(10000, "The Estimable {0}")
|
||||
}),
|
||||
new FameEntry(4999, new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Nefarious {0}"),
|
||||
new KarmaEntry(-5000, "The Wicked {0}"),
|
||||
new KarmaEntry(-2500, "The Vile {0}"),
|
||||
new KarmaEntry(-1250, "The Ignoble {0}"),
|
||||
new KarmaEntry(-625, "The Notorious {0}"),
|
||||
new KarmaEntry(624, "The Prominent {0}"),
|
||||
new KarmaEntry(1249, "The Reputable {0}"),
|
||||
new KarmaEntry(2499, "The Proper {0}"),
|
||||
new KarmaEntry(4999, "The Admirable {0}"),
|
||||
new KarmaEntry(9999, "The Famed {0}"),
|
||||
new KarmaEntry(10000, "The Great {0}")
|
||||
}),
|
||||
new FameEntry(9999, new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Dread {0}"),
|
||||
new KarmaEntry(-5000, "The Evil {0}"),
|
||||
new KarmaEntry(-2500, "The Villainous {0}"),
|
||||
new KarmaEntry(-1250, "The Sinister {0}"),
|
||||
new KarmaEntry(-625, "The Infamous {0}"),
|
||||
new KarmaEntry(624, "The Renowned {0}"),
|
||||
new KarmaEntry(1249, "The Distinguished {0}"),
|
||||
new KarmaEntry(2499, "The Eminent {0}"),
|
||||
new KarmaEntry(4999, "The Noble {0}"),
|
||||
new KarmaEntry(9999, "The Illustrious {0}"),
|
||||
new KarmaEntry(10000, "The Glorious {0}")
|
||||
}),
|
||||
new FameEntry(10000, new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Dread {1} {0}"),
|
||||
new KarmaEntry(-5000, "The Evil {1} {0}"),
|
||||
new KarmaEntry(-2500, "The Dark {1} {0}"),
|
||||
new KarmaEntry(-1250, "The Sinister {1} {0}"),
|
||||
new KarmaEntry(-625, "The Dishonored {1} {0}"),
|
||||
new KarmaEntry(624, "{1} {0}"),
|
||||
new KarmaEntry(1249, "The Distinguished {1} {0}"),
|
||||
new KarmaEntry(2499, "The Eminent {1} {0}"),
|
||||
new KarmaEntry(4999, "The Noble {1} {0}"),
|
||||
new KarmaEntry(9999, "The Illustrious {1} {0}"),
|
||||
new KarmaEntry(10000, "The Glorious {1} {0}")
|
||||
})
|
||||
};
|
||||
|
||||
public static void AwardFame(Mobile m, int offset, bool message)
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
if (m.Fame >= MaxFame)
|
||||
return;
|
||||
|
||||
offset = Math.Max(offset - m.Fame / 100, 0);
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
if (m.Fame <= MinFame)
|
||||
return;
|
||||
|
||||
offset = Math.Min(offset - m.Fame / 100, 0);
|
||||
}
|
||||
|
||||
if (m.Fame + offset > MaxFame)
|
||||
offset = MaxFame - m.Fame;
|
||||
else if (m.Fame + offset < MinFame)
|
||||
offset = MinFame - m.Fame;
|
||||
|
||||
m.Fame += offset;
|
||||
|
||||
if (message)
|
||||
{
|
||||
if (offset > 40)
|
||||
m.SendLocalizedMessage(1019054); // You have gained a lot of fame.
|
||||
else if (offset > 20)
|
||||
m.SendLocalizedMessage(1019053); // You have gained a good amount of fame.
|
||||
else if (offset > 10)
|
||||
m.SendLocalizedMessage(1019052); // You have gained some fame.
|
||||
else if (offset > 0)
|
||||
m.SendLocalizedMessage(1019051); // You have gained a little fame.
|
||||
else if (offset < -40)
|
||||
m.SendLocalizedMessage(1019058); // You have lost a lot of fame.
|
||||
else if (offset < -20)
|
||||
m.SendLocalizedMessage(1019057); // You have lost a good amount of fame.
|
||||
else if (offset < -10)
|
||||
m.SendLocalizedMessage(1019056); // You have lost some fame.
|
||||
else if (offset < 0)
|
||||
m.SendLocalizedMessage(1019055); // You have lost a little fame.
|
||||
}
|
||||
}
|
||||
|
||||
public static void AwardKarma(Mobile m, int offset, bool message)
|
||||
{
|
||||
PlayerMobile pm = m as PlayerMobile;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
if (pm?.KarmaLocked == true)
|
||||
return;
|
||||
|
||||
if (m.Karma >= MaxKarma)
|
||||
return;
|
||||
|
||||
offset = Math.Max(offset - m.Karma / 100, 0);
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
if (m.Karma <= MinKarma)
|
||||
return;
|
||||
|
||||
offset = Math.Min(offset - m.Karma / 100, 0);
|
||||
}
|
||||
|
||||
if (m.Karma + offset > MaxKarma)
|
||||
offset = MaxKarma - m.Karma;
|
||||
else if (m.Karma + offset < MinKarma)
|
||||
offset = MinKarma - m.Karma;
|
||||
|
||||
bool wasPositiveKarma = m.Karma >= 0;
|
||||
|
||||
m.Karma += offset;
|
||||
|
||||
if (message)
|
||||
{
|
||||
if (offset > 40)
|
||||
m.SendLocalizedMessage(1019062); // You have gained a lot of karma.
|
||||
else if (offset > 20)
|
||||
m.SendLocalizedMessage(1019061); // You have gained a good amount of karma.
|
||||
else if (offset > 10)
|
||||
m.SendLocalizedMessage(1019060); // You have gained some karma.
|
||||
else if (offset > 0)
|
||||
m.SendLocalizedMessage(1019059); // You have gained a little karma.
|
||||
else if (offset < -40)
|
||||
m.SendLocalizedMessage(1019066); // You have lost a lot of karma.
|
||||
else if (offset < -20)
|
||||
m.SendLocalizedMessage(1019065); // You have lost a good amount of karma.
|
||||
else if (offset < -10)
|
||||
m.SendLocalizedMessage(1019064); // You have lost some karma.
|
||||
else if (offset < 0)
|
||||
m.SendLocalizedMessage(1019063); // You have lost a little karma.
|
||||
}
|
||||
|
||||
if (!Core.AOS && wasPositiveKarma && m.Karma < 0 && pm?.KarmaLocked == false)
|
||||
{
|
||||
pm.KarmaLocked = true;
|
||||
m.SendLocalizedMessage(1042511, "",
|
||||
0x22); // Karma is locked. A mantra spoken at a shrine will unlock it again.
|
||||
}
|
||||
}
|
||||
|
||||
public static string ComputeTitle(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
StringBuilder title = new StringBuilder();
|
||||
|
||||
int fame = beheld.Fame;
|
||||
int karma = beheld.Karma;
|
||||
|
||||
bool showSkillTitle = beheld.ShowFameTitle && (beholder == beheld || fame >= 5000);
|
||||
|
||||
/*if (beheld.Kills >= 5)
|
||||
{
|
||||
title.AppendFormat( beheld.Fame >= 10000 ? "The Murderer {1} {0}" : "The Murderer {0}", beheld.Name, beheld.Female ? "Lady" : "Lord" );
|
||||
}
|
||||
else*/
|
||||
if (beheld.ShowFameTitle || beholder == beheld)
|
||||
for (int i = 0; i < m_FameEntries.Length; ++i)
|
||||
public static string[] HarrowerTitles =
|
||||
{
|
||||
FameEntry fe = m_FameEntries[i];
|
||||
"Spite", "Opponent", "Hunter", "Venom", "Executioner", "Annihilator", "Champion", "Assailant", "Purifier",
|
||||
"Nullifier"
|
||||
};
|
||||
|
||||
if (fame <= fe.m_Fame || i == m_FameEntries.Length - 1)
|
||||
{
|
||||
KarmaEntry[] karmaEntries = fe.m_Karma;
|
||||
private static readonly string[,] m_Levels =
|
||||
{
|
||||
{ "Neophyte", "Neophyte", "Neophyte" },
|
||||
{ "Novice", "Novice", "Novice" },
|
||||
{ "Apprentice", "Apprentice", "Apprentice" },
|
||||
{ "Journeyman", "Journeyman", "Journeyman" },
|
||||
{ "Expert", "Expert", "Expert" },
|
||||
{ "Adept", "Adept", "Adept" },
|
||||
{ "Master", "Master", "Master" },
|
||||
{ "Grandmaster", "Grandmaster", "Grandmaster" },
|
||||
{ "Elder", "Tatsujin", "Shinobi" },
|
||||
{ "Legendary", "Kengo", "Ka-ge" }
|
||||
};
|
||||
|
||||
for (int j = 0; j < karmaEntries.Length; ++j)
|
||||
private static readonly FameEntry[] m_FameEntries =
|
||||
{
|
||||
new FameEntry(
|
||||
1249,
|
||||
new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Outcast {0}"),
|
||||
new KarmaEntry(-5000, "The Despicable {0}"),
|
||||
new KarmaEntry(-2500, "The Scoundrel {0}"),
|
||||
new KarmaEntry(-1250, "The Unsavory {0}"),
|
||||
new KarmaEntry(-625, "The Rude {0}"),
|
||||
new KarmaEntry(624, "{0}"),
|
||||
new KarmaEntry(1249, "The Fair {0}"),
|
||||
new KarmaEntry(2499, "The Kind {0}"),
|
||||
new KarmaEntry(4999, "The Good {0}"),
|
||||
new KarmaEntry(9999, "The Honest {0}"),
|
||||
new KarmaEntry(10000, "The Trustworthy {0}")
|
||||
}
|
||||
),
|
||||
new FameEntry(
|
||||
2499,
|
||||
new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Wretched {0}"),
|
||||
new KarmaEntry(-5000, "The Dastardly {0}"),
|
||||
new KarmaEntry(-2500, "The Malicious {0}"),
|
||||
new KarmaEntry(-1250, "The Dishonorable {0}"),
|
||||
new KarmaEntry(-625, "The Disreputable {0}"),
|
||||
new KarmaEntry(624, "The Notable {0}"),
|
||||
new KarmaEntry(1249, "The Upstanding {0}"),
|
||||
new KarmaEntry(2499, "The Respectable {0}"),
|
||||
new KarmaEntry(4999, "The Honorable {0}"),
|
||||
new KarmaEntry(9999, "The Commendable {0}"),
|
||||
new KarmaEntry(10000, "The Estimable {0}")
|
||||
}
|
||||
),
|
||||
new FameEntry(
|
||||
4999,
|
||||
new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Nefarious {0}"),
|
||||
new KarmaEntry(-5000, "The Wicked {0}"),
|
||||
new KarmaEntry(-2500, "The Vile {0}"),
|
||||
new KarmaEntry(-1250, "The Ignoble {0}"),
|
||||
new KarmaEntry(-625, "The Notorious {0}"),
|
||||
new KarmaEntry(624, "The Prominent {0}"),
|
||||
new KarmaEntry(1249, "The Reputable {0}"),
|
||||
new KarmaEntry(2499, "The Proper {0}"),
|
||||
new KarmaEntry(4999, "The Admirable {0}"),
|
||||
new KarmaEntry(9999, "The Famed {0}"),
|
||||
new KarmaEntry(10000, "The Great {0}")
|
||||
}
|
||||
),
|
||||
new FameEntry(
|
||||
9999,
|
||||
new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Dread {0}"),
|
||||
new KarmaEntry(-5000, "The Evil {0}"),
|
||||
new KarmaEntry(-2500, "The Villainous {0}"),
|
||||
new KarmaEntry(-1250, "The Sinister {0}"),
|
||||
new KarmaEntry(-625, "The Infamous {0}"),
|
||||
new KarmaEntry(624, "The Renowned {0}"),
|
||||
new KarmaEntry(1249, "The Distinguished {0}"),
|
||||
new KarmaEntry(2499, "The Eminent {0}"),
|
||||
new KarmaEntry(4999, "The Noble {0}"),
|
||||
new KarmaEntry(9999, "The Illustrious {0}"),
|
||||
new KarmaEntry(10000, "The Glorious {0}")
|
||||
}
|
||||
),
|
||||
new FameEntry(
|
||||
10000,
|
||||
new[]
|
||||
{
|
||||
new KarmaEntry(-10000, "The Dread {1} {0}"),
|
||||
new KarmaEntry(-5000, "The Evil {1} {0}"),
|
||||
new KarmaEntry(-2500, "The Dark {1} {0}"),
|
||||
new KarmaEntry(-1250, "The Sinister {1} {0}"),
|
||||
new KarmaEntry(-625, "The Dishonored {1} {0}"),
|
||||
new KarmaEntry(624, "{1} {0}"),
|
||||
new KarmaEntry(1249, "The Distinguished {1} {0}"),
|
||||
new KarmaEntry(2499, "The Eminent {1} {0}"),
|
||||
new KarmaEntry(4999, "The Noble {1} {0}"),
|
||||
new KarmaEntry(9999, "The Illustrious {1} {0}"),
|
||||
new KarmaEntry(10000, "The Glorious {1} {0}")
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
public static void AwardFame(Mobile m, int offset, bool message)
|
||||
{
|
||||
if (offset > 0)
|
||||
{
|
||||
KarmaEntry ke = karmaEntries[j];
|
||||
if (m.Fame >= MaxFame)
|
||||
return;
|
||||
|
||||
if (karma <= ke.m_Karma || j == karmaEntries.Length - 1)
|
||||
{
|
||||
title.AppendFormat(ke.m_Title, beheld.Name, beheld.Female ? "Lady" : "Lord");
|
||||
break;
|
||||
}
|
||||
offset = Math.Max(offset - m.Fame / 100, 0);
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
if (m.Fame <= MinFame)
|
||||
return;
|
||||
|
||||
offset = Math.Min(offset - m.Fame / 100, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
title.Append(beheld.Name);
|
||||
if (m.Fame + offset > MaxFame)
|
||||
offset = MaxFame - m.Fame;
|
||||
else if (m.Fame + offset < MinFame)
|
||||
offset = MinFame - m.Fame;
|
||||
|
||||
if (beheld is PlayerMobile mobile && mobile.DisplayChampionTitle)
|
||||
{
|
||||
PlayerMobile.ChampionTitleInfo info = mobile.ChampionTitles;
|
||||
m.Fame += offset;
|
||||
|
||||
if (info.Harrower > 0)
|
||||
{
|
||||
title.AppendFormat(": {0} of Evil", HarrowerTitles[Math.Min(HarrowerTitles.Length, info.Harrower) - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
int highestValue = 0, highestType = 0;
|
||||
for (int i = 0; i < ChampionSpawnInfo.Table.Length; i++)
|
||||
{
|
||||
int v = info.GetValue(i);
|
||||
|
||||
if (v > highestValue)
|
||||
if (message)
|
||||
{
|
||||
highestValue = v;
|
||||
highestType = i;
|
||||
if (offset > 40)
|
||||
m.SendLocalizedMessage(1019054); // You have gained a lot of fame.
|
||||
else if (offset > 20)
|
||||
m.SendLocalizedMessage(1019053); // You have gained a good amount of fame.
|
||||
else if (offset > 10)
|
||||
m.SendLocalizedMessage(1019052); // You have gained some fame.
|
||||
else if (offset > 0)
|
||||
m.SendLocalizedMessage(1019051); // You have gained a little fame.
|
||||
else if (offset < -40)
|
||||
m.SendLocalizedMessage(1019058); // You have lost a lot of fame.
|
||||
else if (offset < -20)
|
||||
m.SendLocalizedMessage(1019057); // You have lost a good amount of fame.
|
||||
else if (offset < -10)
|
||||
m.SendLocalizedMessage(1019056); // You have lost some fame.
|
||||
else if (offset < 0)
|
||||
m.SendLocalizedMessage(1019055); // You have lost a little fame.
|
||||
}
|
||||
}
|
||||
|
||||
int offset = 0;
|
||||
if (highestValue > 800)
|
||||
offset = 3;
|
||||
else if (highestValue > 300)
|
||||
offset = highestValue / 300;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
ChampionSpawnInfo champInfo = ChampionSpawnInfo.GetInfo((ChampionSpawnType)highestType);
|
||||
title.AppendFormat(": {0} of the {1}",
|
||||
champInfo.LevelNames[Math.Min(offset, champInfo.LevelNames.Length) - 1], champInfo.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string customTitle = beheld.Title;
|
||||
public static void AwardKarma(Mobile m, int offset, bool message)
|
||||
{
|
||||
var pm = m as PlayerMobile;
|
||||
|
||||
if (customTitle != null && (customTitle = customTitle.Trim()).Length > 0)
|
||||
{
|
||||
title.AppendFormat(" {0}", customTitle);
|
||||
}
|
||||
else if (showSkillTitle && beheld.Player)
|
||||
{
|
||||
string skillTitle = GetSkillTitle(beheld);
|
||||
if (offset > 0)
|
||||
{
|
||||
if (pm?.KarmaLocked == true)
|
||||
return;
|
||||
|
||||
if (skillTitle != null) title.Append(", ").Append(skillTitle);
|
||||
}
|
||||
if (m.Karma >= MaxKarma)
|
||||
return;
|
||||
|
||||
return title.ToString();
|
||||
offset = Math.Max(offset - m.Karma / 100, 0);
|
||||
}
|
||||
else if (offset < 0)
|
||||
{
|
||||
if (m.Karma <= MinKarma)
|
||||
return;
|
||||
|
||||
offset = Math.Min(offset - m.Karma / 100, 0);
|
||||
}
|
||||
|
||||
if (m.Karma + offset > MaxKarma)
|
||||
offset = MaxKarma - m.Karma;
|
||||
else if (m.Karma + offset < MinKarma)
|
||||
offset = MinKarma - m.Karma;
|
||||
|
||||
var wasPositiveKarma = m.Karma >= 0;
|
||||
|
||||
m.Karma += offset;
|
||||
|
||||
if (message)
|
||||
{
|
||||
if (offset > 40)
|
||||
m.SendLocalizedMessage(1019062); // You have gained a lot of karma.
|
||||
else if (offset > 20)
|
||||
m.SendLocalizedMessage(1019061); // You have gained a good amount of karma.
|
||||
else if (offset > 10)
|
||||
m.SendLocalizedMessage(1019060); // You have gained some karma.
|
||||
else if (offset > 0)
|
||||
m.SendLocalizedMessage(1019059); // You have gained a little karma.
|
||||
else if (offset < -40)
|
||||
m.SendLocalizedMessage(1019066); // You have lost a lot of karma.
|
||||
else if (offset < -20)
|
||||
m.SendLocalizedMessage(1019065); // You have lost a good amount of karma.
|
||||
else if (offset < -10)
|
||||
m.SendLocalizedMessage(1019064); // You have lost some karma.
|
||||
else if (offset < 0)
|
||||
m.SendLocalizedMessage(1019063); // You have lost a little karma.
|
||||
}
|
||||
|
||||
if (!Core.AOS && wasPositiveKarma && m.Karma < 0 && pm?.KarmaLocked == false)
|
||||
{
|
||||
pm.KarmaLocked = true;
|
||||
m.SendLocalizedMessage(
|
||||
1042511,
|
||||
"",
|
||||
0x22
|
||||
); // Karma is locked. A mantra spoken at a shrine will unlock it again.
|
||||
}
|
||||
}
|
||||
|
||||
public static string ComputeTitle(Mobile beholder, Mobile beheld)
|
||||
{
|
||||
var title = new StringBuilder();
|
||||
|
||||
var fame = beheld.Fame;
|
||||
var karma = beheld.Karma;
|
||||
|
||||
var showSkillTitle = beheld.ShowFameTitle && (beholder == beheld || fame >= 5000);
|
||||
|
||||
/*if (beheld.Kills >= 5)
|
||||
{
|
||||
title.AppendFormat( beheld.Fame >= 10000 ? "The Murderer {1} {0}" : "The Murderer {0}", beheld.Name, beheld.Female ? "Lady" : "Lord" );
|
||||
}
|
||||
else*/
|
||||
if (beheld.ShowFameTitle || beholder == beheld)
|
||||
for (var i = 0; i < m_FameEntries.Length; ++i)
|
||||
{
|
||||
var fe = m_FameEntries[i];
|
||||
|
||||
if (fame <= fe.m_Fame || i == m_FameEntries.Length - 1)
|
||||
{
|
||||
var karmaEntries = fe.m_Karma;
|
||||
|
||||
for (var j = 0; j < karmaEntries.Length; ++j)
|
||||
{
|
||||
var ke = karmaEntries[j];
|
||||
|
||||
if (karma <= ke.m_Karma || j == karmaEntries.Length - 1)
|
||||
{
|
||||
title.AppendFormat(ke.m_Title, beheld.Name, beheld.Female ? "Lady" : "Lord");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
title.Append(beheld.Name);
|
||||
|
||||
if (beheld is PlayerMobile mobile && mobile.DisplayChampionTitle)
|
||||
{
|
||||
var info = mobile.ChampionTitles;
|
||||
|
||||
if (info.Harrower > 0)
|
||||
{
|
||||
title.AppendFormat(": {0} of Evil", HarrowerTitles[Math.Min(HarrowerTitles.Length, info.Harrower) - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
int highestValue = 0, highestType = 0;
|
||||
for (var i = 0; i < ChampionSpawnInfo.Table.Length; i++)
|
||||
{
|
||||
var v = info.GetValue(i);
|
||||
|
||||
if (v > highestValue)
|
||||
{
|
||||
highestValue = v;
|
||||
highestType = i;
|
||||
}
|
||||
}
|
||||
|
||||
var offset = 0;
|
||||
if (highestValue > 800)
|
||||
offset = 3;
|
||||
else if (highestValue > 300)
|
||||
offset = highestValue / 300;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
var champInfo = ChampionSpawnInfo.GetInfo((ChampionSpawnType)highestType);
|
||||
title.AppendFormat(
|
||||
": {0} of the {1}",
|
||||
champInfo.LevelNames[Math.Min(offset, champInfo.LevelNames.Length) - 1],
|
||||
champInfo.Name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var customTitle = beheld.Title;
|
||||
|
||||
if (customTitle != null && (customTitle = customTitle.Trim()).Length > 0)
|
||||
{
|
||||
title.AppendFormat(" {0}", customTitle);
|
||||
}
|
||||
else if (showSkillTitle && beheld.Player)
|
||||
{
|
||||
var skillTitle = GetSkillTitle(beheld);
|
||||
|
||||
if (skillTitle != null) title.Append(", ").Append(skillTitle);
|
||||
}
|
||||
|
||||
return title.ToString();
|
||||
}
|
||||
|
||||
public static string GetSkillTitle(Mobile mob)
|
||||
{
|
||||
var highest = GetHighestSkill(mob); // beheld.Skills.Highest;
|
||||
|
||||
if (highest?.BaseFixedPoint >= 300)
|
||||
{
|
||||
var skillLevel = GetSkillLevel(highest);
|
||||
var skillTitle = highest.Info.Title;
|
||||
|
||||
if (mob.Female && skillTitle.EndsWith("man"))
|
||||
skillTitle = $"{skillTitle.Substring(0, skillTitle.Length - 3)}woman";
|
||||
|
||||
return $"{skillLevel} {skillTitle}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Skill GetHighestSkill(Mobile m)
|
||||
{
|
||||
var skills = m.Skills;
|
||||
|
||||
if (!Core.AOS)
|
||||
return skills.Highest;
|
||||
|
||||
Skill highest = null;
|
||||
|
||||
for (var i = 0; i < m.Skills.Length; ++i)
|
||||
{
|
||||
var check = m.Skills[i];
|
||||
|
||||
if (highest == null || check.BaseFixedPoint > highest.BaseFixedPoint)
|
||||
highest = check;
|
||||
else if (highest.Lock != SkillLock.Up && check.Lock == SkillLock.Up &&
|
||||
check.BaseFixedPoint == highest.BaseFixedPoint)
|
||||
highest = check;
|
||||
}
|
||||
|
||||
return highest;
|
||||
}
|
||||
|
||||
private static string GetSkillLevel(Skill skill) => m_Levels[GetTableIndex(skill), GetTableType(skill)];
|
||||
|
||||
private static int GetTableType(Skill skill)
|
||||
{
|
||||
return skill.SkillName switch
|
||||
{
|
||||
SkillName.Bushido => 1,
|
||||
SkillName.Ninjitsu => 2,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetTableIndex(Skill skill)
|
||||
{
|
||||
var fp = Math.Min(skill.BaseFixedPoint, 1200);
|
||||
|
||||
return (fp - 300) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetSkillTitle(Mobile mob)
|
||||
public class FameEntry
|
||||
{
|
||||
Skill highest = GetHighestSkill(mob); // beheld.Skills.Highest;
|
||||
public int m_Fame;
|
||||
public KarmaEntry[] m_Karma;
|
||||
|
||||
if (highest?.BaseFixedPoint >= 300)
|
||||
{
|
||||
string skillLevel = GetSkillLevel(highest);
|
||||
string skillTitle = highest.Info.Title;
|
||||
|
||||
if (mob.Female && skillTitle.EndsWith("man"))
|
||||
skillTitle = $"{skillTitle.Substring(0, skillTitle.Length - 3)}woman";
|
||||
|
||||
return $"{skillLevel} {skillTitle}";
|
||||
}
|
||||
|
||||
return null;
|
||||
public FameEntry(int fame, KarmaEntry[] karma)
|
||||
{
|
||||
m_Fame = fame;
|
||||
m_Karma = karma;
|
||||
}
|
||||
}
|
||||
|
||||
private static Skill GetHighestSkill(Mobile m)
|
||||
public class KarmaEntry
|
||||
{
|
||||
Skills skills = m.Skills;
|
||||
public int m_Karma;
|
||||
public string m_Title;
|
||||
|
||||
if (!Core.AOS)
|
||||
return skills.Highest;
|
||||
|
||||
Skill highest = null;
|
||||
|
||||
for (int i = 0; i < m.Skills.Length; ++i)
|
||||
{
|
||||
Skill check = m.Skills[i];
|
||||
|
||||
if (highest == null || check.BaseFixedPoint > highest.BaseFixedPoint)
|
||||
highest = check;
|
||||
else if (highest.Lock != SkillLock.Up && check.Lock == SkillLock.Up &&
|
||||
check.BaseFixedPoint == highest.BaseFixedPoint)
|
||||
highest = check;
|
||||
}
|
||||
|
||||
return highest;
|
||||
public KarmaEntry(int karma, string title)
|
||||
{
|
||||
m_Karma = karma;
|
||||
m_Title = title;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSkillLevel(Skill skill) => m_Levels[GetTableIndex(skill), GetTableType(skill)];
|
||||
|
||||
private static int GetTableType(Skill skill)
|
||||
{
|
||||
return skill.SkillName switch
|
||||
{
|
||||
SkillName.Bushido => 1,
|
||||
SkillName.Ninjitsu => 2,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetTableIndex(Skill skill)
|
||||
{
|
||||
int fp = Math.Min(skill.BaseFixedPoint, 1200);
|
||||
|
||||
return (fp - 300) / 100;
|
||||
}
|
||||
}
|
||||
|
||||
public class FameEntry
|
||||
{
|
||||
public int m_Fame;
|
||||
public KarmaEntry[] m_Karma;
|
||||
|
||||
public FameEntry(int fame, KarmaEntry[] karma)
|
||||
{
|
||||
m_Fame = fame;
|
||||
m_Karma = karma;
|
||||
}
|
||||
}
|
||||
|
||||
public class KarmaEntry
|
||||
{
|
||||
public int m_Karma;
|
||||
public string m_Title;
|
||||
|
||||
public KarmaEntry(int karma, string title)
|
||||
{
|
||||
m_Karma = karma;
|
||||
m_Title = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,105 +2,105 @@ using Server.Commands.Generic;
|
|||
|
||||
namespace Server.Items
|
||||
{
|
||||
public class ToggleItem : Item
|
||||
{
|
||||
[Constructible]
|
||||
public ToggleItem(int inactiveItemID, int activeItemID, bool playersCanToggle = false)
|
||||
: base(inactiveItemID)
|
||||
public class ToggleItem : Item
|
||||
{
|
||||
Movable = false;
|
||||
|
||||
InactiveItemID = inactiveItemID;
|
||||
ActiveItemID = activeItemID;
|
||||
PlayersCanToggle = playersCanToggle;
|
||||
}
|
||||
|
||||
public ToggleItem(Serial serial)
|
||||
: base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int InactiveItemID { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ActiveItemID { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool PlayersCanToggle { get; set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
TargetCommands.Register(new ToggleCommand());
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from.AccessLevel >= AccessLevel.GameMaster)
|
||||
{
|
||||
Toggle();
|
||||
}
|
||||
else if (PlayersCanToggle)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 1))
|
||||
Toggle();
|
||||
else
|
||||
from.SendLocalizedMessage(500446); // That is too far away.
|
||||
}
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
ItemID = ItemID == ActiveItemID ? InactiveItemID : ActiveItemID;
|
||||
Visible = ItemID != 0x1;
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
|
||||
writer.Write(InactiveItemID);
|
||||
writer.Write(ActiveItemID);
|
||||
writer.Write(PlayersCanToggle);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
InactiveItemID = reader.ReadInt();
|
||||
ActiveItemID = reader.ReadInt();
|
||||
PlayersCanToggle = reader.ReadBool();
|
||||
}
|
||||
|
||||
public class ToggleCommand : BaseCommand
|
||||
{
|
||||
public ToggleCommand()
|
||||
{
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Supports = CommandSupport.AllItems;
|
||||
Commands = new[] { "Toggle" };
|
||||
ObjectTypes = ObjectTypes.Items;
|
||||
Usage = "Toggle";
|
||||
Description = "Toggles a targeted ToggleItem.";
|
||||
}
|
||||
|
||||
public override void Execute(CommandEventArgs e, object obj)
|
||||
{
|
||||
if (obj is ToggleItem item)
|
||||
[Constructible]
|
||||
public ToggleItem(int inactiveItemID, int activeItemID, bool playersCanToggle = false)
|
||||
: base(inactiveItemID)
|
||||
{
|
||||
item.Toggle();
|
||||
AddResponse("The item has been toggled.");
|
||||
Movable = false;
|
||||
|
||||
InactiveItemID = inactiveItemID;
|
||||
ActiveItemID = activeItemID;
|
||||
PlayersCanToggle = playersCanToggle;
|
||||
}
|
||||
else
|
||||
|
||||
public ToggleItem(Serial serial)
|
||||
: base(serial)
|
||||
{
|
||||
LogFailure("That is not a ToggleItem.");
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int InactiveItemID { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int ActiveItemID { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool PlayersCanToggle { get; set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
TargetCommands.Register(new ToggleCommand());
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (from.AccessLevel >= AccessLevel.GameMaster)
|
||||
{
|
||||
Toggle();
|
||||
}
|
||||
else if (PlayersCanToggle)
|
||||
{
|
||||
if (from.InRange(GetWorldLocation(), 1))
|
||||
Toggle();
|
||||
else
|
||||
from.SendLocalizedMessage(500446); // That is too far away.
|
||||
}
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
ItemID = ItemID == ActiveItemID ? InactiveItemID : ActiveItemID;
|
||||
Visible = ItemID != 0x1;
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
|
||||
writer.Write(InactiveItemID);
|
||||
writer.Write(ActiveItemID);
|
||||
writer.Write(PlayersCanToggle);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
|
||||
InactiveItemID = reader.ReadInt();
|
||||
ActiveItemID = reader.ReadInt();
|
||||
PlayersCanToggle = reader.ReadBool();
|
||||
}
|
||||
|
||||
public class ToggleCommand : BaseCommand
|
||||
{
|
||||
public ToggleCommand()
|
||||
{
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Supports = CommandSupport.AllItems;
|
||||
Commands = new[] { "Toggle" };
|
||||
ObjectTypes = ObjectTypes.Items;
|
||||
Usage = "Toggle";
|
||||
Description = "Toggles a targeted ToggleItem.";
|
||||
}
|
||||
|
||||
public override void Execute(CommandEventArgs e, object obj)
|
||||
{
|
||||
if (obj is ToggleItem item)
|
||||
{
|
||||
item.Toggle();
|
||||
AddResponse("The item has been toggled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogFailure("That is not a ToggleItem.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,69 +4,73 @@ using Server.Regions;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class TreasureRegion : BaseRegion
|
||||
{
|
||||
private const int Range = 5; // No house may be placed within 5 tiles of the treasure
|
||||
|
||||
public TreasureRegion(int x, int y, Map map) : base(null, map, DefaultPriority,
|
||||
new Rectangle2D(x - Range, y - Range, 1 + Range * 2, 1 + Range * 2))
|
||||
public class TreasureRegion : BaseRegion
|
||||
{
|
||||
GoLocation = new Point3D(x, y, map.GetAverageZ(x, y));
|
||||
private const int Range = 5; // No house may be placed within 5 tiles of the treasure
|
||||
|
||||
Register();
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
string filePath = Path.Combine(Core.BaseDirectory, "Data/treasure.cfg");
|
||||
int i = 0, x = 0, y = 0;
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
using StreamReader ip = new StreamReader(filePath);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
public TreasureRegion(int x, int y, Map map) : base(
|
||||
null,
|
||||
map,
|
||||
DefaultPriority,
|
||||
new Rectangle2D(x - Range, y - Range, 1 + Range * 2, 1 + Range * 2)
|
||||
)
|
||||
{
|
||||
i++;
|
||||
GoLocation = new Point3D(x, y, map.GetAverageZ(x, y));
|
||||
|
||||
try
|
||||
{
|
||||
string[] split = line.Split(' ');
|
||||
|
||||
x = Convert.ToInt32(split[0]);
|
||||
y = Convert.ToInt32(split[1]);
|
||||
|
||||
try
|
||||
{
|
||||
new TreasureRegion(x, y, Map.Felucca);
|
||||
new TreasureRegion(x, y, Map.Trammel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("{0} {1} {2} {3}", i, x, y, e);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Warning: Error in Line '{0}' of Data/treasure.cfg", line);
|
||||
}
|
||||
Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AllowHousing(Mobile from, Point3D p) => false;
|
||||
public static void Initialize()
|
||||
{
|
||||
var filePath = Path.Combine(Core.BaseDirectory, "Data/treasure.cfg");
|
||||
int i = 0, x = 0, y = 0;
|
||||
|
||||
public override void OnEnter(Mobile m)
|
||||
{
|
||||
if (m.AccessLevel > AccessLevel.Player)
|
||||
m.SendMessage("You have entered a protected treasure map area.");
|
||||
}
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
using var ip = new StreamReader(filePath);
|
||||
string line;
|
||||
|
||||
public override void OnExit(Mobile m)
|
||||
{
|
||||
if (m.AccessLevel > AccessLevel.Player)
|
||||
m.SendMessage("You have left a protected treasure map area.");
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
i++;
|
||||
|
||||
try
|
||||
{
|
||||
var split = line.Split(' ');
|
||||
|
||||
x = Convert.ToInt32(split[0]);
|
||||
y = Convert.ToInt32(split[1]);
|
||||
|
||||
try
|
||||
{
|
||||
new TreasureRegion(x, y, Map.Felucca);
|
||||
new TreasureRegion(x, y, Map.Trammel);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("{0} {1} {2} {3}", i, x, y, e);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Warning: Error in Line '{0}' of Data/treasure.cfg", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool AllowHousing(Mobile from, Point3D p) => false;
|
||||
|
||||
public override void OnEnter(Mobile m)
|
||||
{
|
||||
if (m.AccessLevel > AccessLevel.Player)
|
||||
m.SendMessage("You have entered a protected treasure map area.");
|
||||
}
|
||||
|
||||
public override void OnExit(Mobile m)
|
||||
{
|
||||
if (m.AccessLevel > AccessLevel.Player)
|
||||
m.SendMessage("You have left a protected treasure map area.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,49 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public delegate void ValidationEventHandler();
|
||||
public delegate void ValidationEventHandler();
|
||||
|
||||
public static class ValidationQueue
|
||||
{
|
||||
public static event ValidationEventHandler StartValidation;
|
||||
|
||||
public static void Initialize()
|
||||
public static class ValidationQueue
|
||||
{
|
||||
StartValidation?.Invoke();
|
||||
public static event ValidationEventHandler StartValidation;
|
||||
|
||||
StartValidation = null;
|
||||
}
|
||||
}
|
||||
public static void Initialize()
|
||||
{
|
||||
StartValidation?.Invoke();
|
||||
|
||||
public static class ValidationQueue<T>
|
||||
{
|
||||
private static List<T> m_Queue;
|
||||
|
||||
static ValidationQueue()
|
||||
{
|
||||
m_Queue = new List<T>();
|
||||
ValidationQueue.StartValidation += ValidateAll;
|
||||
StartValidation = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(T obj)
|
||||
public static class ValidationQueue<T>
|
||||
{
|
||||
m_Queue.Add(obj);
|
||||
private static List<T> m_Queue;
|
||||
|
||||
static ValidationQueue()
|
||||
{
|
||||
m_Queue = new List<T>();
|
||||
ValidationQueue.StartValidation += ValidateAll;
|
||||
}
|
||||
|
||||
public static void Add(T obj)
|
||||
{
|
||||
m_Queue.Add(obj);
|
||||
}
|
||||
|
||||
private static void ValidateAll()
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
var m = type.GetMethod("Validate", BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
if (m != null)
|
||||
for (var i = 0; i < m_Queue.Count; ++i)
|
||||
m.Invoke(m_Queue[i], null);
|
||||
|
||||
m_Queue.Clear();
|
||||
m_Queue = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateAll()
|
||||
{
|
||||
Type type = typeof(T);
|
||||
|
||||
MethodInfo m = type.GetMethod("Validate", BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
if (m != null)
|
||||
for (int i = 0; i < m_Queue.Count; ++i)
|
||||
m.Invoke(m_Queue[i], null);
|
||||
|
||||
m_Queue.Clear();
|
||||
m_Queue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,436 +5,438 @@ using Server.Engines.Spawners;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class VendorGenerator
|
||||
{
|
||||
private static readonly Rectangle2D[] m_BritRegions =
|
||||
public class VendorGenerator
|
||||
{
|
||||
new Rectangle2D(new Point2D(250, 750), new Point2D(775, 1330)),
|
||||
new Rectangle2D(new Point2D(525, 2095), new Point2D(925, 2430)),
|
||||
new Rectangle2D(new Point2D(1025, 2155), new Point2D(1265, 2310)),
|
||||
new Rectangle2D(new Point2D(1635, 2430), new Point2D(1705, 2508)),
|
||||
new Rectangle2D(new Point2D(1775, 2605), new Point2D(2165, 2975)),
|
||||
new Rectangle2D(new Point2D(1055, 3520), new Point2D(1570, 4075)),
|
||||
new Rectangle2D(new Point2D(2860, 3310), new Point2D(3120, 3630)),
|
||||
new Rectangle2D(new Point2D(2470, 1855), new Point2D(3950, 3045)),
|
||||
new Rectangle2D(new Point2D(3425, 990), new Point2D(3900, 1455)),
|
||||
new Rectangle2D(new Point2D(4175, 735), new Point2D(4840, 1600)),
|
||||
new Rectangle2D(new Point2D(2375, 330), new Point2D(3100, 1045)),
|
||||
new Rectangle2D(new Point2D(2100, 1090), new Point2D(2310, 1450)),
|
||||
new Rectangle2D(new Point2D(1495, 1400), new Point2D(1550, 1475)),
|
||||
new Rectangle2D(new Point2D(1085, 1520), new Point2D(1415, 1910)),
|
||||
new Rectangle2D(new Point2D(1410, 1500), new Point2D(1745, 1795)),
|
||||
new Rectangle2D(new Point2D(5120, 2300), new Point2D(6143, 4095))
|
||||
};
|
||||
|
||||
private static readonly Rectangle2D[] m_IlshRegions =
|
||||
{
|
||||
new Rectangle2D(new Point2D(0, 0), new Point2D(288 * 8, 200 * 8))
|
||||
};
|
||||
|
||||
private static Dictionary<Point2D, ShopInfo> m_ShopTable;
|
||||
private static List<ShopInfo> m_ShopList;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("VendorGen", AccessLevel.Administrator, VendorGen_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("VendorGen")]
|
||||
[Description("Generates vendors based on display cases and floor plans. Analyzes the map files, slow.")]
|
||||
private static void VendorGen_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Process(Map.Trammel, m_BritRegions);
|
||||
Process(Map.Felucca, m_BritRegions);
|
||||
Process(Map.Ilshenar, m_IlshRegions);
|
||||
}
|
||||
|
||||
private static bool GetFloorZ(Map map, int x, int y, out int z)
|
||||
{
|
||||
LandTile lt = map.Tiles.GetLandTile(x, y);
|
||||
|
||||
if (IsFloor(lt.ID) && map.CanFit(x, y, lt.Z, 16, false, false))
|
||||
{
|
||||
z = lt.Z;
|
||||
return true;
|
||||
}
|
||||
|
||||
StaticTile[] tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
for (int i = 0; i < tiles.Length; ++i)
|
||||
{
|
||||
StaticTile t = tiles[i];
|
||||
ItemData id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
|
||||
|
||||
if (IsStaticFloor(t.ID) && map.CanFit(x, y, t.Z + (id.Surface ? id.CalcHeight : 0), 16, false, false))
|
||||
private static readonly Rectangle2D[] m_BritRegions =
|
||||
{
|
||||
z = t.Z + (id.Surface ? id.CalcHeight : 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new Rectangle2D(new Point2D(250, 750), new Point2D(775, 1330)),
|
||||
new Rectangle2D(new Point2D(525, 2095), new Point2D(925, 2430)),
|
||||
new Rectangle2D(new Point2D(1025, 2155), new Point2D(1265, 2310)),
|
||||
new Rectangle2D(new Point2D(1635, 2430), new Point2D(1705, 2508)),
|
||||
new Rectangle2D(new Point2D(1775, 2605), new Point2D(2165, 2975)),
|
||||
new Rectangle2D(new Point2D(1055, 3520), new Point2D(1570, 4075)),
|
||||
new Rectangle2D(new Point2D(2860, 3310), new Point2D(3120, 3630)),
|
||||
new Rectangle2D(new Point2D(2470, 1855), new Point2D(3950, 3045)),
|
||||
new Rectangle2D(new Point2D(3425, 990), new Point2D(3900, 1455)),
|
||||
new Rectangle2D(new Point2D(4175, 735), new Point2D(4840, 1600)),
|
||||
new Rectangle2D(new Point2D(2375, 330), new Point2D(3100, 1045)),
|
||||
new Rectangle2D(new Point2D(2100, 1090), new Point2D(2310, 1450)),
|
||||
new Rectangle2D(new Point2D(1495, 1400), new Point2D(1550, 1475)),
|
||||
new Rectangle2D(new Point2D(1085, 1520), new Point2D(1415, 1910)),
|
||||
new Rectangle2D(new Point2D(1410, 1500), new Point2D(1745, 1795)),
|
||||
new Rectangle2D(new Point2D(5120, 2300), new Point2D(6143, 4095))
|
||||
};
|
||||
|
||||
z = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsFloor(Map map, int x, int y, bool canFit)
|
||||
{
|
||||
LandTile lt = map.Tiles.GetLandTile(x, y);
|
||||
|
||||
if (IsFloor(lt.ID) && (canFit || CanFit(map, x, y, lt.Z)))
|
||||
return true;
|
||||
|
||||
StaticTile[] tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
for (int i = 0; i < tiles.Length; ++i)
|
||||
{
|
||||
StaticTile t = tiles[i];
|
||||
ItemData id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
|
||||
|
||||
if (IsStaticFloor(t.ID) && (canFit || CanFit(map, x, y, t.Z + (id.Surface ? id.CalcHeight : 0))))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsFloor(int itemID)
|
||||
{
|
||||
itemID &= TileData.MaxLandValue;
|
||||
|
||||
return itemID >= 0x406 && itemID <= 0x51A;
|
||||
}
|
||||
|
||||
private static bool IsStaticFloor(int itemID) =>
|
||||
(itemID >= 0x495 && itemID <= 0x514)
|
||||
|| (itemID >= 0x519 && itemID <= 0x53A);
|
||||
|
||||
private static bool IsDisplayCase(int itemID) =>
|
||||
(itemID >= 0xB00 && itemID <= 0xB02)
|
||||
|| (itemID >= 0xB06 && itemID <= 0xB0A)
|
||||
|| (itemID >= 0xB0D && itemID <= 0xB17);
|
||||
|
||||
private static void Process(Map map, Rectangle2D[] regions)
|
||||
{
|
||||
m_ShopTable = new Dictionary<Point2D, ShopInfo>();
|
||||
m_ShopList = new List<ShopInfo>();
|
||||
|
||||
World.Broadcast(0x35, true, "Generating vendor spawns for {0}, please wait.", map);
|
||||
|
||||
for (int i = 0; i < regions.Length; ++i)
|
||||
for (int x = 0; x < map.Width; ++x)
|
||||
for (int y = 0; y < map.Height; ++y)
|
||||
CheckPoint(map, regions[i].X + x, regions[i].Y + y);
|
||||
|
||||
for (int i = 0; i < m_ShopList.Count; ++i)
|
||||
{
|
||||
ShopInfo si = m_ShopList[i];
|
||||
|
||||
int xTotal = 0;
|
||||
int yTotal = 0;
|
||||
|
||||
bool hasSpawner = false;
|
||||
|
||||
for (int j = 0; j < si.m_Floor.Count; ++j)
|
||||
private static readonly Rectangle2D[] m_IlshRegions =
|
||||
{
|
||||
Point2D fp = si.m_Floor[j];
|
||||
new Rectangle2D(new Point2D(0, 0), new Point2D(288 * 8, 200 * 8))
|
||||
};
|
||||
|
||||
xTotal += fp.X;
|
||||
yTotal += fp.Y;
|
||||
private static Dictionary<Point2D, ShopInfo> m_ShopTable;
|
||||
private static List<ShopInfo> m_ShopList;
|
||||
|
||||
IPooledEnumerable<Spawner> eable = map.GetItemsInRange<Spawner>(new Point3D(fp.X, fp.Y, 0), 0);
|
||||
hasSpawner = eable.Any();
|
||||
eable.Free();
|
||||
|
||||
if (hasSpawner)
|
||||
break;
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("VendorGen", AccessLevel.Administrator, VendorGen_OnCommand);
|
||||
}
|
||||
|
||||
if (hasSpawner)
|
||||
continue;
|
||||
|
||||
int xAvg = xTotal / si.m_Floor.Count;
|
||||
int yAvg = yTotal / si.m_Floor.Count;
|
||||
|
||||
List<string> names = new List<string>();
|
||||
ShopFlags flags = si.m_Flags;
|
||||
|
||||
if ((flags & ShopFlags.Armor) != 0)
|
||||
names.Add("armorer");
|
||||
|
||||
if ((flags & ShopFlags.MetalWeapon) != 0)
|
||||
names.Add("weaponsmith");
|
||||
|
||||
if ((flags & ShopFlags.ArcheryWeapon) != 0)
|
||||
names.Add("bowyer");
|
||||
|
||||
if ((flags & ShopFlags.Scroll) != 0)
|
||||
names.Add("mage");
|
||||
|
||||
if ((flags & ShopFlags.Spellbook) != 0)
|
||||
names.Add("mage");
|
||||
|
||||
if ((flags & ShopFlags.Bread) != 0)
|
||||
names.Add("baker");
|
||||
|
||||
if ((flags & ShopFlags.Jewel) != 0)
|
||||
names.Add("jeweler");
|
||||
|
||||
if ((flags & ShopFlags.Potion) != 0)
|
||||
[Usage("VendorGen")]
|
||||
[Description("Generates vendors based on display cases and floor plans. Analyzes the map files, slow.")]
|
||||
private static void VendorGen_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
names.Add("herbalist");
|
||||
names.Add("alchemist");
|
||||
names.Add("mage");
|
||||
Process(Map.Trammel, m_BritRegions);
|
||||
Process(Map.Felucca, m_BritRegions);
|
||||
Process(Map.Ilshenar, m_IlshRegions);
|
||||
}
|
||||
|
||||
if ((flags & ShopFlags.Reagent) != 0)
|
||||
private static bool GetFloorZ(Map map, int x, int y, out int z)
|
||||
{
|
||||
names.Add("mage");
|
||||
names.Add("herbalist");
|
||||
}
|
||||
var lt = map.Tiles.GetLandTile(x, y);
|
||||
|
||||
if ((flags & ShopFlags.Clothes) != 0)
|
||||
{
|
||||
names.Add("tailor");
|
||||
names.Add("weaver");
|
||||
}
|
||||
|
||||
for (int j = 0; j < names.Count; ++j)
|
||||
{
|
||||
Point2D cp = Point2D.Zero;
|
||||
int dist = 100000;
|
||||
|
||||
for (int k = 0; k < si.m_Floor.Count; ++k)
|
||||
{
|
||||
Point2D fp = si.m_Floor[k];
|
||||
|
||||
int rx = fp.X - xAvg;
|
||||
int ry = fp.Y - yAvg;
|
||||
int fd = (int)Math.Sqrt(rx * rx + ry * ry);
|
||||
|
||||
if (fd > 0 && fd < 5)
|
||||
fd -= Utility.Random(10);
|
||||
|
||||
if (fd < dist && GetFloorZ(map, fp.X, fp.Y, out _))
|
||||
if (IsFloor(lt.ID) && map.CanFit(x, y, lt.Z, 16, false, false))
|
||||
{
|
||||
dist = fd;
|
||||
cp = fp;
|
||||
z = lt.Z;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (cp == Point2D.Zero)
|
||||
continue;
|
||||
var tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
if (!GetFloorZ(map, cp.X, cp.Y, out int z))
|
||||
continue;
|
||||
for (var i = 0; i < tiles.Length; ++i)
|
||||
{
|
||||
var t = tiles[i];
|
||||
var id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
|
||||
|
||||
new Spawner(1, 1, 1, 0, 4, names[j]).MoveToWorld(new Point3D(cp.X, cp.Y, z), map);
|
||||
}
|
||||
}
|
||||
if (IsStaticFloor(t.ID) && map.CanFit(x, y, t.Z + (id.Surface ? id.CalcHeight : 0), 16, false, false))
|
||||
{
|
||||
z = t.Z + (id.Surface ? id.CalcHeight : 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
World.Broadcast(0x35, true, "Generation complete. {0} spawners generated.", m_ShopList.Count);
|
||||
}
|
||||
|
||||
private static void CheckPoint(Map map, int x, int y)
|
||||
{
|
||||
if (IsFloor(map, x, y, true))
|
||||
CheckFloor(map, x, y);
|
||||
}
|
||||
|
||||
private static void CheckFloor(Map map, int x, int y)
|
||||
{
|
||||
StaticTile[] tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
for (int i = 0; i < tiles.Length; ++i)
|
||||
if (IsDisplayCase(tiles[i].ID))
|
||||
{
|
||||
ProcessDisplayCase(map, tiles, x, y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsClothes(int itemID) =>
|
||||
(itemID >= 0x1515 && itemID <= 0x1518) || (itemID >= 0x152E && itemID <= 0x1531) || (itemID >= 0x1537
|
||||
&& itemID <= 0x154C) || (itemID >= 0x1EFD && itemID <= 0x1F04) || (itemID >= 0x170B && itemID <= 0x171C);
|
||||
|
||||
private static bool IsArmor(int itemID) =>
|
||||
(itemID >= 0x13BB && itemID <= 0x13E2) || (itemID >= 0x13E5 && itemID <= 0x13F2) ||
|
||||
(itemID >= 0x1408 && itemID <= 0x141A) || (itemID >= 0x144E && itemID <= 0x1457);
|
||||
|
||||
private static bool IsMetalWeapon(int itemID) =>
|
||||
(itemID >= 0xF43 && itemID <= 0xF4E) || (itemID >= 0xF51 && itemID <= 0xF52) ||
|
||||
(itemID >= 0xF5C && itemID <= 0xF63) || (itemID >= 0x13AF && itemID <= 0x13B0) ||
|
||||
(itemID >= 0x13B5 && itemID <= 0x13BA) || (itemID >= 0x13FA && itemID <= 0x13FB) ||
|
||||
(itemID >= 0x13FE && itemID <= 0x1407) || (itemID >= 0x1438 && itemID <= 0x1443);
|
||||
|
||||
private static bool IsArcheryWeapon(int itemID) =>
|
||||
(itemID >= 0xF4F && itemID <= 0xF50) || (itemID >= 0x13B1 && itemID <= 0x13B2) ||
|
||||
(itemID >= 0x13FC && itemID <= 0x13FD);
|
||||
|
||||
private static ShopFlags ProcessDisplayedItem(int itemID)
|
||||
{
|
||||
itemID &= TileData.MaxItemValue;
|
||||
|
||||
ShopFlags res = ShopFlags.None;
|
||||
|
||||
ItemData id = TileData.ItemTable[itemID];
|
||||
TileFlag flags = id.Flags;
|
||||
|
||||
if ((flags & TileFlag.Wearable) != 0)
|
||||
{
|
||||
if (IsClothes(itemID))
|
||||
res |= ShopFlags.Clothes;
|
||||
else if (IsArmor(itemID))
|
||||
res |= ShopFlags.Armor;
|
||||
else if (IsMetalWeapon(itemID))
|
||||
res |= ShopFlags.MetalWeapon;
|
||||
else if (IsArcheryWeapon(itemID))
|
||||
res |= ShopFlags.ArcheryWeapon;
|
||||
}
|
||||
|
||||
if (itemID == 0x98C || itemID == 0x103B || itemID == 0x103C)
|
||||
res |= ShopFlags.Bread;
|
||||
|
||||
if (itemID >= 0xF0F && itemID <= 0xF30)
|
||||
res |= ShopFlags.Jewel;
|
||||
|
||||
if (itemID >= 0xEFB && itemID <= 0xF0D)
|
||||
res |= ShopFlags.Potion;
|
||||
|
||||
if (itemID >= 0xF78 && itemID <= 0xF91)
|
||||
res |= ShopFlags.Reagent;
|
||||
|
||||
if ((itemID >= 0xE35 && itemID <= 0xE3A) || (itemID >= 0xEF4 && itemID <= 0xEF9) ||
|
||||
(itemID >= 0x1F2D && itemID <= 0x1F72))
|
||||
res |= ShopFlags.Scroll;
|
||||
|
||||
if (itemID == 0xE38 || itemID == 0xEFA)
|
||||
res |= ShopFlags.Spellbook;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void ProcessDisplayCase(Map map, StaticTile[] tiles, int x, int y)
|
||||
{
|
||||
ShopFlags flags = tiles.Aggregate(ShopFlags.None, (current, t) => current | ProcessDisplayedItem(t.ID));
|
||||
|
||||
if (flags != ShopFlags.None)
|
||||
{
|
||||
Point2D p = new Point2D(x, y);
|
||||
|
||||
if (m_ShopTable.TryGetValue(p, out ShopInfo si))
|
||||
si.m_Flags |= flags;
|
||||
else
|
||||
{
|
||||
List<Point2D> floor = new List<Point2D>();
|
||||
|
||||
RecurseFindFloor(map, x, y, floor);
|
||||
|
||||
if (floor.Count == 0)
|
||||
return;
|
||||
|
||||
si = new ShopInfo { m_Flags = flags, m_Floor = floor };
|
||||
m_ShopList.Add(si);
|
||||
|
||||
for (int i = 0; i < floor.Count; ++i)
|
||||
m_ShopTable[floor[i]] = si;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanFit(Map map, int x, int y, int z)
|
||||
{
|
||||
bool hasSurface = false;
|
||||
|
||||
LandTile lt = map.Tiles.GetLandTile(x, y);
|
||||
int lowZ = 0, avgZ = 0, topZ = 0;
|
||||
|
||||
map.GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ);
|
||||
TileFlag landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags;
|
||||
|
||||
if ((landFlags & TileFlag.Impassable) != 0 && topZ > z && z + 16 > lowZ)
|
||||
return false;
|
||||
if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored)
|
||||
hasSurface = true;
|
||||
|
||||
StaticTile[] staticTiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
bool surface, impassable;
|
||||
|
||||
for (int i = 0; i < staticTiles.Length; ++i)
|
||||
{
|
||||
if (IsDisplayCase(staticTiles[i].ID))
|
||||
continue;
|
||||
|
||||
ItemData id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue];
|
||||
|
||||
surface = id.Surface;
|
||||
impassable = id.Impassable;
|
||||
|
||||
if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + 16 > staticTiles[i].Z)
|
||||
return false;
|
||||
if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight)
|
||||
hasSurface = true;
|
||||
}
|
||||
|
||||
Sector sector = map.GetSector(x, y);
|
||||
List<Item> items = sector.Items;
|
||||
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
Item item = items[i];
|
||||
|
||||
if (item.AtWorldPoint(x, y))
|
||||
{
|
||||
ItemData id = item.ItemData;
|
||||
surface = id.Surface;
|
||||
impassable = id.Impassable;
|
||||
|
||||
if ((surface || impassable) && item.Z + id.CalcHeight > z && z + 16 > item.Z)
|
||||
z = 0;
|
||||
return false;
|
||||
if (surface && !impassable && z == item.Z + id.CalcHeight)
|
||||
hasSurface = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasSurface;
|
||||
private static bool IsFloor(Map map, int x, int y, bool canFit)
|
||||
{
|
||||
var lt = map.Tiles.GetLandTile(x, y);
|
||||
|
||||
if (IsFloor(lt.ID) && (canFit || CanFit(map, x, y, lt.Z)))
|
||||
return true;
|
||||
|
||||
var tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
for (var i = 0; i < tiles.Length; ++i)
|
||||
{
|
||||
var t = tiles[i];
|
||||
var id = TileData.ItemTable[t.ID & TileData.MaxItemValue];
|
||||
|
||||
if (IsStaticFloor(t.ID) && (canFit || CanFit(map, x, y, t.Z + (id.Surface ? id.CalcHeight : 0))))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsFloor(int itemID)
|
||||
{
|
||||
itemID &= TileData.MaxLandValue;
|
||||
|
||||
return itemID >= 0x406 && itemID <= 0x51A;
|
||||
}
|
||||
|
||||
private static bool IsStaticFloor(int itemID) =>
|
||||
itemID >= 0x495 && itemID <= 0x514
|
||||
|| itemID >= 0x519 && itemID <= 0x53A;
|
||||
|
||||
private static bool IsDisplayCase(int itemID) =>
|
||||
itemID >= 0xB00 && itemID <= 0xB02
|
||||
|| itemID >= 0xB06 && itemID <= 0xB0A
|
||||
|| itemID >= 0xB0D && itemID <= 0xB17;
|
||||
|
||||
private static void Process(Map map, Rectangle2D[] regions)
|
||||
{
|
||||
m_ShopTable = new Dictionary<Point2D, ShopInfo>();
|
||||
m_ShopList = new List<ShopInfo>();
|
||||
|
||||
World.Broadcast(0x35, true, "Generating vendor spawns for {0}, please wait.", map);
|
||||
|
||||
for (var i = 0; i < regions.Length; ++i)
|
||||
for (var x = 0; x < map.Width; ++x)
|
||||
for (var y = 0; y < map.Height; ++y)
|
||||
CheckPoint(map, regions[i].X + x, regions[i].Y + y);
|
||||
|
||||
for (var i = 0; i < m_ShopList.Count; ++i)
|
||||
{
|
||||
var si = m_ShopList[i];
|
||||
|
||||
var xTotal = 0;
|
||||
var yTotal = 0;
|
||||
|
||||
var hasSpawner = false;
|
||||
|
||||
for (var j = 0; j < si.m_Floor.Count; ++j)
|
||||
{
|
||||
var fp = si.m_Floor[j];
|
||||
|
||||
xTotal += fp.X;
|
||||
yTotal += fp.Y;
|
||||
|
||||
var eable = map.GetItemsInRange<Spawner>(new Point3D(fp.X, fp.Y, 0), 0);
|
||||
hasSpawner = eable.Any();
|
||||
eable.Free();
|
||||
|
||||
if (hasSpawner)
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasSpawner)
|
||||
continue;
|
||||
|
||||
var xAvg = xTotal / si.m_Floor.Count;
|
||||
var yAvg = yTotal / si.m_Floor.Count;
|
||||
|
||||
var names = new List<string>();
|
||||
var flags = si.m_Flags;
|
||||
|
||||
if ((flags & ShopFlags.Armor) != 0)
|
||||
names.Add("armorer");
|
||||
|
||||
if ((flags & ShopFlags.MetalWeapon) != 0)
|
||||
names.Add("weaponsmith");
|
||||
|
||||
if ((flags & ShopFlags.ArcheryWeapon) != 0)
|
||||
names.Add("bowyer");
|
||||
|
||||
if ((flags & ShopFlags.Scroll) != 0)
|
||||
names.Add("mage");
|
||||
|
||||
if ((flags & ShopFlags.Spellbook) != 0)
|
||||
names.Add("mage");
|
||||
|
||||
if ((flags & ShopFlags.Bread) != 0)
|
||||
names.Add("baker");
|
||||
|
||||
if ((flags & ShopFlags.Jewel) != 0)
|
||||
names.Add("jeweler");
|
||||
|
||||
if ((flags & ShopFlags.Potion) != 0)
|
||||
{
|
||||
names.Add("herbalist");
|
||||
names.Add("alchemist");
|
||||
names.Add("mage");
|
||||
}
|
||||
|
||||
if ((flags & ShopFlags.Reagent) != 0)
|
||||
{
|
||||
names.Add("mage");
|
||||
names.Add("herbalist");
|
||||
}
|
||||
|
||||
if ((flags & ShopFlags.Clothes) != 0)
|
||||
{
|
||||
names.Add("tailor");
|
||||
names.Add("weaver");
|
||||
}
|
||||
|
||||
for (var j = 0; j < names.Count; ++j)
|
||||
{
|
||||
var cp = Point2D.Zero;
|
||||
var dist = 100000;
|
||||
|
||||
for (var k = 0; k < si.m_Floor.Count; ++k)
|
||||
{
|
||||
var fp = si.m_Floor[k];
|
||||
|
||||
var rx = fp.X - xAvg;
|
||||
var ry = fp.Y - yAvg;
|
||||
var fd = (int)Math.Sqrt(rx * rx + ry * ry);
|
||||
|
||||
if (fd > 0 && fd < 5)
|
||||
fd -= Utility.Random(10);
|
||||
|
||||
if (fd < dist && GetFloorZ(map, fp.X, fp.Y, out _))
|
||||
{
|
||||
dist = fd;
|
||||
cp = fp;
|
||||
}
|
||||
}
|
||||
|
||||
if (cp == Point2D.Zero)
|
||||
continue;
|
||||
|
||||
if (!GetFloorZ(map, cp.X, cp.Y, out var z))
|
||||
continue;
|
||||
|
||||
new Spawner(1, 1, 1, 0, 4, names[j]).MoveToWorld(new Point3D(cp.X, cp.Y, z), map);
|
||||
}
|
||||
}
|
||||
|
||||
World.Broadcast(0x35, true, "Generation complete. {0} spawners generated.", m_ShopList.Count);
|
||||
}
|
||||
|
||||
private static void CheckPoint(Map map, int x, int y)
|
||||
{
|
||||
if (IsFloor(map, x, y, true))
|
||||
CheckFloor(map, x, y);
|
||||
}
|
||||
|
||||
private static void CheckFloor(Map map, int x, int y)
|
||||
{
|
||||
var tiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
for (var i = 0; i < tiles.Length; ++i)
|
||||
if (IsDisplayCase(tiles[i].ID))
|
||||
{
|
||||
ProcessDisplayCase(map, tiles, x, y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsClothes(int itemID) =>
|
||||
itemID >= 0x1515 && itemID <= 0x1518 || itemID >= 0x152E && itemID <= 0x1531 || itemID >= 0x1537
|
||||
&& itemID <= 0x154C || itemID >= 0x1EFD && itemID <= 0x1F04 || itemID >= 0x170B && itemID <= 0x171C;
|
||||
|
||||
private static bool IsArmor(int itemID) =>
|
||||
itemID >= 0x13BB && itemID <= 0x13E2 || itemID >= 0x13E5 && itemID <= 0x13F2 ||
|
||||
itemID >= 0x1408 && itemID <= 0x141A || itemID >= 0x144E && itemID <= 0x1457;
|
||||
|
||||
private static bool IsMetalWeapon(int itemID) =>
|
||||
itemID >= 0xF43 && itemID <= 0xF4E || itemID >= 0xF51 && itemID <= 0xF52 ||
|
||||
itemID >= 0xF5C && itemID <= 0xF63 || itemID >= 0x13AF && itemID <= 0x13B0 ||
|
||||
itemID >= 0x13B5 && itemID <= 0x13BA || itemID >= 0x13FA && itemID <= 0x13FB ||
|
||||
itemID >= 0x13FE && itemID <= 0x1407 || itemID >= 0x1438 && itemID <= 0x1443;
|
||||
|
||||
private static bool IsArcheryWeapon(int itemID) =>
|
||||
itemID >= 0xF4F && itemID <= 0xF50 || itemID >= 0x13B1 && itemID <= 0x13B2 ||
|
||||
itemID >= 0x13FC && itemID <= 0x13FD;
|
||||
|
||||
private static ShopFlags ProcessDisplayedItem(int itemID)
|
||||
{
|
||||
itemID &= TileData.MaxItemValue;
|
||||
|
||||
var res = ShopFlags.None;
|
||||
|
||||
var id = TileData.ItemTable[itemID];
|
||||
var flags = id.Flags;
|
||||
|
||||
if ((flags & TileFlag.Wearable) != 0)
|
||||
{
|
||||
if (IsClothes(itemID))
|
||||
res |= ShopFlags.Clothes;
|
||||
else if (IsArmor(itemID))
|
||||
res |= ShopFlags.Armor;
|
||||
else if (IsMetalWeapon(itemID))
|
||||
res |= ShopFlags.MetalWeapon;
|
||||
else if (IsArcheryWeapon(itemID))
|
||||
res |= ShopFlags.ArcheryWeapon;
|
||||
}
|
||||
|
||||
if (itemID == 0x98C || itemID == 0x103B || itemID == 0x103C)
|
||||
res |= ShopFlags.Bread;
|
||||
|
||||
if (itemID >= 0xF0F && itemID <= 0xF30)
|
||||
res |= ShopFlags.Jewel;
|
||||
|
||||
if (itemID >= 0xEFB && itemID <= 0xF0D)
|
||||
res |= ShopFlags.Potion;
|
||||
|
||||
if (itemID >= 0xF78 && itemID <= 0xF91)
|
||||
res |= ShopFlags.Reagent;
|
||||
|
||||
if (itemID >= 0xE35 && itemID <= 0xE3A || itemID >= 0xEF4 && itemID <= 0xEF9 ||
|
||||
itemID >= 0x1F2D && itemID <= 0x1F72)
|
||||
res |= ShopFlags.Scroll;
|
||||
|
||||
if (itemID == 0xE38 || itemID == 0xEFA)
|
||||
res |= ShopFlags.Spellbook;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void ProcessDisplayCase(Map map, StaticTile[] tiles, int x, int y)
|
||||
{
|
||||
var flags = tiles.Aggregate(ShopFlags.None, (current, t) => current | ProcessDisplayedItem(t.ID));
|
||||
|
||||
if (flags != ShopFlags.None)
|
||||
{
|
||||
var p = new Point2D(x, y);
|
||||
|
||||
if (m_ShopTable.TryGetValue(p, out var si))
|
||||
{
|
||||
si.m_Flags |= flags;
|
||||
}
|
||||
else
|
||||
{
|
||||
var floor = new List<Point2D>();
|
||||
|
||||
RecurseFindFloor(map, x, y, floor);
|
||||
|
||||
if (floor.Count == 0)
|
||||
return;
|
||||
|
||||
si = new ShopInfo { m_Flags = flags, m_Floor = floor };
|
||||
m_ShopList.Add(si);
|
||||
|
||||
for (var i = 0; i < floor.Count; ++i)
|
||||
m_ShopTable[floor[i]] = si;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanFit(Map map, int x, int y, int z)
|
||||
{
|
||||
var hasSurface = false;
|
||||
|
||||
var lt = map.Tiles.GetLandTile(x, y);
|
||||
int lowZ = 0, avgZ = 0, topZ = 0;
|
||||
|
||||
map.GetAverageZ(x, y, ref lowZ, ref avgZ, ref topZ);
|
||||
var landFlags = TileData.LandTable[lt.ID & TileData.MaxLandValue].Flags;
|
||||
|
||||
if ((landFlags & TileFlag.Impassable) != 0 && topZ > z && z + 16 > lowZ)
|
||||
return false;
|
||||
if ((landFlags & TileFlag.Impassable) == 0 && z == avgZ && !lt.Ignored)
|
||||
hasSurface = true;
|
||||
|
||||
var staticTiles = map.Tiles.GetStaticTiles(x, y);
|
||||
|
||||
bool surface, impassable;
|
||||
|
||||
for (var i = 0; i < staticTiles.Length; ++i)
|
||||
{
|
||||
if (IsDisplayCase(staticTiles[i].ID))
|
||||
continue;
|
||||
|
||||
var id = TileData.ItemTable[staticTiles[i].ID & TileData.MaxItemValue];
|
||||
|
||||
surface = id.Surface;
|
||||
impassable = id.Impassable;
|
||||
|
||||
if ((surface || impassable) && staticTiles[i].Z + id.CalcHeight > z && z + 16 > staticTiles[i].Z)
|
||||
return false;
|
||||
if (surface && !impassable && z == staticTiles[i].Z + id.CalcHeight)
|
||||
hasSurface = true;
|
||||
}
|
||||
|
||||
var sector = map.GetSector(x, y);
|
||||
var items = sector.Items;
|
||||
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
var item = items[i];
|
||||
|
||||
if (item.AtWorldPoint(x, y))
|
||||
{
|
||||
var id = item.ItemData;
|
||||
surface = id.Surface;
|
||||
impassable = id.Impassable;
|
||||
|
||||
if ((surface || impassable) && item.Z + id.CalcHeight > z && z + 16 > item.Z)
|
||||
return false;
|
||||
if (surface && !impassable && z == item.Z + id.CalcHeight)
|
||||
hasSurface = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasSurface;
|
||||
}
|
||||
|
||||
private static void RecurseFindFloor(Map map, int x, int y, List<Point2D> floor)
|
||||
{
|
||||
var p = new Point2D(x, y);
|
||||
|
||||
if (floor.Contains(p))
|
||||
return;
|
||||
|
||||
floor.Add(p);
|
||||
|
||||
for (var xo = -1; xo <= 1; ++xo)
|
||||
for (var yo = -1; yo <= 1; ++yo)
|
||||
if ((xo != 0 || yo != 0) && IsFloor(map, x + xo, y + yo, false))
|
||||
RecurseFindFloor(map, x + xo, y + yo, floor);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ShopFlags
|
||||
{
|
||||
None = 0x000,
|
||||
Armor = 0x001,
|
||||
MetalWeapon = 0x002,
|
||||
Jewel = 0x004,
|
||||
Reagent = 0x008,
|
||||
Potion = 0x010,
|
||||
Bread = 0x020,
|
||||
Clothes = 0x040,
|
||||
ArcheryWeapon = 0x080,
|
||||
Scroll = 0x100,
|
||||
Spellbook = 0x200
|
||||
}
|
||||
|
||||
private class ShopInfo
|
||||
{
|
||||
public ShopFlags m_Flags;
|
||||
public List<Point2D> m_Floor;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RecurseFindFloor(Map map, int x, int y, List<Point2D> floor)
|
||||
{
|
||||
Point2D p = new Point2D(x, y);
|
||||
|
||||
if (floor.Contains(p))
|
||||
return;
|
||||
|
||||
floor.Add(p);
|
||||
|
||||
for (int xo = -1; xo <= 1; ++xo)
|
||||
for (int yo = -1; yo <= 1; ++yo)
|
||||
if ((xo != 0 || yo != 0) && IsFloor(map, x + xo, y + yo, false))
|
||||
RecurseFindFloor(map, x + xo, y + yo, floor);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum ShopFlags
|
||||
{
|
||||
None = 0x000,
|
||||
Armor = 0x001,
|
||||
MetalWeapon = 0x002,
|
||||
Jewel = 0x004,
|
||||
Reagent = 0x008,
|
||||
Potion = 0x010,
|
||||
Bread = 0x020,
|
||||
Clothes = 0x040,
|
||||
ArcheryWeapon = 0x080,
|
||||
Scroll = 0x100,
|
||||
Spellbook = 0x200
|
||||
}
|
||||
|
||||
private class ShopInfo
|
||||
{
|
||||
public ShopFlags m_Flags;
|
||||
public List<Point2D> m_Floor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,346 +5,391 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class Weather
|
||||
{
|
||||
private static Map[] m_Facets;
|
||||
private static readonly Dictionary<Map, List<Weather>> m_WeatherByFacet = new Dictionary<Map, List<Weather>>();
|
||||
|
||||
public static void Initialize()
|
||||
public class Weather
|
||||
{
|
||||
m_Facets = new[] { Map.Felucca, Map.Trammel };
|
||||
private static Map[] m_Facets;
|
||||
private static readonly Dictionary<Map, List<Weather>> m_WeatherByFacet = new Dictionary<Map, List<Weather>>();
|
||||
private bool m_Active;
|
||||
private bool m_ExtremeTemperature;
|
||||
|
||||
/* Static weather:
|
||||
*
|
||||
* Format:
|
||||
* AddWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, <area ...> );
|
||||
*/
|
||||
private int m_Stage;
|
||||
|
||||
// ice island
|
||||
AddWeather(-15, 100, 5, new Rectangle2D(3850, 160, 390, 320), new Rectangle2D(3900, 480, 380, 180), new Rectangle2D(4160, 660, 150, 110));
|
||||
|
||||
// covetous entrance, around vesper and minoc
|
||||
AddWeather(+15, 50, 5, new Rectangle2D(2425, 725, 250, 250));
|
||||
|
||||
// despise entrance, north of britain
|
||||
AddWeather(+15, 50, 5, new Rectangle2D(1245, 1045, 250, 250));
|
||||
|
||||
/* Dynamic weather:
|
||||
*
|
||||
* Format:
|
||||
* AddDynamicWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, moveSpeed, width, height, bounds );
|
||||
*/
|
||||
|
||||
for (int i = 0; i < 15; ++i)
|
||||
AddDynamicWeather(+15, 100, 5, 8, 400, 400, new Rectangle2D(0, 0, 5120, 4096));
|
||||
}
|
||||
|
||||
public static List<Weather> GetWeatherList(Map facet)
|
||||
{
|
||||
if (facet == null)
|
||||
return null;
|
||||
|
||||
if (!m_WeatherByFacet.TryGetValue(facet, out List<Weather> list))
|
||||
m_WeatherByFacet[facet] = list = new List<Weather>();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void AddDynamicWeather(int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, int moveSpeed, int width, int height, Rectangle2D bounds)
|
||||
{
|
||||
for (int i = 0; i < m_Facets.Length; ++i)
|
||||
{
|
||||
Rectangle2D area = new Rectangle2D();
|
||||
bool isValid = false;
|
||||
|
||||
for (int j = 0; j < 10; ++j)
|
||||
public Weather(
|
||||
Map facet, Rectangle2D[] area, int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature,
|
||||
TimeSpan interval
|
||||
)
|
||||
{
|
||||
area = new Rectangle2D(bounds.X + Utility.Random(bounds.Width - width), bounds.Y + Utility.Random(bounds.Height - height), width, height);
|
||||
Facet = facet;
|
||||
Area = area;
|
||||
Temperature = temperature;
|
||||
ChanceOfPercipitation = chanceOfPercipitation;
|
||||
ChanceOfExtremeTemperature = chanceOfExtremeTemperature;
|
||||
|
||||
if (!CheckWeatherConflict(m_Facets[i], null, area))
|
||||
isValid = true;
|
||||
var list = GetWeatherList(facet);
|
||||
|
||||
if (isValid)
|
||||
break;
|
||||
list?.Add(this);
|
||||
|
||||
Timer.DelayCall(
|
||||
TimeSpan.FromSeconds((0.2 + Utility.RandomDouble() * 0.8) * interval.TotalSeconds),
|
||||
interval,
|
||||
OnTick
|
||||
);
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
continue;
|
||||
public Map Facet { get; }
|
||||
|
||||
new Weather(m_Facets[i], new[] { area }, temperature, chanceOfPercipitation, chanceOfExtremeTemperature,
|
||||
TimeSpan.FromSeconds(30.0))
|
||||
{ Bounds = bounds, MoveSpeed = moveSpeed };
|
||||
}
|
||||
}
|
||||
public Rectangle2D[] Area { get; set; }
|
||||
|
||||
public static void AddWeather(int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, params Rectangle2D[] area)
|
||||
{
|
||||
for (int i = 0; i < m_Facets.Length; ++i)
|
||||
new Weather(m_Facets[i], area, temperature, chanceOfPercipitation, chanceOfExtremeTemperature, TimeSpan.FromSeconds(30.0));
|
||||
}
|
||||
public int Temperature { get; set; }
|
||||
|
||||
public static bool CheckWeatherConflict(Map facet, Weather exclude, Rectangle2D area)
|
||||
{
|
||||
List<Weather> list = GetWeatherList(facet);
|
||||
public int ChanceOfPercipitation { get; set; }
|
||||
|
||||
if (list == null)
|
||||
return false;
|
||||
public int ChanceOfExtremeTemperature { get; set; }
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
Weather w = list[i];
|
||||
// For dynamic weather:
|
||||
|
||||
if (w != exclude && w.IntersectsWith(area))
|
||||
return true;
|
||||
}
|
||||
public Rectangle2D Bounds { get; set; }
|
||||
|
||||
return false;
|
||||
}
|
||||
public int MoveSpeed { get; set; }
|
||||
|
||||
public Map Facet { get; }
|
||||
public int MoveAngleX { get; set; }
|
||||
|
||||
public Rectangle2D[] Area { get; set; }
|
||||
public int MoveAngleY { get; set; }
|
||||
|
||||
public int Temperature { get; set; }
|
||||
|
||||
public int ChanceOfPercipitation { get; set; }
|
||||
|
||||
public int ChanceOfExtremeTemperature { get; set; }
|
||||
|
||||
// For dynamic weather:
|
||||
|
||||
public Rectangle2D Bounds { get; set; }
|
||||
|
||||
public int MoveSpeed { get; set; }
|
||||
|
||||
public int MoveAngleX { get; set; }
|
||||
|
||||
public int MoveAngleY { get; set; }
|
||||
|
||||
public static bool CheckIntersection(Rectangle2D r1, Rectangle2D r2) => r1.X < r2.X + r2.Width && r2.X < r1.X + r1.Width && r1.Y < r2.Y + r2.Height && r2.Y < r1.Y + r1.Height;
|
||||
|
||||
public static bool CheckContains(Rectangle2D big, Rectangle2D small) =>
|
||||
small.X >= big.X && small.Y >= big.Y && small.X + small.Width <= big.X + big.Width
|
||||
&& small.Y + small.Height <= big.Y + big.Height;
|
||||
|
||||
public virtual bool IntersectsWith(Rectangle2D area)
|
||||
{
|
||||
for (int i = 0; i < Area.Length; ++i)
|
||||
if (CheckIntersection(area, Area[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Weather(Map facet, Rectangle2D[] area, int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, TimeSpan interval)
|
||||
{
|
||||
Facet = facet;
|
||||
Area = area;
|
||||
Temperature = temperature;
|
||||
ChanceOfPercipitation = chanceOfPercipitation;
|
||||
ChanceOfExtremeTemperature = chanceOfExtremeTemperature;
|
||||
|
||||
List<Weather> list = GetWeatherList(facet);
|
||||
|
||||
list?.Add(this);
|
||||
|
||||
Timer.DelayCall(TimeSpan.FromSeconds((0.2 + Utility.RandomDouble() * 0.8) * interval.TotalSeconds), interval, OnTick);
|
||||
}
|
||||
|
||||
public virtual void Reposition()
|
||||
{
|
||||
if (Area.Length == 0)
|
||||
return;
|
||||
|
||||
int width = Area[0].Width;
|
||||
int height = Area[0].Height;
|
||||
|
||||
Rectangle2D area = new Rectangle2D();
|
||||
bool isValid = false;
|
||||
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
area = new Rectangle2D(Bounds.X + Utility.Random(Bounds.Width - width), Bounds.Y + Utility.Random(Bounds.Height - height), width, height);
|
||||
|
||||
if (!CheckWeatherConflict(Facet, this, area))
|
||||
isValid = true;
|
||||
|
||||
if (isValid)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
Area[0] = area;
|
||||
}
|
||||
|
||||
public virtual void RecalculateMovementAngle()
|
||||
{
|
||||
double angle = Utility.RandomDouble() * Math.PI * 2.0;
|
||||
|
||||
double cos = Math.Cos(angle);
|
||||
double sin = Math.Sin(angle);
|
||||
|
||||
MoveAngleX = (int)(100 * cos);
|
||||
MoveAngleY = (int)(100 * sin);
|
||||
}
|
||||
|
||||
public virtual void MoveForward()
|
||||
{
|
||||
if (Area.Length == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 5; ++i) // try 5 times to find a valid spot
|
||||
{
|
||||
int xOffset = MoveSpeed * MoveAngleX / 100;
|
||||
int yOffset = MoveSpeed * MoveAngleY / 100;
|
||||
|
||||
Rectangle2D oldArea = Area[0];
|
||||
Rectangle2D newArea = new Rectangle2D(oldArea.X + xOffset, oldArea.Y + yOffset, oldArea.Width, oldArea.Height);
|
||||
|
||||
if (!CheckWeatherConflict(Facet, this, newArea) && CheckContains(Bounds, newArea))
|
||||
public static void Initialize()
|
||||
{
|
||||
Area[0] = newArea;
|
||||
break;
|
||||
m_Facets = new[] { Map.Felucca, Map.Trammel };
|
||||
|
||||
/* Static weather:
|
||||
*
|
||||
* Format:
|
||||
* AddWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, <area ...> );
|
||||
*/
|
||||
|
||||
// ice island
|
||||
AddWeather(
|
||||
-15,
|
||||
100,
|
||||
5,
|
||||
new Rectangle2D(3850, 160, 390, 320),
|
||||
new Rectangle2D(3900, 480, 380, 180),
|
||||
new Rectangle2D(4160, 660, 150, 110)
|
||||
);
|
||||
|
||||
// covetous entrance, around vesper and minoc
|
||||
AddWeather(+15, 50, 5, new Rectangle2D(2425, 725, 250, 250));
|
||||
|
||||
// despise entrance, north of britain
|
||||
AddWeather(+15, 50, 5, new Rectangle2D(1245, 1045, 250, 250));
|
||||
|
||||
/* Dynamic weather:
|
||||
*
|
||||
* Format:
|
||||
* AddDynamicWeather( temperature, chanceOfPercipitation, chanceOfExtremeTemperature, moveSpeed, width, height, bounds );
|
||||
*/
|
||||
|
||||
for (var i = 0; i < 15; ++i)
|
||||
AddDynamicWeather(+15, 100, 5, 8, 400, 400, new Rectangle2D(0, 0, 5120, 4096));
|
||||
}
|
||||
|
||||
RecalculateMovementAngle();
|
||||
}
|
||||
}
|
||||
|
||||
private int m_Stage;
|
||||
private bool m_Active;
|
||||
private bool m_ExtremeTemperature;
|
||||
|
||||
public virtual void OnTick()
|
||||
{
|
||||
if (m_Stage == 0)
|
||||
{
|
||||
m_Active = ChanceOfPercipitation > Utility.Random(100);
|
||||
m_ExtremeTemperature = ChanceOfExtremeTemperature > Utility.Random(100);
|
||||
|
||||
if (MoveSpeed > 0)
|
||||
public static List<Weather> GetWeatherList(Map facet)
|
||||
{
|
||||
Reposition();
|
||||
RecalculateMovementAngle();
|
||||
}
|
||||
}
|
||||
if (facet == null)
|
||||
return null;
|
||||
|
||||
if (m_Active)
|
||||
{
|
||||
if (m_Stage > 0 && MoveSpeed > 0)
|
||||
MoveForward();
|
||||
if (!m_WeatherByFacet.TryGetValue(facet, out var list))
|
||||
m_WeatherByFacet[facet] = list = new List<Weather>();
|
||||
|
||||
int type, density;
|
||||
int temperature = Temperature;
|
||||
|
||||
if (m_ExtremeTemperature)
|
||||
temperature *= -1;
|
||||
|
||||
if (m_Stage < 15)
|
||||
{
|
||||
density = m_Stage * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
density = 150 - m_Stage * 5;
|
||||
|
||||
if (density < 10)
|
||||
density = 10;
|
||||
else if (density > 70)
|
||||
density = 70;
|
||||
return list;
|
||||
}
|
||||
|
||||
if (density == 0)
|
||||
type = 0xFE;
|
||||
else if (temperature > 0)
|
||||
type = 0;
|
||||
else
|
||||
type = 2;
|
||||
|
||||
List<NetState> states = TcpServer.Instances;
|
||||
|
||||
Packet weatherPacket = null;
|
||||
|
||||
for (int i = 0; i < states.Count; ++i)
|
||||
public static void AddDynamicWeather(
|
||||
int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, int moveSpeed, int width, int height,
|
||||
Rectangle2D bounds
|
||||
)
|
||||
{
|
||||
NetState ns = states[i];
|
||||
Mobile mob = ns.Mobile;
|
||||
for (var i = 0; i < m_Facets.Length; ++i)
|
||||
{
|
||||
var area = new Rectangle2D();
|
||||
var isValid = false;
|
||||
|
||||
if (mob == null || mob.Map != Facet)
|
||||
continue;
|
||||
for (var j = 0; j < 10; ++j)
|
||||
{
|
||||
area = new Rectangle2D(
|
||||
bounds.X + Utility.Random(bounds.Width - width),
|
||||
bounds.Y + Utility.Random(bounds.Height - height),
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
bool contains = Area.Length == 0;
|
||||
if (!CheckWeatherConflict(m_Facets[i], null, area))
|
||||
isValid = true;
|
||||
|
||||
for (int j = 0; !contains && j < Area.Length; ++j)
|
||||
contains = Area[j].Contains(mob.Location);
|
||||
if (isValid)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!contains)
|
||||
continue;
|
||||
if (!isValid)
|
||||
continue;
|
||||
|
||||
if (weatherPacket == null)
|
||||
weatherPacket = Packet.Acquire(new Server.Network.Weather(type, density, temperature));
|
||||
|
||||
ns.Send(weatherPacket);
|
||||
new Weather(
|
||||
m_Facets[i],
|
||||
new[] { area },
|
||||
temperature,
|
||||
chanceOfPercipitation,
|
||||
chanceOfExtremeTemperature,
|
||||
TimeSpan.FromSeconds(30.0)
|
||||
)
|
||||
{ Bounds = bounds, MoveSpeed = moveSpeed };
|
||||
}
|
||||
}
|
||||
|
||||
Packet.Release(weatherPacket);
|
||||
}
|
||||
public static void AddWeather(
|
||||
int temperature, int chanceOfPercipitation, int chanceOfExtremeTemperature, params Rectangle2D[] area
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < m_Facets.Length; ++i)
|
||||
new Weather(
|
||||
m_Facets[i],
|
||||
area,
|
||||
temperature,
|
||||
chanceOfPercipitation,
|
||||
chanceOfExtremeTemperature,
|
||||
TimeSpan.FromSeconds(30.0)
|
||||
);
|
||||
}
|
||||
|
||||
m_Stage++;
|
||||
m_Stage %= 30;
|
||||
public static bool CheckWeatherConflict(Map facet, Weather exclude, Rectangle2D area)
|
||||
{
|
||||
var list = GetWeatherList(facet);
|
||||
|
||||
if (list == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var w = list[i];
|
||||
|
||||
if (w != exclude && w.IntersectsWith(area))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CheckIntersection(Rectangle2D r1, Rectangle2D r2) => r1.X < r2.X + r2.Width &&
|
||||
r2.X < r1.X + r1.Width &&
|
||||
r1.Y < r2.Y + r2.Height &&
|
||||
r2.Y < r1.Y + r1.Height;
|
||||
|
||||
public static bool CheckContains(Rectangle2D big, Rectangle2D small) =>
|
||||
small.X >= big.X && small.Y >= big.Y && small.X + small.Width <= big.X + big.Width
|
||||
&& small.Y + small.Height <= big.Y + big.Height;
|
||||
|
||||
public virtual bool IntersectsWith(Rectangle2D area)
|
||||
{
|
||||
for (var i = 0; i < Area.Length; ++i)
|
||||
if (CheckIntersection(area, Area[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Reposition()
|
||||
{
|
||||
if (Area.Length == 0)
|
||||
return;
|
||||
|
||||
var width = Area[0].Width;
|
||||
var height = Area[0].Height;
|
||||
|
||||
var area = new Rectangle2D();
|
||||
var isValid = false;
|
||||
|
||||
for (var j = 0; j < 10; ++j)
|
||||
{
|
||||
area = new Rectangle2D(
|
||||
Bounds.X + Utility.Random(Bounds.Width - width),
|
||||
Bounds.Y + Utility.Random(Bounds.Height - height),
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
if (!CheckWeatherConflict(Facet, this, area))
|
||||
isValid = true;
|
||||
|
||||
if (isValid)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isValid)
|
||||
return;
|
||||
|
||||
Area[0] = area;
|
||||
}
|
||||
|
||||
public virtual void RecalculateMovementAngle()
|
||||
{
|
||||
var angle = Utility.RandomDouble() * Math.PI * 2.0;
|
||||
|
||||
var cos = Math.Cos(angle);
|
||||
var sin = Math.Sin(angle);
|
||||
|
||||
MoveAngleX = (int)(100 * cos);
|
||||
MoveAngleY = (int)(100 * sin);
|
||||
}
|
||||
|
||||
public virtual void MoveForward()
|
||||
{
|
||||
if (Area.Length == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < 5; ++i) // try 5 times to find a valid spot
|
||||
{
|
||||
var xOffset = MoveSpeed * MoveAngleX / 100;
|
||||
var yOffset = MoveSpeed * MoveAngleY / 100;
|
||||
|
||||
var oldArea = Area[0];
|
||||
var newArea = new Rectangle2D(oldArea.X + xOffset, oldArea.Y + yOffset, oldArea.Width, oldArea.Height);
|
||||
|
||||
if (!CheckWeatherConflict(Facet, this, newArea) && CheckContains(Bounds, newArea))
|
||||
{
|
||||
Area[0] = newArea;
|
||||
break;
|
||||
}
|
||||
|
||||
RecalculateMovementAngle();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnTick()
|
||||
{
|
||||
if (m_Stage == 0)
|
||||
{
|
||||
m_Active = ChanceOfPercipitation > Utility.Random(100);
|
||||
m_ExtremeTemperature = ChanceOfExtremeTemperature > Utility.Random(100);
|
||||
|
||||
if (MoveSpeed > 0)
|
||||
{
|
||||
Reposition();
|
||||
RecalculateMovementAngle();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Active)
|
||||
{
|
||||
if (m_Stage > 0 && MoveSpeed > 0)
|
||||
MoveForward();
|
||||
|
||||
int type, density;
|
||||
var temperature = Temperature;
|
||||
|
||||
if (m_ExtremeTemperature)
|
||||
temperature *= -1;
|
||||
|
||||
if (m_Stage < 15)
|
||||
{
|
||||
density = m_Stage * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
density = 150 - m_Stage * 5;
|
||||
|
||||
if (density < 10)
|
||||
density = 10;
|
||||
else if (density > 70)
|
||||
density = 70;
|
||||
}
|
||||
|
||||
if (density == 0)
|
||||
type = 0xFE;
|
||||
else if (temperature > 0)
|
||||
type = 0;
|
||||
else
|
||||
type = 2;
|
||||
|
||||
var states = TcpServer.Instances;
|
||||
|
||||
Packet weatherPacket = null;
|
||||
|
||||
for (var i = 0; i < states.Count; ++i)
|
||||
{
|
||||
var ns = states[i];
|
||||
var mob = ns.Mobile;
|
||||
|
||||
if (mob == null || mob.Map != Facet)
|
||||
continue;
|
||||
|
||||
var contains = Area.Length == 0;
|
||||
|
||||
for (var j = 0; !contains && j < Area.Length; ++j)
|
||||
contains = Area[j].Contains(mob.Location);
|
||||
|
||||
if (!contains)
|
||||
continue;
|
||||
|
||||
if (weatherPacket == null)
|
||||
weatherPacket = Packet.Acquire(new Network.Weather(type, density, temperature));
|
||||
|
||||
ns.Send(weatherPacket);
|
||||
}
|
||||
|
||||
Packet.Release(weatherPacket);
|
||||
}
|
||||
|
||||
m_Stage++;
|
||||
m_Stage %= 30;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class WeatherMap : MapItem
|
||||
{
|
||||
public override string DefaultName => "weather map";
|
||||
|
||||
[Constructible]
|
||||
public WeatherMap()
|
||||
public class WeatherMap : MapItem
|
||||
{
|
||||
SetDisplay(0, 0, 5119, 4095, 400, 400);
|
||||
[Constructible]
|
||||
public WeatherMap()
|
||||
{
|
||||
SetDisplay(0, 0, 5119, 4095, 400, 400);
|
||||
}
|
||||
|
||||
public WeatherMap(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override string DefaultName => "weather map";
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
var facet = from.Map;
|
||||
|
||||
if (facet == null)
|
||||
return;
|
||||
|
||||
var list = Weather.GetWeatherList(facet);
|
||||
|
||||
ClearPins();
|
||||
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var w = list[i];
|
||||
|
||||
for (var j = 0; j < w.Area.Length; ++j)
|
||||
AddWorldPin(w.Area[j].X + w.Area[j].Width / 2, w.Area[j].Y + w.Area[j].Height / 2);
|
||||
}
|
||||
|
||||
base.OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
var version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
Map facet = from.Map;
|
||||
|
||||
if (facet == null)
|
||||
return;
|
||||
|
||||
List<Weather> list = Weather.GetWeatherList(facet);
|
||||
|
||||
ClearPins();
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
Weather w = list[i];
|
||||
|
||||
for (int j = 0; j < w.Area.Length; ++j)
|
||||
AddWorldPin(w.Area[j].X + w.Area[j].Width / 2, w.Area[j].Y + w.Area[j].Height / 2);
|
||||
}
|
||||
|
||||
base.OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public WeatherMap(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,173 +8,174 @@ using Server.Network;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class StatusPage : Timer
|
||||
{
|
||||
public static readonly bool Enabled = false;
|
||||
|
||||
private static HttpListener _Listener;
|
||||
|
||||
private static string _StatusPage = string.Empty;
|
||||
private static byte[] _StatusBuffer = Array.Empty<byte>();
|
||||
|
||||
private static readonly object _StatusLock = new object();
|
||||
|
||||
public StatusPage()
|
||||
: base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(60.0)) =>
|
||||
Priority = TimerPriority.FiveSeconds;
|
||||
|
||||
public static void Initialize()
|
||||
public class StatusPage : Timer
|
||||
{
|
||||
if (!Enabled) return;
|
||||
public static readonly bool Enabled = false;
|
||||
|
||||
new StatusPage().Start();
|
||||
private static HttpListener _Listener;
|
||||
|
||||
Listen();
|
||||
}
|
||||
private static string _StatusPage = string.Empty;
|
||||
private static byte[] _StatusBuffer = Array.Empty<byte>();
|
||||
|
||||
private static void Listen()
|
||||
{
|
||||
if (!HttpListener.IsSupported) return;
|
||||
private static readonly object _StatusLock = new object();
|
||||
|
||||
if (_Listener == null)
|
||||
{
|
||||
_Listener = new HttpListener();
|
||||
_Listener.Prefixes.Add("http://*:80/status/");
|
||||
_Listener.Start();
|
||||
}
|
||||
else if (!_Listener.IsListening)
|
||||
{
|
||||
_Listener.Start();
|
||||
}
|
||||
public StatusPage()
|
||||
: base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(60.0)) =>
|
||||
Priority = TimerPriority.FiveSeconds;
|
||||
|
||||
if (_Listener.IsListening) _Listener.BeginGetContext(ListenerCallback, null);
|
||||
}
|
||||
|
||||
private static void ListenerCallback(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
HttpListenerContext context = _Listener.EndGetContext(result);
|
||||
|
||||
byte[] buffer;
|
||||
|
||||
lock (_StatusLock)
|
||||
public static void Initialize()
|
||||
{
|
||||
buffer = _StatusBuffer;
|
||||
if (!Enabled) return;
|
||||
|
||||
new StatusPage().Start();
|
||||
|
||||
Listen();
|
||||
}
|
||||
|
||||
context.Response.ContentLength64 = buffer.Length;
|
||||
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
context.Response.OutputStream.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
Listen();
|
||||
}
|
||||
|
||||
private static string Encode(string input)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(input);
|
||||
|
||||
sb.Replace("&", "&");
|
||||
sb.Replace("<", "<");
|
||||
sb.Replace(">", ">");
|
||||
sb.Replace("\"", """);
|
||||
sb.Replace("'", "'");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!Directory.Exists("web")) Directory.CreateDirectory("web");
|
||||
|
||||
using (StreamWriter op = new StreamWriter("web/status.html"))
|
||||
{
|
||||
op.WriteLine("<!DOCTYPE html>");
|
||||
op.WriteLine("<html>");
|
||||
op.WriteLine(" <head>");
|
||||
op.WriteLine($" <title>{ServerList.ServerName} Server Status</title>");
|
||||
op.WriteLine(" </head>");
|
||||
op.WriteLine(" <style type=\"text/css\">");
|
||||
op.WriteLine(" body { background: #999; }");
|
||||
op.WriteLine(" table { width: 100%; }");
|
||||
op.WriteLine(" tr.ruo-header td { background: #000; color: #FFF; }");
|
||||
op.WriteLine(" tr.odd td { background: #222; color: #DDD; }");
|
||||
op.WriteLine(" tr.even td { background: #DDD; color: #222; }");
|
||||
op.WriteLine(" </style>");
|
||||
op.WriteLine(" <body>");
|
||||
op.WriteLine(" <h1>RunUO Server Status</h1>");
|
||||
op.WriteLine(" <h3>Online clients</h3>");
|
||||
op.WriteLine(" <table cellpadding=\"0\" cellspacing=\"0\">");
|
||||
op.WriteLine(
|
||||
" <tr class=\"ruo-header\"><td>Name</td><td>Location</td><td>Kills</td><td>Karma/Fame</td></tr>");
|
||||
|
||||
int index = 0;
|
||||
|
||||
foreach (Mobile m in TcpServer.Instances.Where(state => state.Mobile != null).Select(state => state.Mobile))
|
||||
private static void Listen()
|
||||
{
|
||||
++index;
|
||||
if (!HttpListener.IsSupported) return;
|
||||
|
||||
Guild g = m.Guild as Guild;
|
||||
|
||||
op.Write($" <tr class=\"ruo-result {(index % 2 == 0 ? "even" : "odd")}\"><td>");
|
||||
|
||||
if (g != null)
|
||||
{
|
||||
op.Write(Encode(m.Name));
|
||||
op.Write(" [");
|
||||
|
||||
string title = m.GuildTitle;
|
||||
|
||||
title = title?.Trim() ?? string.Empty;
|
||||
|
||||
if (title.Length > 0)
|
||||
if (_Listener == null)
|
||||
{
|
||||
op.Write(Encode(title));
|
||||
op.Write(", ");
|
||||
_Listener = new HttpListener();
|
||||
_Listener.Prefixes.Add("http://*:80/status/");
|
||||
_Listener.Start();
|
||||
}
|
||||
else if (!_Listener.IsListening)
|
||||
{
|
||||
_Listener.Start();
|
||||
}
|
||||
|
||||
op.Write(Encode(g.Abbreviation));
|
||||
|
||||
op.Write(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
op.Write(Encode(m.Name));
|
||||
}
|
||||
|
||||
op.Write("</td><td>");
|
||||
op.Write(m.X);
|
||||
op.Write(", ");
|
||||
op.Write(m.Y);
|
||||
op.Write(", ");
|
||||
op.Write(m.Z);
|
||||
op.Write(" (");
|
||||
op.Write(m.Map);
|
||||
op.Write(")</td><td>");
|
||||
op.Write(m.Kills);
|
||||
op.Write("</td><td>");
|
||||
op.Write(m.Karma);
|
||||
op.Write(" / ");
|
||||
op.Write(m.Fame);
|
||||
op.WriteLine("</td></tr>");
|
||||
if (_Listener.IsListening) _Listener.BeginGetContext(ListenerCallback, null);
|
||||
}
|
||||
|
||||
op.WriteLine(" <tr>");
|
||||
op.WriteLine(" </table>");
|
||||
op.WriteLine(" </body>");
|
||||
op.WriteLine("</html>");
|
||||
}
|
||||
private static void ListenerCallback(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var context = _Listener.EndGetContext(result);
|
||||
|
||||
lock (_StatusLock)
|
||||
{
|
||||
_StatusPage = File.ReadAllText("web/status.html");
|
||||
_StatusBuffer = Encoding.UTF8.GetBytes(_StatusPage);
|
||||
}
|
||||
byte[] buffer;
|
||||
|
||||
lock (_StatusLock)
|
||||
{
|
||||
buffer = _StatusBuffer;
|
||||
}
|
||||
|
||||
context.Response.ContentLength64 = buffer.Length;
|
||||
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
context.Response.OutputStream.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
Listen();
|
||||
}
|
||||
|
||||
private static string Encode(string input)
|
||||
{
|
||||
var sb = new StringBuilder(input);
|
||||
|
||||
sb.Replace("&", "&");
|
||||
sb.Replace("<", "<");
|
||||
sb.Replace(">", ">");
|
||||
sb.Replace("\"", """);
|
||||
sb.Replace("'", "'");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (!Directory.Exists("web")) Directory.CreateDirectory("web");
|
||||
|
||||
using (var op = new StreamWriter("web/status.html"))
|
||||
{
|
||||
op.WriteLine("<!DOCTYPE html>");
|
||||
op.WriteLine("<html>");
|
||||
op.WriteLine(" <head>");
|
||||
op.WriteLine($" <title>{ServerList.ServerName} Server Status</title>");
|
||||
op.WriteLine(" </head>");
|
||||
op.WriteLine(" <style type=\"text/css\">");
|
||||
op.WriteLine(" body { background: #999; }");
|
||||
op.WriteLine(" table { width: 100%; }");
|
||||
op.WriteLine(" tr.ruo-header td { background: #000; color: #FFF; }");
|
||||
op.WriteLine(" tr.odd td { background: #222; color: #DDD; }");
|
||||
op.WriteLine(" tr.even td { background: #DDD; color: #222; }");
|
||||
op.WriteLine(" </style>");
|
||||
op.WriteLine(" <body>");
|
||||
op.WriteLine(" <h1>RunUO Server Status</h1>");
|
||||
op.WriteLine(" <h3>Online clients</h3>");
|
||||
op.WriteLine(" <table cellpadding=\"0\" cellspacing=\"0\">");
|
||||
op.WriteLine(
|
||||
" <tr class=\"ruo-header\"><td>Name</td><td>Location</td><td>Kills</td><td>Karma/Fame</td></tr>"
|
||||
);
|
||||
|
||||
var index = 0;
|
||||
|
||||
foreach (var m in TcpServer.Instances.Where(state => state.Mobile != null).Select(state => state.Mobile))
|
||||
{
|
||||
++index;
|
||||
|
||||
var g = m.Guild as Guild;
|
||||
|
||||
op.Write($" <tr class=\"ruo-result {(index % 2 == 0 ? "even" : "odd")}\"><td>");
|
||||
|
||||
if (g != null)
|
||||
{
|
||||
op.Write(Encode(m.Name));
|
||||
op.Write(" [");
|
||||
|
||||
var title = m.GuildTitle;
|
||||
|
||||
title = title?.Trim() ?? string.Empty;
|
||||
|
||||
if (title.Length > 0)
|
||||
{
|
||||
op.Write(Encode(title));
|
||||
op.Write(", ");
|
||||
}
|
||||
|
||||
op.Write(Encode(g.Abbreviation));
|
||||
|
||||
op.Write(']');
|
||||
}
|
||||
else
|
||||
{
|
||||
op.Write(Encode(m.Name));
|
||||
}
|
||||
|
||||
op.Write("</td><td>");
|
||||
op.Write(m.X);
|
||||
op.Write(", ");
|
||||
op.Write(m.Y);
|
||||
op.Write(", ");
|
||||
op.Write(m.Z);
|
||||
op.Write(" (");
|
||||
op.Write(m.Map);
|
||||
op.Write(")</td><td>");
|
||||
op.Write(m.Kills);
|
||||
op.Write("</td><td>");
|
||||
op.Write(m.Karma);
|
||||
op.Write(" / ");
|
||||
op.Write(m.Fame);
|
||||
op.WriteLine("</td></tr>");
|
||||
}
|
||||
|
||||
op.WriteLine(" <tr>");
|
||||
op.WriteLine(" </table>");
|
||||
op.WriteLine(" </body>");
|
||||
op.WriteLine("</html>");
|
||||
}
|
||||
|
||||
lock (_StatusLock)
|
||||
{
|
||||
_StatusPage = File.ReadAllText("web/status.html");
|
||||
_StatusBuffer = Encoding.UTF8.GetBytes(_StatusPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,117 +4,118 @@ using Server.Spells.Ninjitsu;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public enum DFAlgorithm
|
||||
{
|
||||
Standard,
|
||||
PainSpike
|
||||
}
|
||||
|
||||
public static class WeightOverloading
|
||||
{
|
||||
public const int OverloadAllowance = 4; // We can be four stones overweight without getting fatigued
|
||||
|
||||
public static DFAlgorithm DFA { get; set; }
|
||||
|
||||
public static void Initialize()
|
||||
public enum DFAlgorithm
|
||||
{
|
||||
EventSink.Movement += EventSink_Movement;
|
||||
Standard,
|
||||
PainSpike
|
||||
}
|
||||
|
||||
public static void FatigueOnDamage(Mobile m, int damage)
|
||||
public static class WeightOverloading
|
||||
{
|
||||
double fatigue = 0.0;
|
||||
public const int OverloadAllowance = 4; // We can be four stones overweight without getting fatigued
|
||||
|
||||
switch (DFA)
|
||||
{
|
||||
case DFAlgorithm.Standard:
|
||||
{
|
||||
fatigue = damage * (100.0 / m.Hits) * ((double)m.Stam / 100) - 5.0;
|
||||
break;
|
||||
}
|
||||
case DFAlgorithm.PainSpike:
|
||||
{
|
||||
fatigue = damage * (100.0 / m.Hits + (50.0 + m.Stam) / 100 - 1.0) - 5.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
public static DFAlgorithm DFA { get; set; }
|
||||
|
||||
if (fatigue > 0)
|
||||
m.Stam -= (int)fatigue;
|
||||
}
|
||||
|
||||
public static int GetMaxWeight(Mobile m) => m.MaxWeight;
|
||||
|
||||
public static void EventSink_Movement(MovementEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (!from.Alive || from.AccessLevel > AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (!from.Player)
|
||||
{
|
||||
// Else it won't work on monsters.
|
||||
DeathStrike.AddStep(from);
|
||||
return;
|
||||
}
|
||||
|
||||
int maxWeight = GetMaxWeight(from) + OverloadAllowance;
|
||||
int overWeight = Mobile.BodyWeight + from.TotalWeight - maxWeight;
|
||||
|
||||
if (overWeight > 0)
|
||||
{
|
||||
from.Stam -= GetStamLoss(from, overWeight, (e.Direction & Direction.Running) != 0);
|
||||
|
||||
if (from.Stam == 0)
|
||||
public static void Initialize()
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
500109); // You are too fatigued to move, because you are carrying too much weight!
|
||||
e.Blocked = true;
|
||||
return;
|
||||
EventSink.Movement += EventSink_Movement;
|
||||
}
|
||||
}
|
||||
|
||||
if (from.Stam * 100 / Math.Max(from.StamMax, 1) < 10)
|
||||
--from.Stam;
|
||||
public static void FatigueOnDamage(Mobile m, int damage)
|
||||
{
|
||||
var fatigue = 0.0;
|
||||
|
||||
if (from.Stam == 0)
|
||||
{
|
||||
from.SendLocalizedMessage(500110); // You are too fatigued to move.
|
||||
e.Blocked = true;
|
||||
return;
|
||||
}
|
||||
switch (DFA)
|
||||
{
|
||||
case DFAlgorithm.Standard:
|
||||
{
|
||||
fatigue = damage * (100.0 / m.Hits) * ((double)m.Stam / 100) - 5.0;
|
||||
break;
|
||||
}
|
||||
case DFAlgorithm.PainSpike:
|
||||
{
|
||||
fatigue = damage * (100.0 / m.Hits + (50.0 + m.Stam) / 100 - 1.0) - 5.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (from is PlayerMobile pm)
|
||||
{
|
||||
int amt = pm.Mounted ? 48 : 16;
|
||||
if (fatigue > 0)
|
||||
m.Stam -= (int)fatigue;
|
||||
}
|
||||
|
||||
if (++pm.StepsTaken % amt == 0)
|
||||
--pm.Stam;
|
||||
}
|
||||
public static int GetMaxWeight(Mobile m) => m.MaxWeight;
|
||||
|
||||
DeathStrike.AddStep(from);
|
||||
public static void EventSink_Movement(MovementEventArgs e)
|
||||
{
|
||||
var from = e.Mobile;
|
||||
|
||||
if (!from.Alive || from.AccessLevel > AccessLevel.Player)
|
||||
return;
|
||||
|
||||
if (!from.Player)
|
||||
{
|
||||
// Else it won't work on monsters.
|
||||
DeathStrike.AddStep(from);
|
||||
return;
|
||||
}
|
||||
|
||||
var maxWeight = GetMaxWeight(from) + OverloadAllowance;
|
||||
var overWeight = Mobile.BodyWeight + from.TotalWeight - maxWeight;
|
||||
|
||||
if (overWeight > 0)
|
||||
{
|
||||
from.Stam -= GetStamLoss(from, overWeight, (e.Direction & Direction.Running) != 0);
|
||||
|
||||
if (from.Stam == 0)
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
500109
|
||||
); // You are too fatigued to move, because you are carrying too much weight!
|
||||
e.Blocked = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (from.Stam * 100 / Math.Max(from.StamMax, 1) < 10)
|
||||
--from.Stam;
|
||||
|
||||
if (from.Stam == 0)
|
||||
{
|
||||
from.SendLocalizedMessage(500110); // You are too fatigued to move.
|
||||
e.Blocked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (from is PlayerMobile pm)
|
||||
{
|
||||
var amt = pm.Mounted ? 48 : 16;
|
||||
|
||||
if (++pm.StepsTaken % amt == 0)
|
||||
--pm.Stam;
|
||||
}
|
||||
|
||||
DeathStrike.AddStep(from);
|
||||
}
|
||||
|
||||
public static int GetStamLoss(Mobile from, int overWeight, bool running)
|
||||
{
|
||||
var loss = 5 + overWeight / 25;
|
||||
|
||||
if (from.Mounted)
|
||||
loss /= 3;
|
||||
|
||||
if (running)
|
||||
loss *= 2;
|
||||
|
||||
return loss;
|
||||
}
|
||||
|
||||
public static bool IsOverloaded(Mobile m)
|
||||
{
|
||||
if (!m.Player || !m.Alive || m.AccessLevel > AccessLevel.Player)
|
||||
return false;
|
||||
|
||||
return Mobile.BodyWeight + m.TotalWeight > GetMaxWeight(m) + OverloadAllowance;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetStamLoss(Mobile from, int overWeight, bool running)
|
||||
{
|
||||
int loss = 5 + overWeight / 25;
|
||||
|
||||
if (from.Mounted)
|
||||
loss /= 3;
|
||||
|
||||
if (running)
|
||||
loss *= 2;
|
||||
|
||||
return loss;
|
||||
}
|
||||
|
||||
public static bool IsOverloaded(Mobile m)
|
||||
{
|
||||
if (!m.Player || !m.Alive || m.AccessLevel > AccessLevel.Player)
|
||||
return false;
|
||||
|
||||
return Mobile.BodyWeight + m.TotalWeight > GetMaxWeight(m) + OverloadAllowance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,53 +2,54 @@ using System;
|
|||
|
||||
namespace Server.Misc
|
||||
{
|
||||
/// <summary>
|
||||
/// This timer spouts some welcome messages to a user at a set interval. It is used on character creation and login.
|
||||
/// </summary>
|
||||
public class WelcomeTimer : Timer
|
||||
{
|
||||
private static readonly string[] m_Messages = TestCenter.Enabled
|
||||
? new[]
|
||||
{
|
||||
"Welcome to this test shard. You are able to customize your character's stats and skills at anytime to anything you wish. To see the commands to do this just say 'help'.",
|
||||
"You will find a bank check worth 1,000,000 gold in your bank!",
|
||||
"A spellbook and a bag of reagents has been placed into your bank box.",
|
||||
"Various tools have been placed into your bank.",
|
||||
"Various raw materials like ingots, logs, feathers, hides, bottles, etc, have been placed into your bank.",
|
||||
"5 unmarked recall runes, 5 Felucca moonstones and 5 Trammel moonstones have been placed into your bank box.",
|
||||
"One of each level of treasure map has been placed in your bank box.",
|
||||
"You will find 9000 silver pieces deposited into your bank box. Spend it as you see fit and enjoy yourself!",
|
||||
"You will find 9000 gold pieces deposited into your bank box. Spend it as you see fit and enjoy yourself!",
|
||||
"A bag of PowerScrolls has been placed in your bank box."
|
||||
}
|
||||
: new[]
|
||||
{
|
||||
// Yes, this message is a pathetic message, It's suggested that you change it.
|
||||
"Welcome to this shard.",
|
||||
"Please enjoy your stay."
|
||||
};
|
||||
|
||||
private readonly Mobile m_Mobile;
|
||||
private int m_State;
|
||||
private readonly int m_Count;
|
||||
|
||||
public WelcomeTimer(Mobile m) : this(m, m_Messages.Length)
|
||||
/// <summary>
|
||||
/// This timer spouts some welcome messages to a user at a set interval. It is used on character creation and login.
|
||||
/// </summary>
|
||||
public class WelcomeTimer : Timer
|
||||
{
|
||||
}
|
||||
private static readonly string[] m_Messages = TestCenter.Enabled
|
||||
? new[]
|
||||
{
|
||||
"Welcome to this test shard. You are able to customize your character's stats and skills at anytime to anything you wish. To see the commands to do this just say 'help'.",
|
||||
"You will find a bank check worth 1,000,000 gold in your bank!",
|
||||
"A spellbook and a bag of reagents has been placed into your bank box.",
|
||||
"Various tools have been placed into your bank.",
|
||||
"Various raw materials like ingots, logs, feathers, hides, bottles, etc, have been placed into your bank.",
|
||||
"5 unmarked recall runes, 5 Felucca moonstones and 5 Trammel moonstones have been placed into your bank box.",
|
||||
"One of each level of treasure map has been placed in your bank box.",
|
||||
"You will find 9000 silver pieces deposited into your bank box. Spend it as you see fit and enjoy yourself!",
|
||||
"You will find 9000 gold pieces deposited into your bank box. Spend it as you see fit and enjoy yourself!",
|
||||
"A bag of PowerScrolls has been placed in your bank box."
|
||||
}
|
||||
: new[]
|
||||
{
|
||||
// Yes, this message is a pathetic message, It's suggested that you change it.
|
||||
"Welcome to this shard.",
|
||||
"Please enjoy your stay."
|
||||
};
|
||||
|
||||
public WelcomeTimer(Mobile m, int count) : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(10.0))
|
||||
{
|
||||
m_Mobile = m;
|
||||
m_Count = count;
|
||||
}
|
||||
private readonly int m_Count;
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (m_State < m_Count)
|
||||
m_Mobile.SendMessage(0x35, m_Messages[m_State++]);
|
||||
private readonly Mobile m_Mobile;
|
||||
private int m_State;
|
||||
|
||||
if (m_State == m_Count)
|
||||
Stop();
|
||||
public WelcomeTimer(Mobile m) : this(m, m_Messages.Length)
|
||||
{
|
||||
}
|
||||
|
||||
public WelcomeTimer(Mobile m, int count) : base(TimeSpan.FromSeconds(5.0), TimeSpan.FromSeconds(10.0))
|
||||
{
|
||||
m_Mobile = m;
|
||||
m_Count = count;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
{
|
||||
if (m_State < m_Count)
|
||||
m_Mobile.SendMessage(0x35, m_Messages[m_State++]);
|
||||
|
||||
if (m_State == m_Count)
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,345 +7,345 @@ using Server.Engines.Spawners;
|
|||
|
||||
namespace Server
|
||||
{
|
||||
public class UOAMVendorGenerator
|
||||
{
|
||||
// configuration
|
||||
private const int
|
||||
NPCCount = 2; // 2 npcs per type (so a mage spawner will spawn 2 npcs, a alchemist and herbalist spawner will spawn 4 npcs total)
|
||||
|
||||
private const int HomeRange = 5; // How far should they wander?
|
||||
private const bool TotalRespawn = true; // Should we spawn them up right away?
|
||||
private const int Team = 0; // "team" the npcs are on
|
||||
private static int m_Count;
|
||||
private static readonly TimeSpan MinTime = TimeSpan.FromMinutes(2.5); // min spawn time
|
||||
private static readonly TimeSpan MaxTime = TimeSpan.FromMinutes(10.0); // max spawn time
|
||||
|
||||
public static void Initialize()
|
||||
public class UOAMVendorGenerator
|
||||
{
|
||||
CommandSystem.Register("UOAMVendors", AccessLevel.Administrator, Generate_OnCommand);
|
||||
}
|
||||
// configuration
|
||||
private const int
|
||||
NPCCount = 2; // 2 npcs per type (so a mage spawner will spawn 2 npcs, a alchemist and herbalist spawner will spawn 4 npcs total)
|
||||
|
||||
[Usage("UOAMVendors")]
|
||||
[Description("Generates vendor spawners from Data/Common.MAP (taken from UOAutoMap)")]
|
||||
private static void Generate_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Parse(e.Mobile);
|
||||
}
|
||||
private const int HomeRange = 5; // How far should they wander?
|
||||
private const bool TotalRespawn = true; // Should we spawn them up right away?
|
||||
private const int Team = 0; // "team" the npcs are on
|
||||
private static int m_Count;
|
||||
private static readonly TimeSpan MinTime = TimeSpan.FromMinutes(2.5); // min spawn time
|
||||
private static readonly TimeSpan MaxTime = TimeSpan.FromMinutes(10.0); // max spawn time
|
||||
|
||||
public static void Parse(Mobile from)
|
||||
{
|
||||
string vendor_path = Path.Combine(Core.BaseDirectory, "Data/Common.map");
|
||||
m_Count = 0;
|
||||
|
||||
if (!File.Exists(vendor_path))
|
||||
{
|
||||
from.SendMessage("{0} not found!", vendor_path);
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage("Generating Vendors...");
|
||||
|
||||
using (StreamReader ip = new StreamReader(vendor_path))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
public static void Initialize()
|
||||
{
|
||||
int indexOf = line.IndexOf(':');
|
||||
|
||||
if (indexOf == -1)
|
||||
continue;
|
||||
|
||||
string type = line.Substring(0, ++indexOf).Trim();
|
||||
string sub = line.Substring(indexOf).Trim();
|
||||
|
||||
string[] split = sub.Split(' ');
|
||||
|
||||
if (split.Length < 3)
|
||||
continue;
|
||||
|
||||
split = new[] { type, split[0], split[1], split[2] };
|
||||
|
||||
switch (split[0].ToLower())
|
||||
{
|
||||
case "-healer:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Healer", "HealerGuildmaster");
|
||||
break;
|
||||
case "-baker:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Baker");
|
||||
break;
|
||||
case "-vet:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Veterinarian");
|
||||
break;
|
||||
case "-gypsymaiden:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyMaiden");
|
||||
break;
|
||||
case "-gypsybank:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyBanker");
|
||||
break;
|
||||
case "-bank:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Banker", "Minter");
|
||||
break;
|
||||
case "-inn:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Innkeeper");
|
||||
break;
|
||||
case "-provisioner:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Provisioner", "Cobbler");
|
||||
break;
|
||||
case "-tailor:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tailor", "Weaver", "TailorGuildmaster");
|
||||
break;
|
||||
case "-tavern:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tavernkeeper", "Waiter", "Cook", "Barkeeper");
|
||||
break;
|
||||
case "-reagents:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Herbalist", "Alchemist", "CustomHairstylist");
|
||||
break;
|
||||
case "-fortuneteller:":
|
||||
PlaceNPC(split[1], split[2], split[3], "FortuneTeller");
|
||||
break;
|
||||
case "-holymage:":
|
||||
PlaceNPC(split[1], split[2], split[3], "HolyMage");
|
||||
break;
|
||||
case "-chivalrykeeper:":
|
||||
PlaceNPC(split[1], split[2], split[3], "KeeperOfChivalry");
|
||||
break;
|
||||
case "-mage:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Mage", "Alchemist", "MageGuildmaster");
|
||||
break;
|
||||
case "-arms:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Armorer", "Weaponsmith");
|
||||
break;
|
||||
case "-tinker:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tinker", "TinkerGuildmaster");
|
||||
break;
|
||||
case "-gypsystable:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyAnimalTrainer");
|
||||
break;
|
||||
case "-stable:":
|
||||
PlaceNPC(split[1], split[2], split[3], "AnimalTrainer");
|
||||
break;
|
||||
case "-blacksmith:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Blacksmith", "BlacksmithGuildmaster");
|
||||
break;
|
||||
case "-bowyer:":
|
||||
case "-fletcher:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Bowyer");
|
||||
break;
|
||||
case "-carpenter:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Carpenter", "Architect", "RealEstateBroker");
|
||||
break;
|
||||
case "-butcher:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Butcher");
|
||||
break;
|
||||
case "-jeweler:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Jeweler");
|
||||
break;
|
||||
case "-tanner:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tanner", "Furtrader");
|
||||
break;
|
||||
case "-bard:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Bard", "BardGuildmaster");
|
||||
break;
|
||||
case "-market:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Butcher", "Farmer");
|
||||
break;
|
||||
case "-library:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Scribe");
|
||||
break;
|
||||
case "-shipwright:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Shipwright", "Mapmaker");
|
||||
break;
|
||||
case "-docks:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Fisherman");
|
||||
break;
|
||||
|
||||
case "-beekeeper:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Beekeeper");
|
||||
break;
|
||||
|
||||
// Guilds & Misc
|
||||
case "-tinkers guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "TinkerGuildmaster");
|
||||
break;
|
||||
case "-blacksmiths guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "BlacksmithGuildmaster");
|
||||
break;
|
||||
case "-sorcerors guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MageGuildmaster");
|
||||
break;
|
||||
case "-customs:": break;
|
||||
case "-painter:": break;
|
||||
case "-theater:": break;
|
||||
case "-warriors guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "WarriorGuildmaster");
|
||||
break;
|
||||
case "-archers guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "RangerGuildmaster");
|
||||
break;
|
||||
case "-thieves guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "ThiefGuildmaster");
|
||||
break;
|
||||
case "-miners guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MinerGuildmaster");
|
||||
break;
|
||||
case "-fishermans guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "FisherGuildmaster");
|
||||
break;
|
||||
case "-merchants guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MerchantGuildmaster");
|
||||
break;
|
||||
case "-illusionists guild:": break;
|
||||
case "-armourers guild:": break;
|
||||
case "-sorcerers guild:": break;
|
||||
case "-mages guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MageGuildmaster");
|
||||
break;
|
||||
case "-weapons guild:": break;
|
||||
case "-bardic guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "BardGuildmaster");
|
||||
break;
|
||||
case "-rogues guild:":
|
||||
break;
|
||||
|
||||
// Skip
|
||||
case "+landmark:":
|
||||
case "-point of interest:":
|
||||
case "+shrine:":
|
||||
case "+moongate:":
|
||||
case "+dungeon:":
|
||||
case "+scenic:":
|
||||
case "-gate:":
|
||||
case "+Body of Water:":
|
||||
case "+ruins:":
|
||||
case "+teleporter:":
|
||||
case "+Terrain:":
|
||||
case "-exit:":
|
||||
case "-bridge:":
|
||||
case "-other:":
|
||||
case "+champion:":
|
||||
case "-stairs:":
|
||||
case "-guild:":
|
||||
case "+graveyard:":
|
||||
case "+Island:":
|
||||
case "+town:":
|
||||
break;
|
||||
/*default:
|
||||
Console.WriteLine(split[0]);
|
||||
break;*/
|
||||
}
|
||||
CommandSystem.Register("UOAMVendors", AccessLevel.Administrator, Generate_OnCommand);
|
||||
}
|
||||
}
|
||||
|
||||
from.SendMessage("Done, added {0} spawners", m_Count);
|
||||
[Usage("UOAMVendors")]
|
||||
[Description("Generates vendor spawners from Data/Common.MAP (taken from UOAutoMap)")]
|
||||
private static void Generate_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Parse(e.Mobile);
|
||||
}
|
||||
|
||||
public static void Parse(Mobile from)
|
||||
{
|
||||
var vendor_path = Path.Combine(Core.BaseDirectory, "Data/Common.map");
|
||||
m_Count = 0;
|
||||
|
||||
if (!File.Exists(vendor_path))
|
||||
{
|
||||
from.SendMessage("{0} not found!", vendor_path);
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage("Generating Vendors...");
|
||||
|
||||
using (var ip = new StreamReader(vendor_path))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
var indexOf = line.IndexOf(':');
|
||||
|
||||
if (indexOf == -1)
|
||||
continue;
|
||||
|
||||
var type = line.Substring(0, ++indexOf).Trim();
|
||||
var sub = line.Substring(indexOf).Trim();
|
||||
|
||||
var split = sub.Split(' ');
|
||||
|
||||
if (split.Length < 3)
|
||||
continue;
|
||||
|
||||
split = new[] { type, split[0], split[1], split[2] };
|
||||
|
||||
switch (split[0].ToLower())
|
||||
{
|
||||
case "-healer:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Healer", "HealerGuildmaster");
|
||||
break;
|
||||
case "-baker:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Baker");
|
||||
break;
|
||||
case "-vet:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Veterinarian");
|
||||
break;
|
||||
case "-gypsymaiden:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyMaiden");
|
||||
break;
|
||||
case "-gypsybank:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyBanker");
|
||||
break;
|
||||
case "-bank:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Banker", "Minter");
|
||||
break;
|
||||
case "-inn:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Innkeeper");
|
||||
break;
|
||||
case "-provisioner:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Provisioner", "Cobbler");
|
||||
break;
|
||||
case "-tailor:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tailor", "Weaver", "TailorGuildmaster");
|
||||
break;
|
||||
case "-tavern:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tavernkeeper", "Waiter", "Cook", "Barkeeper");
|
||||
break;
|
||||
case "-reagents:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Herbalist", "Alchemist", "CustomHairstylist");
|
||||
break;
|
||||
case "-fortuneteller:":
|
||||
PlaceNPC(split[1], split[2], split[3], "FortuneTeller");
|
||||
break;
|
||||
case "-holymage:":
|
||||
PlaceNPC(split[1], split[2], split[3], "HolyMage");
|
||||
break;
|
||||
case "-chivalrykeeper:":
|
||||
PlaceNPC(split[1], split[2], split[3], "KeeperOfChivalry");
|
||||
break;
|
||||
case "-mage:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Mage", "Alchemist", "MageGuildmaster");
|
||||
break;
|
||||
case "-arms:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Armorer", "Weaponsmith");
|
||||
break;
|
||||
case "-tinker:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tinker", "TinkerGuildmaster");
|
||||
break;
|
||||
case "-gypsystable:":
|
||||
PlaceNPC(split[1], split[2], split[3], "GypsyAnimalTrainer");
|
||||
break;
|
||||
case "-stable:":
|
||||
PlaceNPC(split[1], split[2], split[3], "AnimalTrainer");
|
||||
break;
|
||||
case "-blacksmith:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Blacksmith", "BlacksmithGuildmaster");
|
||||
break;
|
||||
case "-bowyer:":
|
||||
case "-fletcher:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Bowyer");
|
||||
break;
|
||||
case "-carpenter:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Carpenter", "Architect", "RealEstateBroker");
|
||||
break;
|
||||
case "-butcher:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Butcher");
|
||||
break;
|
||||
case "-jeweler:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Jeweler");
|
||||
break;
|
||||
case "-tanner:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Tanner", "Furtrader");
|
||||
break;
|
||||
case "-bard:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Bard", "BardGuildmaster");
|
||||
break;
|
||||
case "-market:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Butcher", "Farmer");
|
||||
break;
|
||||
case "-library:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Scribe");
|
||||
break;
|
||||
case "-shipwright:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Shipwright", "Mapmaker");
|
||||
break;
|
||||
case "-docks:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Fisherman");
|
||||
break;
|
||||
|
||||
case "-beekeeper:":
|
||||
PlaceNPC(split[1], split[2], split[3], "Beekeeper");
|
||||
break;
|
||||
|
||||
// Guilds & Misc
|
||||
case "-tinkers guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "TinkerGuildmaster");
|
||||
break;
|
||||
case "-blacksmiths guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "BlacksmithGuildmaster");
|
||||
break;
|
||||
case "-sorcerors guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MageGuildmaster");
|
||||
break;
|
||||
case "-customs:": break;
|
||||
case "-painter:": break;
|
||||
case "-theater:": break;
|
||||
case "-warriors guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "WarriorGuildmaster");
|
||||
break;
|
||||
case "-archers guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "RangerGuildmaster");
|
||||
break;
|
||||
case "-thieves guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "ThiefGuildmaster");
|
||||
break;
|
||||
case "-miners guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MinerGuildmaster");
|
||||
break;
|
||||
case "-fishermans guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "FisherGuildmaster");
|
||||
break;
|
||||
case "-merchants guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MerchantGuildmaster");
|
||||
break;
|
||||
case "-illusionists guild:": break;
|
||||
case "-armourers guild:": break;
|
||||
case "-sorcerers guild:": break;
|
||||
case "-mages guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "MageGuildmaster");
|
||||
break;
|
||||
case "-weapons guild:": break;
|
||||
case "-bardic guild:":
|
||||
PlaceNPC(split[1], split[2], split[3], "BardGuildmaster");
|
||||
break;
|
||||
case "-rogues guild:":
|
||||
break;
|
||||
|
||||
// Skip
|
||||
case "+landmark:":
|
||||
case "-point of interest:":
|
||||
case "+shrine:":
|
||||
case "+moongate:":
|
||||
case "+dungeon:":
|
||||
case "+scenic:":
|
||||
case "-gate:":
|
||||
case "+Body of Water:":
|
||||
case "+ruins:":
|
||||
case "+teleporter:":
|
||||
case "+Terrain:":
|
||||
case "-exit:":
|
||||
case "-bridge:":
|
||||
case "-other:":
|
||||
case "+champion:":
|
||||
case "-stairs:":
|
||||
case "-guild:":
|
||||
case "+graveyard:":
|
||||
case "+Island:":
|
||||
case "+town:":
|
||||
break;
|
||||
/*default:
|
||||
Console.WriteLine(split[0]);
|
||||
break;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
from.SendMessage("Done, added {0} spawners", m_Count);
|
||||
}
|
||||
|
||||
public static void PlaceNPC(string sx, string sy, string sm, params string[] types)
|
||||
{
|
||||
if (types.Length == 0)
|
||||
return;
|
||||
|
||||
var x = Utility.ToInt32(sx);
|
||||
var y = Utility.ToInt32(sy);
|
||||
var map = Utility.ToInt32(sm);
|
||||
|
||||
switch (map)
|
||||
{
|
||||
case 0: // Trammel and Felucca
|
||||
MakeSpawner(types, x, y, Map.Felucca);
|
||||
MakeSpawner(types, x, y, Map.Trammel);
|
||||
break;
|
||||
case 1: // Felucca
|
||||
MakeSpawner(types, x, y, Map.Felucca);
|
||||
break;
|
||||
case 2:
|
||||
MakeSpawner(types, x, y, Map.Trammel);
|
||||
break;
|
||||
case 3:
|
||||
MakeSpawner(types, x, y, Map.Ilshenar);
|
||||
break;
|
||||
case 4:
|
||||
MakeSpawner(types, x, y, Map.Malas);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("UOAM Vendor Parser: Warning, unknown map {0}", map);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSpawnerZ(int x, int y, Map map)
|
||||
{
|
||||
var z = map.GetAverageZ(x, y);
|
||||
|
||||
if (map.CanFit(x, y, z, 16, false, false))
|
||||
return z;
|
||||
|
||||
for (var i = 1; i <= 5; ++i)
|
||||
{
|
||||
if (map.CanFit(x, y, z + i, 16, false, false))
|
||||
return z + i;
|
||||
|
||||
if (map.CanFit(x, y, z - i, 16, false, false))
|
||||
return z - i;
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
public static void ClearSpawners(int x, int y, int z, Map map)
|
||||
{
|
||||
var eable = map.GetItemsInRange<Spawner>(new Point3D(x, y, z), 0);
|
||||
var m_ToDelete = new Queue<Spawner>();
|
||||
|
||||
foreach (var item in eable)
|
||||
if (item.Z == z)
|
||||
m_ToDelete.Enqueue(item);
|
||||
|
||||
eable.Free();
|
||||
|
||||
while (m_ToDelete.Count > 0)
|
||||
m_ToDelete.Dequeue().Delete();
|
||||
}
|
||||
|
||||
private static void MakeSpawner(string[] types, int x, int y, Map map)
|
||||
{
|
||||
if (types.Length == 0)
|
||||
return;
|
||||
|
||||
var z = GetSpawnerZ(x, y, map);
|
||||
|
||||
ClearSpawners(x, y, z, map);
|
||||
|
||||
var sp = new Spawner
|
||||
{
|
||||
MinDelay = MinTime,
|
||||
MaxDelay = MaxTime,
|
||||
Team = Team,
|
||||
HomeRange = HomeRange
|
||||
};
|
||||
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < types.Length; ++i)
|
||||
{
|
||||
var isGuildMaster = types[i].EndsWith("Guildmaster");
|
||||
count += isGuildMaster ? 1 : NPCCount;
|
||||
if (isGuildMaster)
|
||||
count++;
|
||||
|
||||
sp.AddEntry(types[i], 100, isGuildMaster ? 1 : NPCCount);
|
||||
}
|
||||
|
||||
sp.Count = count;
|
||||
sp.MoveToWorld(new Point3D(x, y, z), map);
|
||||
|
||||
if (TotalRespawn)
|
||||
{
|
||||
sp.Respawn();
|
||||
sp.BringToHome();
|
||||
}
|
||||
|
||||
m_Count += types.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public static void PlaceNPC(string sx, string sy, string sm, params string[] types)
|
||||
{
|
||||
if (types.Length == 0)
|
||||
return;
|
||||
|
||||
int x = Utility.ToInt32(sx);
|
||||
int y = Utility.ToInt32(sy);
|
||||
int map = Utility.ToInt32(sm);
|
||||
|
||||
switch (map)
|
||||
{
|
||||
case 0: // Trammel and Felucca
|
||||
MakeSpawner(types, x, y, Map.Felucca);
|
||||
MakeSpawner(types, x, y, Map.Trammel);
|
||||
break;
|
||||
case 1: // Felucca
|
||||
MakeSpawner(types, x, y, Map.Felucca);
|
||||
break;
|
||||
case 2:
|
||||
MakeSpawner(types, x, y, Map.Trammel);
|
||||
break;
|
||||
case 3:
|
||||
MakeSpawner(types, x, y, Map.Ilshenar);
|
||||
break;
|
||||
case 4:
|
||||
MakeSpawner(types, x, y, Map.Malas);
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("UOAM Vendor Parser: Warning, unknown map {0}", map);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetSpawnerZ(int x, int y, Map map)
|
||||
{
|
||||
int z = map.GetAverageZ(x, y);
|
||||
|
||||
if (map.CanFit(x, y, z, 16, false, false))
|
||||
return z;
|
||||
|
||||
for (int i = 1; i <= 5; ++i)
|
||||
{
|
||||
if (map.CanFit(x, y, z + i, 16, false, false))
|
||||
return z + i;
|
||||
|
||||
if (map.CanFit(x, y, z - i, 16, false, false))
|
||||
return z - i;
|
||||
}
|
||||
|
||||
return z;
|
||||
}
|
||||
|
||||
public static void ClearSpawners(int x, int y, int z, Map map)
|
||||
{
|
||||
IPooledEnumerable<Spawner> eable = map.GetItemsInRange<Spawner>(new Point3D(x, y, z), 0);
|
||||
Queue<Spawner> m_ToDelete = new Queue<Spawner>();
|
||||
|
||||
foreach (Spawner item in eable)
|
||||
if (item.Z == z)
|
||||
m_ToDelete.Enqueue(item);
|
||||
|
||||
eable.Free();
|
||||
|
||||
while (m_ToDelete.Count > 0)
|
||||
m_ToDelete.Dequeue().Delete();
|
||||
}
|
||||
|
||||
private static void MakeSpawner(string[] types, int x, int y, Map map)
|
||||
{
|
||||
if (types.Length == 0)
|
||||
return;
|
||||
|
||||
int z = GetSpawnerZ(x, y, map);
|
||||
|
||||
ClearSpawners(x, y, z, map);
|
||||
|
||||
Spawner sp = new Spawner
|
||||
{
|
||||
MinDelay = MinTime,
|
||||
MaxDelay = MaxTime,
|
||||
Team = Team,
|
||||
HomeRange = HomeRange
|
||||
};
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < types.Length; ++i)
|
||||
{
|
||||
bool isGuildMaster = types[i].EndsWith("Guildmaster");
|
||||
count += isGuildMaster ? 1 : NPCCount;
|
||||
if (isGuildMaster)
|
||||
count++;
|
||||
|
||||
sp.AddEntry(types[i], 100, isGuildMaster ? 1 : NPCCount);
|
||||
}
|
||||
|
||||
sp.Count = count;
|
||||
sp.MoveToWorld(new Point3D(x, y, z), map);
|
||||
|
||||
if (TotalRespawn)
|
||||
{
|
||||
sp.Respawn();
|
||||
sp.BringToHome();
|
||||
}
|
||||
|
||||
m_Count += types.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue