Adds assemblies config, fixes crash bugs. (#134)
This commit is contained in:
parent
0140eb73b4
commit
8ec166bcd0
3223 changed files with 191 additions and 191 deletions
46
Projects/UOContent/Accounting/AccessRestrictions.cs
Normal file
46
Projects/UOContent/Accounting/AccessRestrictions.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class AccessRestrictions
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.SocketConnect += EventSink_SocketConnect;
|
||||
}
|
||||
|
||||
private static void EventSink_SocketConnect(SocketConnectEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPAddress ip = ((IPEndPoint)e.Context.RemoteEndPoint).Address;
|
||||
|
||||
if (Firewall.IsBlocked(ip))
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Firewall blocked connection attempt.", ip);
|
||||
e.AllowConnection = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IPLimiter.SocketBlock && !IPLimiter.Verify(ip))
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Past IP limit threshold", ip);
|
||||
|
||||
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
|
||||
{
|
||||
op.WriteLine("{0}\tPast IP limit threshold\t{1}", ip, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
e.AllowConnection = false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
e.AllowConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1049
Projects/UOContent/Accounting/Account.cs
Normal file
1049
Projects/UOContent/Accounting/Account.cs
Normal file
File diff suppressed because it is too large
Load diff
132
Projects/UOContent/Accounting/AccountAttackLimiter.cs
Normal file
132
Projects/UOContent/Accounting/AccountAttackLimiter.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public class AccountAttackLimiter
|
||||
{
|
||||
public static bool Enabled;
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("accountAttackLimiter.enable", true);
|
||||
}
|
||||
|
||||
private static readonly List<InvalidAccountAccessLog> m_List = new List<InvalidAccountAccessLog>();
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
PacketHandlers.RegisterThrottler(0x80, Throttle_Callback);
|
||||
PacketHandlers.RegisterThrottler(0x91, Throttle_Callback);
|
||||
PacketHandlers.RegisterThrottler(0xCF, Throttle_Callback);
|
||||
}
|
||||
|
||||
public static TimeSpan Throttle_Callback(NetState ns)
|
||||
{
|
||||
InvalidAccountAccessLog accessLog = FindAccessLog(ns);
|
||||
|
||||
if (accessLog == null)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
DateTime date = DateTime.UtcNow;
|
||||
DateTime access = accessLog.LastAccessTime + ComputeThrottle(accessLog.Counts);
|
||||
return date >= access ? TimeSpan.Zero : date - access;
|
||||
}
|
||||
|
||||
public static InvalidAccountAccessLog FindAccessLog(NetState ns)
|
||||
{
|
||||
if (ns == null)
|
||||
return null;
|
||||
|
||||
IPAddress ipAddress = ns.Address;
|
||||
|
||||
for (int i = 0; i < m_List.Count; ++i)
|
||||
{
|
||||
InvalidAccountAccessLog accessLog = m_List[i];
|
||||
|
||||
if (accessLog.HasExpired)
|
||||
m_List.RemoveAt(i--);
|
||||
else if (accessLog.Address.Equals(ipAddress))
|
||||
return accessLog;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void RegisterInvalidAccess(NetState ns)
|
||||
{
|
||||
if (ns == null || !Enabled)
|
||||
return;
|
||||
|
||||
InvalidAccountAccessLog accessLog = FindAccessLog(ns);
|
||||
|
||||
if (accessLog == null)
|
||||
m_List.Add(accessLog = new InvalidAccountAccessLog(ns.Address));
|
||||
|
||||
accessLog.Counts += 1;
|
||||
accessLog.RefreshAccessTime();
|
||||
|
||||
if (accessLog.Counts >= 3)
|
||||
try
|
||||
{
|
||||
using StreamWriter op = new StreamWriter("throttle.log", true);
|
||||
op.WriteLine(
|
||||
"{0}\t{1}\t{2}",
|
||||
DateTime.UtcNow,
|
||||
ns,
|
||||
accessLog.Counts);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static TimeSpan ComputeThrottle(int counts)
|
||||
{
|
||||
if (counts >= 15)
|
||||
return TimeSpan.FromMinutes(5.0);
|
||||
|
||||
if (counts >= 10)
|
||||
return TimeSpan.FromMinutes(1.0);
|
||||
|
||||
if (counts >= 5)
|
||||
return TimeSpan.FromSeconds(20.0);
|
||||
|
||||
if (counts >= 3)
|
||||
return TimeSpan.FromSeconds(10.0);
|
||||
|
||||
if (counts >= 1)
|
||||
return TimeSpan.FromSeconds(2.0);
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidAccountAccessLog
|
||||
{
|
||||
public InvalidAccountAccessLog(IPAddress address)
|
||||
{
|
||||
Address = address;
|
||||
RefreshAccessTime();
|
||||
}
|
||||
|
||||
public IPAddress Address { get; set; }
|
||||
|
||||
public DateTime LastAccessTime { get; set; }
|
||||
|
||||
public bool HasExpired => DateTime.UtcNow >= LastAccessTime + TimeSpan.FromHours(1.0);
|
||||
|
||||
public int Counts { get; set; }
|
||||
|
||||
public void RefreshAccessTime()
|
||||
{
|
||||
LastAccessTime = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Projects/UOContent/Accounting/AccountComment.cs
Normal file
73
Projects/UOContent/Accounting/AccountComment.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Xml;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public class AccountComment
|
||||
{
|
||||
private string m_Content;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new AccountComment instance.
|
||||
/// </summary>
|
||||
/// <param name="addedBy">Initial AddedBy value.</param>
|
||||
/// <param name="content">Initial Content value.</param>
|
||||
public AccountComment(string addedBy, string content)
|
||||
{
|
||||
AddedBy = addedBy;
|
||||
m_Content = content;
|
||||
LastModified = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes an AccountComment instance from an xml element.
|
||||
/// </summary>
|
||||
/// <param name="node">The XmlElement instance from which to deserialize.</param>
|
||||
public AccountComment(XmlElement node)
|
||||
{
|
||||
AddedBy = Utility.GetAttribute(node, "addedBy", "empty");
|
||||
LastModified = Utility.GetXMLDateTime(Utility.GetAttribute(node, "lastModified"), DateTime.UtcNow);
|
||||
m_Content = Utility.GetText(node, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A string representing who added this comment.
|
||||
/// </summary>
|
||||
public string AddedBy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the body of this comment. Setting this value will reset LastModified.
|
||||
/// </summary>
|
||||
public string Content
|
||||
{
|
||||
get => m_Content;
|
||||
set
|
||||
{
|
||||
m_Content = value;
|
||||
LastModified = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The date and time when this account was last modified -or- the comment creation time, if never modified.
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes this AccountComment instance to an XmlTextWriter.
|
||||
/// </summary>
|
||||
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
|
||||
public void Save(XmlTextWriter xml)
|
||||
{
|
||||
xml.WriteStartElement("comment");
|
||||
|
||||
xml.WriteAttributeString("addedBy", AddedBy);
|
||||
|
||||
xml.WriteAttributeString("lastModified", XmlConvert.ToString(LastModified, XmlDateTimeSerializationMode.Utc));
|
||||
|
||||
xml.WriteString(m_Content);
|
||||
|
||||
xml.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
415
Projects/UOContent/Accounting/AccountHandler.cs
Normal file
415
Projects/UOContent/Accounting/AccountHandler.cs
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Accounting;
|
||||
using Server.Engines.Help;
|
||||
using Server.Network;
|
||||
using Server.Regions;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class AccountHandler
|
||||
{
|
||||
private static int MaxAccountsPerIP;
|
||||
private static bool AutoAccountCreation;
|
||||
private static bool RestrictDeletion = !TestCenter.Enabled;
|
||||
private static TimeSpan DeleteDelay = TimeSpan.FromDays(7.0);
|
||||
private static bool PasswordCommandEnabled;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1);
|
||||
AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true);
|
||||
PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting("accountHandler.enablePlayerPasswordCommand", false);
|
||||
}
|
||||
|
||||
private static readonly CityInfo[] StartingCities =
|
||||
{
|
||||
new CityInfo("New Haven", "New Haven Bank", 1150168, 3667, 2625, 0),
|
||||
new CityInfo("Yew", "The Empath Abbey", 1075072, 633, 858, 0),
|
||||
new CityInfo("Minoc", "The Barnacle", 1075073, 2476, 413, 15),
|
||||
new CityInfo("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20),
|
||||
new CityInfo("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0),
|
||||
new CityInfo("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0),
|
||||
new CityInfo("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0),
|
||||
new CityInfo("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0),
|
||||
new CityInfo("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0)
|
||||
};
|
||||
|
||||
/* Old Haven/Magincia Locations
|
||||
new CityInfo( "Britain", "Sweet Dreams Inn", 1496, 1628, 10 );
|
||||
// ..
|
||||
// Trinsic
|
||||
new CityInfo( "Magincia", "The Great Horns Tavern", 3734, 2222, 20 ),
|
||||
// Jhelom
|
||||
// ..
|
||||
new CityInfo( "Haven", "Buckler's Hideaway", 3667, 2625, 0 )
|
||||
|
||||
if (Core.AOS)
|
||||
{
|
||||
//CityInfo haven = new CityInfo( "Haven", "Uzeraan's Mansion", 3618, 2591, 0 );
|
||||
CityInfo haven = new CityInfo( "Haven", "Uzeraan's Mansion", 3503, 2574, 14 );
|
||||
StartingCities[StartingCities.Length - 1] = haven;
|
||||
}
|
||||
*/
|
||||
|
||||
private static Dictionary<IPAddress, int> m_IPTable;
|
||||
|
||||
private static readonly char[] m_ForbiddenChars =
|
||||
{
|
||||
'<', '>', ':', '"', '/', '\\', '|', '?', '*'
|
||||
};
|
||||
|
||||
public static AccessLevel LockdownLevel { get; set; }
|
||||
|
||||
public static Dictionary<IPAddress, int> IPTable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_IPTable == null)
|
||||
{
|
||||
m_IPTable = new Dictionary<IPAddress, int>();
|
||||
|
||||
foreach (Account a in Accounts.GetAccounts())
|
||||
if (a.LoginIPs.Length > 0)
|
||||
{
|
||||
IPAddress ip = a.LoginIPs[0];
|
||||
m_IPTable[ip] = (m_IPTable.TryGetValue(ip, out int value) ? value : 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return m_IPTable;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.DeleteRequest += EventSink_DeleteRequest;
|
||||
EventSink.AccountLogin += EventSink_AccountLogin;
|
||||
EventSink.GameLogin += EventSink_GameLogin;
|
||||
|
||||
if (PasswordCommandEnabled)
|
||||
CommandSystem.Register("Password", AccessLevel.Player, Password_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Password <newPassword> <repeatPassword>")]
|
||||
[Description(
|
||||
"Changes the password of the commanding players account. Requires the same C-class IP address as the account's creator.")]
|
||||
public static void Password_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (!(from.Account is Account acct))
|
||||
return;
|
||||
|
||||
IPAddress[] accessList = acct.LoginIPs;
|
||||
|
||||
if (accessList.Length == 0)
|
||||
return;
|
||||
|
||||
NetState ns = from.NetState;
|
||||
|
||||
if (ns == null)
|
||||
return;
|
||||
|
||||
if (e.Length == 0)
|
||||
{
|
||||
from.SendMessage("You must specify the new password.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Length == 1)
|
||||
{
|
||||
from.SendMessage("To prevent potential typing mistakes, you must type the password twice. Use the format:");
|
||||
from.SendMessage("Password \"(newPassword)\" \"(repeated)\"");
|
||||
return;
|
||||
}
|
||||
|
||||
string pass = e.GetString(0);
|
||||
string pass2 = e.GetString(1);
|
||||
|
||||
if (pass != pass2)
|
||||
{
|
||||
from.SendMessage("The passwords do not match.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isSafe = true;
|
||||
|
||||
for (int i = 0; isSafe && i < pass.Length; ++i)
|
||||
isSafe = pass[i] >= 0x20 && pass[i] < 0x7F;
|
||||
|
||||
if (!isSafe)
|
||||
{
|
||||
from.SendMessage("That is not a valid password.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IPAddress ipAddress = ns.Address;
|
||||
|
||||
if (Utility.IPMatchClassC(accessList[0], ipAddress))
|
||||
{
|
||||
acct.SetPassword(pass);
|
||||
from.SendMessage("The password to your account has changed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
PageEntry entry = PageQueue.GetEntry(from);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
if (entry.Message.StartsWith("[Automated: Change Password]"))
|
||||
from.SendMessage("You already have a password change request in the help system queue.");
|
||||
else
|
||||
from.SendMessage("Your IP address does not match that which created this account.");
|
||||
}
|
||||
else if (PageQueue.CheckAllowedToPage(from))
|
||||
{
|
||||
from.SendMessage(
|
||||
"Your IP address does not match that which created this account. A page has been entered into the help system on your behalf.");
|
||||
|
||||
/* The next available Counselor/Game Master will respond as soon as possible.
|
||||
* Please check your Journal for messages every few minutes.
|
||||
*/
|
||||
from.SendLocalizedMessage(501234, "", 0x35);
|
||||
|
||||
PageQueue.Enqueue(new PageEntry(from,
|
||||
$"[Automated: Change Password]<br>Desired password: {pass}<br>Current IP address: {ipAddress}<br>Account IP address: {accessList[0]}",
|
||||
PageType.Account));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static void EventSink_DeleteRequest(NetState state, int index)
|
||||
{
|
||||
if (!(state.Account is Account acct))
|
||||
{
|
||||
state.Dispose();
|
||||
}
|
||||
else if (index < 0 || index >= acct.Length)
|
||||
{
|
||||
state.Send(new DeleteResult(DeleteResultType.BadRequest));
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
else
|
||||
{
|
||||
Mobile m = acct[index];
|
||||
|
||||
if (m == null)
|
||||
{
|
||||
state.Send(new DeleteResult(DeleteResultType.CharNotExist));
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
else if (m.NetState != null)
|
||||
{
|
||||
state.Send(new DeleteResult(DeleteResultType.CharBeingPlayed));
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
else if (RestrictDeletion && DateTime.UtcNow < m.CreationTime + DeleteDelay)
|
||||
{
|
||||
state.Send(new DeleteResult(DeleteResultType.CharTooYoung));
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
else if (m.AccessLevel == AccessLevel.Player &&
|
||||
Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf<Jail>()) // Don't need to check current location, if netstate is null, they're logged out
|
||||
{
|
||||
state.Send(new DeleteResult(DeleteResultType.BadRequest));
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Client: {0}: Deleting character {1} (0x{2:X})", state, index, m.Serial.Value);
|
||||
|
||||
acct.Comments.Add(new AccountComment("System", $"Character #{index + 1} {m} deleted by {state}"));
|
||||
|
||||
m.Delete();
|
||||
state.Send(new CharacterListUpdate(acct));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CanCreate(IPAddress ip)
|
||||
{
|
||||
if (!IPTable.ContainsKey(ip))
|
||||
return true;
|
||||
|
||||
return IPTable[ip] < MaxAccountsPerIP;
|
||||
}
|
||||
|
||||
private static bool IsForbiddenChar(char c)
|
||||
{
|
||||
for (int i = 0; i < m_ForbiddenChars.Length; ++i)
|
||||
if (c == m_ForbiddenChars[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Account CreateAccount(NetState state, string un, string pw)
|
||||
{
|
||||
if (un.Length == 0 || pw.Length == 0)
|
||||
return null;
|
||||
|
||||
bool isSafe = !(un.StartsWith(" ") || un.EndsWith(" ") || un.EndsWith("."));
|
||||
|
||||
for (int i = 0; isSafe && i < un.Length; ++i)
|
||||
isSafe = un[i] >= 0x20 && un[i] < 0x7F && !IsForbiddenChar(un[i]);
|
||||
|
||||
for (int i = 0; isSafe && i < pw.Length; ++i)
|
||||
isSafe = pw[i] >= 0x20 && pw[i] < 0x7F;
|
||||
|
||||
if (!isSafe)
|
||||
return null;
|
||||
|
||||
if (!CanCreate(state.Address))
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Account '{1}' not created, ip already has {2} account{3}.", state, un,
|
||||
MaxAccountsPerIP, MaxAccountsPerIP == 1 ? "" : "s");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine("Login: {0}: Creating new account '{1}'", state, un);
|
||||
|
||||
Account a = new Account(un, pw);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public static void EventSink_AccountLogin(AccountLoginEventArgs e)
|
||||
{
|
||||
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
|
||||
{
|
||||
e.Accepted = false;
|
||||
e.RejectReason = ALRReason.InUse;
|
||||
|
||||
Console.WriteLine("Login: {0}: Past IP limit threshold", e.State);
|
||||
|
||||
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
|
||||
{
|
||||
op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string un = e.Username;
|
||||
string pw = e.Password;
|
||||
|
||||
e.Accepted = false;
|
||||
|
||||
if (!(Accounts.GetAccount(un) is Account acct))
|
||||
{
|
||||
// To prevent someone from making an account of just '' or a bunch of meaningless spaces
|
||||
if (AutoAccountCreation && un.Trim().Length > 0)
|
||||
{
|
||||
e.State.Account = acct = CreateAccount(e.State, un, pw);
|
||||
e.Accepted = acct?.CheckAccess(e.State) ?? false;
|
||||
|
||||
if (!e.Accepted)
|
||||
e.RejectReason = ALRReason.BadComm;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid username '{1}'", e.State, un);
|
||||
e.RejectReason = ALRReason.Invalid;
|
||||
}
|
||||
}
|
||||
else if (!acct.HasAccess(e.State))
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Access denied for '{1}'", e.State, un);
|
||||
e.RejectReason = LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass;
|
||||
}
|
||||
else if (!acct.CheckPassword(pw))
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid password for '{1}'", e.State, un);
|
||||
e.RejectReason = ALRReason.BadPass;
|
||||
}
|
||||
else if (acct.Banned)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Banned account '{1}'", e.State, un);
|
||||
e.RejectReason = ALRReason.Blocked;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Valid credentials for '{1}'", e.State, un);
|
||||
e.State.Account = acct;
|
||||
e.Accepted = true;
|
||||
|
||||
acct.LogAccess(e.State);
|
||||
}
|
||||
|
||||
if (!e.Accepted)
|
||||
AccountAttackLimiter.RegisterInvalidAccess(e.State);
|
||||
}
|
||||
|
||||
public static void EventSink_GameLogin(GameLoginEventArgs e)
|
||||
{
|
||||
if (!IPLimiter.SocketBlock && !IPLimiter.Verify(e.State.Address))
|
||||
{
|
||||
e.Accepted = false;
|
||||
|
||||
Console.WriteLine("Login: {0}: Past IP limit threshold", e.State);
|
||||
|
||||
using (StreamWriter op = new StreamWriter("ipLimits.log", true))
|
||||
{
|
||||
op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
string un = e.Username;
|
||||
string pw = e.Password;
|
||||
|
||||
if (!(Accounts.GetAccount(un) is Account acct))
|
||||
{
|
||||
e.Accepted = false;
|
||||
}
|
||||
else if (!acct.HasAccess(e.State))
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Access denied for '{1}'", e.State, un);
|
||||
e.Accepted = false;
|
||||
}
|
||||
else if (!acct.CheckPassword(pw))
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Invalid password for '{1}'", e.State, un);
|
||||
e.Accepted = false;
|
||||
}
|
||||
else if (acct.Banned)
|
||||
{
|
||||
Console.WriteLine("Login: {0}: Banned account '{1}'", e.State, un);
|
||||
e.Accepted = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
acct.LogAccess(e.State);
|
||||
|
||||
Console.WriteLine("Login: {0}: Account '{1}' at character list", e.State, un);
|
||||
e.State.Account = acct;
|
||||
e.Accepted = true;
|
||||
e.CityInfo = StartingCities;
|
||||
}
|
||||
|
||||
if (!e.Accepted)
|
||||
AccountAttackLimiter.RegisterInvalidAccess(e.State);
|
||||
}
|
||||
|
||||
public static bool CheckAccount(Mobile mobCheck, Mobile accCheck)
|
||||
{
|
||||
if (accCheck?.Account is Account a)
|
||||
for (int i = 0; i < a.Length; ++i)
|
||||
if (a[i] == mobCheck)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Projects/UOContent/Accounting/AccountTag.cs
Normal file
50
Projects/UOContent/Accounting/AccountTag.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Xml;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public class AccountTag
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new AccountTag instance with a specific name and value.
|
||||
/// </summary>
|
||||
/// <param name="name">Initial name.</param>
|
||||
/// <param name="value">Initial value.</param>
|
||||
public AccountTag(string name, string value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes an AccountTag instance from an xml element.
|
||||
/// </summary>
|
||||
/// <param name="node">The XmlElement instance from which to deserialize.</param>
|
||||
public AccountTag(XmlElement node)
|
||||
{
|
||||
Name = Utility.GetAttribute(node, "name", "empty");
|
||||
Value = Utility.GetText(node, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of this tag.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of this tag.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes this AccountTag instance to an XmlTextWriter.
|
||||
/// </summary>
|
||||
/// <param name="xml">The XmlTextWriter instance from which to serialize.</param>
|
||||
public void Save(XmlTextWriter xml)
|
||||
{
|
||||
xml.WriteStartElement("tag");
|
||||
xml.WriteAttributeString("name", Name);
|
||||
xml.WriteString(Value);
|
||||
xml.WriteEndElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Projects/UOContent/Accounting/Accounts.cs
Normal file
92
Projects/UOContent/Accounting/Accounts.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public class Accounts
|
||||
{
|
||||
private static Dictionary<string, IAccount> m_Accounts = new Dictionary<string, IAccount>();
|
||||
|
||||
static Accounts()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Count => m_Accounts.Count;
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
EventSink.WorldLoad += Load;
|
||||
EventSink.WorldSave += Save;
|
||||
}
|
||||
|
||||
public static IEnumerable<IAccount> GetAccounts() => m_Accounts.Values;
|
||||
|
||||
public static IAccount GetAccount(string username)
|
||||
{
|
||||
m_Accounts.TryGetValue(username, out IAccount a);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
public static void Add(IAccount a)
|
||||
{
|
||||
m_Accounts[a.Username] = a;
|
||||
}
|
||||
|
||||
public static void Remove(string username)
|
||||
{
|
||||
m_Accounts.Remove(username);
|
||||
}
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
m_Accounts = new Dictionary<string, IAccount>(32, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
string filePath = Path.Combine("Saves/Accounts", "accounts.xml");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
return;
|
||||
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.Load(filePath);
|
||||
|
||||
XmlElement root = doc["accounts"];
|
||||
|
||||
foreach (XmlElement account in root.GetElementsByTagName("account"))
|
||||
try
|
||||
{
|
||||
new Account(account);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Warning: Account instance load failed");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(bool message)
|
||||
{
|
||||
if (!Directory.Exists("Saves/Accounts"))
|
||||
Directory.CreateDirectory("Saves/Accounts");
|
||||
|
||||
string filePath = Path.Combine("Saves/Accounts", "accounts.xml");
|
||||
|
||||
using StreamWriter op = new StreamWriter(filePath);
|
||||
XmlTextWriter xml = new XmlTextWriter(op) { Formatting = Formatting.Indented, IndentChar = '\t', Indentation = 1 };
|
||||
|
||||
xml.WriteStartDocument(true);
|
||||
|
||||
xml.WriteStartElement("accounts");
|
||||
|
||||
xml.WriteAttributeString("count", m_Accounts.Count.ToString());
|
||||
|
||||
foreach (Account a in GetAccounts())
|
||||
a.Save(xml);
|
||||
|
||||
xml.WriteEndElement();
|
||||
|
||||
xml.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
270
Projects/UOContent/Accounting/Firewall.cs
Normal file
270
Projects/UOContent/Accounting/Firewall.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class Firewall
|
||||
{
|
||||
static Firewall()
|
||||
{
|
||||
List = new List<IFirewallEntry>();
|
||||
|
||||
string path = "firewall.cfg";
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
line = line.Trim();
|
||||
|
||||
if (line.Length == 0)
|
||||
continue;
|
||||
|
||||
List.Add(ToFirewallEntry(line));
|
||||
|
||||
/*
|
||||
object toAdd;
|
||||
|
||||
IPAddress addr;
|
||||
if (IPAddress.TryParse( line, out addr ))
|
||||
toAdd = addr;
|
||||
else
|
||||
toAdd = line;
|
||||
|
||||
m_Blocked.Add( toAdd.ToString() );
|
||||
* */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IFirewallEntry> List { get; }
|
||||
|
||||
public static IFirewallEntry ToFirewallEntry(object entry)
|
||||
{
|
||||
if (entry is IFirewallEntry firewallEntry)
|
||||
return firewallEntry;
|
||||
if (entry is IPAddress address)
|
||||
return new IPFirewallEntry(address);
|
||||
if (entry is string s)
|
||||
return ToFirewallEntry(s);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IFirewallEntry ToFirewallEntry(string entry)
|
||||
{
|
||||
if (IPAddress.TryParse(entry, out IPAddress addr))
|
||||
return new IPFirewallEntry(addr);
|
||||
|
||||
// Try CIDR parse
|
||||
string[] str = entry.Split('/');
|
||||
|
||||
if (str.Length == 2)
|
||||
if (IPAddress.TryParse(str[0], out IPAddress cidrPrefix))
|
||||
if (int.TryParse(str[1], out int cidrLength))
|
||||
return new CIDRFirewallEntry(cidrPrefix, cidrLength);
|
||||
|
||||
return new WildcardIPFirewallEntry(entry);
|
||||
}
|
||||
|
||||
public static void RemoveAt(int index)
|
||||
{
|
||||
List.RemoveAt(index);
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void Remove(object obj)
|
||||
{
|
||||
IFirewallEntry entry = ToFirewallEntry(obj);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
List.Remove(entry);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add(object obj)
|
||||
{
|
||||
if (obj is IPAddress address)
|
||||
Add(address);
|
||||
else if (obj is string s)
|
||||
Add(s);
|
||||
else if (obj is IFirewallEntry entry)
|
||||
Add(entry);
|
||||
}
|
||||
|
||||
public static void Add(IFirewallEntry entry)
|
||||
{
|
||||
if (!List.Contains(entry))
|
||||
List.Add(entry);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void Add(string pattern)
|
||||
{
|
||||
IFirewallEntry entry = ToFirewallEntry(pattern);
|
||||
|
||||
if (!List.Contains(entry))
|
||||
List.Add(entry);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void Add(IPAddress ip)
|
||||
{
|
||||
IFirewallEntry entry = new IPFirewallEntry(ip);
|
||||
|
||||
if (!List.Contains(entry))
|
||||
List.Add(entry);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
public static void Save()
|
||||
{
|
||||
string path = "firewall.cfg";
|
||||
|
||||
using StreamWriter op = new StreamWriter(path);
|
||||
for (int i = 0; i < List.Count; ++i)
|
||||
op.WriteLine(List[i]);
|
||||
}
|
||||
|
||||
public static bool IsBlocked(IPAddress ip)
|
||||
{
|
||||
for (int i = 0; i < List.Count; i++)
|
||||
if (List[i].IsBlocked(ip))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
/*
|
||||
bool contains = false;
|
||||
|
||||
for ( int i = 0; !contains && i < m_Blocked.Count; ++i )
|
||||
{
|
||||
if (m_Blocked[i] is IPAddress)
|
||||
contains = ip.Equals( m_Blocked[i] );
|
||||
else if (m_Blocked[i] is String)
|
||||
{
|
||||
string s = (string)m_Blocked[i];
|
||||
|
||||
contains = Utility.IPMatchCIDR( s, ip );
|
||||
|
||||
if (!contains)
|
||||
contains = Utility.IPMatch( s, ip );
|
||||
}
|
||||
}
|
||||
|
||||
return contains;
|
||||
* */
|
||||
}
|
||||
|
||||
public interface IFirewallEntry
|
||||
{
|
||||
bool IsBlocked(IPAddress address);
|
||||
}
|
||||
|
||||
public class IPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly IPAddress m_Address;
|
||||
|
||||
public IPFirewallEntry(IPAddress address) => m_Address = address;
|
||||
|
||||
public bool IsBlocked(IPAddress address) => m_Address.Equals(address);
|
||||
|
||||
public override string ToString() => m_Address.ToString();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is IPAddress)
|
||||
return obj.Equals(m_Address);
|
||||
if (obj is string s)
|
||||
{
|
||||
if (IPAddress.TryParse(s, out IPAddress otherAddress))
|
||||
return otherAddress.Equals(m_Address);
|
||||
}
|
||||
else if (obj is IPFirewallEntry entry)
|
||||
{
|
||||
return m_Address.Equals(entry.m_Address);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => m_Address.GetHashCode();
|
||||
}
|
||||
|
||||
public class CIDRFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly int m_CIDRLength;
|
||||
private readonly IPAddress m_CIDRPrefix;
|
||||
|
||||
public CIDRFirewallEntry(IPAddress cidrPrefix, int cidrLength)
|
||||
{
|
||||
m_CIDRPrefix = cidrPrefix;
|
||||
m_CIDRLength = cidrLength;
|
||||
}
|
||||
|
||||
public bool IsBlocked(IPAddress address) => Utility.IPMatchCIDR(m_CIDRPrefix, address, m_CIDRLength);
|
||||
|
||||
public override string ToString() => $"{m_CIDRPrefix}/{m_CIDRLength}";
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is string entry)
|
||||
{
|
||||
string[] str = entry.Split('/');
|
||||
|
||||
if (str.Length == 2)
|
||||
if (IPAddress.TryParse(str[0], out IPAddress cidrPrefix))
|
||||
if (int.TryParse(str[1], out int cidrLength))
|
||||
return m_CIDRPrefix.Equals(cidrPrefix) && m_CIDRLength.Equals(cidrLength);
|
||||
}
|
||||
else if (obj is CIDRFirewallEntry cidrEntry)
|
||||
{
|
||||
return m_CIDRPrefix.Equals(cidrEntry.m_CIDRPrefix) && m_CIDRLength.Equals(cidrEntry.m_CIDRLength);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => m_CIDRPrefix.GetHashCode() ^ m_CIDRLength.GetHashCode();
|
||||
}
|
||||
|
||||
public class WildcardIPFirewallEntry : IFirewallEntry
|
||||
{
|
||||
private readonly string m_Entry;
|
||||
|
||||
private bool m_Valid;
|
||||
|
||||
public WildcardIPFirewallEntry(string entry) => m_Entry = entry;
|
||||
|
||||
public bool IsBlocked(IPAddress address)
|
||||
{
|
||||
if (!m_Valid)
|
||||
return false; // Why process if it's invalid? it'll return false anyway after processing it.
|
||||
|
||||
bool matched = Utility.IPMatch(m_Entry, address, out bool valid);
|
||||
m_Valid = valid;
|
||||
return matched;
|
||||
}
|
||||
|
||||
public override string ToString() => m_Entry;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is string)
|
||||
return obj.Equals(m_Entry);
|
||||
|
||||
return obj is WildcardIPFirewallEntry entry && m_Entry.Equals(entry.m_Entry);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => m_Entry.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Projects/UOContent/Accounting/IPLimiter.cs
Normal file
53
Projects/UOContent/Accounting/IPLimiter.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Misc
|
||||
{
|
||||
public class IPLimiter
|
||||
{
|
||||
public static bool Enabled { get; private set; }
|
||||
public static bool SocketBlock { get; private set; }
|
||||
public static int MaxAddresses { get; private set; }
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("ipLimiter.enable", true);
|
||||
SocketBlock = ServerConfiguration.GetOrUpdateSetting("ipLimiter.blockAtConnection", true);
|
||||
MaxAddresses = ServerConfiguration.GetOrUpdateSetting("ipLimiter.maxConnectionsPerIP", 10);
|
||||
}
|
||||
|
||||
public static IPAddress[] Exemptions =
|
||||
{
|
||||
// IPAddress.Parse( "127.0.0.1" ),
|
||||
};
|
||||
|
||||
public static bool IsExempt(IPAddress ip) => Exemptions.Contains(ip);
|
||||
|
||||
public static bool Verify(IPAddress ourAddress)
|
||||
{
|
||||
if (!Enabled || IsExempt(ourAddress))
|
||||
return true;
|
||||
|
||||
List<NetState> netStates = TcpServer.Instances;
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < netStates.Count; ++i)
|
||||
{
|
||||
NetState compState = netStates[i];
|
||||
|
||||
if (ourAddress.Equals(compState.Address))
|
||||
{
|
||||
++count;
|
||||
|
||||
if (count >= MaxAddresses)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Projects/UOContent/Accounting/IPasswordProtection.cs
Normal file
8
Projects/UOContent/Accounting/IPasswordProtection.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Server.Accounting
|
||||
{
|
||||
public interface IPasswordProtection
|
||||
{
|
||||
string EncryptPassword(string plainPassword);
|
||||
bool ValidatePassword(string encryptedPassword, string plainPassword);
|
||||
}
|
||||
}
|
||||
71
Projects/UOContent/Accounting/Security/AccountSecurity.cs
Normal file
71
Projects/UOContent/Accounting/Security/AccountSecurity.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: AccountSecurity.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public enum PasswordProtectionAlgorithm
|
||||
{
|
||||
// Obsolete algorithms from RunUO. These are not secure!
|
||||
// They are included for password upgrades only.
|
||||
None,
|
||||
MD5,
|
||||
SHA1,
|
||||
|
||||
// Supported algorithms
|
||||
SHA2, // ServUO compatibility
|
||||
PBKDF2,
|
||||
Argon2 // Recommended algorithm for real security.
|
||||
}
|
||||
|
||||
public static class AccountSecurity
|
||||
{
|
||||
public static PasswordProtectionAlgorithm CurrentAlgorithm { get; set; }
|
||||
|
||||
public static IPasswordProtection CurrentPasswordProtection => GetPasswordProtection(CurrentAlgorithm);
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
CurrentAlgorithm =
|
||||
ServerConfiguration.GetOrUpdateSetting("accountSecurity.encryptionAlgorithm", PasswordProtectionAlgorithm.Argon2);
|
||||
|
||||
if (CurrentAlgorithm < PasswordProtectionAlgorithm.SHA2)
|
||||
throw new Exception($"Security: {CurrentAlgorithm} is obsolete and not secure. Do not use it.");
|
||||
}
|
||||
|
||||
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
|
||||
{
|
||||
var passwordProtection = algorithm switch
|
||||
{
|
||||
PasswordProtectionAlgorithm.MD5 => MD5PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.SHA1 => SHA1PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.SHA2 => SHA2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance,
|
||||
PasswordProtectionAlgorithm.None => throw new Exception("Do not use PasswordProtectionAlgorithm.None"),
|
||||
_ => throw new Exception("No algorithm")
|
||||
};
|
||||
|
||||
return passwordProtection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: Argon2PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class Argon2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new Argon2PasswordProtection();
|
||||
private Argon2PasswordHasher m_PasswordHasher = new Argon2PasswordHasher();
|
||||
|
||||
public string EncryptPassword(string plainPassword) =>
|
||||
m_PasswordHasher.Hash(plainPassword);
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
m_PasswordHasher.Verify(encryptedPassword, plainPassword);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: MD5PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class MD5PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new MD5PasswordProtection();
|
||||
private MD5CryptoServiceProvider m_MD5HashProvider = new MD5CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_MD5HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: PBKDF2PasswordProtection.cs *
|
||||
* Created: 2020/04/30 - Updated: 2020/05/01 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class PBKDF2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new PBKDF2PasswordProtection();
|
||||
|
||||
private const ushort m_MinIterations = 1024;
|
||||
private const ushort m_MaxIterations = 1536;
|
||||
private static readonly HashAlgorithmName m_Algorithm = HashAlgorithmName.SHA256;
|
||||
private const int m_SaltSize = 8;
|
||||
private const int m_HashSize = 32;
|
||||
private const int m_OutputSize = 2 + m_SaltSize + m_HashSize;
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
Span<byte> output = stackalloc byte[m_OutputSize];
|
||||
int iterations = Utility.RandomMinMax(m_MinIterations, m_MaxIterations);
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(output.Slice(0, 2), (ushort)iterations);
|
||||
|
||||
var rfc2898 = new Rfc2898DeriveBytes(plainPassword, m_SaltSize, iterations, m_Algorithm);
|
||||
rfc2898.Salt.CopyTo(output.Slice(2, m_SaltSize));
|
||||
rfc2898.GetBytes(m_HashSize).CopyTo(output.Slice(m_SaltSize + 2));
|
||||
|
||||
return HexStringConverter.GetString(output);
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword)
|
||||
{
|
||||
Span<byte> encryptedBytes = stackalloc byte[m_OutputSize];
|
||||
HexStringConverter.GetBytes(encryptedPassword, encryptedBytes);
|
||||
|
||||
ushort iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes.Slice(0, 2));
|
||||
Span<byte> salt = encryptedBytes.Slice(2, m_SaltSize);
|
||||
|
||||
ReadOnlySpan<byte> hash =
|
||||
new Rfc2898DeriveBytes(plainPassword, salt.ToArray(), iterations, m_Algorithm).GetBytes(m_HashSize);
|
||||
|
||||
return hash.SequenceEqual(encryptedBytes.Slice(m_SaltSize + 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: SHA1PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class SHA1PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new SHA1PasswordProtection();
|
||||
private SHA1CryptoServiceProvider m_SHA1HashProvider = new SHA1CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_SHA1HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: SHA2PasswordProtection.cs *
|
||||
* Created: 2020/05/01 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Server.Misc;
|
||||
|
||||
namespace Server.Accounting.Security
|
||||
{
|
||||
public class SHA2PasswordProtection : IPasswordProtection
|
||||
{
|
||||
public static IPasswordProtection Instance = new SHA2PasswordProtection();
|
||||
private SHA512CryptoServiceProvider m_SHA2HashProvider = new SHA512CryptoServiceProvider();
|
||||
|
||||
public string EncryptPassword(string plainPassword)
|
||||
{
|
||||
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
|
||||
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
|
||||
Encoding.ASCII.GetBytes(password, bytes);
|
||||
|
||||
return HexStringConverter.GetString(m_SHA2HashProvider.ComputeHash(bytes));
|
||||
}
|
||||
|
||||
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
|
||||
EncryptPassword(plainPassword) == encryptedPassword;
|
||||
}
|
||||
}
|
||||
683
Projects/UOContent/Commands/Add.cs
Normal file
683
Projects/UOContent/Commands/Add.cs
Normal file
|
|
@ -0,0 +1,683 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Server.Items;
|
||||
using CPA = Server.CommandPropertyAttribute;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Add
|
||||
{
|
||||
private static readonly Type m_EntityType = typeof(IEntity);
|
||||
|
||||
private static readonly Type m_ConstructibleType = typeof(ConstructibleAttribute);
|
||||
|
||||
private static readonly Type m_EnumType = typeof(Enum);
|
||||
|
||||
private static readonly Type m_TypeType = typeof(Type);
|
||||
|
||||
private static readonly Type m_ParsableType = typeof(ParsableAttribute);
|
||||
|
||||
private static readonly Type[] m_ParseTypes = { typeof(string) };
|
||||
private static readonly object[] m_ParseArgs = new object[1];
|
||||
|
||||
private static readonly Type[] m_SignedNumerics =
|
||||
{
|
||||
typeof(long),
|
||||
typeof(int),
|
||||
typeof(short),
|
||||
typeof(sbyte)
|
||||
};
|
||||
|
||||
private static readonly Type[] m_UnsignedNumerics =
|
||||
{
|
||||
typeof(ulong),
|
||||
typeof(uint),
|
||||
typeof(ushort),
|
||||
typeof(byte)
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Tile", AccessLevel.GameMaster, Tile_OnCommand);
|
||||
CommandSystem.Register("TileRXYZ", AccessLevel.GameMaster, TileRXYZ_OnCommand);
|
||||
CommandSystem.Register("TileXYZ", AccessLevel.GameMaster, TileXYZ_OnCommand);
|
||||
CommandSystem.Register("TileZ", AccessLevel.GameMaster, TileZ_OnCommand);
|
||||
CommandSystem.Register("TileAvg", AccessLevel.GameMaster, TileAvg_OnCommand);
|
||||
|
||||
CommandSystem.Register("Outline", AccessLevel.GameMaster, Outline_OnCommand);
|
||||
CommandSystem.Register("OutlineRXYZ", AccessLevel.GameMaster, OutlineRXYZ_OnCommand);
|
||||
CommandSystem.Register("OutlineXYZ", AccessLevel.GameMaster, OutlineXYZ_OnCommand);
|
||||
CommandSystem.Register("OutlineZ", AccessLevel.GameMaster, OutlineZ_OnCommand);
|
||||
CommandSystem.Register("OutlineAvg", AccessLevel.GameMaster, OutlineAvg_OnCommand);
|
||||
}
|
||||
|
||||
public static void Invoke(Mobile from, Point3D start, Point3D end, string[] args, List<Container> packs = null,
|
||||
bool outline = false, bool mapAvg = false)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.AppendFormat("{0} {1} building ", from.AccessLevel, CommandLogging.Format(from));
|
||||
|
||||
if (start == end)
|
||||
sb.AppendFormat("at {0} in {1}", start, from.Map);
|
||||
else
|
||||
sb.AppendFormat("from {0} to {1} in {2}", start, end, from.Map);
|
||||
|
||||
sb.Append(":");
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
sb.AppendFormat(" \"{0}\"", args[i]);
|
||||
|
||||
CommandLogging.WriteLine(from, sb.ToString());
|
||||
|
||||
string name = args[0];
|
||||
|
||||
FixArgs(ref args);
|
||||
|
||||
string[,] props = null;
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
if (Insensitive.Equals(args[i], "set"))
|
||||
{
|
||||
int remains = args.Length - i - 1;
|
||||
|
||||
if (remains >= 2)
|
||||
{
|
||||
props = new string[remains / 2, 2];
|
||||
|
||||
remains /= 2;
|
||||
|
||||
for (int j = 0; j < remains; ++j)
|
||||
{
|
||||
props[j, 0] = args[i + j * 2 + 1];
|
||||
props[j, 1] = args[i + j * 2 + 2];
|
||||
}
|
||||
|
||||
FixSetString(ref args, i);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Type type = AssemblyHandler.FindFirstTypeForName(name);
|
||||
|
||||
if (!IsEntity(type))
|
||||
{
|
||||
from.SendMessage("No type with that name was found.");
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime time = DateTime.UtcNow;
|
||||
|
||||
int built = BuildObjects(from, type, start, end, args, props, packs, outline, mapAvg);
|
||||
|
||||
if (built > 0)
|
||||
from.SendMessage("{0} object{1} generated in {2:F1} seconds.", built, built != 1 ? "s" : "",
|
||||
(DateTime.UtcNow - time).TotalSeconds);
|
||||
else
|
||||
SendUsage(type, from);
|
||||
}
|
||||
|
||||
public static void FixSetString(ref string[] args, int index)
|
||||
{
|
||||
string[] old = args;
|
||||
args = new string[index];
|
||||
|
||||
Array.Copy(old, 0, args, 0, index);
|
||||
}
|
||||
|
||||
public static void FixArgs(ref string[] args)
|
||||
{
|
||||
string[] old = args;
|
||||
args = new string[args.Length - 1];
|
||||
|
||||
Array.Copy(old, 1, args, 0, args.Length);
|
||||
}
|
||||
|
||||
public static int BuildObjects(Mobile from, Type type, Point3D start, Point3D end, string[] args, string[,] props,
|
||||
List<Container> packs, bool outline = false, bool mapAvg = false)
|
||||
{
|
||||
Utility.FixPoints(ref start, ref end);
|
||||
|
||||
PropertyInfo[] realProps = null;
|
||||
|
||||
if (props != null)
|
||||
{
|
||||
realProps = new PropertyInfo[props.GetLength(0)];
|
||||
|
||||
PropertyInfo[] allProps =
|
||||
type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
for (int i = 0; i < realProps.Length; ++i)
|
||||
{
|
||||
PropertyInfo thisProp = null;
|
||||
|
||||
string propName = props[i, 0];
|
||||
|
||||
for (int j = 0; thisProp == null && j < allProps.Length; ++j)
|
||||
if (Insensitive.Equals(propName, allProps[j].Name))
|
||||
thisProp = allProps[j];
|
||||
|
||||
if (thisProp == null)
|
||||
{
|
||||
from.SendMessage("Property not found: {0}", propName);
|
||||
}
|
||||
else
|
||||
{
|
||||
CPA attr = Properties.GetCPA(thisProp);
|
||||
|
||||
if (attr == null)
|
||||
from.SendMessage("Property ({0}) not found.", propName);
|
||||
else if (from.AccessLevel < attr.WriteLevel)
|
||||
from.SendMessage("Setting this property ({0}) requires at least {1} access level.", propName,
|
||||
Mobile.GetAccessLevelName(attr.WriteLevel));
|
||||
else if (!thisProp.CanWrite || attr.ReadOnly)
|
||||
from.SendMessage("Property ({0}) is read only.", propName);
|
||||
else
|
||||
realProps[i] = thisProp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConstructorInfo[] ctors = type.GetConstructors();
|
||||
|
||||
for (int i = 0; i < ctors.Length; ++i)
|
||||
{
|
||||
ConstructorInfo ctor = ctors[i];
|
||||
|
||||
if (!IsConstructible(ctor, from.AccessLevel))
|
||||
continue;
|
||||
|
||||
int totalParams = 0;
|
||||
|
||||
// Handle optional constructors
|
||||
ParameterInfo[] paramList = ctor.GetParameters();
|
||||
for (int j = 0; j < paramList.Length; j++)
|
||||
if (!paramList[j].HasDefaultValue)
|
||||
totalParams += 1;
|
||||
|
||||
if (args.Length >= totalParams && args.Length <= paramList.Length)
|
||||
{
|
||||
object[] paramValues = ParseValues(paramList, args);
|
||||
|
||||
if (paramValues == null)
|
||||
continue;
|
||||
|
||||
int built = Build(from, start, end, ctor, paramValues, props, realProps, packs, outline, mapAvg);
|
||||
|
||||
if (built > 0)
|
||||
return built;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static object[] ParseValues(ParameterInfo[] paramList, string[] args)
|
||||
{
|
||||
object[] values = new object[paramList.Length];
|
||||
|
||||
for (int i = 0, a = 0; i < paramList.Length; i++)
|
||||
{
|
||||
ParameterInfo param = paramList[i];
|
||||
object value = ParseValue(param.ParameterType, a < args.Length ? args[a++] : null);
|
||||
|
||||
if (value != null)
|
||||
values[i] = value;
|
||||
else if (param.HasDefaultValue)
|
||||
values[i] = Type.Missing;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static object ParseValue(Type type, string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsEnum(type)) return Enum.Parse(type, value, true);
|
||||
if (IsType(type)) return AssemblyHandler.FindFirstTypeForName(value);
|
||||
if (IsParsable(type)) return ParseParsable(type, value);
|
||||
object obj = value;
|
||||
|
||||
if (value?.StartsWith("0x") == true)
|
||||
{
|
||||
if (IsSignedNumeric(type))
|
||||
obj = Convert.ToInt64(value.Substring(2), 16);
|
||||
else if (IsUnsignedNumeric(type))
|
||||
obj = Convert.ToUInt64(value.Substring(2), 16);
|
||||
else
|
||||
obj = Convert.ToInt32(value.Substring(2), 16);
|
||||
}
|
||||
|
||||
if (obj == null && !type.IsValueType)
|
||||
return null;
|
||||
|
||||
return Convert.ChangeType(obj, type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEntity Build(Mobile from, ConstructorInfo ctor, object[] values, string[,] props,
|
||||
PropertyInfo[] realProps, ref bool sendError)
|
||||
{
|
||||
object built = ctor.Invoke(values);
|
||||
|
||||
if (built != null && realProps != null)
|
||||
{
|
||||
bool hadError = false;
|
||||
|
||||
for (int i = 0; i < realProps.Length; ++i)
|
||||
{
|
||||
if (realProps[i] == null)
|
||||
continue;
|
||||
|
||||
string result =
|
||||
Properties.InternalSetValue(from, built, built, realProps[i], props[i, 1], props[i, 1], false);
|
||||
|
||||
if (result != "Property has been set.")
|
||||
{
|
||||
if (sendError)
|
||||
from.SendMessage(result);
|
||||
|
||||
hadError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hadError)
|
||||
sendError = false;
|
||||
}
|
||||
|
||||
return (IEntity)built;
|
||||
}
|
||||
|
||||
public static int Build(Mobile from, Point3D start, Point3D end, ConstructorInfo ctor, object[] values,
|
||||
string[,] props, PropertyInfo[] realProps, List<Container> packs, bool outline = false, bool mapAvg = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Map map = from.Map;
|
||||
|
||||
int width = end.X - start.X + 1;
|
||||
int height = end.Y - start.Y + 1;
|
||||
|
||||
if (outline && (width < 3 || height < 3))
|
||||
outline = false;
|
||||
|
||||
int objectCount;
|
||||
|
||||
if (packs != null)
|
||||
objectCount = packs.Count;
|
||||
else if (outline)
|
||||
objectCount = (width + height - 2) * 2;
|
||||
else
|
||||
objectCount = width * height;
|
||||
|
||||
if (objectCount >= 20)
|
||||
from.SendMessage("Constructing {0} objects, please wait.", objectCount);
|
||||
|
||||
bool sendError = true;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Serials: ");
|
||||
|
||||
if (packs != null)
|
||||
{
|
||||
for (int i = 0; i < packs.Count; ++i)
|
||||
{
|
||||
IEntity built = Build(from, ctor, values, props, realProps, ref sendError);
|
||||
|
||||
sb.AppendFormat("0x{0:X}; ", built.Serial.Value);
|
||||
|
||||
if (built is Item item)
|
||||
packs[i].DropItem(item);
|
||||
else if (built is Mobile m)
|
||||
m.MoveToWorld(new Point3D(start.X, start.Y, start.Z), map);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int z = start.Z;
|
||||
|
||||
for (int x = start.X; x <= end.X; ++x)
|
||||
for (int y = start.Y; y <= end.Y; ++y)
|
||||
{
|
||||
if (outline && x != start.X && x != end.X && y != start.Y && y != end.Y)
|
||||
continue;
|
||||
|
||||
if (mapAvg)
|
||||
z = map.GetAverageZ(x, y);
|
||||
|
||||
IEntity built = Build(from, ctor, values, props, realProps, ref sendError);
|
||||
|
||||
sb.AppendFormat("0x{0:X}; ", built.Serial.Value);
|
||||
|
||||
if (built is Item item)
|
||||
item.MoveToWorld(new Point3D(x, y, z), map);
|
||||
else if (built is Mobile m)
|
||||
m.MoveToWorld(new Point3D(x, y, z), map);
|
||||
}
|
||||
}
|
||||
|
||||
CommandLogging.WriteLine(from, sb.ToString());
|
||||
|
||||
return objectCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SendUsage(Type type, Mobile from)
|
||||
{
|
||||
ConstructorInfo[] ctors = type.GetConstructors();
|
||||
bool foundCtor = false;
|
||||
|
||||
for (int i = 0; i < ctors.Length; ++i)
|
||||
{
|
||||
ConstructorInfo ctor = ctors[i];
|
||||
|
||||
if (!IsConstructible(ctor, from.AccessLevel))
|
||||
continue;
|
||||
|
||||
if (!foundCtor)
|
||||
{
|
||||
foundCtor = true;
|
||||
from.SendMessage("Usage:");
|
||||
}
|
||||
|
||||
SendCtor(type, ctor, from);
|
||||
}
|
||||
|
||||
if (!foundCtor)
|
||||
from.SendMessage("That type is not marked constructible.");
|
||||
}
|
||||
|
||||
public static void SendCtor(Type type, ConstructorInfo ctor, Mobile from)
|
||||
{
|
||||
ParameterInfo[] paramList = ctor.GetParameters();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append(type.Name);
|
||||
|
||||
for (int i = 0; i < paramList.Length; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
sb.Append(',');
|
||||
|
||||
sb.Append(' ');
|
||||
|
||||
sb.Append(paramList[i].ParameterType.Name);
|
||||
sb.Append(' ');
|
||||
sb.Append(paramList[i].Name);
|
||||
}
|
||||
|
||||
from.SendMessage(sb.ToString());
|
||||
}
|
||||
|
||||
private static void TileBox_Callback(Mobile from, Point3D start, Point3D end, TileState ts)
|
||||
{
|
||||
bool mapAvg = false;
|
||||
|
||||
switch (ts.m_ZType)
|
||||
{
|
||||
case TileZType.Fixed:
|
||||
{
|
||||
start.Z = end.Z = ts.m_FixedZ;
|
||||
break;
|
||||
}
|
||||
case TileZType.MapAverage:
|
||||
{
|
||||
mapAvg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Invoke(from, start, end, ts.m_Args, null, ts.m_Outline, mapAvg);
|
||||
}
|
||||
|
||||
private static void Internal_OnCommand(CommandEventArgs e, bool outline)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (e.Length >= 1)
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) =>
|
||||
TileBox_Callback(from, start, end, new TileState(TileZType.Start, 0, e.Arguments, outline)));
|
||||
else
|
||||
from.SendMessage("Format: {0} <type> [params] [set {{<propertyName> <value> ...}}]",
|
||||
outline ? "Outline" : "Tile");
|
||||
}
|
||||
|
||||
private static void InternalRXYZ_OnCommand(CommandEventArgs e, bool outline)
|
||||
{
|
||||
if (e.Length >= 6)
|
||||
{
|
||||
Point3D p = new Point3D(e.Mobile.X + e.GetInt32(0), e.Mobile.Y + e.GetInt32(1), e.Mobile.Z + e.GetInt32(4));
|
||||
Point3D p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, p.Z);
|
||||
|
||||
string[] subArgs = new string[e.Length - 5];
|
||||
|
||||
for (int i = 0; i < subArgs.Length; ++i)
|
||||
subArgs[i] = e.Arguments[i + 5];
|
||||
|
||||
Invoke(e.Mobile, p, p2, subArgs, null, outline);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"Format: {0}RXYZ <x> <y> <w> <h> <z> <type> [params] [set {{<propertyName> <value> ...}}]",
|
||||
outline ? "Outline" : "Tile");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InternalXYZ_OnCommand(CommandEventArgs e, bool outline)
|
||||
{
|
||||
if (e.Length >= 6)
|
||||
{
|
||||
Point3D p = new Point3D(e.GetInt32(0), e.GetInt32(1), e.GetInt32(4));
|
||||
Point3D p2 = new Point3D(p.X + e.GetInt32(2) - 1, p.Y + e.GetInt32(3) - 1, e.GetInt32(4));
|
||||
|
||||
string[] subArgs = new string[e.Length - 5];
|
||||
|
||||
for (int i = 0; i < subArgs.Length; ++i)
|
||||
subArgs[i] = e.Arguments[i + 5];
|
||||
|
||||
Invoke(e.Mobile, p, p2, subArgs, null, outline);
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"Format: {0}XYZ <x> <y> <w> <h> <z> <type> [params] [set {{<propertyName> <value> ...}}]",
|
||||
outline ? "Outline" : "Tile");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InternalZ_OnCommand(CommandEventArgs e, bool outline)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (e.Length >= 2)
|
||||
{
|
||||
string[] subArgs = new string[e.Length - 1];
|
||||
|
||||
for (int i = 0; i < subArgs.Length; ++i)
|
||||
subArgs[i] = e.Arguments[i + 1];
|
||||
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) =>
|
||||
TileBox_Callback(from, start, end, new TileState(TileZType.Fixed, e.GetInt32(0), subArgs, outline)));
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("Format: {0}Z <z> <type> [params] [set {{<propertyName> <value> ...}}]",
|
||||
outline ? "Outline" : "Tile");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InternalAvg_OnCommand(CommandEventArgs e, bool outline)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (e.Length >= 1)
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) =>
|
||||
TileBox_Callback(from, start, end, new TileState(TileZType.MapAverage, 0, e.Arguments, outline)));
|
||||
else
|
||||
from.SendMessage("Format: {0}Avg <type> [params] [set {{<propertyName> <value> ...}}]",
|
||||
outline ? "Outline" : "Tile");
|
||||
}
|
||||
|
||||
[Usage("Tile <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name into a targeted bounding box. Optional constructor parameters. Optional set property list.")]
|
||||
public static void Tile_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Internal_OnCommand(e, false);
|
||||
}
|
||||
|
||||
[Usage("TileRXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name into a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list.")]
|
||||
public static void TileRXYZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalRXYZ_OnCommand(e, false);
|
||||
}
|
||||
|
||||
[Usage("TileXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name into a given bounding box. Optional constructor parameters. Optional set property list.")]
|
||||
public static void TileXYZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalXYZ_OnCommand(e, false);
|
||||
}
|
||||
|
||||
[Usage("TileZ <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name into a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list.")]
|
||||
public static void TileZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalZ_OnCommand(e, false);
|
||||
}
|
||||
|
||||
[Usage("TileAvg <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name into a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list.")]
|
||||
public static void TileAvg_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalAvg_OnCommand(e, false);
|
||||
}
|
||||
|
||||
[Usage("Outline <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name around a targeted bounding box. Optional constructor parameters. Optional set property list.")]
|
||||
public static void Outline_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Internal_OnCommand(e, true);
|
||||
}
|
||||
|
||||
[Usage("OutlineRXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name around a given bounding box, (x, y) parameters are relative to your characters position. Optional constructor parameters. Optional set property list.")]
|
||||
public static void OutlineRXYZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalRXYZ_OnCommand(e, true);
|
||||
}
|
||||
|
||||
[Usage("OutlineXYZ <x> <y> <w> <h> <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name around a given bounding box. Optional constructor parameters. Optional set property list.")]
|
||||
public static void OutlineXYZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalXYZ_OnCommand(e, true);
|
||||
}
|
||||
|
||||
[Usage("OutlineZ <z> <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name around a targeted bounding box at a fixed Z location. Optional constructor parameters. Optional set property list.")]
|
||||
public static void OutlineZ_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalZ_OnCommand(e, true);
|
||||
}
|
||||
|
||||
[Usage("OutlineAvg <name> [params] [set {<propertyName> <value> ...}]")]
|
||||
[Description(
|
||||
"Tiles an item or npc by name around a targeted bounding box on the map's average Z elevation. Optional constructor parameters. Optional set property list.")]
|
||||
public static void OutlineAvg_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
InternalAvg_OnCommand(e, true);
|
||||
}
|
||||
|
||||
public static bool IsEntity(Type t) => m_EntityType.IsAssignableFrom(t);
|
||||
|
||||
public static bool IsConstructible(ConstructorInfo ctor, AccessLevel accessLevel)
|
||||
{
|
||||
object[] attrs = ctor.GetCustomAttributes(m_ConstructibleType, false);
|
||||
|
||||
return attrs.Length != 0 && accessLevel >= ((ConstructibleAttribute)attrs[0]).AccessLevel;
|
||||
}
|
||||
|
||||
public static bool IsEnum(Type type) => type.IsSubclassOf(m_EnumType);
|
||||
|
||||
public static bool IsType(Type type) => type == m_TypeType || type.IsSubclassOf(m_TypeType);
|
||||
|
||||
public static bool IsParsable(Type type) => type.IsDefined(m_ParsableType, false);
|
||||
|
||||
public static object ParseParsable(Type type, string value)
|
||||
{
|
||||
MethodInfo method = type.GetMethod("Parse", m_ParseTypes);
|
||||
|
||||
m_ParseArgs[0] = value;
|
||||
|
||||
return method?.Invoke(null, m_ParseArgs);
|
||||
}
|
||||
|
||||
public static bool IsSignedNumeric(Type type)
|
||||
{
|
||||
for (int i = 0; i < m_SignedNumerics.Length; ++i)
|
||||
if (type == m_SignedNumerics[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsUnsignedNumeric(Type type)
|
||||
{
|
||||
for (int i = 0; i < m_UnsignedNumerics.Length; ++i)
|
||||
if (type == m_UnsignedNumerics[i])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum TileZType
|
||||
{
|
||||
Start,
|
||||
Fixed,
|
||||
MapAverage
|
||||
}
|
||||
|
||||
private class TileState
|
||||
{
|
||||
public readonly string[] m_Args;
|
||||
public readonly int m_FixedZ;
|
||||
public readonly bool m_Outline;
|
||||
public readonly TileZType m_ZType;
|
||||
|
||||
public TileState(TileZType zType, int fixedZ, string[] args, bool outline)
|
||||
{
|
||||
m_ZType = zType;
|
||||
m_FixedZ = fixedZ;
|
||||
m_Args = args;
|
||||
m_Outline = outline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Projects/UOContent/Commands/Attributes.cs
Normal file
25
Projects/UOContent/Commands/Attributes.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class UsageAttribute : Attribute
|
||||
{
|
||||
public UsageAttribute(string usage) => Usage = usage;
|
||||
|
||||
public string Usage { get; }
|
||||
}
|
||||
|
||||
public class DescriptionAttribute : Attribute
|
||||
{
|
||||
public DescriptionAttribute(string description) => Description = description;
|
||||
|
||||
public string Description { get; }
|
||||
}
|
||||
|
||||
public class AliasesAttribute : Attribute
|
||||
{
|
||||
public AliasesAttribute(params string[] aliases) => Aliases = aliases;
|
||||
|
||||
public string[] Aliases { get; }
|
||||
}
|
||||
}
|
||||
418
Projects/UOContent/Commands/Batch.cs
Normal file
418
Projects/UOContent/Commands/Batch.cs
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Batch : BaseCommand
|
||||
{
|
||||
public Batch()
|
||||
{
|
||||
Commands = new[] { "Batch" };
|
||||
ListOptimized = true;
|
||||
}
|
||||
|
||||
public BaseCommandImplementor Scope { get; set; }
|
||||
|
||||
public string Condition { get; set; } = "";
|
||||
|
||||
public List<BatchCommand> BatchCommands { get; } = new List<BatchCommand>();
|
||||
|
||||
public override void ExecuteList(CommandEventArgs e, List<object> list)
|
||||
{
|
||||
if (list.Count == 0)
|
||||
{
|
||||
LogFailure("Nothing was found to use this command on.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
BaseCommand[] commands = new BaseCommand[BatchCommands.Count];
|
||||
CommandEventArgs[] eventArgs = new CommandEventArgs[BatchCommands.Count];
|
||||
|
||||
for (int i = 0; i < BatchCommands.Count; ++i)
|
||||
{
|
||||
BatchCommand bc = BatchCommands[i];
|
||||
|
||||
bc.GetDetails(out string commandString, out string argString, out string[] args);
|
||||
|
||||
BaseCommand command = Scope.Commands[commandString];
|
||||
|
||||
commands[i] = command;
|
||||
eventArgs[i] = new CommandEventArgs(e.Mobile, commandString, argString, args);
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"That is either an invalid command name or one that does not support this modifier: {0}.",
|
||||
commandString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Mobile.AccessLevel < command.AccessLevel)
|
||||
{
|
||||
e.Mobile.SendMessage("You do not have access to that command: {0}.", commandString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command.ValidateArgs(Scope, eventArgs[i])) return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < commands.Length; ++i)
|
||||
{
|
||||
BaseCommand command = commands[i];
|
||||
BatchCommand bc = BatchCommands[i];
|
||||
|
||||
if (list.Count > 20)
|
||||
CommandLogging.Enabled = false;
|
||||
|
||||
List<object> usedList;
|
||||
|
||||
if (Utility.InsensitiveCompare(bc.Object, "Current") == 0)
|
||||
{
|
||||
usedList = list;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dictionary<Type, PropertyInfo[]> propertyChains = new Dictionary<Type, PropertyInfo[]>();
|
||||
|
||||
usedList = new List<object>(list.Count);
|
||||
|
||||
for (int j = 0; j < list.Count; ++j)
|
||||
{
|
||||
object obj = list[j];
|
||||
|
||||
if (obj == null)
|
||||
continue;
|
||||
|
||||
Type type = obj.GetType();
|
||||
string failReason = "";
|
||||
|
||||
if (!propertyChains.TryGetValue(type, out PropertyInfo[] chain))
|
||||
propertyChains[type] = chain = Properties.GetPropertyInfoChain(e.Mobile, type, bc.Object,
|
||||
PropertyAccess.Read, ref failReason);
|
||||
|
||||
if (chain == null)
|
||||
continue;
|
||||
|
||||
PropertyInfo endProp = Properties.GetPropertyInfo(ref obj, chain, ref failReason);
|
||||
|
||||
if (endProp == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
obj = endProp.GetValue(obj, null);
|
||||
|
||||
if (obj != null)
|
||||
usedList.Add(obj);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command.ExecuteList(eventArgs[i], usedList);
|
||||
|
||||
if (list.Count > 20)
|
||||
CommandLogging.Enabled = true;
|
||||
|
||||
command.Flush(e.Mobile, list.Count > 20);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
e.Mobile.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Run(Mobile from)
|
||||
{
|
||||
if (Scope == null)
|
||||
{
|
||||
from.SendMessage("You must select the batch command scope.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Condition.Length > 0 && !Scope.SupportsConditionals)
|
||||
{
|
||||
from.SendMessage("This command scope does not support conditionals.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Condition.Length > 0 && !Utility.InsensitiveStartsWith(Condition, "where"))
|
||||
{
|
||||
from.SendMessage("The condition field must start with \"where\".");
|
||||
return false;
|
||||
}
|
||||
|
||||
string[] args = CommandSystem.Split(Condition);
|
||||
|
||||
Scope.Process(from, this, args);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Batch", AccessLevel.Counselor, Batch_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Batch")]
|
||||
[Description("Allows multiple commands to be run at the same time.")]
|
||||
public static void Batch_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.SendGump(new BatchGump(e.Mobile, new Batch()));
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchCommand
|
||||
{
|
||||
public BatchCommand(string command, string obj)
|
||||
{
|
||||
Command = command;
|
||||
Object = obj;
|
||||
}
|
||||
|
||||
public string Command { get; set; }
|
||||
|
||||
public string Object { get; set; }
|
||||
|
||||
public void GetDetails(out string command, out string argString, out string[] args)
|
||||
{
|
||||
int indexOf = Command.IndexOf(' ');
|
||||
|
||||
if (indexOf >= 0)
|
||||
{
|
||||
argString = Command.Substring(indexOf + 1);
|
||||
|
||||
command = Command.Substring(0, indexOf);
|
||||
args = CommandSystem.Split(argString);
|
||||
}
|
||||
else
|
||||
{
|
||||
argString = "";
|
||||
command = Command.ToLower();
|
||||
args = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchGump : BaseGridGump
|
||||
{
|
||||
private readonly Batch m_Batch;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public BatchGump(Mobile from, Batch batch) : base(30, 30)
|
||||
{
|
||||
m_From = from;
|
||||
m_Batch = batch;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
AddNewPage();
|
||||
|
||||
/* Header */
|
||||
AddEntryHeader(20);
|
||||
AddEntryHtml(180, Center("Batch Commands"));
|
||||
AddEntryHeader(20);
|
||||
AddNewLine();
|
||||
|
||||
AddEntryHeader(9);
|
||||
AddEntryLabel(191, "Run Batch");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 0), ArrowRightWidth, ArrowRightHeight);
|
||||
AddNewLine();
|
||||
|
||||
AddBlankLine();
|
||||
|
||||
/* Scope */
|
||||
AddEntryHeader(20);
|
||||
AddEntryHtml(180, Center("Scope"));
|
||||
AddEntryHeader(20);
|
||||
AddNewLine();
|
||||
|
||||
AddEntryHeader(9);
|
||||
AddEntryLabel(191, m_Batch.Scope == null ? "Select Scope" : m_Batch.Scope.Accessors[0]);
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 1), ArrowRightWidth, ArrowRightHeight);
|
||||
AddNewLine();
|
||||
|
||||
AddBlankLine();
|
||||
|
||||
/* Condition */
|
||||
AddEntryHeader(20);
|
||||
AddEntryHtml(180, Center("Condition"));
|
||||
AddEntryHeader(20);
|
||||
AddNewLine();
|
||||
|
||||
AddEntryHeader(9);
|
||||
AddEntryText(202, 0, m_Batch.Condition);
|
||||
AddEntryHeader(9);
|
||||
AddNewLine();
|
||||
|
||||
AddBlankLine();
|
||||
|
||||
/* Commands */
|
||||
AddEntryHeader(20);
|
||||
AddEntryHtml(180, Center("Commands"));
|
||||
AddEntryHeader(20);
|
||||
|
||||
for (int i = 0; i < m_Batch.BatchCommands.Count; ++i)
|
||||
{
|
||||
BatchCommand bc = m_Batch.BatchCommands[i];
|
||||
|
||||
AddNewLine();
|
||||
|
||||
AddImageTiled(CurrentX, CurrentY, 9, 2, 0x24A8);
|
||||
AddImageTiled(CurrentX, CurrentY + 2, 2, EntryHeight + OffsetSize + EntryHeight - 4, 0x24A8);
|
||||
AddImageTiled(CurrentX, CurrentY + EntryHeight + OffsetSize + EntryHeight - 2, 9, 2, 0x24A8);
|
||||
AddImageTiled(CurrentX + 3, CurrentY + 3, 6, EntryHeight + EntryHeight - 4 - OffsetSize, HeaderGumpID);
|
||||
|
||||
IncreaseX(9);
|
||||
AddEntryText(202, 1 + i * 2, bc.Command);
|
||||
AddEntryHeader(9, 2);
|
||||
|
||||
AddNewLine();
|
||||
|
||||
IncreaseX(9);
|
||||
AddEntryText(202, 2 + i * 2, bc.Object);
|
||||
}
|
||||
|
||||
AddNewLine();
|
||||
|
||||
AddEntryHeader(9);
|
||||
AddEntryLabel(191, "Add New Command");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, 2), ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (!SplitButtonID(info.ButtonID, 1, out int type, out int index))
|
||||
return;
|
||||
|
||||
TextRelay entry = info.GetTextEntry(0);
|
||||
|
||||
if (entry != null)
|
||||
m_Batch.Condition = entry.Text;
|
||||
|
||||
for (int i = m_Batch.BatchCommands.Count - 1; i >= 0; --i)
|
||||
{
|
||||
BatchCommand sc = m_Batch.BatchCommands[i];
|
||||
|
||||
entry = info.GetTextEntry(1 + i * 2);
|
||||
|
||||
if (entry != null)
|
||||
sc.Command = entry.Text;
|
||||
|
||||
entry = info.GetTextEntry(2 + i * 2);
|
||||
|
||||
if (entry != null)
|
||||
sc.Object = entry.Text;
|
||||
|
||||
if (sc.Command.Length == 0 && sc.Object.Length == 0)
|
||||
m_Batch.BatchCommands.RemoveAt(i);
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 0: // main
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: // run
|
||||
{
|
||||
m_Batch.Run(m_From);
|
||||
break;
|
||||
}
|
||||
case 1: // set scope
|
||||
{
|
||||
m_From.SendGump(new BatchScopeGump(m_From, m_Batch));
|
||||
return;
|
||||
}
|
||||
case 2: // add command
|
||||
{
|
||||
m_Batch.BatchCommands.Add(new BatchCommand("", ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_From.SendGump(new BatchGump(m_From, m_Batch));
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchScopeGump : BaseGridGump
|
||||
{
|
||||
private readonly Batch m_Batch;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public BatchScopeGump(Mobile from, Batch batch) : base(30, 30)
|
||||
{
|
||||
m_From = from;
|
||||
m_Batch = batch;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
AddNewPage();
|
||||
|
||||
/* Header */
|
||||
AddEntryHeader(20);
|
||||
AddEntryHtml(140, Center("Change Scope"));
|
||||
AddEntryHeader(20);
|
||||
|
||||
/* Options */
|
||||
for (int i = 0; i < BaseCommandImplementor.Implementors.Count; ++i)
|
||||
{
|
||||
BaseCommandImplementor impl = BaseCommandImplementor.Implementors[i];
|
||||
|
||||
if (m_From.AccessLevel < impl.AccessLevel)
|
||||
continue;
|
||||
|
||||
AddNewLine();
|
||||
|
||||
AddEntryLabel(20 + OffsetSize + 140, impl.Accessors[0]);
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, GetButtonID(1, 0, i), ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (SplitButtonID(info.ButtonID, 1, out int type, out int index))
|
||||
switch (type)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
if (index < BaseCommandImplementor.Implementors.Count)
|
||||
{
|
||||
BaseCommandImplementor impl = BaseCommandImplementor.Implementors[index];
|
||||
|
||||
if (m_From.AccessLevel >= impl.AccessLevel)
|
||||
m_Batch.Scope = impl;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_From.SendGump(new BatchGump(m_From, m_Batch));
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Projects/UOContent/Commands/BoundingBoxPicker.cs
Normal file
63
Projects/UOContent/Commands/BoundingBoxPicker.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using Server.Targeting;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public delegate void BoundingBoxCallback(Map map, Point3D start, Point3D end);
|
||||
|
||||
public static class BoundingBoxPicker
|
||||
{
|
||||
public static void Begin(Mobile from, BoundingBoxCallback callback)
|
||||
{
|
||||
from.SendMessage("Target the first location of the bounding box.");
|
||||
from.Target = new PickTarget(callback);
|
||||
}
|
||||
|
||||
private class PickTarget : Target
|
||||
{
|
||||
private readonly BoundingBoxCallback m_Callback;
|
||||
private readonly bool m_First;
|
||||
private readonly Map m_Map;
|
||||
private readonly Point3D m_Store;
|
||||
|
||||
public PickTarget(BoundingBoxCallback callback) : this(Point3D.Zero, true, null, callback)
|
||||
{
|
||||
}
|
||||
|
||||
public PickTarget(Point3D store, bool first, Map map, BoundingBoxCallback callback) : base(-1, true, TargetFlags.None)
|
||||
{
|
||||
m_Store = store;
|
||||
m_First = first;
|
||||
m_Map = map;
|
||||
m_Callback = callback;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (!(targeted is IPoint3D p))
|
||||
return;
|
||||
|
||||
if (p is Item item)
|
||||
p = item.GetWorldTop();
|
||||
|
||||
if (m_First)
|
||||
{
|
||||
from.SendMessage("Target another location to complete the bounding box.");
|
||||
from.Target = new PickTarget(new Point3D(p), false, from.Map, m_Callback);
|
||||
}
|
||||
else if (from.Map != m_Map)
|
||||
{
|
||||
from.SendMessage("Both locations must reside on the same map.");
|
||||
}
|
||||
else if (m_Map != null && m_Map != Map.Internal && m_Callback != null)
|
||||
{
|
||||
Point3D start = m_Store;
|
||||
Point3D end = new Point3D(p);
|
||||
|
||||
Utility.FixPoints(ref start, ref end);
|
||||
|
||||
m_Callback(m_Map, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1103
Projects/UOContent/Commands/Decorate.cs
Normal file
1103
Projects/UOContent/Commands/Decorate.cs
Normal file
File diff suppressed because it is too large
Load diff
1092
Projects/UOContent/Commands/DecorateMag.cs
Normal file
1092
Projects/UOContent/Commands/DecorateMag.cs
Normal file
File diff suppressed because it is too large
Load diff
2610
Projects/UOContent/Commands/Docs.cs
Normal file
2610
Projects/UOContent/Commands/Docs.cs
Normal file
File diff suppressed because it is too large
Load diff
26
Projects/UOContent/Commands/DragEffects.cs
Normal file
26
Projects/UOContent/Commands/DragEffects.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
namespace Server.Commands
|
||||
{
|
||||
public static class DragEffects
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("DragEffects", AccessLevel.Developer, DragEffects_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("DragEffects [enable=false]")]
|
||||
[Description("Enables or disables the item drag and drop effects.")]
|
||||
public static void DragEffects_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 0)
|
||||
{
|
||||
e.Mobile.SendMessage("Drag effects are currently {0}.", Mobile.DragEffects ? "enabled" : "disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
Mobile.DragEffects = e.GetBoolean(0);
|
||||
|
||||
e.Mobile.SendMessage("Drag effects have been {0}.", Mobile.DragEffects ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Projects/UOContent/Commands/Dupe.cs
Normal file
136
Projects/UOContent/Commands/Dupe.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Server.Items;
|
||||
using Server.Targeting;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Dupe
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Dupe", AccessLevel.GameMaster, Dupe_OnCommand);
|
||||
CommandSystem.Register("DupeInBag", AccessLevel.GameMaster, DupeInBag_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Dupe [amount]")]
|
||||
[Description("Dupes a targeted item.")]
|
||||
private static void Dupe_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
int amount = 1;
|
||||
if (e.Length >= 1)
|
||||
amount = e.GetInt32(0);
|
||||
e.Mobile.Target = new DupeTarget(false, amount > 0 ? amount : 1);
|
||||
e.Mobile.SendMessage("What do you wish to dupe?");
|
||||
}
|
||||
|
||||
[Usage("DupeInBag <count>")]
|
||||
[Description("Dupes an item at it's current location (count) number of times.")]
|
||||
private static void DupeInBag_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
int amount = 1;
|
||||
if (e.Length >= 1)
|
||||
amount = e.GetInt32(0);
|
||||
|
||||
e.Mobile.Target = new DupeTarget(true, amount > 0 ? amount : 1);
|
||||
e.Mobile.SendMessage("What do you wish to dupe?");
|
||||
}
|
||||
|
||||
public static void CopyProperties(Item dest, Item src)
|
||||
{
|
||||
PropertyInfo[] props = src.GetType().GetProperties();
|
||||
|
||||
for (int i = 0; i < props.Length; i++)
|
||||
try
|
||||
{
|
||||
if (props[i].CanRead && props[i].CanWrite) props[i].SetValue(dest, props[i].GetValue(src, null), null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Console.WriteLine( "Denied" );
|
||||
}
|
||||
}
|
||||
|
||||
private class DupeTarget : Target
|
||||
{
|
||||
private readonly int m_Amount;
|
||||
private readonly bool m_InBag;
|
||||
|
||||
public DupeTarget(bool inbag, int amount)
|
||||
: base(15, false, TargetFlags.None)
|
||||
{
|
||||
m_InBag = inbag;
|
||||
m_Amount = amount;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targ)
|
||||
{
|
||||
bool done = false;
|
||||
if (!(targ is Item))
|
||||
{
|
||||
from.SendMessage("You can only dupe items.");
|
||||
return;
|
||||
}
|
||||
|
||||
CommandLogging.WriteLine(from, "{0} {1} duping {2} (inBag={3}; amount={4})", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(targ), m_InBag, m_Amount);
|
||||
|
||||
Item copy = (Item)targ;
|
||||
Container pack = null;
|
||||
|
||||
if (m_InBag)
|
||||
{
|
||||
if (copy.Parent is Container cont)
|
||||
pack = cont;
|
||||
else if (copy.Parent is Mobile m)
|
||||
pack = m.Backpack;
|
||||
}
|
||||
else
|
||||
{
|
||||
pack = from.Backpack;
|
||||
}
|
||||
|
||||
ConstructorInfo c = ActivatorUtil.GetConstructor(copy.GetType());
|
||||
if (c != null)
|
||||
{
|
||||
var paramList = c.GetParameters();
|
||||
object[] args = paramList.Length == 0 ? null : new object[paramList.Length];
|
||||
if (args != null) Array.Fill(args, Type.Missing);
|
||||
try
|
||||
{
|
||||
from.SendMessage("Duping {0}...", m_Amount);
|
||||
for (int i = 0; i < m_Amount; i++)
|
||||
if (c.Invoke(args) is Item newItem)
|
||||
{
|
||||
CopyProperties(newItem, copy); // copy.Dupe( item, copy.Amount );
|
||||
copy.OnAfterDuped(newItem);
|
||||
newItem.Parent = null;
|
||||
|
||||
if (pack != null)
|
||||
pack.DropItem(newItem);
|
||||
else
|
||||
newItem.MoveToWorld(from.Location, from.Map);
|
||||
|
||||
newItem.InvalidateProperties();
|
||||
|
||||
CommandLogging.WriteLine(from, "{0} {1} duped {2} creating {3}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(targ),
|
||||
CommandLogging.Format(newItem));
|
||||
}
|
||||
|
||||
from.SendMessage("Done");
|
||||
done = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
from.SendMessage("Error!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) from.SendMessage("Unable to dupe. Item must have a 0 parameter constructor.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Projects/UOContent/Commands/ExportWSC.cs
Normal file
79
Projects/UOContent/Commands/ExportWSC.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class ExportCommand
|
||||
{
|
||||
private const string ExportFile = @"C:\Uo\WorldForge\items.wsc";
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("ExportWSC", AccessLevel.Administrator, Export_OnCommand);
|
||||
}
|
||||
|
||||
public static void Export_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
StreamWriter w = new StreamWriter(ExportFile);
|
||||
List<Item> remove = new List<Item>();
|
||||
int count = 0;
|
||||
|
||||
e.Mobile.SendMessage("Exporting all static items to \"{0}\"...", ExportFile);
|
||||
e.Mobile.SendMessage("This will delete all static items in the world. Please make a backup.");
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
if ((item is Static || item is BaseFloor || item is BaseWall)
|
||||
&& item.RootParent == null)
|
||||
{
|
||||
w.WriteLine("SECTION WORLDITEM {0}", count);
|
||||
w.WriteLine("{");
|
||||
w.WriteLine("SERIAL {0}", item.Serial);
|
||||
w.WriteLine("NAME #");
|
||||
w.WriteLine("NAME2 #");
|
||||
w.WriteLine("ID {0}", item.ItemID);
|
||||
w.WriteLine("X {0}", item.X);
|
||||
w.WriteLine("Y {0}", item.Y);
|
||||
w.WriteLine("Z {0}", item.Z);
|
||||
w.WriteLine("COLOR {0}", item.Hue);
|
||||
w.WriteLine("CONT -1");
|
||||
w.WriteLine("TYPE 0");
|
||||
w.WriteLine("AMOUNT 1");
|
||||
w.WriteLine("WEIGHT 255");
|
||||
w.WriteLine("OWNER -1");
|
||||
w.WriteLine("SPAWN -1");
|
||||
w.WriteLine("VALUE 1");
|
||||
w.WriteLine("}");
|
||||
w.WriteLine("");
|
||||
|
||||
count++;
|
||||
remove.Add(item);
|
||||
w.Flush();
|
||||
}
|
||||
|
||||
w.Close();
|
||||
|
||||
foreach (Item item in remove)
|
||||
item.Delete();
|
||||
|
||||
e.Mobile.SendMessage("Export complete. Exported {0} statics.", count);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*SECTION WORLDITEM 1
|
||||
{
|
||||
SERIAL 1073741830
|
||||
NAME #
|
||||
NAME2 #
|
||||
ID 1709
|
||||
X 1439
|
||||
Y 1613
|
||||
Z 20
|
||||
CONT -1
|
||||
TYPE 12
|
||||
AMOUNT 1
|
||||
WEIGHT 25500
|
||||
OWNER -1
|
||||
SPAWN -1
|
||||
VALUE 1
|
||||
}*/
|
||||
403
Projects/UOContent/Commands/GenCategorization.cs
Normal file
403
Projects/UOContent/Commands/GenCategorization.cs
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using Server.Items;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Categorization
|
||||
{
|
||||
private static CategoryEntry m_RootItems, m_RootMobiles;
|
||||
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
private static readonly Type typeofConstructible = typeof(ConstructibleAttribute);
|
||||
|
||||
public static CategoryEntry Items
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootItems == null)
|
||||
Load();
|
||||
|
||||
return m_RootItems;
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryEntry Mobiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_RootMobiles == null)
|
||||
Load();
|
||||
|
||||
return m_RootMobiles;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("RebuildCategorization", AccessLevel.Administrator, RebuildCategorization_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("RebuildCategorization")]
|
||||
[Description("Rebuilds the categorization data file used by the Add command.")]
|
||||
public static void RebuildCategorization_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
CategoryEntry root = new CategoryEntry(null, "Add Menu", new[] { Items, Mobiles });
|
||||
|
||||
Export(root, "Data/objects.xml", "Objects");
|
||||
|
||||
e.Mobile.SendMessage("Categorization menu rebuilt.");
|
||||
}
|
||||
|
||||
public static void Export(CategoryEntry ce, string fileName, string title)
|
||||
{
|
||||
XmlTextWriter xml = new XmlTextWriter(fileName, Encoding.UTF8);
|
||||
|
||||
xml.Indentation = 1;
|
||||
xml.IndentChar = '\t';
|
||||
xml.Formatting = Formatting.Indented;
|
||||
|
||||
xml.WriteStartDocument(true);
|
||||
|
||||
RecurseExport(xml, ce);
|
||||
|
||||
xml.Flush();
|
||||
xml.Close();
|
||||
}
|
||||
|
||||
public static void RecurseExport(XmlTextWriter xml, CategoryEntry ce)
|
||||
{
|
||||
xml.WriteStartElement("category");
|
||||
|
||||
xml.WriteAttributeString("title", ce.Title);
|
||||
|
||||
List<CategoryEntry> subCats = new List<CategoryEntry>(ce.SubCategories);
|
||||
|
||||
subCats.Sort(new CategorySorter());
|
||||
|
||||
for (int i = 0; i < subCats.Count; ++i)
|
||||
RecurseExport(xml, subCats[i]);
|
||||
|
||||
ce.Matched.Sort(new CategoryTypeSorter());
|
||||
|
||||
for (int i = 0; i < ce.Matched.Count; ++i)
|
||||
{
|
||||
CategoryTypeEntry cte = ce.Matched[i];
|
||||
|
||||
xml.WriteStartElement("object");
|
||||
|
||||
xml.WriteAttributeString("type", cte.Type.ToString());
|
||||
|
||||
if (cte.Object is Item item)
|
||||
{
|
||||
int itemID = item.ItemID;
|
||||
|
||||
if (item is BaseAddon addon && addon.Components.Count == 1)
|
||||
itemID = addon.Components[0].ItemID;
|
||||
|
||||
if (itemID > TileData.MaxItemValue)
|
||||
itemID = 1;
|
||||
|
||||
xml.WriteAttributeString("gfx", XmlConvert.ToString(itemID));
|
||||
|
||||
int hue = item.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
if (hue != 0)
|
||||
xml.WriteAttributeString("hue", XmlConvert.ToString(hue));
|
||||
|
||||
item.Delete();
|
||||
}
|
||||
else if (cte.Object is Mobile mob)
|
||||
{
|
||||
int itemID = ShrinkTable.Lookup(mob, 1);
|
||||
|
||||
xml.WriteAttributeString("gfx", XmlConvert.ToString(itemID));
|
||||
|
||||
int hue = mob.Hue & 0x7FFF;
|
||||
|
||||
if ((hue & 0x4000) != 0)
|
||||
hue = 0;
|
||||
|
||||
if (hue != 0)
|
||||
xml.WriteAttributeString("hue", XmlConvert.ToString(hue));
|
||||
|
||||
mob.Delete();
|
||||
}
|
||||
|
||||
xml.WriteEndElement();
|
||||
}
|
||||
|
||||
xml.WriteEndElement();
|
||||
}
|
||||
|
||||
public static void Load()
|
||||
{
|
||||
List<Type> types = new List<Type>();
|
||||
|
||||
AddTypes(Core.Assembly, types);
|
||||
|
||||
for (int i = 0; i < AssemblyHandler.Assemblies.Length; ++i)
|
||||
AddTypes(AssemblyHandler.Assemblies[i], types);
|
||||
|
||||
m_RootItems = Load(types, "Data/items.cfg");
|
||||
m_RootMobiles = Load(types, "Data/mobiles.cfg");
|
||||
}
|
||||
|
||||
private static CategoryEntry Load(List<Type> types, string config)
|
||||
{
|
||||
CategoryLine[] lines = CategoryLine.Load(config);
|
||||
|
||||
if (lines.Length > 0)
|
||||
{
|
||||
int index = 0;
|
||||
CategoryEntry root = new CategoryEntry(null, lines, ref index);
|
||||
|
||||
Fill(root, types);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
return new CategoryEntry();
|
||||
}
|
||||
|
||||
private static bool IsConstructible(Type type)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeofItem) && !type.IsSubclassOf(typeofMobile))
|
||||
return false;
|
||||
|
||||
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
|
||||
|
||||
return ctor?.IsDefined(typeofConstructible, false) == true;
|
||||
}
|
||||
|
||||
private static void AddTypes(Assembly asm, List<Type> types)
|
||||
{
|
||||
Type[] allTypes = asm.GetTypes();
|
||||
|
||||
for (int i = 0; i < allTypes.Length; ++i)
|
||||
{
|
||||
Type type = allTypes[i];
|
||||
|
||||
if (type.IsAbstract)
|
||||
continue;
|
||||
|
||||
if (IsConstructible(type))
|
||||
types.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Fill(CategoryEntry root, List<Type> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
Type type = list[i];
|
||||
CategoryEntry match = GetDeepestMatch(root, type);
|
||||
|
||||
if (match == null)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
match.Matched.Add(new CategoryTypeEntry(type));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CategoryEntry GetDeepestMatch(CategoryEntry root, Type type)
|
||||
{
|
||||
if (!root.IsMatch(type))
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < root.SubCategories.Length; ++i)
|
||||
{
|
||||
CategoryEntry check = GetDeepestMatch(root.SubCategories[i], type);
|
||||
|
||||
if (check != null)
|
||||
return check;
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategorySorter : IComparer<CategoryEntry>
|
||||
{
|
||||
public int Compare(CategoryEntry x, CategoryEntry y)
|
||||
{
|
||||
string a = x?.Title;
|
||||
string b = y?.Title;
|
||||
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
|
||||
if (a == null)
|
||||
return 1;
|
||||
|
||||
return a.CompareTo(b);
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeSorter : IComparer<CategoryTypeEntry>
|
||||
{
|
||||
public int Compare(CategoryTypeEntry x, CategoryTypeEntry y)
|
||||
{
|
||||
string a = x?.Type.Name;
|
||||
string b = y?.Type.Name;
|
||||
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
|
||||
if (a == null)
|
||||
return 1;
|
||||
|
||||
return a.CompareTo(b);
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryTypeEntry
|
||||
{
|
||||
public CategoryTypeEntry(Type type)
|
||||
{
|
||||
Type = type;
|
||||
Object = ActivatorUtil.CreateInstance(type);
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Object { get; }
|
||||
}
|
||||
|
||||
public class CategoryEntry
|
||||
{
|
||||
public CategoryEntry(CategoryEntry parent = null, string title = "(empty)", CategoryEntry[] subCats = null)
|
||||
{
|
||||
Parent = parent;
|
||||
Title = title;
|
||||
SubCategories = subCats ?? Array.Empty<CategoryEntry>();
|
||||
Matches = Array.Empty<Type>();
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public CategoryEntry(CategoryEntry parent, CategoryLine[] lines, ref int index)
|
||||
{
|
||||
Parent = parent;
|
||||
|
||||
string text = lines[index].Text;
|
||||
|
||||
int start = text.IndexOf('(');
|
||||
|
||||
if (start < 0)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
Title = text.Substring(0, start).Trim();
|
||||
|
||||
int end = text.IndexOf(')', ++start);
|
||||
|
||||
if (end < start)
|
||||
throw new FormatException($"Input string not correctly formatted ('{text}')");
|
||||
|
||||
text = text.Substring(start, end - start);
|
||||
string[] split = text.Split(';');
|
||||
|
||||
List<Type> list = new List<Type>();
|
||||
|
||||
for (int i = 0; i < split.Length; ++i)
|
||||
{
|
||||
Type type = AssemblyHandler.FindFirstTypeForName(split[i].Trim());
|
||||
|
||||
if (type == null)
|
||||
Console.WriteLine("Match type not found ('{0}')", split[i].Trim());
|
||||
else
|
||||
list.Add(type);
|
||||
}
|
||||
|
||||
Matches = list.ToArray();
|
||||
list.Clear();
|
||||
|
||||
int ourIndentation = lines[index].Indentation;
|
||||
|
||||
++index;
|
||||
|
||||
List<CategoryEntry> entryList = new List<CategoryEntry>();
|
||||
|
||||
while (index < lines.Length && lines[index].Indentation > ourIndentation)
|
||||
entryList.Add(new CategoryEntry(this, lines, ref index));
|
||||
|
||||
SubCategories = entryList.ToArray();
|
||||
entryList.Clear();
|
||||
|
||||
Matched = new List<CategoryTypeEntry>();
|
||||
}
|
||||
|
||||
public string Title { get; }
|
||||
|
||||
public Type[] Matches { get; }
|
||||
|
||||
public CategoryEntry Parent { get; }
|
||||
|
||||
public CategoryEntry[] SubCategories { get; }
|
||||
|
||||
public List<CategoryTypeEntry> Matched { get; }
|
||||
|
||||
public bool IsMatch(Type type)
|
||||
{
|
||||
bool isMatch = false;
|
||||
|
||||
for (int i = 0; !isMatch && i < Matches.Length; ++i)
|
||||
isMatch = type == Matches[i] || type.IsSubclassOf(Matches[i]);
|
||||
|
||||
return isMatch;
|
||||
}
|
||||
}
|
||||
|
||||
public class CategoryLine
|
||||
{
|
||||
public CategoryLine(string input)
|
||||
{
|
||||
int index;
|
||||
|
||||
for (index = 0; index < input.Length; ++index)
|
||||
if (char.IsLetter(input, index))
|
||||
break;
|
||||
|
||||
if (index >= input.Length)
|
||||
throw new FormatException($"Input string not correctly formatted ('{input}')");
|
||||
|
||||
Indentation = index;
|
||||
Text = input.Substring(index);
|
||||
}
|
||||
|
||||
public int Indentation { get; }
|
||||
|
||||
public string Text { get; }
|
||||
|
||||
public static CategoryLine[] Load(string path)
|
||||
{
|
||||
List<CategoryLine> list = new List<CategoryLine>();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
list.Add(new CategoryLine(line));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Projects/UOContent/Commands/GenTeleporter.cs
Normal file
154
Projects/UOContent/Commands/GenTeleporter.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Server.Items;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public struct Location
|
||||
{
|
||||
[JsonPropertyName("point"), JsonConverter(typeof(Point3dConverter))]
|
||||
public Point3D Pos { get; set; }
|
||||
[JsonPropertyName("map"), JsonConverter(typeof(MapConverter))]
|
||||
public Map Map { get; set; }
|
||||
public override string ToString() => $"({Map.Name}:{Pos.X},{Pos.Y},{Pos.Z})";
|
||||
public override int GetHashCode() => ToString().GetHashCode();
|
||||
}
|
||||
|
||||
public struct TeleporterDefinition
|
||||
{
|
||||
[JsonPropertyName("source")]
|
||||
public Location Source { get; set; }
|
||||
[JsonPropertyName("destination")]
|
||||
public Location Destination { get; set; }
|
||||
[JsonPropertyName("back")]
|
||||
public bool Back { get; set; }
|
||||
public override string ToString() => $"{{{Source},{Destination},{Back}}}";
|
||||
public override int GetHashCode() => ToString().GetHashCode();
|
||||
}
|
||||
|
||||
public static class GenTeleporter
|
||||
{
|
||||
private const int SuccessHue = 72, WarningHue = 53, ErrorHue = 33;
|
||||
private static readonly string TeleporterJsonDataPath = Path.Combine("Data", "teleporters.json");
|
||||
private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("TelGen", AccessLevel.Administrator, GenTeleporter_OnCommand);
|
||||
CommandSystem.Register("TelGenDelete", AccessLevel.Administrator, TelGenDelete_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("TelGenDelete")]
|
||||
[Description("Destroys world/dungeon teleporters for all facets.")]
|
||||
public static void TelGenDelete_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
from.SendMessage("Removing teleporters, please wait.");
|
||||
int count = 0;
|
||||
|
||||
void ProcessDeletion(TeleporterDefinition x)
|
||||
{
|
||||
count += TeleportersCreator.DeleteTeleporters(x.Source);
|
||||
if (x.Back) count += TeleportersCreator.DeleteTeleporters(x.Destination);
|
||||
}
|
||||
|
||||
if (!ProcessTeleporterData(from, ProcessDeletion))
|
||||
{
|
||||
if (count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion, {count} Teleporters Removed.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(WarningHue, $"{count} Teleporters Removed.");
|
||||
}
|
||||
|
||||
[Usage("TelGen")]
|
||||
[Description("Generates world/dungeon teleporters for all facets.")]
|
||||
public static void GenTeleporter_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
from.SendMessage("Generating teleporters, please wait.");
|
||||
TeleportersCreator c = new TeleportersCreator();
|
||||
|
||||
if (!ProcessTeleporterData(from, c.CreateTeleporter))
|
||||
{
|
||||
if (c.DelCount > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.DelCount} Teleporters Removed.");
|
||||
if (c.Count > 0)
|
||||
from.SendMessage(WarningHue, $"Partial Completion: {c.Count} Teleporters Added.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.SendMessage(SuccessHue, "Teleporter generating complete.");
|
||||
from.SendMessage(WarningHue, $"{c.DelCount} Teleporters Removed.");
|
||||
from.SendMessage(SuccessHue, $"{c.Count} Teleporters Added.");
|
||||
}
|
||||
|
||||
private static bool ProcessTeleporterData(Mobile m, Action<TeleporterDefinition> processor)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json;
|
||||
using (StreamReader reader = new StreamReader(TeleporterJsonDataPath))
|
||||
json = reader.ReadToEnd();
|
||||
var teleporters = JsonSerializer.Deserialize<List<TeleporterDefinition>>(json, JsonOptions);
|
||||
for (int i = 0; i < teleporters.Count; i++)
|
||||
processor(teleporters[i]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(ex.ToString());
|
||||
m.SendMessage(ErrorHue, $"Failed to load/process data file '{TeleporterJsonDataPath}'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class TeleportersCreator
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
public int DelCount { get; private set; }
|
||||
|
||||
private static bool IsWithinZ(int delta) => delta >= -12 && delta <= 12;
|
||||
|
||||
public static int DeleteTeleporters(Location location)
|
||||
{
|
||||
IPooledEnumerable<Teleporter> eable = location.Map.GetItemsInRange<Teleporter>(location.Pos, 0);
|
||||
var items = eable
|
||||
.Where(x => !(x is KeywordTeleporter || x is SkillTeleporter) && IsWithinZ(x.Z - location.Pos.Z));
|
||||
int count = 0;
|
||||
foreach (var item in items)
|
||||
{
|
||||
count++;
|
||||
item.Delete();
|
||||
}
|
||||
eable.Free();
|
||||
return count;
|
||||
}
|
||||
|
||||
public void CreateTeleporter(TeleporterDefinition telDef)
|
||||
{
|
||||
DelCount += DeleteTeleporters(telDef.Source);
|
||||
Count++;
|
||||
new Teleporter(telDef.Destination.Pos, telDef.Destination.Map).MoveToWorld(telDef.Source.Pos, telDef.Source.Map);
|
||||
if (!telDef.Back) return;
|
||||
DelCount += DeleteTeleporters(telDef.Destination);
|
||||
Count++;
|
||||
new Teleporter(telDef.Source.Pos, telDef.Source.Map).MoveToWorld(telDef.Destination.Pos, telDef.Destination.Map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Projects/UOContent/Commands/Generic/Commands/BaseCommand.cs
Normal file
131
Projects/UOContent/Commands/Generic/Commands/BaseCommand.cs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public enum ObjectTypes
|
||||
{
|
||||
Both,
|
||||
Items,
|
||||
Mobiles,
|
||||
All
|
||||
}
|
||||
|
||||
public abstract class BaseCommand
|
||||
{
|
||||
private readonly List<MessageEntry> m_Responses = new List<MessageEntry>();
|
||||
private readonly List<MessageEntry> m_Failures = new List<MessageEntry>();
|
||||
|
||||
public bool ListOptimized { get; set; }
|
||||
|
||||
public string[] Commands { get; set; }
|
||||
|
||||
public string Usage { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public AccessLevel AccessLevel { get; set; }
|
||||
|
||||
public ObjectTypes ObjectTypes { get; set; }
|
||||
|
||||
public CommandSupport Supports { get; set; }
|
||||
|
||||
public static bool IsAccessible(Mobile from, object obj)
|
||||
{
|
||||
if (from.AccessLevel >= AccessLevel.Administrator || obj == null)
|
||||
return true;
|
||||
|
||||
Mobile mob = null;
|
||||
|
||||
if (obj is Mobile m)
|
||||
mob = m;
|
||||
else if (obj is Item item)
|
||||
mob = item.RootParent as Mobile;
|
||||
|
||||
return mob == null || mob == from || from.AccessLevel > mob.AccessLevel;
|
||||
}
|
||||
|
||||
public virtual void ExecuteList(CommandEventArgs e, List<object> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
Execute(e, list[i]);
|
||||
}
|
||||
|
||||
public virtual void Execute(CommandEventArgs e, object obj)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool ValidateArgs(BaseCommandImplementor impl, CommandEventArgs e) => true;
|
||||
|
||||
public void AddResponse(string message)
|
||||
{
|
||||
for (int i = 0; i < m_Responses.Count; ++i)
|
||||
{
|
||||
MessageEntry entry = m_Responses[i];
|
||||
|
||||
if (entry.m_Message == message)
|
||||
{
|
||||
++entry.m_Count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Responses.Count == 10)
|
||||
return;
|
||||
|
||||
m_Responses.Add(new MessageEntry(message));
|
||||
}
|
||||
|
||||
public void LogFailure(string message)
|
||||
{
|
||||
for (int i = 0; i < m_Failures.Count; ++i)
|
||||
{
|
||||
MessageEntry entry = m_Failures[i];
|
||||
|
||||
if (entry.m_Message == message)
|
||||
{
|
||||
++entry.m_Count;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Failures.Count == 10)
|
||||
return;
|
||||
|
||||
m_Failures.Add(new MessageEntry(message));
|
||||
}
|
||||
|
||||
public void Flush(Mobile from, bool flushToLog)
|
||||
{
|
||||
if (m_Responses.Count > 0)
|
||||
for (int i = 0; i < m_Responses.Count; ++i)
|
||||
{
|
||||
MessageEntry entry = m_Responses[i];
|
||||
|
||||
from.SendMessage(entry.ToString());
|
||||
|
||||
if (flushToLog)
|
||||
CommandLogging.WriteLine(from, entry.ToString());
|
||||
}
|
||||
else
|
||||
for (int i = 0; i < m_Failures.Count; ++i)
|
||||
from.SendMessage(m_Failures[i].ToString());
|
||||
|
||||
m_Responses.Clear();
|
||||
m_Failures.Clear();
|
||||
}
|
||||
|
||||
private class MessageEntry
|
||||
{
|
||||
public int m_Count;
|
||||
public readonly string m_Message;
|
||||
|
||||
public MessageEntry(string message)
|
||||
{
|
||||
m_Message = message;
|
||||
m_Count = 1;
|
||||
}
|
||||
|
||||
public override string ToString() => m_Count > 1 ? $"{m_Message} ({m_Count})" : m_Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
1112
Projects/UOContent/Commands/Generic/Commands/Commands.cs
Normal file
1112
Projects/UOContent/Commands/Generic/Commands/Commands.cs
Normal file
File diff suppressed because it is too large
Load diff
195
Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs
Normal file
195
Projects/UOContent/Commands/Generic/Commands/DesignInsert.cs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
using System.Collections.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
using Server.Multis;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class DesignInsertCommand : BaseCommand
|
||||
{
|
||||
public enum DesignInsertResult
|
||||
{
|
||||
Valid,
|
||||
InvalidItem,
|
||||
NotInHouse,
|
||||
OutsideHouseBounds
|
||||
}
|
||||
|
||||
public DesignInsertCommand()
|
||||
{
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Supports = CommandSupport.Single | CommandSupport.Area;
|
||||
Commands = new[] { "DesignInsert" };
|
||||
ObjectTypes = ObjectTypes.Items;
|
||||
Usage = "DesignInsert [allItems=false]";
|
||||
Description = "Inserts multiple targeted items into a customizable house's design.";
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
TargetCommands.Register(new DesignInsertCommand());
|
||||
}
|
||||
|
||||
public static DesignInsertResult ProcessInsert(Item item, bool staticsOnly, out HouseFoundation house)
|
||||
{
|
||||
house = null;
|
||||
|
||||
if (item == null || item is BaseMulti || item is HouseSign || (staticsOnly && !(item is Static)))
|
||||
return DesignInsertResult.InvalidItem;
|
||||
|
||||
house = BaseHouse.FindHouseAt(item) as HouseFoundation;
|
||||
|
||||
if (house == null)
|
||||
return DesignInsertResult.NotInHouse;
|
||||
|
||||
int x = item.X - house.X;
|
||||
int y = item.Y - house.Y;
|
||||
int z = item.Z - house.Z;
|
||||
|
||||
if (!TryInsertIntoState(house.CurrentState, item.ItemID, x, y, z))
|
||||
return DesignInsertResult.OutsideHouseBounds;
|
||||
|
||||
TryInsertIntoState(house.DesignState, item.ItemID, x, y, z);
|
||||
item.Delete();
|
||||
|
||||
return DesignInsertResult.Valid;
|
||||
}
|
||||
|
||||
private static bool TryInsertIntoState(DesignState state, int itemID, int x, int y, int z)
|
||||
{
|
||||
MultiComponentList mcl = state.Components;
|
||||
|
||||
if (x < mcl.Min.X || y < mcl.Min.Y || x > mcl.Max.X || y > mcl.Max.Y)
|
||||
return false;
|
||||
|
||||
mcl.Add(itemID, x, y, z);
|
||||
state.OnRevised();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Execute(CommandEventArgs e, object obj)
|
||||
{
|
||||
Target t = new DesignInsertTarget(new List<HouseFoundation>(), e.Length < 1 || !e.GetBoolean(0));
|
||||
t.Invoke(e.Mobile, obj);
|
||||
}
|
||||
|
||||
private class DesignInsertTarget : Target
|
||||
{
|
||||
private readonly List<HouseFoundation> m_Foundations;
|
||||
private readonly bool m_StaticsOnly;
|
||||
|
||||
public DesignInsertTarget(List<HouseFoundation> foundations, bool staticsOnly)
|
||||
: base(-1, false, TargetFlags.None)
|
||||
{
|
||||
m_Foundations = foundations;
|
||||
m_StaticsOnly = staticsOnly;
|
||||
}
|
||||
|
||||
protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType)
|
||||
{
|
||||
if (m_Foundations.Count != 0)
|
||||
{
|
||||
from.SendMessage("Your changes have been committed. Updating...");
|
||||
|
||||
foreach (HouseFoundation house in m_Foundations)
|
||||
house.Delta(ItemDelta.Update);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object obj)
|
||||
{
|
||||
DesignInsertResult result = ProcessInsert(obj as Item, m_StaticsOnly, out HouseFoundation house);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case DesignInsertResult.Valid:
|
||||
{
|
||||
if (m_Foundations.Count == 0)
|
||||
from.SendMessage(
|
||||
"The item has been inserted into the house design. Press ESC when you are finished.");
|
||||
else
|
||||
from.SendMessage("The item has been inserted into the house design.");
|
||||
|
||||
if (!m_Foundations.Contains(house))
|
||||
m_Foundations.Add(house);
|
||||
|
||||
break;
|
||||
}
|
||||
case DesignInsertResult.InvalidItem:
|
||||
{
|
||||
from.SendMessage("That cannot be inserted. Try again.");
|
||||
break;
|
||||
}
|
||||
case DesignInsertResult.NotInHouse:
|
||||
case DesignInsertResult.OutsideHouseBounds:
|
||||
{
|
||||
from.SendMessage("That item is not inside a customizable house. Try again.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
from.Target = new DesignInsertTarget(m_Foundations, m_StaticsOnly);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExecuteList(CommandEventArgs e, List<object> list)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
from.SendGump(new WarningGump(1060637, 30720,
|
||||
$"You are about to insert {list.Count} objects. This cannot be undone without a full server revert.<br><br>Continue?",
|
||||
0xFFC000, 420, 280, okay => OnConfirmCallback(from, okay, list, e.Length < 1 || !e.GetBoolean(0))));
|
||||
AddResponse("Awaiting confirmation...");
|
||||
}
|
||||
|
||||
private void OnConfirmCallback(Mobile from, bool okay, List<object> list, bool staticsOnly)
|
||||
{
|
||||
bool flushToLog = false;
|
||||
|
||||
if (okay)
|
||||
{
|
||||
List<HouseFoundation> foundations = new List<HouseFoundation>();
|
||||
flushToLog = list.Count > 20;
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
DesignInsertResult result = ProcessInsert(list[i] as Item, staticsOnly, out HouseFoundation house);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case DesignInsertResult.Valid:
|
||||
{
|
||||
AddResponse("The item has been inserted into the house design.");
|
||||
|
||||
if (!foundations.Contains(house))
|
||||
foundations.Add(house);
|
||||
|
||||
break;
|
||||
}
|
||||
case DesignInsertResult.InvalidItem:
|
||||
{
|
||||
LogFailure("That cannot be inserted.");
|
||||
break;
|
||||
}
|
||||
case DesignInsertResult.NotInHouse:
|
||||
case DesignInsertResult.OutsideHouseBounds:
|
||||
{
|
||||
LogFailure("That item is not inside a customizable house.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (HouseFoundation house in foundations)
|
||||
house.Delta(ItemDelta.Update);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddResponse("Command aborted.");
|
||||
}
|
||||
|
||||
Flush(from, flushToLog);
|
||||
}
|
||||
}
|
||||
}
|
||||
567
Projects/UOContent/Commands/Generic/Commands/Interface.cs
Normal file
567
Projects/UOContent/Commands/Generic/Commands/Interface.cs
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
using Server.Targets;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class InterfaceCommand : BaseCommand
|
||||
{
|
||||
public InterfaceCommand()
|
||||
{
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Supports = CommandSupport.Complex | CommandSupport.Simple;
|
||||
Commands = new[] { "Interface" };
|
||||
ObjectTypes = ObjectTypes.Both;
|
||||
Usage = "Interface [view <properties ...>]";
|
||||
Description = "Opens an interface to interact with matched objects. Generally used with condition arguments.";
|
||||
ListOptimized = true;
|
||||
}
|
||||
|
||||
public override void ExecuteList(CommandEventArgs e, List<object> list)
|
||||
{
|
||||
if (list.Count > 0)
|
||||
{
|
||||
List<string> columns = new List<string> { "Object" };
|
||||
|
||||
if (e.Length > 0)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
if (Insensitive.Equals(e.GetString(0), "view"))
|
||||
++offset;
|
||||
|
||||
while (offset < e.Length)
|
||||
columns.Add(e.GetString(offset++));
|
||||
}
|
||||
|
||||
e.Mobile.SendGump(new InterfaceGump(e.Mobile, columns.ToArray(), list, 0, null));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddResponse("No matching objects found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InterfaceGump : BaseGridGump
|
||||
{
|
||||
private const int EntriesPerPage = 15;
|
||||
|
||||
private readonly string[] m_Columns;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
private readonly List<object> m_List;
|
||||
private readonly int m_Page;
|
||||
|
||||
private readonly object m_Select;
|
||||
|
||||
public InterfaceGump(Mobile from, string[] columns, List<object> list, int page, object select) : base(30, 30)
|
||||
{
|
||||
m_From = from;
|
||||
|
||||
m_Columns = columns;
|
||||
|
||||
m_List = list;
|
||||
m_Page = page;
|
||||
|
||||
m_Select = select;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
AddNewPage();
|
||||
|
||||
if (m_Page > 0)
|
||||
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
|
||||
else
|
||||
AddEntryHeader(20);
|
||||
|
||||
AddEntryHtml(40 + m_Columns.Length * 130 - 20 + (m_Columns.Length - 2) * OffsetSize, Center(
|
||||
$"Page {m_Page + 1} of {(m_List.Count + EntriesPerPage - 1) / EntriesPerPage}"));
|
||||
|
||||
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
|
||||
else
|
||||
AddEntryHeader(20);
|
||||
|
||||
if (m_Columns.Length > 1)
|
||||
{
|
||||
AddNewLine();
|
||||
|
||||
for (int i = 0; i < m_Columns.Length; ++i)
|
||||
{
|
||||
if (i > 0 && m_List.Count > 0)
|
||||
{
|
||||
object obj = m_List[0];
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
string failReason = null;
|
||||
PropertyInfo[] chain = Properties.GetPropertyInfoChain(m_From, obj.GetType(), m_Columns[i],
|
||||
PropertyAccess.Read, ref failReason);
|
||||
|
||||
if (chain?.Length > 0)
|
||||
{
|
||||
m_Columns[i] = "";
|
||||
|
||||
for (int j = 0; j < chain.Length; ++j)
|
||||
{
|
||||
if (j > 0)
|
||||
m_Columns[i] += '.';
|
||||
|
||||
m_Columns[i] += chain[j].Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddEntryHtml(130 + (i == 0 ? 40 : 0), m_Columns[i]);
|
||||
}
|
||||
|
||||
AddEntryHeader(20);
|
||||
}
|
||||
|
||||
for (int i = m_Page * EntriesPerPage, line = 0; line < EntriesPerPage && i < m_List.Count; ++i, ++line)
|
||||
{
|
||||
AddNewLine();
|
||||
|
||||
object obj = m_List[i];
|
||||
bool isDeleted = false;
|
||||
|
||||
if (obj is Item item)
|
||||
{
|
||||
if (!(isDeleted = item.Deleted))
|
||||
AddEntryHtml(40 + 130, item.GetType().Name);
|
||||
}
|
||||
else if (obj is Mobile mob)
|
||||
{
|
||||
if (!(isDeleted = mob.Deleted))
|
||||
AddEntryHtml(40 + 130, mob.Name);
|
||||
}
|
||||
|
||||
if (isDeleted)
|
||||
{
|
||||
AddEntryHtml(40 + 130, "(deleted)");
|
||||
|
||||
for (int j = 1; j < m_Columns.Length; ++j)
|
||||
AddEntryHtml(130, "---");
|
||||
|
||||
AddEntryHeader(20);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 1; j < m_Columns.Length; ++j)
|
||||
{
|
||||
object src = obj;
|
||||
|
||||
string value;
|
||||
string failReason = "";
|
||||
|
||||
PropertyInfo[] chain = Properties.GetPropertyInfoChain(m_From, src.GetType(), m_Columns[j],
|
||||
PropertyAccess.Read, ref failReason);
|
||||
|
||||
if (chain == null || chain.Length == 0)
|
||||
{
|
||||
value = "---";
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyInfo p = Properties.GetPropertyInfo(ref src, chain, ref failReason);
|
||||
|
||||
if (p == null)
|
||||
value = "---";
|
||||
else
|
||||
value = PropertiesGump.ValueToString(src, p);
|
||||
}
|
||||
|
||||
AddEntryHtml(130, value);
|
||||
}
|
||||
|
||||
bool isSelected = m_Select != null && obj == m_Select;
|
||||
|
||||
AddEntryButton(20, isSelected ? 9762 : ArrowRightID1, isSelected ? 9763 : ArrowRightID2, 3 + i,
|
||||
ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
}
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
if (m_Page > 0)
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page - 1, m_Select));
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page + 1, m_Select));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
int v = info.ButtonID - 3;
|
||||
|
||||
if (v >= 0 && v < m_List.Count)
|
||||
{
|
||||
object obj = m_List[v];
|
||||
|
||||
if (!BaseCommand.IsAccessible(m_From, obj))
|
||||
{
|
||||
m_From.SendLocalizedMessage(500447); // That is not accessible.
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Select));
|
||||
break;
|
||||
}
|
||||
|
||||
if (obj is Item item && !item.Deleted)
|
||||
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, item));
|
||||
else if (obj is Mobile mobile && !mobile.Deleted)
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, mobile));
|
||||
else
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Select));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InterfaceItemGump : BaseGridGump
|
||||
{
|
||||
private readonly string[] m_Columns;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
private readonly Item m_Item;
|
||||
|
||||
private readonly List<object> m_List;
|
||||
private readonly int m_Page;
|
||||
|
||||
public InterfaceItemGump(Mobile from, string[] columns, List<object> list, int page, Item item) : base(30, 30)
|
||||
{
|
||||
m_From = from;
|
||||
|
||||
m_Columns = columns;
|
||||
|
||||
m_List = list;
|
||||
m_Page = page;
|
||||
|
||||
m_Item = item;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
AddNewPage();
|
||||
|
||||
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
|
||||
AddEntryHtml(160, m_Item.GetType().Name);
|
||||
AddEntryHeader(20);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Properties");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Delete");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Go there");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 4, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Move to target");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 5, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Bring to pack");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 6, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
private void InvokeCommand(string ip)
|
||||
{
|
||||
CommandSystem.Handle(m_From, $"{CommandSystem.Prefix}{ip}");
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Item.Deleted)
|
||||
{
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BaseCommand.IsAccessible(m_From, m_Item))
|
||||
{
|
||||
m_From.SendMessage("That is no longer accessible.");
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
{
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
break;
|
||||
}
|
||||
case 2: // Properties
|
||||
{
|
||||
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
m_From.SendGump(new PropertiesGump(m_From, m_Item));
|
||||
break;
|
||||
}
|
||||
case 3: // Delete
|
||||
{
|
||||
CommandLogging.WriteLine(m_From, "{0} {1} deleting {2}", m_From.AccessLevel,
|
||||
CommandLogging.Format(m_From), CommandLogging.Format(m_Item));
|
||||
m_Item.Delete();
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
break;
|
||||
}
|
||||
case 4: // Go there
|
||||
{
|
||||
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
InvokeCommand($"Go {m_Item.Serial.Value}");
|
||||
break;
|
||||
}
|
||||
case 5: // Move to target
|
||||
{
|
||||
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
m_From.Target = new MoveTarget(m_Item);
|
||||
break;
|
||||
}
|
||||
case 6: // Bring to pack
|
||||
{
|
||||
Mobile owner = m_Item.RootParent as Mobile;
|
||||
|
||||
if (owner?.Map != null && owner.Map != Map.Internal &&
|
||||
!BaseCommand.IsAccessible(m_From, owner) /* !m_From.CanSee( owner )*/)
|
||||
{
|
||||
m_From.SendMessage("You can not get what you can not see.");
|
||||
}
|
||||
else if (owner != null && (owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
|
||||
owner.AccessLevel >= m_From.AccessLevel)
|
||||
{
|
||||
m_From.SendMessage("You can not get what you can not see.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendGump(new InterfaceItemGump(m_From, m_Columns, m_List, m_Page, m_Item));
|
||||
m_From.AddToBackpack(m_Item);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InterfaceMobileGump : BaseGridGump
|
||||
{
|
||||
private readonly string[] m_Columns;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
private readonly List<object> m_List;
|
||||
|
||||
private readonly Mobile m_Mobile;
|
||||
private readonly int m_Page;
|
||||
|
||||
public InterfaceMobileGump(Mobile from, string[] columns, List<object> list, int page, Mobile mob)
|
||||
: base(30, 30)
|
||||
{
|
||||
m_From = from;
|
||||
|
||||
m_Columns = columns;
|
||||
|
||||
m_List = list;
|
||||
m_Page = page;
|
||||
|
||||
m_Mobile = mob;
|
||||
|
||||
Render();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
AddNewPage();
|
||||
|
||||
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
|
||||
AddEntryHtml(160, m_Mobile.Name);
|
||||
AddEntryHeader(20);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Properties");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
if (!m_Mobile.Player)
|
||||
{
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Delete");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
|
||||
if (m_Mobile != m_From)
|
||||
{
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Go to there");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 4, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Bring them here");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 5, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Move to target");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 6, ArrowRightWidth, ArrowRightHeight);
|
||||
|
||||
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
|
||||
{
|
||||
AddNewLine();
|
||||
if (m_Mobile.Alive)
|
||||
{
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Kill");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 7, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Resurrect");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 8, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Mobile.NetState != null)
|
||||
{
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, "Client");
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 9, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
private void InvokeCommand(string ip)
|
||||
{
|
||||
CommandSystem.Handle(m_From, $"{CommandSystem.Prefix}{ip}");
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Mobile.Deleted)
|
||||
{
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BaseCommand.IsAccessible(m_From, m_Mobile))
|
||||
{
|
||||
m_From.SendMessage("That is no longer accessible.");
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
{
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
break;
|
||||
}
|
||||
case 2: // Properties
|
||||
{
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
m_From.SendGump(new PropertiesGump(m_From, m_Mobile));
|
||||
break;
|
||||
}
|
||||
case 3: // Delete
|
||||
{
|
||||
if (!m_Mobile.Player)
|
||||
{
|
||||
CommandLogging.WriteLine(m_From, "{0} {1} deleting {2}", m_From.AccessLevel,
|
||||
CommandLogging.Format(m_From), CommandLogging.Format(m_Mobile));
|
||||
m_Mobile.Delete();
|
||||
m_From.SendGump(new InterfaceGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: // Go there
|
||||
{
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
InvokeCommand($"Go {m_Mobile.Serial.Value}");
|
||||
break;
|
||||
}
|
||||
case 5: // Bring them here
|
||||
{
|
||||
if (m_From.Map == null || m_From.Map == Map.Internal)
|
||||
{
|
||||
m_From.SendMessage("You cannot bring that person here.");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
m_Mobile.MoveToWorld(m_From.Location, m_From.Map);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 6: // Move to target
|
||||
{
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
m_From.Target = new MoveTarget(m_Mobile);
|
||||
break;
|
||||
}
|
||||
case 7: // Kill
|
||||
{
|
||||
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
|
||||
m_Mobile.Kill();
|
||||
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
|
||||
break;
|
||||
}
|
||||
case 8: // Res
|
||||
{
|
||||
if (m_From == m_Mobile || m_From.AccessLevel > m_Mobile.AccessLevel)
|
||||
{
|
||||
m_Mobile.PlaySound(0x214);
|
||||
m_Mobile.FixedEffect(0x376A, 10, 16);
|
||||
|
||||
m_Mobile.Resurrect();
|
||||
}
|
||||
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
|
||||
break;
|
||||
}
|
||||
case 9: // Client
|
||||
{
|
||||
m_From.SendGump(new InterfaceMobileGump(m_From, m_Columns, m_List, m_Page, m_Mobile));
|
||||
|
||||
if (m_Mobile.NetState != null)
|
||||
m_From.SendGump(new ClientGump(m_From, m_Mobile.NetState));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Projects/UOContent/Commands/Generic/Extensions/BaseExtension.cs
Normal file
130
Projects/UOContent/Commands/Generic/Extensions/BaseExtension.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public delegate BaseExtension ExtensionConstructor();
|
||||
|
||||
public sealed class ExtensionInfo
|
||||
{
|
||||
public ExtensionInfo(int order, string name, int size, ExtensionConstructor constructor)
|
||||
{
|
||||
Name = name;
|
||||
Size = size;
|
||||
|
||||
Order = order;
|
||||
|
||||
Constructor = constructor;
|
||||
}
|
||||
|
||||
public static Dictionary<string, ExtensionInfo> Table { get; } =
|
||||
new Dictionary<string, ExtensionInfo>(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
public int Order { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public int Size { get; }
|
||||
|
||||
public bool IsFixedSize => Size >= 0;
|
||||
|
||||
public ExtensionConstructor Constructor { get; }
|
||||
|
||||
public static void Register(ExtensionInfo ext)
|
||||
{
|
||||
Table[ext.Name] = ext;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Extensions : List<BaseExtension>
|
||||
{
|
||||
public bool IsValid(object obj)
|
||||
{
|
||||
for (int i = 0; i < Count; ++i)
|
||||
if (!this[i].IsValid(obj))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Filter(List<object> list)
|
||||
{
|
||||
for (int i = 0; i < Count; ++i)
|
||||
this[i].Filter(list);
|
||||
}
|
||||
|
||||
public static Extensions Parse(Mobile from, ref string[] args)
|
||||
{
|
||||
Extensions parsed = new Extensions();
|
||||
|
||||
int size = args.Length;
|
||||
|
||||
Type baseType = null;
|
||||
|
||||
for (int i = args.Length - 1; i >= 0; --i)
|
||||
{
|
||||
if (!ExtensionInfo.Table.TryGetValue(args[i], out ExtensionInfo extInfo))
|
||||
continue;
|
||||
|
||||
if (extInfo.IsFixedSize && i != size - extInfo.Size - 1)
|
||||
throw new Exception("Invalid extended argument count.");
|
||||
|
||||
BaseExtension ext = extInfo.Constructor();
|
||||
|
||||
ext.Parse(from, args, i + 1, size - i - 1);
|
||||
|
||||
if (ext is WhereExtension extension)
|
||||
baseType = extension.Conditional.Type;
|
||||
|
||||
parsed.Add(ext);
|
||||
|
||||
size = i;
|
||||
}
|
||||
|
||||
parsed.Sort((a, b) => a.Order - b.Order);
|
||||
|
||||
AssemblyEmitter emitter = null;
|
||||
|
||||
foreach (BaseExtension update in parsed)
|
||||
update.Optimize(from, baseType, ref emitter);
|
||||
|
||||
if (size != args.Length)
|
||||
{
|
||||
string[] old = args;
|
||||
args = new string[size];
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
args[i] = old[i];
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BaseExtension
|
||||
{
|
||||
public abstract ExtensionInfo Info { get; }
|
||||
|
||||
public string Name => Info.Name;
|
||||
|
||||
public int Size => Info.Size;
|
||||
|
||||
public bool IsFixedSize => Info.IsFixedSize;
|
||||
|
||||
public int Order => Info.Order;
|
||||
|
||||
public virtual void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Parse(Mobile from, string[] arguments, int offset, int size)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual bool IsValid(object obj) => true;
|
||||
|
||||
public virtual void Filter(List<object> list)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,523 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public interface IConditional
|
||||
{
|
||||
bool Verify(object obj);
|
||||
}
|
||||
|
||||
public interface ICondition
|
||||
{
|
||||
// Invoked during the constructor
|
||||
void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
|
||||
|
||||
// Target object will be loaded on the stack
|
||||
void Compile(MethodEmitter emitter);
|
||||
}
|
||||
|
||||
public sealed class TypeCondition : ICondition
|
||||
{
|
||||
public static TypeCondition Default = new TypeCondition();
|
||||
|
||||
void ICondition.Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
|
||||
{
|
||||
}
|
||||
|
||||
void ICondition.Compile(MethodEmitter emitter)
|
||||
{
|
||||
// The object was safely cast to be the conditionals type
|
||||
// If it's null, then the type cast didn't work...
|
||||
|
||||
emitter.LoadNull();
|
||||
emitter.Compare(OpCodes.Ceq);
|
||||
emitter.LogicalNot();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PropertyValue
|
||||
{
|
||||
public PropertyValue(Type type, object value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Value { get; private set; }
|
||||
|
||||
public FieldInfo Field { get; private set; }
|
||||
|
||||
public bool HasField => Field != null;
|
||||
|
||||
public void Load(MethodEmitter method)
|
||||
{
|
||||
if (Field != null)
|
||||
{
|
||||
method.LoadArgument(0);
|
||||
method.LoadField(Field);
|
||||
}
|
||||
else if (Value == null)
|
||||
{
|
||||
method.LoadNull(Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Value is int i)
|
||||
method.Load(i);
|
||||
else if (Value is long l)
|
||||
method.Load(l);
|
||||
else if (Value is float f)
|
||||
method.Load(f);
|
||||
else if (Value is double d)
|
||||
method.Load(d);
|
||||
else if (Value is char c)
|
||||
method.Load(c);
|
||||
else if (Value is bool b)
|
||||
method.Load(b);
|
||||
else if (Value is string s)
|
||||
method.Load(s);
|
||||
else if (Value is Enum e)
|
||||
method.Load(e);
|
||||
else
|
||||
throw new InvalidOperationException("Unrecognized comparison value.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Acquire(TypeBuilder typeBuilder, ILGenerator il, string fieldName)
|
||||
{
|
||||
if (!(Value is string toParse))
|
||||
return;
|
||||
|
||||
if (!Type.IsValueType && toParse == "null")
|
||||
{
|
||||
Value = null;
|
||||
}
|
||||
else if (Type == typeof(string))
|
||||
{
|
||||
if (toParse == @"@""null""")
|
||||
toParse = "null";
|
||||
|
||||
Value = toParse;
|
||||
}
|
||||
else if (Type.IsEnum)
|
||||
{
|
||||
Value = Enum.Parse(Type, toParse, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo parseMethod;
|
||||
object[] parseArgs;
|
||||
|
||||
MethodInfo parseNumber = Type.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
new[] { typeof(string), typeof(NumberStyles) },
|
||||
null);
|
||||
|
||||
if (parseNumber != null)
|
||||
{
|
||||
NumberStyles style = NumberStyles.Integer;
|
||||
|
||||
if (Insensitive.StartsWith(toParse, "0x"))
|
||||
{
|
||||
style = NumberStyles.HexNumber;
|
||||
toParse = toParse.Substring(2);
|
||||
}
|
||||
|
||||
parseMethod = parseNumber;
|
||||
parseArgs = new object[] { toParse, style };
|
||||
}
|
||||
else
|
||||
{
|
||||
MethodInfo parseGeneral = Type.GetMethod(
|
||||
"Parse",
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
new[] { typeof(string) },
|
||||
null);
|
||||
|
||||
parseMethod = parseGeneral;
|
||||
parseArgs = new object[] { toParse };
|
||||
}
|
||||
|
||||
if (parseMethod != null)
|
||||
{
|
||||
Value = parseMethod.Invoke(null, parseArgs);
|
||||
|
||||
if (!Type.IsPrimitive)
|
||||
{
|
||||
Field = typeBuilder.DefineField(
|
||||
fieldName,
|
||||
Type,
|
||||
FieldAttributes.Private | FieldAttributes.InitOnly);
|
||||
|
||||
// parseMethod.Invoke(null,
|
||||
// parseArgs.Length == 2 ? new object[] {toParse, (int) parseArgs[1]} : new object[] {toParse});
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
|
||||
il.Emit(OpCodes.Ldstr, toParse);
|
||||
|
||||
if (parseArgs.Length == 2) // dirty evil hack :-(
|
||||
il.Emit(OpCodes.Ldc_I4, (int)parseArgs[1]);
|
||||
|
||||
il.Emit(OpCodes.Call, parseMethod);
|
||||
il.Emit(OpCodes.Stfld, Field);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to convert string \"{Value}\" into type '{Type}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class PropertyCondition : ICondition
|
||||
{
|
||||
protected bool m_Not;
|
||||
protected Property m_Property;
|
||||
|
||||
public PropertyCondition(Property property, bool not)
|
||||
{
|
||||
m_Property = property;
|
||||
m_Not = not;
|
||||
}
|
||||
|
||||
public abstract void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
|
||||
|
||||
public abstract void Compile(MethodEmitter emitter);
|
||||
}
|
||||
|
||||
public enum StringOperator
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
|
||||
Contains,
|
||||
|
||||
StartsWith,
|
||||
EndsWith
|
||||
}
|
||||
|
||||
public sealed class StringCondition : PropertyCondition
|
||||
{
|
||||
private readonly bool m_IgnoreCase;
|
||||
private readonly StringOperator m_Operator;
|
||||
private readonly PropertyValue m_Value;
|
||||
|
||||
public StringCondition(Property property, bool not, StringOperator op, object value, bool ignoreCase)
|
||||
: base(property, not)
|
||||
{
|
||||
m_Operator = op;
|
||||
m_Value = new PropertyValue(property.Type, value);
|
||||
|
||||
m_IgnoreCase = ignoreCase;
|
||||
}
|
||||
|
||||
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
|
||||
{
|
||||
m_Value.Acquire(typeBuilder, il, $"v{index}");
|
||||
}
|
||||
|
||||
public override void Compile(MethodEmitter emitter)
|
||||
{
|
||||
bool inverse = false;
|
||||
|
||||
string methodName;
|
||||
|
||||
switch (m_Operator)
|
||||
{
|
||||
case StringOperator.Equal:
|
||||
methodName = "Equals";
|
||||
break;
|
||||
|
||||
case StringOperator.NotEqual:
|
||||
methodName = "Equals";
|
||||
inverse = true;
|
||||
break;
|
||||
|
||||
case StringOperator.Contains:
|
||||
methodName = "Contains";
|
||||
break;
|
||||
|
||||
case StringOperator.StartsWith:
|
||||
methodName = "StartsWith";
|
||||
break;
|
||||
|
||||
case StringOperator.EndsWith:
|
||||
methodName = "EndsWith";
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid string comparison operator.");
|
||||
}
|
||||
|
||||
if (m_IgnoreCase || methodName == "Equals")
|
||||
{
|
||||
Type type = m_IgnoreCase ? typeof(Insensitive) : typeof(string);
|
||||
|
||||
emitter.BeginCall(
|
||||
type.GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Public | BindingFlags.Static,
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
typeof(string),
|
||||
typeof(string)
|
||||
},
|
||||
null));
|
||||
|
||||
emitter.Chain(m_Property);
|
||||
m_Value.Load(emitter);
|
||||
|
||||
emitter.FinishCall();
|
||||
}
|
||||
else
|
||||
{
|
||||
Label notNull = emitter.CreateLabel();
|
||||
Label moveOn = emitter.CreateLabel();
|
||||
|
||||
LocalBuilder temp = emitter.AcquireTemp(m_Property.Type);
|
||||
|
||||
emitter.Chain(m_Property);
|
||||
|
||||
emitter.StoreLocal(temp);
|
||||
emitter.LoadLocal(temp);
|
||||
|
||||
emitter.BranchIfTrue(notNull);
|
||||
|
||||
emitter.Load(false);
|
||||
emitter.Pop();
|
||||
emitter.Branch(moveOn);
|
||||
|
||||
emitter.MarkLabel(notNull);
|
||||
emitter.LoadLocal(temp);
|
||||
|
||||
emitter.BeginCall(
|
||||
typeof(string).GetMethod(
|
||||
methodName,
|
||||
BindingFlags.Public | BindingFlags.Instance,
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
typeof(string)
|
||||
},
|
||||
null));
|
||||
|
||||
m_Value.Load(emitter);
|
||||
|
||||
emitter.FinishCall();
|
||||
|
||||
emitter.MarkLabel(moveOn);
|
||||
}
|
||||
|
||||
if (m_Not != inverse)
|
||||
emitter.LogicalNot();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ComparisonOperator
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Lesser,
|
||||
LesserEqual
|
||||
}
|
||||
|
||||
public sealed class ComparisonCondition : PropertyCondition
|
||||
{
|
||||
private readonly ComparisonOperator m_Operator;
|
||||
private readonly PropertyValue m_Value;
|
||||
|
||||
public ComparisonCondition(Property property, bool not, ComparisonOperator op, object value)
|
||||
: base(property, not)
|
||||
{
|
||||
m_Operator = op;
|
||||
m_Value = new PropertyValue(property.Type, value);
|
||||
}
|
||||
|
||||
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
|
||||
{
|
||||
m_Value.Acquire(typeBuilder, il, $"v{index}");
|
||||
}
|
||||
|
||||
public override void Compile(MethodEmitter emitter)
|
||||
{
|
||||
emitter.Chain(m_Property);
|
||||
|
||||
bool inverse = false;
|
||||
|
||||
bool couldCompare =
|
||||
emitter.CompareTo(1, () => { m_Value.Load(emitter); });
|
||||
|
||||
if (couldCompare)
|
||||
{
|
||||
emitter.Load(0);
|
||||
|
||||
switch (m_Operator)
|
||||
{
|
||||
case ComparisonOperator.Equal:
|
||||
emitter.Compare(OpCodes.Ceq);
|
||||
break;
|
||||
|
||||
case ComparisonOperator.NotEqual:
|
||||
emitter.Compare(OpCodes.Ceq);
|
||||
inverse = true;
|
||||
break;
|
||||
|
||||
case ComparisonOperator.Greater:
|
||||
emitter.Compare(OpCodes.Cgt);
|
||||
break;
|
||||
|
||||
case ComparisonOperator.GreaterEqual:
|
||||
emitter.Compare(OpCodes.Clt);
|
||||
inverse = true;
|
||||
break;
|
||||
|
||||
case ComparisonOperator.Lesser:
|
||||
emitter.Compare(OpCodes.Clt);
|
||||
break;
|
||||
|
||||
case ComparisonOperator.LesserEqual:
|
||||
emitter.Compare(OpCodes.Cgt);
|
||||
inverse = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid comparison operator.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This type is -not- comparable
|
||||
// We can only support == and != operations
|
||||
|
||||
m_Value.Load(emitter);
|
||||
|
||||
switch (m_Operator)
|
||||
{
|
||||
case ComparisonOperator.Equal:
|
||||
emitter.Compare(OpCodes.Ceq);
|
||||
break;
|
||||
|
||||
case ComparisonOperator.NotEqual:
|
||||
emitter.Compare(OpCodes.Ceq);
|
||||
inverse = true;
|
||||
break;
|
||||
|
||||
case ComparisonOperator.Greater:
|
||||
case ComparisonOperator.GreaterEqual:
|
||||
case ComparisonOperator.Lesser:
|
||||
case ComparisonOperator.LesserEqual:
|
||||
throw new InvalidOperationException("Property does not support relational comparisons.");
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid operator.");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Not != inverse)
|
||||
emitter.LogicalNot();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConditionalCompiler
|
||||
{
|
||||
public static IConditional Compile(AssemblyEmitter assembly, Type objectType, ICondition[] conditions, int index)
|
||||
{
|
||||
TypeBuilder typeBuilder = assembly.DefineType(
|
||||
$"__conditional{index}",
|
||||
TypeAttributes.Public,
|
||||
typeof(object));
|
||||
{
|
||||
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
|
||||
MethodAttributes.Public,
|
||||
CallingConventions.Standard,
|
||||
Type.EmptyTypes);
|
||||
|
||||
ILGenerator il = ctor.GetILGenerator();
|
||||
|
||||
// : base()
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
|
||||
|
||||
for (int i = 0; i < conditions.Length; ++i)
|
||||
conditions[i].Construct(typeBuilder, il, i);
|
||||
|
||||
// return;
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
typeBuilder.AddInterfaceImplementation(typeof(IConditional));
|
||||
|
||||
MethodBuilder compareMethod;
|
||||
{
|
||||
MethodEmitter emitter = new MethodEmitter(typeBuilder);
|
||||
|
||||
emitter.Define(
|
||||
/* name */ "Verify",
|
||||
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
|
||||
/* return */ typeof(bool),
|
||||
/* params */ new[] { typeof(object) });
|
||||
|
||||
LocalBuilder obj = emitter.CreateLocal(objectType);
|
||||
LocalBuilder eq = emitter.CreateLocal(typeof(bool));
|
||||
|
||||
emitter.LoadArgument(1);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(obj);
|
||||
|
||||
Label done = emitter.CreateLabel();
|
||||
|
||||
for (int i = 0; i < conditions.Length; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
emitter.LoadLocal(eq);
|
||||
|
||||
emitter.BranchIfFalse(done);
|
||||
}
|
||||
|
||||
emitter.LoadLocal(obj);
|
||||
|
||||
conditions[i].Compile(emitter);
|
||||
|
||||
emitter.StoreLocal(eq);
|
||||
}
|
||||
|
||||
emitter.MarkLabel(done);
|
||||
|
||||
emitter.LoadLocal(eq);
|
||||
|
||||
emitter.Return();
|
||||
|
||||
typeBuilder.DefineMethodOverride(
|
||||
emitter.Method,
|
||||
typeof(IConditional).GetMethod(
|
||||
"Verify",
|
||||
new[]
|
||||
{
|
||||
typeof(object)
|
||||
}));
|
||||
|
||||
compareMethod = emitter.Method;
|
||||
}
|
||||
|
||||
Type conditionalType = typeBuilder.CreateType();
|
||||
|
||||
return (IConditional)ActivatorUtil.CreateInstance(conditionalType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public static class DistinctCompiler
|
||||
{
|
||||
public static IComparer<T> Compile<T>(AssemblyEmitter assembly, Type objectType, Property[] props)
|
||||
{
|
||||
TypeBuilder typeBuilder = assembly.DefineType(
|
||||
"__distinct",
|
||||
TypeAttributes.Public,
|
||||
typeof(object));
|
||||
{
|
||||
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
|
||||
MethodAttributes.Public,
|
||||
CallingConventions.Standard,
|
||||
Type.EmptyTypes);
|
||||
|
||||
ILGenerator il = ctor.GetILGenerator();
|
||||
|
||||
// : base()
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes) ??
|
||||
throw new Exception($"Could not find empty constructor for type {typeof(T).FullName}"));
|
||||
|
||||
// return;
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
typeBuilder.AddInterfaceImplementation(typeof(IComparer<T>));
|
||||
|
||||
MethodBuilder compareMethod;
|
||||
{
|
||||
MethodEmitter emitter = new MethodEmitter(typeBuilder);
|
||||
|
||||
emitter.Define(
|
||||
/* name */ "Compare",
|
||||
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
|
||||
/* return */ typeof(int),
|
||||
/* params */ new[] { typeof(T), typeof(T) });
|
||||
|
||||
LocalBuilder a = emitter.CreateLocal(objectType);
|
||||
LocalBuilder b = emitter.CreateLocal(objectType);
|
||||
|
||||
LocalBuilder v = emitter.CreateLocal(typeof(int));
|
||||
|
||||
emitter.LoadArgument(1);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(a);
|
||||
|
||||
emitter.LoadArgument(2);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(b);
|
||||
|
||||
emitter.Load(0);
|
||||
emitter.StoreLocal(v);
|
||||
|
||||
Label end = emitter.CreateLabel();
|
||||
|
||||
for (int i = 0; i < props.Length; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
emitter.LoadLocal(v);
|
||||
emitter.BranchIfTrue(end);
|
||||
}
|
||||
|
||||
Property prop = props[i];
|
||||
|
||||
emitter.LoadLocal(a);
|
||||
emitter.Chain(prop);
|
||||
|
||||
bool couldCompare =
|
||||
emitter.CompareTo(1, () =>
|
||||
{
|
||||
emitter.LoadLocal(b);
|
||||
emitter.Chain(prop);
|
||||
});
|
||||
|
||||
if (!couldCompare)
|
||||
throw new InvalidOperationException("Property is not comparable.");
|
||||
|
||||
emitter.StoreLocal(v);
|
||||
}
|
||||
|
||||
emitter.MarkLabel(end);
|
||||
|
||||
emitter.LoadLocal(v);
|
||||
emitter.Return();
|
||||
|
||||
typeBuilder.DefineMethodOverride(
|
||||
emitter.Method,
|
||||
typeof(IComparer<T>).GetMethod(
|
||||
"Compare",
|
||||
new[]
|
||||
{
|
||||
typeof(T),
|
||||
typeof(T)
|
||||
}) ?? throw new Exception($"No Compare method found for type {typeof(T).FullName}"));
|
||||
|
||||
compareMethod = emitter.Method;
|
||||
}
|
||||
|
||||
typeBuilder.AddInterfaceImplementation(typeof(IEqualityComparer<T>));
|
||||
{
|
||||
MethodEmitter emitter = new MethodEmitter(typeBuilder);
|
||||
|
||||
emitter.Define(
|
||||
/* name */ "Equals",
|
||||
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
|
||||
/* return */ typeof(bool),
|
||||
/* params */ new[] { typeof(T), typeof(T) });
|
||||
|
||||
emitter.Generator.Emit(OpCodes.Ldarg_0);
|
||||
emitter.Generator.Emit(OpCodes.Ldarg_1);
|
||||
emitter.Generator.Emit(OpCodes.Ldarg_2);
|
||||
|
||||
emitter.Generator.Emit(OpCodes.Call, compareMethod);
|
||||
|
||||
emitter.Generator.Emit(OpCodes.Ldc_I4_0);
|
||||
|
||||
emitter.Generator.Emit(OpCodes.Ceq);
|
||||
|
||||
emitter.Generator.Emit(OpCodes.Ret);
|
||||
|
||||
typeBuilder.DefineMethodOverride(
|
||||
emitter.Method,
|
||||
typeof(IEqualityComparer<T>).GetMethod(
|
||||
"Equals",
|
||||
new[]
|
||||
{
|
||||
typeof(T),
|
||||
typeof(T)
|
||||
}) ?? throw new Exception($"No Equals method found for type {typeof(T).FullName}"));
|
||||
}
|
||||
|
||||
{
|
||||
MethodEmitter emitter = new MethodEmitter(typeBuilder);
|
||||
|
||||
emitter.Define(
|
||||
/* name */ "GetHashCode",
|
||||
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
|
||||
/* return */ typeof(int),
|
||||
/* params */ new[] { typeof(T) });
|
||||
|
||||
LocalBuilder obj = emitter.CreateLocal(objectType);
|
||||
|
||||
emitter.LoadArgument(1);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(obj);
|
||||
|
||||
for (int i = 0; i < props.Length; ++i)
|
||||
{
|
||||
Property prop = props[i];
|
||||
|
||||
emitter.LoadLocal(obj);
|
||||
emitter.Chain(prop);
|
||||
|
||||
Type active = emitter.Active;
|
||||
|
||||
MethodInfo getHashCode = active.GetMethod("GetHashCode", Type.EmptyTypes);
|
||||
|
||||
getHashCode ??= typeof(T).GetMethod("GetHashCode", Type.EmptyTypes);
|
||||
|
||||
if (active != typeof(int))
|
||||
{
|
||||
if (!active.IsValueType)
|
||||
{
|
||||
LocalBuilder value = emitter.AcquireTemp(active);
|
||||
|
||||
Label valueNotNull = emitter.CreateLabel();
|
||||
Label done = emitter.CreateLabel();
|
||||
|
||||
emitter.StoreLocal(value);
|
||||
emitter.LoadLocal(value);
|
||||
|
||||
emitter.BranchIfTrue(valueNotNull);
|
||||
|
||||
emitter.Load(0);
|
||||
emitter.Pop(typeof(int));
|
||||
|
||||
emitter.Branch(done);
|
||||
|
||||
emitter.MarkLabel(valueNotNull);
|
||||
|
||||
emitter.LoadLocal(value);
|
||||
emitter.Call(getHashCode);
|
||||
|
||||
emitter.ReleaseTemp(value);
|
||||
|
||||
emitter.MarkLabel(done);
|
||||
}
|
||||
else
|
||||
{
|
||||
emitter.Call(getHashCode);
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
emitter.Xor();
|
||||
}
|
||||
|
||||
emitter.Return();
|
||||
|
||||
typeBuilder.DefineMethodOverride(
|
||||
emitter.Method,
|
||||
typeof(IEqualityComparer<T>).GetMethod(
|
||||
"GetHashCode",
|
||||
new[]
|
||||
{
|
||||
typeof(T)
|
||||
}) ?? throw new Exception($"No GetHashCode method found for type {typeof(T).FullName}"));
|
||||
}
|
||||
|
||||
Type comparerType = typeBuilder.CreateType();
|
||||
|
||||
return (IComparer<T>)ActivatorUtil.CreateInstance(comparerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Server.Utilities;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class OrderInfo
|
||||
{
|
||||
private int m_Order;
|
||||
|
||||
public OrderInfo(Property property, bool isAscending)
|
||||
{
|
||||
Property = property;
|
||||
|
||||
IsAscending = isAscending;
|
||||
}
|
||||
|
||||
public Property Property { get; set; }
|
||||
|
||||
public bool IsAscending
|
||||
{
|
||||
get => m_Order > 0;
|
||||
set => m_Order = value ? +1 : -1;
|
||||
}
|
||||
|
||||
public bool IsDescending
|
||||
{
|
||||
get => m_Order < 0;
|
||||
set => m_Order = value ? -1 : +1;
|
||||
}
|
||||
|
||||
public int Sign
|
||||
{
|
||||
get => Math.Sign(m_Order);
|
||||
set
|
||||
{
|
||||
m_Order = Math.Sign(value);
|
||||
|
||||
if (m_Order == 0)
|
||||
throw new InvalidOperationException("Sign cannot be zero.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SortCompiler
|
||||
{
|
||||
public static IComparer<T> Compile<T>(AssemblyEmitter assembly, Type objectType, OrderInfo[] orders)
|
||||
{
|
||||
TypeBuilder typeBuilder = assembly.DefineType(
|
||||
"__sort",
|
||||
TypeAttributes.Public,
|
||||
typeof(T));
|
||||
{
|
||||
ConstructorBuilder ctor = typeBuilder.DefineConstructor(
|
||||
MethodAttributes.Public,
|
||||
CallingConventions.Standard,
|
||||
Type.EmptyTypes);
|
||||
|
||||
ILGenerator il = ctor.GetILGenerator();
|
||||
|
||||
// : base()
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, typeof(T).GetConstructor(Type.EmptyTypes) ??
|
||||
throw new Exception($"Could not find empty constructor for type {typeof(T).FullName}"));
|
||||
|
||||
// return;
|
||||
il.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
typeBuilder.AddInterfaceImplementation(typeof(IComparer<T>));
|
||||
{
|
||||
MethodEmitter emitter = new MethodEmitter(typeBuilder);
|
||||
|
||||
emitter.Define(
|
||||
/* name */ "Compare",
|
||||
/* attr */ MethodAttributes.Public | MethodAttributes.Virtual,
|
||||
/* return */ typeof(int),
|
||||
/* params */ new[] { typeof(T), typeof(T) });
|
||||
|
||||
LocalBuilder a = emitter.CreateLocal(objectType);
|
||||
LocalBuilder b = emitter.CreateLocal(objectType);
|
||||
|
||||
LocalBuilder v = emitter.CreateLocal(typeof(int));
|
||||
|
||||
emitter.LoadArgument(1);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(a);
|
||||
|
||||
emitter.LoadArgument(2);
|
||||
emitter.CastAs(objectType);
|
||||
emitter.StoreLocal(b);
|
||||
|
||||
emitter.Load(0);
|
||||
emitter.StoreLocal(v);
|
||||
|
||||
Label end = emitter.CreateLabel();
|
||||
|
||||
for (int i = 0; i < orders.Length; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
emitter.LoadLocal(v);
|
||||
emitter.BranchIfTrue(end);
|
||||
}
|
||||
|
||||
OrderInfo orderInfo = orders[i];
|
||||
|
||||
Property prop = orderInfo.Property;
|
||||
int sign = orderInfo.Sign;
|
||||
|
||||
emitter.LoadLocal(a);
|
||||
emitter.Chain(prop);
|
||||
|
||||
bool couldCompare =
|
||||
emitter.CompareTo(sign, () =>
|
||||
{
|
||||
emitter.LoadLocal(b);
|
||||
emitter.Chain(prop);
|
||||
});
|
||||
|
||||
if (!couldCompare)
|
||||
throw new InvalidOperationException("Property is not comparable.");
|
||||
|
||||
emitter.StoreLocal(v);
|
||||
}
|
||||
|
||||
emitter.MarkLabel(end);
|
||||
|
||||
emitter.LoadLocal(v);
|
||||
emitter.Return();
|
||||
|
||||
typeBuilder.DefineMethodOverride(
|
||||
emitter.Method,
|
||||
typeof(IComparer<T>).GetMethod(
|
||||
"Compare",
|
||||
new[]
|
||||
{
|
||||
typeof(T),
|
||||
typeof(T)
|
||||
}) ?? throw new Exception($"No Compare method found for type {typeof(T).FullName}"));
|
||||
}
|
||||
|
||||
Type comparerType = typeBuilder.CreateType();
|
||||
return (IComparer<T>)ActivatorUtil.CreateInstance(comparerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class DistinctExtension : BaseExtension
|
||||
{
|
||||
public static ExtensionInfo ExtInfo =
|
||||
new ExtensionInfo(30, "Distinct", -1, () => new DistinctExtension());
|
||||
|
||||
private IComparer<object> m_Comparer;
|
||||
|
||||
private readonly List<Property> m_Properties;
|
||||
|
||||
public DistinctExtension() => m_Properties = new List<Property>();
|
||||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
ExtensionInfo.Register(ExtInfo);
|
||||
}
|
||||
|
||||
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
|
||||
{
|
||||
if (baseType == null)
|
||||
throw new Exception("Distinct extension may only be used in combination with an object conditional.");
|
||||
|
||||
foreach (Property prop in m_Properties)
|
||||
{
|
||||
prop.BindTo(baseType, PropertyAccess.Read);
|
||||
prop.CheckAccess(from);
|
||||
}
|
||||
|
||||
assembly ??= new AssemblyEmitter("__dynamic");
|
||||
|
||||
m_Comparer = DistinctCompiler.Compile<object>(assembly, baseType, m_Properties.ToArray());
|
||||
}
|
||||
|
||||
public override void Parse(Mobile from, string[] arguments, int offset, int size)
|
||||
{
|
||||
if (size < 1)
|
||||
throw new Exception("Invalid distinction syntax.");
|
||||
|
||||
int end = offset + size;
|
||||
|
||||
while (offset < end)
|
||||
{
|
||||
string binding = arguments[offset++];
|
||||
|
||||
m_Properties.Add(new Property(binding));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Filter(List<object> list)
|
||||
{
|
||||
if (m_Comparer == null)
|
||||
throw new InvalidOperationException("The extension must first be optimized.");
|
||||
|
||||
List<object> copy = new List<object>(list);
|
||||
|
||||
copy.Sort(m_Comparer);
|
||||
|
||||
list.Clear();
|
||||
|
||||
object last = null;
|
||||
|
||||
for (int i = 0; i < copy.Count; ++i)
|
||||
{
|
||||
object obj = copy[i];
|
||||
|
||||
if (last == null || m_Comparer.Compare(obj, last) != 0)
|
||||
{
|
||||
list.Add(obj);
|
||||
last = obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class LimitExtension : BaseExtension
|
||||
{
|
||||
public static ExtensionInfo ExtInfo = new ExtensionInfo(80, "Limit", 1, () => new LimitExtension());
|
||||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
public int Limit { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
ExtensionInfo.Register(ExtInfo);
|
||||
}
|
||||
|
||||
public override void Parse(Mobile from, string[] arguments, int offset, int size)
|
||||
{
|
||||
Limit = Utility.ToInt32(arguments[offset]);
|
||||
|
||||
if (Limit < 0)
|
||||
throw new Exception("Limit cannot be less than zero.");
|
||||
}
|
||||
|
||||
public override void Filter(List<object> list)
|
||||
{
|
||||
if (list.Count > Limit)
|
||||
list.RemoveRange(Limit, list.Count - Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class SortExtension : BaseExtension
|
||||
{
|
||||
public static ExtensionInfo ExtInfo = new ExtensionInfo(40, "Order", -1, () => new SortExtension());
|
||||
|
||||
private IComparer<object> m_Comparer;
|
||||
|
||||
private readonly List<OrderInfo> m_Orders;
|
||||
|
||||
public SortExtension() => m_Orders = new List<OrderInfo>();
|
||||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
ExtensionInfo.Register(ExtInfo);
|
||||
}
|
||||
|
||||
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
|
||||
{
|
||||
if (baseType == null)
|
||||
throw new Exception("The ordering extension may only be used in combination with an object conditional.");
|
||||
|
||||
foreach (OrderInfo order in m_Orders)
|
||||
{
|
||||
order.Property.BindTo(baseType, PropertyAccess.Read);
|
||||
order.Property.CheckAccess(from);
|
||||
}
|
||||
|
||||
assembly ??= new AssemblyEmitter("__dynamic");
|
||||
|
||||
m_Comparer = SortCompiler.Compile<object>(assembly, baseType, m_Orders.ToArray());
|
||||
}
|
||||
|
||||
public override void Parse(Mobile from, string[] arguments, int offset, int size)
|
||||
{
|
||||
if (size < 1)
|
||||
throw new Exception("Invalid ordering syntax.");
|
||||
|
||||
if (Insensitive.Equals(arguments[offset], "by"))
|
||||
{
|
||||
++offset;
|
||||
--size;
|
||||
|
||||
if (size < 1)
|
||||
throw new Exception("Invalid ordering syntax.");
|
||||
}
|
||||
|
||||
int end = offset + size;
|
||||
|
||||
while (offset < end)
|
||||
{
|
||||
string binding = arguments[offset++];
|
||||
|
||||
bool isAscending = true;
|
||||
|
||||
if (offset < end)
|
||||
{
|
||||
string next = arguments[offset];
|
||||
|
||||
switch (next.ToLower())
|
||||
{
|
||||
case "+":
|
||||
case "up":
|
||||
case "asc":
|
||||
case "ascending":
|
||||
isAscending = true;
|
||||
++offset;
|
||||
break;
|
||||
|
||||
case "-":
|
||||
case "down":
|
||||
case "desc":
|
||||
case "descending":
|
||||
isAscending = false;
|
||||
++offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Property property = new Property(binding);
|
||||
|
||||
m_Orders.Add(new OrderInfo(property, isAscending));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Filter(List<object> list)
|
||||
{
|
||||
if (m_Comparer == null)
|
||||
throw new InvalidOperationException("The extension must first be optimized.");
|
||||
|
||||
list.Sort(m_Comparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class WhereExtension : BaseExtension
|
||||
{
|
||||
public static ExtensionInfo ExtInfo = new ExtensionInfo(20, "Where", -1, () => new WhereExtension());
|
||||
|
||||
public override ExtensionInfo Info => ExtInfo;
|
||||
|
||||
public ObjectConditional Conditional { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
ExtensionInfo.Register(ExtInfo);
|
||||
}
|
||||
|
||||
public override void Optimize(Mobile from, Type baseType, ref AssemblyEmitter assembly)
|
||||
{
|
||||
if (baseType == null)
|
||||
throw new InvalidOperationException("Insanity.");
|
||||
|
||||
Conditional.Compile(ref assembly);
|
||||
}
|
||||
|
||||
public override void Parse(Mobile from, string[] arguments, int offset, int size)
|
||||
{
|
||||
if (size < 1)
|
||||
throw new Exception("Invalid condition syntax.");
|
||||
|
||||
Conditional = ObjectConditional.ParseDirect(from, arguments, offset, size);
|
||||
}
|
||||
|
||||
public override bool IsValid(object obj) => Conditional.CheckCondition(obj);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class AreaCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public AreaCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Area", "Group" };
|
||||
SupportRequirement = CommandSupport.Area;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Area <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate objects in a targeted area. Optional condition arguments can further restrict the set of objects.";
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static AreaCommandImplementor Instance { get; private set; }
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) => OnTarget(from, map, start, end, command, args));
|
||||
}
|
||||
|
||||
public void OnTarget(Mobile from, Map map, Point3D start, Point3D end, BaseCommand command, string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Rectangle2D rect = new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1);
|
||||
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool items, out bool mobiles))
|
||||
return;
|
||||
|
||||
if (!(items || mobiles))
|
||||
return;
|
||||
|
||||
IPooledEnumerable<IEntity> eable = map.GetObjectsInBounds(rect, items, mobiles);
|
||||
|
||||
List<object> objs = new List<object>();
|
||||
|
||||
foreach (IEntity obj in eable)
|
||||
if ((!mobiles || obj is Mobile) && BaseCommand.IsAccessible(from, obj) && ext.IsValid(obj))
|
||||
objs.Add(obj);
|
||||
|
||||
eable.Free();
|
||||
ext.Filter(objs);
|
||||
|
||||
RunCommand(from, objs, command, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
[Flags]
|
||||
public enum CommandSupport
|
||||
{
|
||||
Single = 0x0001,
|
||||
Global = 0x0002,
|
||||
Online = 0x0004,
|
||||
Multi = 0x0008,
|
||||
Area = 0x0010,
|
||||
Self = 0x0020,
|
||||
Region = 0x0040,
|
||||
Contained = 0x0080,
|
||||
IPAddress = 0x0100,
|
||||
|
||||
All = Single | Global | Online | Multi | Area | Self | Region | Contained | IPAddress,
|
||||
AllMobiles = All & ~Contained,
|
||||
AllNPCs = All & ~(IPAddress | Online | Self | Contained),
|
||||
AllItems = All & ~(IPAddress | Online | Self | Region),
|
||||
|
||||
Simple = Single | Multi,
|
||||
Complex = Global | Online | Area | Region | Contained | IPAddress
|
||||
}
|
||||
|
||||
public abstract class BaseCommandImplementor
|
||||
{
|
||||
private static List<BaseCommandImplementor> m_Implementors;
|
||||
|
||||
public BaseCommandImplementor() => Commands = new Dictionary<string, BaseCommand>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public bool SupportsConditionals { get; set; }
|
||||
|
||||
public string[] Accessors { get; set; }
|
||||
|
||||
public string Usage { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public AccessLevel AccessLevel { get; set; }
|
||||
|
||||
public CommandSupport SupportRequirement { get; set; }
|
||||
|
||||
public Dictionary<string, BaseCommand> Commands { get; }
|
||||
|
||||
public static List<BaseCommandImplementor> Implementors
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Implementors == null)
|
||||
{
|
||||
m_Implementors = new List<BaseCommandImplementor>();
|
||||
RegisterImplementors();
|
||||
}
|
||||
|
||||
return m_Implementors;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RegisterImplementors()
|
||||
{
|
||||
Register(new RegionCommandImplementor());
|
||||
Register(new GlobalCommandImplementor());
|
||||
Register(new OnlineCommandImplementor());
|
||||
Register(new SingleCommandImplementor());
|
||||
Register(new SerialCommandImplementor());
|
||||
Register(new MultiCommandImplementor());
|
||||
Register(new AreaCommandImplementor());
|
||||
Register(new SelfCommandImplementor());
|
||||
Register(new ContainedCommandImplementor());
|
||||
Register(new IPAddressCommandImplementor());
|
||||
|
||||
Register(new RangeCommandImplementor());
|
||||
Register(new ScreenCommandImplementor());
|
||||
Register(new FacetCommandImplementor());
|
||||
}
|
||||
|
||||
public virtual void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
obj = null;
|
||||
}
|
||||
|
||||
public virtual void Register(BaseCommand command)
|
||||
{
|
||||
for (int i = 0; i < command.Commands.Length; ++i)
|
||||
Commands[command.Commands[i]] = command;
|
||||
}
|
||||
|
||||
public bool CheckObjectTypes(Mobile from, BaseCommand command, Extensions ext, out bool items, out bool mobiles)
|
||||
{
|
||||
items = mobiles = false;
|
||||
|
||||
ObjectConditional cond = ObjectConditional.Empty;
|
||||
|
||||
foreach (BaseExtension check in ext)
|
||||
if (check is WhereExtension extension)
|
||||
{
|
||||
cond = extension.Conditional;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bool condIsItem = cond.IsItem;
|
||||
bool condIsMobile = cond.IsMobile;
|
||||
|
||||
switch (command.ObjectTypes)
|
||||
{
|
||||
case ObjectTypes.All:
|
||||
case ObjectTypes.Both:
|
||||
{
|
||||
if (condIsItem)
|
||||
items = true;
|
||||
|
||||
if (condIsMobile)
|
||||
mobiles = true;
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Items:
|
||||
{
|
||||
if (condIsItem)
|
||||
{
|
||||
items = true;
|
||||
}
|
||||
else if (condIsMobile)
|
||||
{
|
||||
from.SendMessage("You may not use a mobile type condition for this command.");
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Mobiles:
|
||||
{
|
||||
if (condIsMobile)
|
||||
{
|
||||
mobiles = true;
|
||||
}
|
||||
else if (condIsItem)
|
||||
{
|
||||
from.SendMessage("You may not use an item type condition for this command.");
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RunCommand(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
object obj = null;
|
||||
|
||||
Compile(from, command, ref args, ref obj);
|
||||
|
||||
RunCommand(from, obj, command, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public string GenerateArgString(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
return "";
|
||||
|
||||
// NOTE: this does not preserve the case where quotation marks are used on a single word
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
sb.Append(' ');
|
||||
|
||||
if (args[i].IndexOf(' ') >= 0)
|
||||
{
|
||||
sb.Append('"');
|
||||
sb.Append(args[i]);
|
||||
sb.Append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void RunCommand(Mobile from, object obj, BaseCommand command, string[] args)
|
||||
{
|
||||
// try
|
||||
// {
|
||||
CommandEventArgs e = new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args);
|
||||
|
||||
if (!command.ValidateArgs(this, e))
|
||||
return;
|
||||
|
||||
bool flushToLog = false;
|
||||
|
||||
if (obj is List<object> list)
|
||||
{
|
||||
if (list.Count > 20)
|
||||
CommandLogging.Enabled = false;
|
||||
else if (list.Count == 0)
|
||||
command.LogFailure("Nothing was found to use this command on.");
|
||||
|
||||
command.ExecuteList(e, list);
|
||||
|
||||
if (list.Count > 20)
|
||||
{
|
||||
flushToLog = true;
|
||||
CommandLogging.Enabled = true;
|
||||
}
|
||||
}
|
||||
else if (obj != null)
|
||||
{
|
||||
if (command.ListOptimized)
|
||||
command.ExecuteList(e, new List<object> { obj });
|
||||
else
|
||||
command.Execute(e, obj);
|
||||
}
|
||||
|
||||
command.Flush(from, flushToLog);
|
||||
// }
|
||||
// catch ( Exception ex )
|
||||
// {
|
||||
// from.SendMessage( ex.Message );
|
||||
// }
|
||||
}
|
||||
|
||||
public virtual void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
RunCommand(from, command, args);
|
||||
}
|
||||
|
||||
public virtual void Execute(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length >= 1)
|
||||
{
|
||||
if (!Commands.TryGetValue(e.GetString(0), out BaseCommand command))
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"That is either an invalid command name or one that does not support this modifier.");
|
||||
}
|
||||
else if (e.Mobile.AccessLevel < command.AccessLevel)
|
||||
{
|
||||
e.Mobile.SendMessage("You do not have access to that command.");
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] oldArgs = e.Arguments;
|
||||
string[] args = new string[oldArgs.Length - 1];
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
args[i] = oldArgs[i + 1];
|
||||
|
||||
Process(e.Mobile, command, args);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("You must supply a command name.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Register()
|
||||
{
|
||||
if (Accessors == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Accessors.Length; ++i)
|
||||
CommandSystem.Register(Accessors[i], AccessLevel, Execute);
|
||||
}
|
||||
|
||||
public static void Register(BaseCommandImplementor impl)
|
||||
{
|
||||
m_Implementors.Add(impl);
|
||||
impl.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Items;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class ContainedCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public ContainedCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Contained" };
|
||||
SupportRequirement = CommandSupport.Contained;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Contained <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all child items in a targeted container. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
|
||||
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
|
||||
(m, targeted) => OnTarget(m, targeted, command, args));
|
||||
}
|
||||
|
||||
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, targeted))
|
||||
{
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.ObjectTypes == ObjectTypes.Mobiles)
|
||||
return; // sanity check
|
||||
|
||||
if (!(targeted is Container cont))
|
||||
{
|
||||
from.SendMessage("That is not a container.");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool items, out bool _))
|
||||
return;
|
||||
|
||||
if (!items)
|
||||
{
|
||||
from.SendMessage("This command only works on items.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<object> list = new List<object>();
|
||||
|
||||
foreach (Item item in cont.FindItemsByType<Item>())
|
||||
if (ext.IsValid(item))
|
||||
list.Add(item);
|
||||
|
||||
ext.Filter(list);
|
||||
|
||||
RunCommand(from, list, command, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
from.SendMessage(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class FacetCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public FacetCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Facet" };
|
||||
SupportRequirement = CommandSupport.Area;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Facet <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate objects within your facet's map bounds. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
AreaCommandImplementor impl = AreaCommandImplementor.Instance;
|
||||
|
||||
if (impl == null)
|
||||
return;
|
||||
|
||||
Map map = from.Map;
|
||||
|
||||
if (map == null || map == Map.Internal)
|
||||
return;
|
||||
|
||||
impl.OnTarget(from, map, Point3D.Zero, new Point3D(map.Width - 1, map.Height - 1, 0), command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class GlobalCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public GlobalCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Global" };
|
||||
SupportRequirement = CommandSupport.Global;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.Administrator;
|
||||
Usage = "Global <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate objects in the world. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool items, out bool mobiles))
|
||||
return;
|
||||
|
||||
List<object> list = new List<object>();
|
||||
|
||||
if (items)
|
||||
foreach (Item item in World.Items.Values)
|
||||
if (ext.IsValid(item))
|
||||
list.Add(item);
|
||||
|
||||
if (mobiles)
|
||||
foreach (Mobile mob in World.Mobiles.Values)
|
||||
if (ext.IsValid(mob))
|
||||
list.Add(mob);
|
||||
|
||||
ext.Filter(list);
|
||||
|
||||
obj = list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class IPAddressCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public IPAddressCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "IPAddress" };
|
||||
SupportRequirement = CommandSupport.IPAddress;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.Administrator;
|
||||
Usage = "IPAddress <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on one mobile from each IP address that is logged in. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
|
||||
return;
|
||||
|
||||
if (!mobiles) // sanity check
|
||||
{
|
||||
command.LogFailure("This command does not support items.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<object> list = new List<object>();
|
||||
List<IPAddress> addresses = new List<IPAddress>();
|
||||
|
||||
List<NetState> states = TcpServer.Instances;
|
||||
|
||||
for (int i = 0; i < states.Count; ++i)
|
||||
{
|
||||
NetState ns = states[i];
|
||||
Mobile mob = ns.Mobile;
|
||||
|
||||
if (mob != null && !addresses.Contains(ns.Address) && ext.IsValid(mob))
|
||||
{
|
||||
list.Add(mob);
|
||||
addresses.Add(ns.Address);
|
||||
}
|
||||
}
|
||||
|
||||
ext.Filter(list);
|
||||
|
||||
obj = list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class MultiCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public MultiCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Multi", "m" };
|
||||
SupportRequirement = CommandSupport.Multi;
|
||||
AccessLevel = AccessLevel.Counselor;
|
||||
Usage = "Multi <command>";
|
||||
Description = "Invokes the command on multiple targeted objects.";
|
||||
}
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
|
||||
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
|
||||
(m, targeted) => OnTarget(m, targeted, command, args));
|
||||
}
|
||||
|
||||
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, targeted))
|
||||
{
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
|
||||
(m, t) => OnTarget(m, t, command, args));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command.ObjectTypes)
|
||||
{
|
||||
case ObjectTypes.Both:
|
||||
{
|
||||
if (!(targeted is Item || targeted is Mobile))
|
||||
{
|
||||
from.SendMessage("This command does not work on that.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Items:
|
||||
{
|
||||
if (!(targeted is Item))
|
||||
{
|
||||
from.SendMessage("This command only works on items.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Mobiles:
|
||||
{
|
||||
if (!(targeted is Mobile))
|
||||
{
|
||||
from.SendMessage("This command only works on mobiles.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RunCommand(from, targeted, command, args);
|
||||
|
||||
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
|
||||
(m, t) => OnTarget(m, t, command, args));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public sealed class ObjectConditional
|
||||
{
|
||||
private static readonly Type typeofItem = typeof(Item);
|
||||
private static readonly Type typeofMobile = typeof(Mobile);
|
||||
|
||||
public static readonly ObjectConditional Empty = new ObjectConditional(null, null);
|
||||
|
||||
private IConditional[] m_Conditionals;
|
||||
|
||||
private readonly ICondition[][] m_Conditions;
|
||||
|
||||
public ObjectConditional(Type objectType, ICondition[][] conditions)
|
||||
{
|
||||
Type = objectType;
|
||||
m_Conditions = conditions;
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public bool IsItem => Type == null || Type == typeofItem || Type.IsSubclassOf(typeofItem);
|
||||
|
||||
public bool IsMobile => Type == null || Type == typeofMobile || Type.IsSubclassOf(typeofMobile);
|
||||
|
||||
public bool HasCompiled => m_Conditionals != null;
|
||||
|
||||
public void Compile(ref AssemblyEmitter emitter)
|
||||
{
|
||||
emitter ??= new AssemblyEmitter("__dynamic");
|
||||
|
||||
m_Conditionals = new IConditional[m_Conditions.Length];
|
||||
|
||||
for (int i = 0; i < m_Conditionals.Length; ++i)
|
||||
m_Conditionals[i] = ConditionalCompiler.Compile(emitter, Type, m_Conditions[i], i);
|
||||
}
|
||||
|
||||
public bool CheckCondition(object obj)
|
||||
{
|
||||
if (Type == null)
|
||||
return true; // null type means no condition
|
||||
|
||||
if (!HasCompiled)
|
||||
{
|
||||
AssemblyEmitter emitter = null;
|
||||
|
||||
Compile(ref emitter);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_Conditionals.Length; ++i)
|
||||
if (m_Conditionals[i].Verify(obj))
|
||||
return true;
|
||||
|
||||
return false; // all conditions false
|
||||
}
|
||||
|
||||
public static ObjectConditional Parse(Mobile from, ref string[] args)
|
||||
{
|
||||
string[] conditionArgs = null;
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
if (Insensitive.Equals(args[i], "where"))
|
||||
{
|
||||
string[] origArgs = args;
|
||||
|
||||
args = new string[i];
|
||||
|
||||
for (int j = 0; j < args.Length; ++j)
|
||||
args[j] = origArgs[j];
|
||||
|
||||
conditionArgs = new string[origArgs.Length - i - 1];
|
||||
|
||||
for (int j = 0; j < conditionArgs.Length; ++j)
|
||||
conditionArgs[j] = origArgs[i + j + 1];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return ParseDirect(from, conditionArgs, 0, conditionArgs?.Length ?? 0);
|
||||
}
|
||||
|
||||
public static ObjectConditional ParseDirect(Mobile from, string[] args, int offset, int size)
|
||||
{
|
||||
if (args == null || size == 0)
|
||||
return Empty;
|
||||
|
||||
int index = 0;
|
||||
|
||||
Type objectType = AssemblyHandler.FindFirstTypeForName(args[offset + index], true);
|
||||
|
||||
if (objectType == null)
|
||||
throw new Exception($"No type with that name ({args[offset + index]}) was found.");
|
||||
|
||||
++index;
|
||||
|
||||
List<ICondition[]> conditions = new List<ICondition[]>();
|
||||
List<ICondition> current = new List<ICondition>();
|
||||
|
||||
current.Add(TypeCondition.Default);
|
||||
|
||||
while (index < size)
|
||||
{
|
||||
string cur = args[offset + index];
|
||||
|
||||
bool inverse = false;
|
||||
|
||||
if (Insensitive.Equals(cur, "not") || cur == "!")
|
||||
{
|
||||
inverse = true;
|
||||
++index;
|
||||
|
||||
if (index >= size)
|
||||
throw new Exception("Improperly formatted object conditional.");
|
||||
}
|
||||
else if (Insensitive.Equals(cur, "or") || cur == "||")
|
||||
{
|
||||
if (current.Count > 1)
|
||||
{
|
||||
conditions.Add(current.ToArray());
|
||||
|
||||
current.Clear();
|
||||
current.Add(TypeCondition.Default);
|
||||
}
|
||||
|
||||
++index;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
string binding = args[offset + index];
|
||||
index++;
|
||||
|
||||
if (index >= size)
|
||||
throw new Exception("Improperly formatted object conditional.");
|
||||
|
||||
string oper = args[offset + index];
|
||||
index++;
|
||||
|
||||
if (index >= size)
|
||||
throw new Exception("Improperly formatted object conditional.");
|
||||
|
||||
string val = args[offset + index];
|
||||
index++;
|
||||
|
||||
Property prop = new Property(binding);
|
||||
|
||||
prop.BindTo(objectType, PropertyAccess.Read);
|
||||
prop.CheckAccess(from);
|
||||
|
||||
var condition = oper switch
|
||||
{
|
||||
"=" => (ICondition)new ComparisonCondition(prop, inverse, ComparisonOperator.Equal, val),
|
||||
"==" => new ComparisonCondition(prop, inverse, ComparisonOperator.Equal, val),
|
||||
"is" => new ComparisonCondition(prop, inverse, ComparisonOperator.Equal, val),
|
||||
"!=" => new ComparisonCondition(prop, inverse, ComparisonOperator.NotEqual, val),
|
||||
">" => new ComparisonCondition(prop, inverse, ComparisonOperator.Greater, val),
|
||||
"<" => new ComparisonCondition(prop, inverse, ComparisonOperator.Lesser, val),
|
||||
">=" => new ComparisonCondition(prop, inverse, ComparisonOperator.GreaterEqual, val),
|
||||
"<=" => new ComparisonCondition(prop, inverse, ComparisonOperator.LesserEqual, val),
|
||||
"==~" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"~==" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"=~" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"~=" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"is~" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"~is" => new StringCondition(prop, inverse, StringOperator.Equal, val, true),
|
||||
"!=~" => new StringCondition(prop, inverse, StringOperator.NotEqual, val, true),
|
||||
"~!=" => new StringCondition(prop, inverse, StringOperator.NotEqual, val, true),
|
||||
"starts" => new StringCondition(prop, inverse, StringOperator.StartsWith, val, false),
|
||||
"starts~" => new StringCondition(prop, inverse, StringOperator.StartsWith, val, true),
|
||||
"~starts" => new StringCondition(prop, inverse, StringOperator.StartsWith, val, true),
|
||||
"ends" => new StringCondition(prop, inverse, StringOperator.EndsWith, val, false),
|
||||
"ends~" => new StringCondition(prop, inverse, StringOperator.EndsWith, val, true),
|
||||
"~ends" => new StringCondition(prop, inverse, StringOperator.EndsWith, val, true),
|
||||
"contains" => new StringCondition(prop, inverse, StringOperator.Contains, val, false),
|
||||
"contains~" => new StringCondition(prop, inverse, StringOperator.Contains, val, true),
|
||||
"~contains" => new StringCondition(prop, inverse, StringOperator.Contains, val, true),
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (condition == null)
|
||||
throw new InvalidOperationException($"Unrecognized operator (\"{oper}\").");
|
||||
|
||||
current.Add(condition);
|
||||
}
|
||||
|
||||
conditions.Add(current.ToArray());
|
||||
|
||||
return new ObjectConditional(objectType, conditions.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class OnlineCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public OnlineCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Online" };
|
||||
SupportRequirement = CommandSupport.Online;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Online <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all mobiles that are currently logged in. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
|
||||
return;
|
||||
|
||||
if (!mobiles) // sanity check
|
||||
{
|
||||
command.LogFailure("This command does not support items.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<object> list = new List<object>();
|
||||
|
||||
List<NetState> states = TcpServer.Instances;
|
||||
|
||||
for (int i = 0; i < states.Count; ++i)
|
||||
{
|
||||
NetState ns = states[i];
|
||||
Mobile mob = ns.Mobile;
|
||||
|
||||
if (mob == null)
|
||||
continue;
|
||||
|
||||
if (!BaseCommand.IsAccessible(from, mob))
|
||||
continue;
|
||||
|
||||
if (ext.IsValid(mob))
|
||||
list.Add(mob);
|
||||
}
|
||||
|
||||
ext.Filter(list);
|
||||
|
||||
obj = list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class RangeCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public RangeCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Range" };
|
||||
SupportRequirement = CommandSupport.Area;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Range <range> <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate objects within a specified range of you. Optional condition arguments can further restrict the set of objects.";
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public static RangeCommandImplementor Instance { get; private set; }
|
||||
|
||||
public override void Execute(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length >= 2)
|
||||
{
|
||||
int range = e.GetInt32(0);
|
||||
|
||||
if (range < 0)
|
||||
{
|
||||
e.Mobile.SendMessage("The range must not be negative.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Commands.TryGetValue(e.GetString(1), out BaseCommand command);
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"That is either an invalid command name or one that does not support this modifier.");
|
||||
}
|
||||
else if (e.Mobile.AccessLevel < command.AccessLevel)
|
||||
{
|
||||
e.Mobile.SendMessage("You do not have access to that command.");
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] oldArgs = e.Arguments;
|
||||
string[] args = new string[oldArgs.Length - 2];
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
args[i] = oldArgs[i + 2];
|
||||
|
||||
Process(range, e.Mobile, command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("You must supply a range and a command name.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Process(int range, Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
AreaCommandImplementor impl = AreaCommandImplementor.Instance;
|
||||
|
||||
if (impl == null)
|
||||
return;
|
||||
|
||||
Map map = from.Map;
|
||||
|
||||
if (map == null || map == Map.Internal)
|
||||
return;
|
||||
|
||||
Point3D start = new Point3D(from.X - range, from.Y - range, from.Z);
|
||||
Point3D end = new Point3D(from.X + range, from.Y + range, from.Z);
|
||||
|
||||
impl.OnTarget(from, map, start, end, command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class RegionCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public RegionCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Region" };
|
||||
SupportRequirement = CommandSupport.Region;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Region <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate mobiles in your current region. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
Extensions ext = Extensions.Parse(from, ref args);
|
||||
|
||||
if (!CheckObjectTypes(from, command, ext, out bool _, out bool mobiles))
|
||||
return;
|
||||
|
||||
Region reg = from.Region;
|
||||
|
||||
List<object> list = new List<object>();
|
||||
|
||||
if (mobiles)
|
||||
{
|
||||
foreach (Mobile mob in reg.GetMobiles())
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, mob))
|
||||
continue;
|
||||
|
||||
if (ext.IsValid(mob))
|
||||
list.Add(mob);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
command.LogFailure("This command does not support items.");
|
||||
return;
|
||||
}
|
||||
|
||||
ext.Filter(list);
|
||||
|
||||
obj = list;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
from.SendMessage(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class ScreenCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public ScreenCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Screen" };
|
||||
SupportRequirement = CommandSupport.Area;
|
||||
SupportsConditionals = true;
|
||||
AccessLevel = AccessLevel.GameMaster;
|
||||
Usage = "Screen <command> [condition]";
|
||||
Description =
|
||||
"Invokes the command on all appropriate objects in your screen. Optional condition arguments can further restrict the set of objects.";
|
||||
}
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
RangeCommandImplementor impl = RangeCommandImplementor.Instance;
|
||||
|
||||
impl?.Process(18, from, command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class SelfCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public SelfCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Self" };
|
||||
SupportRequirement = CommandSupport.Self;
|
||||
AccessLevel = AccessLevel.Counselor;
|
||||
Usage = "Self <command>";
|
||||
Description = "Invokes the command on the commanding player.";
|
||||
}
|
||||
|
||||
public override void Compile(Mobile from, BaseCommand command, ref string[] args, ref object obj)
|
||||
{
|
||||
if (command.ObjectTypes == ObjectTypes.Items)
|
||||
return; // sanity check
|
||||
|
||||
obj = from;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class SerialCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public SerialCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Serial" };
|
||||
SupportRequirement = CommandSupport.Single;
|
||||
AccessLevel = AccessLevel.Counselor;
|
||||
Usage = "Serial <serial> <command>";
|
||||
Description = "Invokes the command on a single object by serial.";
|
||||
}
|
||||
|
||||
public override void Execute(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length >= 2)
|
||||
{
|
||||
Serial serial = e.GetUInt32(0);
|
||||
|
||||
object obj = null;
|
||||
|
||||
if (serial.IsItem)
|
||||
obj = World.FindItem(serial);
|
||||
else if (serial.IsMobile)
|
||||
obj = World.FindMobile(serial);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
e.Mobile.SendMessage("That is not a valid serial.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Commands.TryGetValue(e.GetString(1), out BaseCommand command);
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
e.Mobile.SendMessage(
|
||||
"That is either an invalid command name or one that does not support this modifier.");
|
||||
}
|
||||
else if (e.Mobile.AccessLevel < command.AccessLevel)
|
||||
{
|
||||
e.Mobile.SendMessage("You do not have access to that command.");
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.ObjectTypes)
|
||||
{
|
||||
case ObjectTypes.Items:
|
||||
{
|
||||
if (!(obj is Item))
|
||||
{
|
||||
e.Mobile.SendMessage("This command only works on items.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Mobiles:
|
||||
{
|
||||
if (!(obj is Mobile))
|
||||
{
|
||||
e.Mobile.SendMessage("This command only works on mobiles.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string[] oldArgs = e.Arguments;
|
||||
string[] args = new string[oldArgs.Length - 2];
|
||||
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
args[i] = oldArgs[i + 2];
|
||||
|
||||
RunCommand(e.Mobile, obj, command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("You must supply an object serial and a command name.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands.Generic
|
||||
{
|
||||
public class SingleCommandImplementor : BaseCommandImplementor
|
||||
{
|
||||
public SingleCommandImplementor()
|
||||
{
|
||||
Accessors = new[] { "Single" };
|
||||
SupportRequirement = CommandSupport.Single;
|
||||
AccessLevel = AccessLevel.Counselor;
|
||||
Usage = "Single <command>";
|
||||
Description =
|
||||
"Invokes the command on a single targeted object. This is the same as just invoking the command directly.";
|
||||
}
|
||||
|
||||
public override void Register(BaseCommand command)
|
||||
{
|
||||
base.Register(command);
|
||||
|
||||
for (int i = 0; i < command.Commands.Length; ++i)
|
||||
CommandSystem.Register(command.Commands[i], command.AccessLevel, Redirect);
|
||||
}
|
||||
|
||||
public void Redirect(CommandEventArgs e)
|
||||
{
|
||||
Commands.TryGetValue(e.Command, out BaseCommand command);
|
||||
|
||||
if (command == null)
|
||||
e.Mobile.SendMessage("That is either an invalid command name or one that does not support this modifier.");
|
||||
else if (e.Mobile.AccessLevel < command.AccessLevel)
|
||||
e.Mobile.SendMessage("You do not have access to that command.");
|
||||
else if (command.ValidateArgs(this, e))
|
||||
Process(e.Mobile, command, e.Arguments);
|
||||
}
|
||||
|
||||
public override void Process(Mobile from, BaseCommand command, string[] args)
|
||||
{
|
||||
if (command.ValidateArgs(this, new CommandEventArgs(from, command.Commands[0], GenerateArgString(args), args)))
|
||||
from.BeginTarget(-1, command.ObjectTypes == ObjectTypes.All, TargetFlags.None,
|
||||
(m, targeted) => OnTarget(m, targeted, command, args));
|
||||
}
|
||||
|
||||
public void OnTarget(Mobile from, object targeted, BaseCommand command, string[] args)
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, targeted))
|
||||
{
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command.ObjectTypes)
|
||||
{
|
||||
case ObjectTypes.Both:
|
||||
{
|
||||
if (!(targeted is Item) && !(targeted is Mobile))
|
||||
{
|
||||
from.SendMessage("This command does not work on that.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Items:
|
||||
{
|
||||
if (!(targeted is Item))
|
||||
{
|
||||
from.SendMessage("This command only works on items.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ObjectTypes.Mobiles:
|
||||
{
|
||||
if (!(targeted is Mobile))
|
||||
{
|
||||
from.SendMessage("This command only works on mobiles.");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RunCommand(from, targeted, command, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
959
Projects/UOContent/Commands/Handlers.cs
Normal file
959
Projects/UOContent/Commands/Handlers.cs
Normal file
|
|
@ -0,0 +1,959 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Engines.Help;
|
||||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
using Server.Menus.ItemLists;
|
||||
using Server.Menus.Questions;
|
||||
using Server.Misc;
|
||||
using Server.Mobiles;
|
||||
using Server.Multis;
|
||||
using Server.Network;
|
||||
using Server.Spells;
|
||||
using Server.Targeting;
|
||||
using Server.Targets;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CommandHandlers
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Prefix = "[";
|
||||
|
||||
Register("Go", AccessLevel.Counselor, Go_OnCommand);
|
||||
|
||||
Register("DropHolding", AccessLevel.Counselor, DropHolding_OnCommand);
|
||||
|
||||
Register("GetFollowers", AccessLevel.GameMaster, GetFollowers_OnCommand);
|
||||
|
||||
Register("ClearFacet", AccessLevel.Administrator, ClearFacet_OnCommand);
|
||||
|
||||
Register("Where", AccessLevel.Counselor, Where_OnCommand);
|
||||
|
||||
Register("AutoPageNotify", AccessLevel.Counselor, APN_OnCommand);
|
||||
Register("APN", AccessLevel.Counselor, APN_OnCommand);
|
||||
|
||||
Register("Animate", AccessLevel.GameMaster, Animate_OnCommand);
|
||||
|
||||
Register("Cast", AccessLevel.Counselor, Cast_OnCommand);
|
||||
|
||||
Register("Stuck", AccessLevel.Counselor, Stuck_OnCommand);
|
||||
|
||||
Register("Help", AccessLevel.Player, Help_OnCommand);
|
||||
|
||||
Register("Save", AccessLevel.Administrator, Save_OnCommand);
|
||||
Register("BackgroundSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
|
||||
Register("BGSave", AccessLevel.Administrator, BackgroundSave_OnCommand);
|
||||
Register("SaveBG", AccessLevel.Administrator, BackgroundSave_OnCommand);
|
||||
|
||||
Register("Move", AccessLevel.GameMaster, Move_OnCommand);
|
||||
Register("Client", AccessLevel.Counselor, Client_OnCommand);
|
||||
|
||||
Register("SMsg", AccessLevel.Counselor, StaffMessage_OnCommand);
|
||||
Register("SM", AccessLevel.Counselor, StaffMessage_OnCommand);
|
||||
Register("S", AccessLevel.Counselor, StaffMessage_OnCommand);
|
||||
|
||||
Register("BCast", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
|
||||
Register("BC", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
|
||||
Register("B", AccessLevel.GameMaster, BroadcastMessage_OnCommand);
|
||||
|
||||
Register("Bank", AccessLevel.GameMaster, Bank_OnCommand);
|
||||
|
||||
Register("Echo", AccessLevel.Counselor, Echo_OnCommand);
|
||||
|
||||
Register("Sound", AccessLevel.GameMaster, Sound_OnCommand);
|
||||
|
||||
Register("ViewEquip", AccessLevel.GameMaster, ViewEquip_OnCommand);
|
||||
|
||||
Register("Light", AccessLevel.Counselor, Light_OnCommand);
|
||||
Register("Stats", AccessLevel.Counselor, Stats_OnCommand);
|
||||
|
||||
Register("SpeedBoost", AccessLevel.Counselor, SpeedBoost_OnCommand);
|
||||
}
|
||||
|
||||
public static void Register(string command, AccessLevel access, CommandEventHandler handler)
|
||||
{
|
||||
CommandSystem.Register(command, access, handler);
|
||||
}
|
||||
|
||||
[Usage("SpeedBoost [true|false]")]
|
||||
[Description("Enables a speed boost for the invoker. Disable with parameters.")]
|
||||
private static void SpeedBoost_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (e.Length <= 1)
|
||||
{
|
||||
if (e.Length == 1 && !e.GetBoolean(0))
|
||||
{
|
||||
from.Send(SpeedControl.Disable);
|
||||
from.SendMessage("Speed boost has been disabled.");
|
||||
}
|
||||
else
|
||||
{
|
||||
from.Send(SpeedControl.MountSpeed);
|
||||
from.SendMessage("Speed boost has been enabled.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("Format: SpeedBoost [true|false]");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("Where")]
|
||||
[Description("Tells the commanding player his coordinates, region, and facet.")]
|
||||
public static void Where_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
Map map = from.Map;
|
||||
|
||||
from.SendMessage("You are at {0} {1} {2} in {3}.", from.X, from.Y, from.Z, map);
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
Region reg = from.Region;
|
||||
|
||||
if (!reg.IsDefault)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.Append(reg);
|
||||
reg = reg.Parent;
|
||||
|
||||
while (reg != null)
|
||||
{
|
||||
builder.Append($" <- {reg}");
|
||||
reg = reg.Parent;
|
||||
}
|
||||
|
||||
from.SendMessage("Your region is {0}.", builder.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("DropHolding")]
|
||||
[Description(
|
||||
"Drops the item, if any, that a targeted player is holding. The item is placed into their backpack, or if that's full, at their feet.")]
|
||||
public static void DropHolding_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.BeginTarget(-1, false, TargetFlags.None, DropHolding_OnTarget);
|
||||
e.Mobile.SendMessage("Target the player to drop what they are holding.");
|
||||
}
|
||||
|
||||
public static void DropHolding_OnTarget(Mobile from, object obj)
|
||||
{
|
||||
if (obj is Mobile targ && targ.Player)
|
||||
{
|
||||
Item held = targ.Holding;
|
||||
|
||||
if (held == null)
|
||||
{
|
||||
from.SendMessage("They are not holding anything.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (from.AccessLevel == AccessLevel.Counselor)
|
||||
{
|
||||
PageEntry pe = PageQueue.GetEntry(targ);
|
||||
|
||||
if (pe?.Handler == from)
|
||||
from.SendMessage("You may only use this command if you are handling their help page.");
|
||||
else
|
||||
from.SendMessage("You may only use this command on someone who has paged you.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (targ.AddToBackpack(held))
|
||||
from.SendMessage("The item they were holding has been placed into their backpack.");
|
||||
else
|
||||
from.SendMessage("The item they were holding has been placed at their feet.");
|
||||
|
||||
held.ClearBounce();
|
||||
|
||||
targ.Holding = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.BeginTarget(-1, false, TargetFlags.None, DropHolding_OnTarget);
|
||||
from.SendMessage("That is not a player. Try again.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteList_Callback(Mobile from, bool okay, List<IEntity> list)
|
||||
{
|
||||
if (okay)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} deleting {2} object{3}", from.AccessLevel,
|
||||
CommandLogging.Format(from), list.Count, list.Count == 1 ? "" : "s");
|
||||
|
||||
NetState.Pause();
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
list[i].Delete();
|
||||
|
||||
NetState.Resume();
|
||||
|
||||
from.SendMessage("You have deleted {0} object{1}.", list.Count, list.Count == 1 ? "" : "s");
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("You have chosen not to delete those objects.");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("ClearFacet")]
|
||||
[Description("Deletes all items and mobiles in your facet. Players and their inventory will not be deleted.")]
|
||||
public static void ClearFacet_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
Map map = from.Map;
|
||||
|
||||
if (map == null || map == Map.Internal)
|
||||
{
|
||||
from.SendMessage("You may not run that command here.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<IEntity> list = new List<IEntity>();
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
if (item.Map == map && item.Parent == null)
|
||||
list.Add(item);
|
||||
|
||||
foreach (Mobile m in World.Mobiles.Values)
|
||||
if (m.Map == map && !m.Player)
|
||||
list.Add(m);
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} starting facet clear of {2} ({3} object{4})",
|
||||
from.AccessLevel, CommandLogging.Format(from), map, list.Count, list.Count == 1 ? "" : "s");
|
||||
|
||||
from.SendGump(
|
||||
new WarningGump(1060635, 30720,
|
||||
$"You are about to delete {list.Count} object{(list.Count == 1 ? "" : "s")} from this facet. Do you really wish to continue?",
|
||||
0xFFC000, 360, 260, okay => DeleteList_Callback(from, okay, list)));
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("There were no objects found to delete.");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("GetFollowers")]
|
||||
[Description("Teleports all pets of a targeted player to your location.")]
|
||||
public static void GetFollowers_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.BeginTarget(-1, false, TargetFlags.None, GetFollowers_OnTarget);
|
||||
e.Mobile.SendMessage("Target a player to get their pets.");
|
||||
}
|
||||
|
||||
public static void GetFollowers_OnTarget(Mobile from, object obj)
|
||||
{
|
||||
if (obj is PlayerMobile pm)
|
||||
{
|
||||
List<Mobile> pets = pm.AllFollowers;
|
||||
|
||||
if (pets.Count > 0)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} getting all followers of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(pm));
|
||||
|
||||
from.SendMessage("That player has {0} pet{1}.", pets.Count, pets.Count != 1 ? "s" : "");
|
||||
|
||||
for (int i = 0; i < pets.Count; ++i)
|
||||
{
|
||||
Mobile pet = pets[i];
|
||||
|
||||
if (pet is IMount mount)
|
||||
mount.Rider = null; // make sure it's dismounted
|
||||
|
||||
pet.MoveToWorld(from.Location, from.Map);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("There were no pets found for that player.");
|
||||
}
|
||||
}
|
||||
else if (obj is Mobile master && master.Player)
|
||||
{
|
||||
List<BaseCreature> pets = new List<BaseCreature>();
|
||||
|
||||
foreach (Mobile m in World.Mobiles.Values)
|
||||
if (m is BaseCreature bc)
|
||||
if ((bc.Controlled && bc.ControlMaster == master) || (bc.Summoned && bc.SummonMaster == master))
|
||||
pets.Add(bc);
|
||||
|
||||
if (pets.Count > 0)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} getting all followers of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(master));
|
||||
|
||||
from.SendMessage("That player has {0} pet{1}.", pets.Count, pets.Count != 1 ? "s" : "");
|
||||
|
||||
for (int i = 0; i < pets.Count; ++i)
|
||||
{
|
||||
Mobile pet = pets[i];
|
||||
|
||||
if (pet is IMount mount)
|
||||
mount.Rider = null; // make sure it's dismounted
|
||||
|
||||
pet.MoveToWorld(from.Location, from.Map);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("There were no pets found for that player.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.BeginTarget(-1, false, TargetFlags.None, GetFollowers_OnTarget);
|
||||
from.SendMessage("That is not a player. Try again.");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("ViewEquip")]
|
||||
[Description("Lists equipment of a targeted mobile. From the list you can move, delete, or open props.")]
|
||||
public static void ViewEquip_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new ViewEqTarget();
|
||||
}
|
||||
|
||||
[Usage("Sound <index> [toAll=true]")]
|
||||
[Description(
|
||||
"Plays a sound to players within 12 tiles of you. The (toAll) argument specifies to everyone, or just those who can see you.")]
|
||||
public static void Sound_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
PlaySound(e.Mobile, e.GetInt32(0), true);
|
||||
else if (e.Length == 2)
|
||||
PlaySound(e.Mobile, e.GetInt32(0), e.GetBoolean(1));
|
||||
else
|
||||
e.Mobile.SendMessage("Format: Sound <index> [toAll]");
|
||||
}
|
||||
|
||||
private static void PlaySound(Mobile m, int index, bool toAll)
|
||||
{
|
||||
Map map = m.Map;
|
||||
|
||||
if (map == null)
|
||||
return;
|
||||
|
||||
CommandLogging.WriteLine(m, "{0} {1} playing sound {2} (toAll={3})", m.AccessLevel, CommandLogging.Format(m),
|
||||
index, toAll);
|
||||
|
||||
Packet p = new PlaySound(index, m.Location);
|
||||
|
||||
p.Acquire();
|
||||
|
||||
foreach (NetState state in m.GetClientsInRange(12))
|
||||
if (toAll || state.Mobile.CanSee(m))
|
||||
state.Send(p);
|
||||
|
||||
p.Release();
|
||||
}
|
||||
|
||||
[Usage("Echo <text>")]
|
||||
[Description("Relays (text) as a system message.")]
|
||||
public static void Echo_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
string toEcho = e.ArgString.Trim();
|
||||
|
||||
if (toEcho.Length > 0)
|
||||
e.Mobile.SendMessage(toEcho);
|
||||
else
|
||||
e.Mobile.SendMessage("Format: Echo \"<text>\"");
|
||||
}
|
||||
|
||||
[Usage("Bank")]
|
||||
[Description("Opens the bank box of a given target.")]
|
||||
public static void Bank_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new BankTarget();
|
||||
}
|
||||
|
||||
[Usage("Client")]
|
||||
[Description("Opens the client gump menu for a given player.")]
|
||||
private static void Client_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new ClientTarget();
|
||||
}
|
||||
|
||||
[Usage("Move")]
|
||||
[Description("Repositions a targeted item or mobile.")]
|
||||
private static void Move_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new PickMoveTarget();
|
||||
}
|
||||
|
||||
[Usage("Save")]
|
||||
[Description("Saves the world.")]
|
||||
private static void Save_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
AutoSave.Save();
|
||||
}
|
||||
|
||||
[Usage("BackgroundSave")]
|
||||
[Aliases("BGSave", "SaveBG")]
|
||||
[Description("Saves the world, writing to the disk in the background")]
|
||||
private static void BackgroundSave_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
AutoSave.Save(true);
|
||||
}
|
||||
|
||||
private static bool FixMap(ref Map map, ref Point3D loc, Item item) => (map != null && map != Map.Internal) || (item.RootParent is Mobile m && FixMap(ref map, ref loc, m));
|
||||
|
||||
private static bool FixMap(ref Map map, ref Point3D loc, Mobile m)
|
||||
{
|
||||
bool validMap = map != null && map != Map.Internal;
|
||||
|
||||
if (!validMap)
|
||||
{
|
||||
map = m.LogoutMap;
|
||||
loc = m.LogoutLocation;
|
||||
}
|
||||
|
||||
return validMap;
|
||||
}
|
||||
|
||||
[Usage("Go [name | serial | (x y [z]) | (deg min (N | S) deg min (E | W))]")]
|
||||
[Description(
|
||||
"With no arguments, this command brings up the go menu. With one argument, (name), you are moved to that regions \"go location.\" Or, if a numerical value is specified for one argument, (serial), you are moved to that object. Two or three arguments, (x y [z]), will move your character to that location. When six arguments are specified, (deg min (N | S) deg min (E | W)), your character will go to an approximate of those sextant coordinates.")]
|
||||
private static void Go_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
|
||||
if (e.Length == 0)
|
||||
{
|
||||
GoGump.DisplayTo(from);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Length == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
uint ser = e.GetUInt32(0);
|
||||
|
||||
IEntity ent = World.FindEntity(ser);
|
||||
|
||||
if (ent is Item item)
|
||||
{
|
||||
Map map = item.Map;
|
||||
Point3D loc = item.GetWorldLocation();
|
||||
|
||||
Mobile owner = item.RootParent as Mobile;
|
||||
|
||||
if (owner?.Map != null && owner.Map != Map.Internal &&
|
||||
!BaseCommand.IsAccessible(from, owner) /* !from.CanSee( owner )*/)
|
||||
{
|
||||
from.SendMessage("You can not go to what you can not see.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (owner != null && (owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
|
||||
owner.AccessLevel >= from.AccessLevel)
|
||||
{
|
||||
from.SendMessage("You can not go to what you can not see.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FixMap(ref map, ref loc, item))
|
||||
{
|
||||
from.SendMessage("That is an internal item and you cannot go to it.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.MoveToWorld(loc, map);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent is Mobile m)
|
||||
{
|
||||
Map map = m.Map;
|
||||
Point3D loc = m.Location;
|
||||
|
||||
Mobile owner = m;
|
||||
|
||||
if (owner.Map != null && owner.Map != Map.Internal &&
|
||||
!BaseCommand.IsAccessible(from, owner) /* !from.CanSee( owner )*/)
|
||||
{
|
||||
from.SendMessage("You can not go to what you can not see.");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((owner.Map == null || owner.Map == Map.Internal) && owner.Hidden &&
|
||||
owner.AccessLevel >= from.AccessLevel)
|
||||
{
|
||||
from.SendMessage("You can not go to what you can not see.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FixMap(ref map, ref loc, m))
|
||||
{
|
||||
from.SendMessage("That is an internal mobile and you cannot go to it.");
|
||||
return;
|
||||
}
|
||||
|
||||
from.MoveToWorld(loc, map);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = e.GetString(0);
|
||||
Map map;
|
||||
|
||||
for (int i = 0; i < Map.AllMaps.Count; ++i)
|
||||
{
|
||||
map = Map.AllMaps[i];
|
||||
|
||||
if (map.MapIndex == 0x7F || map.MapIndex == 0xFF)
|
||||
continue;
|
||||
|
||||
if (Insensitive.Equals(name, map.Name))
|
||||
{
|
||||
from.Map = map;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<string, Region> list = from.Map.Regions;
|
||||
|
||||
foreach (KeyValuePair<string, Region> kvp in list)
|
||||
{
|
||||
Region r = kvp.Value;
|
||||
|
||||
if (Insensitive.Equals(r.Name, name))
|
||||
{
|
||||
from.Location = new Point3D(r.GoLocation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Map.AllMaps.Count; ++i)
|
||||
{
|
||||
map = Map.AllMaps[i];
|
||||
|
||||
if (map.MapIndex == 0x7F || map.MapIndex == 0xFF || from.Map == map)
|
||||
continue;
|
||||
|
||||
foreach (Region r in map.Regions.Values)
|
||||
if (Insensitive.Equals(r.Name, name))
|
||||
{
|
||||
from.MoveToWorld(r.GoLocation, map);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ser != 0)
|
||||
from.SendMessage("No object with that serial was found.");
|
||||
else
|
||||
from.SendMessage("No region with that name was found.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
from.SendMessage("Region name not found");
|
||||
}
|
||||
else if (e.Length == 2 || e.Length == 3)
|
||||
{
|
||||
Map map = from.Map;
|
||||
|
||||
if (map != null)
|
||||
try
|
||||
{
|
||||
/*
|
||||
* This to avoid being teleported to (0,0) if trying to teleport
|
||||
* to a region with spaces in its name.
|
||||
*/
|
||||
int x = int.Parse(e.GetString(0));
|
||||
int y = int.Parse(e.GetString(1));
|
||||
int z = e.Length == 3 ? int.Parse(e.GetString(2)) : map.GetAverageZ(x, y);
|
||||
|
||||
from.Location = new Point3D(x, y, z);
|
||||
}
|
||||
catch
|
||||
{
|
||||
from.SendMessage("Region name not found.");
|
||||
}
|
||||
}
|
||||
else if (e.Length == 6)
|
||||
{
|
||||
Map map = from.Map;
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
Point3D p = Sextant.ReverseLookup(map, e.GetInt32(3), e.GetInt32(0), e.GetInt32(4), e.GetInt32(1),
|
||||
Insensitive.Equals(e.GetString(5), "E"), Insensitive.Equals(e.GetString(2), "S"));
|
||||
|
||||
if (p != Point3D.Zero)
|
||||
from.Location = p;
|
||||
else
|
||||
from.SendMessage("Sextant reverse lookup failed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("Format: Go [name | serial | (x y [z]) | (deg min (N | S) deg min (E | W)]");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("Help")]
|
||||
[Description("Lists all available commands.")]
|
||||
public static void Help_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile m = e.Mobile;
|
||||
|
||||
List<CommandEntry> list = new List<CommandEntry>();
|
||||
|
||||
foreach (CommandEntry entry in CommandSystem.Entries.Values)
|
||||
if (m.AccessLevel >= entry.AccessLevel)
|
||||
list.Add(entry);
|
||||
|
||||
list.Sort();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (list.Count > 0)
|
||||
sb.Append(list[0].Command);
|
||||
|
||||
for (int i = 1; i < list.Count; ++i)
|
||||
{
|
||||
string v = list[i].Command;
|
||||
|
||||
if (sb.Length + 1 + v.Length >= 256)
|
||||
{
|
||||
m.SendAsciiMessage(0x482, sb.ToString());
|
||||
sb = new StringBuilder();
|
||||
sb.Append(v);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append(v);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
m.SendAsciiMessage(0x482, sb.ToString());
|
||||
}
|
||||
|
||||
[Usage("SMsg <text>")]
|
||||
[Aliases("S", "SM")]
|
||||
[Description("Broadcasts a message to all online staff.")]
|
||||
public static void StaffMessage_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BroadcastMessage(AccessLevel.Counselor, e.Mobile.SpeechHue, $"[{e.Mobile.Name}] {e.ArgString}");
|
||||
}
|
||||
|
||||
[Usage("BCast <text>")]
|
||||
[Aliases("B", "BC")]
|
||||
[Description("Broadcasts a message to everyone online.")]
|
||||
public static void BroadcastMessage_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BroadcastMessage(AccessLevel.Player, 0x482, $"Staff message from {e.Mobile.Name}:");
|
||||
BroadcastMessage(AccessLevel.Player, 0x482, e.ArgString);
|
||||
}
|
||||
|
||||
public static void BroadcastMessage(AccessLevel ac, int hue, string message)
|
||||
{
|
||||
foreach (NetState state in TcpServer.Instances)
|
||||
{
|
||||
Mobile m = state.Mobile;
|
||||
|
||||
if (m?.AccessLevel >= ac)
|
||||
m.SendMessage(hue, message);
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("AutoPageNotify")]
|
||||
[Aliases("APN")]
|
||||
[Description("Toggles your auto-page-notify status.")]
|
||||
public static void APN_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile m = e.Mobile;
|
||||
|
||||
m.AutoPageNotify = !m.AutoPageNotify;
|
||||
|
||||
m.SendMessage("Your auto-page-notify has been turned {0}.", m.AutoPageNotify ? "on" : "off");
|
||||
}
|
||||
|
||||
[Usage("Animate <action> <frameCount> <repeatCount> <forward> <repeat> <delay>")]
|
||||
[Description("Makes your character do a specified animation.")]
|
||||
public static void Animate_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 6)
|
||||
e.Mobile.Animate(e.GetInt32(0), e.GetInt32(1), e.GetInt32(2), e.GetBoolean(3), e.GetBoolean(4),
|
||||
e.GetInt32(5));
|
||||
else
|
||||
e.Mobile.SendMessage("Format: Animate <action> <frameCount> <repeatCount> <forward> <repeat> <delay>");
|
||||
}
|
||||
|
||||
[Usage("Cast <name>")]
|
||||
[Description("Casts a spell by name.")]
|
||||
public static void Cast_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
{
|
||||
if (!DesignContext.Check(e.Mobile))
|
||||
return; // They are customizing
|
||||
|
||||
Spell spell = SpellRegistry.NewSpell(e.GetString(0), e.Mobile, null);
|
||||
|
||||
if (spell != null)
|
||||
spell.Cast();
|
||||
else
|
||||
e.Mobile.SendMessage("That spell was not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.SendMessage("Format: Cast <name>");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("Stuck")]
|
||||
[Description("Opens a menu of towns, used for teleporting stuck mobiles.")]
|
||||
public static void Stuck_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new StuckMenuTarget();
|
||||
}
|
||||
|
||||
[Usage("Light <level>")]
|
||||
[Description("Set your local lightlevel.")]
|
||||
public static void Light_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.LightLevel = e.GetInt32(0);
|
||||
}
|
||||
|
||||
[Usage("Stats")]
|
||||
[Description("View some stats about the server.")]
|
||||
public static void Stats_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.SendMessage("Open Connections: {0}", TcpServer.Instances.Count);
|
||||
e.Mobile.SendMessage("Mobiles: {0}", World.Mobiles.Count);
|
||||
e.Mobile.SendMessage("Items: {0}", World.Items.Count);
|
||||
}
|
||||
|
||||
private class ViewEqTarget : Target
|
||||
{
|
||||
public ViewEqTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, targeted))
|
||||
{
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
return;
|
||||
}
|
||||
|
||||
if (targeted is Mobile mobile)
|
||||
from.SendMenu(new EquipMenu(from, mobile, GetEquip(mobile)));
|
||||
}
|
||||
|
||||
private static ItemListEntry[] GetEquip(Mobile m)
|
||||
{
|
||||
ItemListEntry[] entries = new ItemListEntry[m.Items.Count];
|
||||
|
||||
for (int i = 0; i < m.Items.Count; ++i)
|
||||
{
|
||||
Item item = m.Items[i];
|
||||
|
||||
entries[i] = new ItemListEntry($"{item.Layer}: {item.GetType().Name}", item.ItemID, item.Hue);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private class EquipMenu : ItemListMenu
|
||||
{
|
||||
private readonly Mobile m_Mobile;
|
||||
|
||||
public EquipMenu(Mobile from, Mobile m, ItemListEntry[] entries) : base("Equipment", entries)
|
||||
{
|
||||
m_Mobile = m;
|
||||
|
||||
CommandLogging.WriteLine(from, "{0} {1} viewing equipment of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(m));
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, int index)
|
||||
{
|
||||
if (index >= 0 && index < m_Mobile.Items.Count)
|
||||
{
|
||||
Item item = m_Mobile.Items[index];
|
||||
|
||||
state.Mobile.SendMenu(new EquipDetailsMenu(m_Mobile, item));
|
||||
}
|
||||
}
|
||||
|
||||
private class EquipDetailsMenu : QuestionMenu
|
||||
{
|
||||
private readonly Item m_Item;
|
||||
private readonly Mobile m_Mobile;
|
||||
|
||||
public EquipDetailsMenu(Mobile m, Item item) : base($"{item.Layer}: {item.GetType().Name}",
|
||||
new[] { "Move", "Delete", "Props" })
|
||||
{
|
||||
m_Mobile = m;
|
||||
m_Item = item;
|
||||
}
|
||||
|
||||
public override void OnCancel(NetState state)
|
||||
{
|
||||
state.Mobile.SendMenu(new EquipMenu(state.Mobile, m_Mobile, GetEquip(m_Mobile)));
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState state, int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
CommandLogging.WriteLine(state.Mobile, "{0} {1} moving equipment item {2} of {3}",
|
||||
state.Mobile.AccessLevel, CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
|
||||
CommandLogging.Format(m_Mobile));
|
||||
state.Mobile.Target = new MoveTarget(m_Item);
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
CommandLogging.WriteLine(state.Mobile, "{0} {1} deleting equipment item {2} of {3}",
|
||||
state.Mobile.AccessLevel, CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
|
||||
CommandLogging.Format(m_Mobile));
|
||||
m_Item.Delete();
|
||||
}
|
||||
else if (index == 2)
|
||||
{
|
||||
CommandLogging.WriteLine(state.Mobile,
|
||||
"{0} {1} opening properties for equipment item {2} of {3}", state.Mobile.AccessLevel,
|
||||
CommandLogging.Format(state.Mobile), CommandLogging.Format(m_Item),
|
||||
CommandLogging.Format(m_Mobile));
|
||||
state.Mobile.SendGump(new PropertiesGump(state.Mobile, m_Item));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class BankTarget : Target
|
||||
{
|
||||
public BankTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile m)
|
||||
{
|
||||
BankBox box = m.Player ? m.BankBox : m.FindBankNoCreate();
|
||||
|
||||
if (box != null)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} opening bank box of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(m));
|
||||
|
||||
if (from == m)
|
||||
box.Open();
|
||||
else
|
||||
box.DisplayTo(from);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("They have no bank box.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DismountTarget : Target
|
||||
{
|
||||
public DismountTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile targ)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} dismounting {2}", from.AccessLevel, CommandLogging.Format(from),
|
||||
CommandLogging.Format(targ));
|
||||
|
||||
for (int i = 0; i < targ.Items.Count; ++i)
|
||||
{
|
||||
Item item = targ.Items[i];
|
||||
|
||||
if (item is IMountItem mountItem)
|
||||
{
|
||||
IMount mount = mountItem.Mount;
|
||||
|
||||
if (mount != null)
|
||||
mount.Rider = null;
|
||||
|
||||
if (targ.Items.IndexOf(item) == -1)
|
||||
--i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < targ.Items.Count; ++i)
|
||||
{
|
||||
Item item = targ.Items[i];
|
||||
|
||||
if (item.Layer == Layer.Mount)
|
||||
{
|
||||
item.Delete();
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClientTarget : Target
|
||||
{
|
||||
public ClientTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile targ && targ.NetState != null)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} opening client menu of {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), CommandLogging.Format(targ));
|
||||
from.SendGump(new ClientGump(from, targ.NetState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StuckMenuTarget : Target
|
||||
{
|
||||
public StuckMenuTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile mobile)
|
||||
{
|
||||
if (mobile.AccessLevel >= from.AccessLevel && mobile != from)
|
||||
from.SendMessage("You can't do that to someone with higher Accesslevel than you!");
|
||||
else
|
||||
from.SendGump(new StuckMenu(from, mobile, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
394
Projects/UOContent/Commands/HelpInfo.cs
Normal file
394
Projects/UOContent/Commands/HelpInfo.cs
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
using CommandInfo = Server.Commands.Docs.DocCommandEntry;
|
||||
using CommandInfoSorter = Server.Commands.Docs.CommandEntrySorter;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class HelpInfo
|
||||
{
|
||||
public static Dictionary<string, CommandInfo> HelpInfos { get; } = new Dictionary<string, CommandInfo>();
|
||||
|
||||
public static List<CommandInfo> SortedHelpInfo { get; private set; } = new List<CommandInfo>();
|
||||
|
||||
[CallPriority(100)]
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("HelpInfo", AccessLevel.Player, HelpInfo_OnCommand);
|
||||
|
||||
FillTable();
|
||||
}
|
||||
|
||||
[Usage("HelpInfo [<command>]")]
|
||||
[Description(
|
||||
"Gives information on a specified command, or when no argument specified, displays a gump containing all commands")]
|
||||
private static void HelpInfo_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length > 0)
|
||||
{
|
||||
string arg = e.GetString(0).ToLower();
|
||||
if (HelpInfos.TryGetValue(arg, out CommandInfo c))
|
||||
{
|
||||
Mobile m = e.Mobile;
|
||||
|
||||
if (m.AccessLevel >= c.AccessLevel)
|
||||
m.SendGump(new CommandInfoGump(c));
|
||||
else
|
||||
m.SendMessage("You don't have access to that command.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
e.Mobile.SendMessage($"Command '{arg}' not found!");
|
||||
}
|
||||
|
||||
e.Mobile.SendGump(new CommandListGump(0, e.Mobile, null));
|
||||
}
|
||||
|
||||
public static void FillTable()
|
||||
{
|
||||
List<CommandEntry> commands = new List<CommandEntry>(CommandSystem.Entries.Values);
|
||||
List<CommandInfo> list = new List<CommandInfo>();
|
||||
|
||||
commands.Sort();
|
||||
commands.Reverse();
|
||||
Docs.Clean(commands);
|
||||
|
||||
for (int i = 0; i < commands.Count; ++i)
|
||||
{
|
||||
CommandEntry e = commands[i];
|
||||
|
||||
MethodInfo mi = e.Handler.Method;
|
||||
|
||||
object[] attrs = mi.GetCustomAttributes(typeof(UsageAttribute), false);
|
||||
|
||||
if (attrs.Length == 0)
|
||||
continue;
|
||||
|
||||
UsageAttribute usage = attrs[0] as UsageAttribute;
|
||||
|
||||
attrs = mi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
|
||||
if (attrs.Length == 0)
|
||||
continue;
|
||||
|
||||
if (usage == null || !(attrs[0] is DescriptionAttribute desc))
|
||||
continue;
|
||||
|
||||
attrs = mi.GetCustomAttributes(typeof(AliasesAttribute), false);
|
||||
|
||||
AliasesAttribute aliases = attrs.Length == 0 ? null : attrs[0] as AliasesAttribute;
|
||||
|
||||
string descString = desc.Description.Replace("<", "(").Replace(">", ")");
|
||||
|
||||
if (aliases == null)
|
||||
{
|
||||
list.Add(new CommandInfo(e.AccessLevel, e.Command, null, usage.Usage, descString));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new CommandInfo(e.AccessLevel, e.Command, aliases.Aliases, usage.Usage, descString));
|
||||
|
||||
for (int j = 0; j < aliases.Aliases.Length; j++)
|
||||
{
|
||||
string[] newAliases = new string[aliases.Aliases.Length];
|
||||
|
||||
aliases.Aliases.CopyTo(newAliases, 0);
|
||||
|
||||
newAliases[j] = e.Command;
|
||||
|
||||
list.Add(new CommandInfo(e.AccessLevel, aliases.Aliases[j], newAliases, usage.Usage, descString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < TargetCommands.AllCommands.Count; ++i)
|
||||
{
|
||||
BaseCommand command = TargetCommands.AllCommands[i];
|
||||
|
||||
string usage = command.Usage;
|
||||
string desc = command.Description;
|
||||
|
||||
if (usage == null || desc == null)
|
||||
continue;
|
||||
|
||||
string[] cmds = command.Commands;
|
||||
string cmd = cmds[0];
|
||||
string[] aliases = new string[cmds.Length - 1];
|
||||
|
||||
for (int j = 0; j < aliases.Length; ++j)
|
||||
aliases[j] = cmds[j + 1];
|
||||
|
||||
desc = desc.Replace("<", "(").Replace(">", ")");
|
||||
|
||||
if (command.Supports != CommandSupport.Single)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(50 + desc.Length);
|
||||
|
||||
sb.Append("Modifiers: ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Global) != 0)
|
||||
sb.Append("<i>Global</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Online) != 0)
|
||||
sb.Append("<i>Online</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Region) != 0)
|
||||
sb.Append("<i>Region</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Contained) != 0)
|
||||
sb.Append("<i>Contained</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Multi) != 0)
|
||||
sb.Append("<i>Multi</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Area) != 0)
|
||||
sb.Append("<i>Area</i>, ");
|
||||
|
||||
if ((command.Supports & CommandSupport.Self) != 0)
|
||||
sb.Append("<i>Self</i>, ");
|
||||
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
sb.Append("<br>");
|
||||
sb.Append(desc);
|
||||
|
||||
desc = sb.ToString();
|
||||
}
|
||||
|
||||
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
|
||||
|
||||
for (int j = 0; j < aliases.Length; j++)
|
||||
{
|
||||
string[] newAliases = new string[aliases.Length];
|
||||
|
||||
aliases.CopyTo(newAliases, 0);
|
||||
|
||||
newAliases[j] = cmd;
|
||||
|
||||
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
|
||||
}
|
||||
}
|
||||
|
||||
List<BaseCommandImplementor> commandImpls = BaseCommandImplementor.Implementors;
|
||||
|
||||
for (int i = 0; i < commandImpls.Count; ++i)
|
||||
{
|
||||
BaseCommandImplementor command = commandImpls[i];
|
||||
|
||||
string usage = command.Usage;
|
||||
string desc = command.Description;
|
||||
|
||||
if (usage == null || desc == null)
|
||||
continue;
|
||||
|
||||
string[] cmds = command.Accessors;
|
||||
string cmd = cmds[0];
|
||||
string[] aliases = new string[cmds.Length - 1];
|
||||
|
||||
for (int j = 0; j < aliases.Length; ++j)
|
||||
aliases[j] = cmds[j + 1];
|
||||
|
||||
desc = desc.Replace("<", ")").Replace(">", ")");
|
||||
|
||||
list.Add(new CommandInfo(command.AccessLevel, cmd, aliases, usage, desc));
|
||||
|
||||
for (int j = 0; j < aliases.Length; j++)
|
||||
{
|
||||
string[] newAliases = new string[aliases.Length];
|
||||
|
||||
aliases.CopyTo(newAliases, 0);
|
||||
|
||||
newAliases[j] = cmd;
|
||||
|
||||
list.Add(new CommandInfo(command.AccessLevel, aliases[j], newAliases, usage, desc));
|
||||
}
|
||||
}
|
||||
|
||||
list.Sort(new CommandInfoSorter());
|
||||
|
||||
SortedHelpInfo = list;
|
||||
|
||||
foreach (CommandInfo c in SortedHelpInfo)
|
||||
if (!HelpInfos.ContainsKey(c.Name.ToLower()))
|
||||
HelpInfos.Add(c.Name.ToLower(), c);
|
||||
}
|
||||
|
||||
public class CommandListGump : BaseGridGump
|
||||
{
|
||||
private const int EntriesPerPage = 15;
|
||||
private readonly List<CommandInfo> m_List;
|
||||
|
||||
private readonly int m_Page;
|
||||
|
||||
public CommandListGump(int page, Mobile from, List<CommandInfo> list)
|
||||
: base(30, 30)
|
||||
{
|
||||
m_Page = page;
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
m_List = new List<CommandInfo>();
|
||||
|
||||
foreach (CommandInfo c in SortedHelpInfo)
|
||||
if (from.AccessLevel >= c.AccessLevel)
|
||||
m_List.Add(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_List = list;
|
||||
}
|
||||
|
||||
AddNewPage();
|
||||
|
||||
if (m_Page > 0)
|
||||
AddEntryButton(20, ArrowLeftID1, ArrowLeftID2, 1, ArrowLeftWidth, ArrowLeftHeight);
|
||||
else
|
||||
AddEntryHeader(20);
|
||||
|
||||
AddEntryHtml(160, Center(
|
||||
$"Page {m_Page + 1} of {(m_List.Count + EntriesPerPage - 1) / EntriesPerPage}"));
|
||||
|
||||
if ((m_Page + 1) * EntriesPerPage < m_List.Count)
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 2, ArrowRightWidth, ArrowRightHeight);
|
||||
else
|
||||
AddEntryHeader(20);
|
||||
|
||||
int last = (int)AccessLevel.Player - 1;
|
||||
|
||||
for (int i = m_Page * EntriesPerPage, line = 0; line < EntriesPerPage && i < m_List.Count; ++i, ++line)
|
||||
{
|
||||
CommandInfo c = m_List[i];
|
||||
if (from.AccessLevel >= c.AccessLevel)
|
||||
{
|
||||
if ((int)c.AccessLevel != last)
|
||||
{
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, Color(c.AccessLevel.ToString(), 0xFF0000));
|
||||
AddEntryHeader(20);
|
||||
line++;
|
||||
}
|
||||
|
||||
last = (int)c.AccessLevel;
|
||||
|
||||
AddNewLine();
|
||||
AddEntryHtml(20 + OffsetSize + 160, c.Name);
|
||||
AddEntryButton(20, ArrowRightID1, ArrowRightID2, 3 + i, ArrowRightWidth, ArrowRightHeight);
|
||||
}
|
||||
}
|
||||
|
||||
FinishPage();
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
Mobile m = sender.Mobile;
|
||||
switch (info.ButtonID)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m.CloseGump<CommandInfoGump>();
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
if (m_Page > 0)
|
||||
m.SendGump(new CommandListGump(m_Page - 1, m, m_List));
|
||||
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
if ((m_Page + 1) * EntriesPerPage < SortedHelpInfo.Count)
|
||||
m.SendGump(new CommandListGump(m_Page + 1, m, m_List));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
int v = info.ButtonID - 3;
|
||||
|
||||
if (v >= 0 && v < m_List.Count)
|
||||
{
|
||||
CommandInfo c = m_List[v];
|
||||
|
||||
if (m.AccessLevel >= c.AccessLevel)
|
||||
{
|
||||
m.SendGump(new CommandInfoGump(c));
|
||||
m.SendGump(new CommandListGump(m_Page, m, m_List));
|
||||
}
|
||||
else
|
||||
{
|
||||
m.SendMessage("You no longer have access to that command.");
|
||||
m.SendGump(new CommandListGump(m_Page, m, null));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandInfoGump : Gump
|
||||
{
|
||||
public CommandInfoGump(CommandInfo info, int width = 320, int height = 200)
|
||||
: base(300, 50)
|
||||
{
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(0, 0, width, height, 5054);
|
||||
|
||||
// AddImageTiled( 10, 10, width - 20, 20, 2624 );
|
||||
// AddAlphaRegion( 10, 10, width - 20, 20 );
|
||||
// AddHtmlLocalized( 10, 10, width - 20, 20, header, headerColor, false, false );
|
||||
AddHtml(10, 10, width - 20, 20, Color(Center(info.Name), 0xFF0000));
|
||||
|
||||
// AddImageTiled( 10, 40, width - 20, height - 80, 2624 );
|
||||
// AddAlphaRegion( 10, 40, width - 20, height - 80 );
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.Append("Usage: ");
|
||||
sb.Append(info.Usage.Replace("<", "(").Replace(">", ")"));
|
||||
sb.Append("<BR>");
|
||||
|
||||
string[] aliases = info.Aliases;
|
||||
|
||||
if (aliases?.Length > 0)
|
||||
{
|
||||
sb.Append($"Alias{(aliases.Length == 1 ? "" : "es")}: ");
|
||||
|
||||
for (int i = 0; i < aliases.Length; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
sb.Append(", ");
|
||||
|
||||
sb.Append(aliases[i]);
|
||||
}
|
||||
|
||||
sb.Append("<BR>");
|
||||
}
|
||||
|
||||
sb.Append("AccessLevel: ");
|
||||
sb.Append(info.AccessLevel.ToString());
|
||||
sb.Append("<BR>");
|
||||
sb.Append("<BR>");
|
||||
|
||||
sb.Append(info.Description);
|
||||
|
||||
AddHtml(10, 40, width - 20, height - 80, sb.ToString(), false, true);
|
||||
|
||||
// AddImageTiled( 10, height - 30, width - 20, 20, 2624 );
|
||||
// AddAlphaRegion( 10, height - 30, width - 20, 20 );
|
||||
}
|
||||
|
||||
public string Color(string text, int color) => $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
|
||||
|
||||
public string Center(string text) => $"<CENTER>{text}</CENTER>";
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Projects/UOContent/Commands/LocationCommand.cs
Normal file
63
Projects/UOContent/Commands/LocationCommand.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Items;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class LocationCommand : BaseCommand
|
||||
{
|
||||
private static readonly List<int> m_DefaultGraphics = new List<int> { 0x17AF, 0x17B0, 0x17B1, 0x17B2 };
|
||||
|
||||
public LocationCommand()
|
||||
{
|
||||
AccessLevel = AccessLevel.Counselor;
|
||||
Commands = new[] { "Location", "Loc", "Pos" };
|
||||
ObjectTypes = ObjectTypes.All;
|
||||
Supports = CommandSupport.Single | CommandSupport.Multi;
|
||||
Usage = "Location [itemIds ...]";
|
||||
Description = "Retrieves the positional coordinates of a target. Displaying a message above the item, "
|
||||
+ "mobile, or environment. Environment targets will also have a graphic temporarily displayed "
|
||||
+ "on the tile. This graphic can be changed to the provided list of itemIds.";
|
||||
}
|
||||
|
||||
public override void Execute(CommandEventArgs e, object obj)
|
||||
{
|
||||
if (!(obj is IPoint3D point))
|
||||
{
|
||||
LogFailure("That cannot be located.");
|
||||
return;
|
||||
}
|
||||
|
||||
var label = $"(x:{point.X}, y:{point.Y}, z:{point.Z})";
|
||||
if (obj is LandTarget || obj is StaticTarget)
|
||||
{
|
||||
List<int> graphics;
|
||||
if (e.Arguments.Length == 0)
|
||||
graphics = m_DefaultGraphics;
|
||||
else
|
||||
{
|
||||
graphics = new List<int>();
|
||||
foreach (var arg in e.Arguments)
|
||||
{
|
||||
var numberStyles = arg.ToLower().StartsWith("0x") ? NumberStyles.HexNumber : NumberStyles.Integer;
|
||||
|
||||
if (int.TryParse(arg, numberStyles, CultureInfo.InvariantCulture, out int result))
|
||||
graphics.Add(result);
|
||||
}
|
||||
}
|
||||
|
||||
var item = EffectItem.Create(new Point3D(point), e.Mobile.Map, EffectItem.DefaultDuration);
|
||||
foreach (int graphic in graphics)
|
||||
Effects.SendLocationParticles(item, graphic, 10, 50, 2023);
|
||||
|
||||
item.LabelTo(e.Mobile, label);
|
||||
}
|
||||
else if (obj is Mobile entity) entity.SayTo(e.Mobile, label);
|
||||
else if (obj is Item item) item.LabelTo(e.Mobile, label);
|
||||
|
||||
AddResponse($"Location: {label}");
|
||||
}
|
||||
}
|
||||
}
|
||||
139
Projects/UOContent/Commands/Logging.cs
Normal file
139
Projects/UOContent/Commands/Logging.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Server.Accounting;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class CommandLogging
|
||||
{
|
||||
private static readonly char[] m_NotSafe = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
|
||||
public static bool Enabled { get; set; } = true;
|
||||
|
||||
public static StreamWriter Output { get; private set; }
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.Command += EventSink_Command;
|
||||
|
||||
if (!Directory.Exists("Logs"))
|
||||
Directory.CreateDirectory("Logs");
|
||||
|
||||
string directory = "Logs/Commands";
|
||||
|
||||
if (!Directory.Exists(directory))
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
try
|
||||
{
|
||||
Output = new StreamWriter(Path.Combine(directory, $"{DateTime.UtcNow.ToLongDateString()}.log"), true);
|
||||
|
||||
Output.AutoFlush = true;
|
||||
|
||||
Output.WriteLine("##############################");
|
||||
Output.WriteLine("Log started on {0}", DateTime.UtcNow);
|
||||
Output.WriteLine();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static object Format(object o)
|
||||
{
|
||||
if (o is Mobile m)
|
||||
{
|
||||
if (m.Account == null)
|
||||
return $"{m} (no account)";
|
||||
|
||||
return $"{m} ('{m.Account.Username}')";
|
||||
}
|
||||
|
||||
if (o is Item item) return $"0x{item.Serial.Value:X} ({item.GetType().Name})";
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
public static void WriteLine(Mobile from, string format, params object[] args)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
WriteLine(from, string.Format(format, args));
|
||||
}
|
||||
|
||||
public static void WriteLine(Mobile from, string text)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Output.WriteLine("{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text);
|
||||
|
||||
string path = Core.BaseDirectory;
|
||||
|
||||
string name = !(from.Account is Account acct) ? from.Name : acct.Username;
|
||||
|
||||
AppendPath(ref path, "Logs");
|
||||
AppendPath(ref path, "Commands");
|
||||
AppendPath(ref path, from.AccessLevel.ToString());
|
||||
path = Path.Combine(path, $"{name}.log");
|
||||
|
||||
using StreamWriter sw = new StreamWriter(path, true);
|
||||
sw.WriteLine("{0}: {1}: {2}", DateTime.UtcNow, from.NetState, text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public static void AppendPath(ref string path, string toAppend)
|
||||
{
|
||||
path = Path.Combine(path, toAppend);
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
public static string Safe(string ip)
|
||||
{
|
||||
if (ip == null)
|
||||
return "null";
|
||||
|
||||
ip = ip.Trim();
|
||||
|
||||
if (ip.Length == 0)
|
||||
return "empty";
|
||||
|
||||
bool isSafe = true;
|
||||
|
||||
for (int i = 0; isSafe && i < m_NotSafe.Length; ++i)
|
||||
isSafe = ip.IndexOf(m_NotSafe[i]) == -1;
|
||||
|
||||
if (isSafe)
|
||||
return ip;
|
||||
|
||||
StringBuilder sb = new StringBuilder(ip);
|
||||
|
||||
for (int i = 0; i < m_NotSafe.Length; ++i)
|
||||
sb.Replace(m_NotSafe[i], '_');
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static void EventSink_Command(CommandEventArgs e)
|
||||
{
|
||||
WriteLine(e.Mobile, "{0} {1} used command '{2} {3}'", e.Mobile.AccessLevel, Format(e.Mobile), e.Command,
|
||||
e.ArgString);
|
||||
}
|
||||
|
||||
public static void LogChangeProperty(Mobile from, object o, string name, string value)
|
||||
{
|
||||
WriteLine(from, "{0} {1} set property '{2}' of {3} to '{4}'", from.AccessLevel, Format(from), name, Format(o),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
360
Projects/UOContent/Commands/Profiling.cs
Normal file
360
Projects/UOContent/Commands/Profiling.cs
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Server.Diagnostics;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class Profiling
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("DumpTimers", AccessLevel.Administrator, DumpTimers_OnCommand);
|
||||
CommandSystem.Register("CountObjects", AccessLevel.Administrator, CountObjects_OnCommand);
|
||||
CommandSystem.Register("ProfileWorld", AccessLevel.Administrator, ProfileWorld_OnCommand);
|
||||
CommandSystem.Register("TraceInternal", AccessLevel.Administrator, TraceInternal_OnCommand);
|
||||
CommandSystem.Register("TraceExpanded", AccessLevel.Administrator, TraceExpanded_OnCommand);
|
||||
CommandSystem.Register("WriteProfiles", AccessLevel.Administrator, WriteProfiles_OnCommand);
|
||||
CommandSystem.Register("SetProfiles", AccessLevel.Administrator, SetProfiles_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("WriteProfiles")]
|
||||
[Description("Generates a log files containing performance diagnostic information.")]
|
||||
public static void WriteProfiles_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
using StreamWriter sw = new StreamWriter("profiles.log", true);
|
||||
sw.WriteLine("# Dump on {0:f}", DateTime.UtcNow);
|
||||
sw.WriteLine($"# Core profiling for {Core.ProfileTime}");
|
||||
|
||||
sw.WriteLine("# Packet send");
|
||||
BaseProfile.WriteAll(sw, PacketSendProfile.Profiles);
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine("# Packet receive");
|
||||
BaseProfile.WriteAll(sw, PacketReceiveProfile.Profiles);
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine("# Timer");
|
||||
BaseProfile.WriteAll(sw, TimerProfile.Profiles);
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine("# Gump response");
|
||||
BaseProfile.WriteAll(sw, GumpProfile.Profiles);
|
||||
sw.WriteLine();
|
||||
|
||||
sw.WriteLine("# Target response");
|
||||
BaseProfile.WriteAll(sw, TargetProfile.Profiles);
|
||||
sw.WriteLine();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("SetProfiles [true | false]")]
|
||||
[Description("Enables, disables, or toggles the state of core packet and timer profiling.")]
|
||||
public static void SetProfiles_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
Core.Profiling = e.GetBoolean(0);
|
||||
else
|
||||
Core.Profiling = !Core.Profiling;
|
||||
|
||||
e.Mobile.SendMessage("Profiling has been {0}.", Core.Profiling ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
[Usage("DumpTimers")]
|
||||
[Description("Generates a log file of all currently executing timers. Used for tracing timer leaks.")]
|
||||
public static void DumpTimers_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
using StreamWriter sw = new StreamWriter("timerdump.log", true);
|
||||
Timer.DumpInfo(sw);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("CountObjects")]
|
||||
[Description("Generates a log file detailing all item and mobile types in the world.")]
|
||||
public static void CountObjects_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
using (StreamWriter op = new StreamWriter("objects.log"))
|
||||
{
|
||||
Dictionary<Type, int> table = new Dictionary<Type, int>();
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
Type type = item.GetType();
|
||||
|
||||
table[type] = (table.TryGetValue(type, out int value) ? value : 0) + 1;
|
||||
}
|
||||
|
||||
List<KeyValuePair<Type, int>> items = table.ToList();
|
||||
table.Clear();
|
||||
|
||||
foreach (Mobile m in World.Mobiles.Values)
|
||||
{
|
||||
Type type = m.GetType();
|
||||
|
||||
table[type] = (table.TryGetValue(type, out int value) ? value : 0) + 1;
|
||||
}
|
||||
|
||||
List<KeyValuePair<Type, int>> mobiles = table.ToList();
|
||||
|
||||
items.Sort(new CountSorter());
|
||||
mobiles.Sort(new CountSorter());
|
||||
|
||||
op.WriteLine("# Object count table generated on {0}", DateTime.UtcNow);
|
||||
op.WriteLine();
|
||||
op.WriteLine();
|
||||
|
||||
op.WriteLine("# Items:");
|
||||
|
||||
items.ForEach(kvp =>
|
||||
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Items.Count, kvp.Key));
|
||||
|
||||
op.WriteLine();
|
||||
op.WriteLine();
|
||||
|
||||
op.WriteLine("#Mobiles:");
|
||||
|
||||
mobiles.ForEach(kvp =>
|
||||
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / World.Mobiles.Count, kvp.Key));
|
||||
}
|
||||
|
||||
e.Mobile.SendMessage("Object table has been generated. See the file : <runuo root>/objects.log");
|
||||
}
|
||||
|
||||
[Usage("TraceExpanded")]
|
||||
[Description("Generates a log file describing all items using expanded memory.")]
|
||||
public static void TraceExpanded_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Dictionary<Type, int[]> typeTable = new Dictionary<Type, int[]>();
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
ExpandFlag flags = item.GetExpandFlags();
|
||||
|
||||
if ((flags & ~(ExpandFlag.TempFlag | ExpandFlag.SaveFlag)) == 0)
|
||||
continue;
|
||||
|
||||
Type itemType = item.GetType();
|
||||
|
||||
do
|
||||
{
|
||||
if (!typeTable.TryGetValue(itemType, out int[] countTable))
|
||||
typeTable[itemType] = countTable = new int[9];
|
||||
|
||||
if ((flags & ExpandFlag.Name) != 0)
|
||||
++countTable[0];
|
||||
|
||||
if ((flags & ExpandFlag.Items) != 0)
|
||||
++countTable[1];
|
||||
|
||||
if ((flags & ExpandFlag.Bounce) != 0)
|
||||
++countTable[2];
|
||||
|
||||
if ((flags & ExpandFlag.Holder) != 0)
|
||||
++countTable[3];
|
||||
|
||||
if ((flags & ExpandFlag.Blessed) != 0)
|
||||
++countTable[4];
|
||||
|
||||
/*if (( flags & ExpandFlag.TempFlag ) != 0)
|
||||
++countTable[5];
|
||||
|
||||
if (( flags & ExpandFlag.SaveFlag ) != 0)
|
||||
++countTable[6];*/
|
||||
|
||||
if ((flags & ExpandFlag.Weight) != 0)
|
||||
++countTable[7];
|
||||
|
||||
if ((flags & ExpandFlag.Spawner) != 0)
|
||||
++countTable[8];
|
||||
|
||||
itemType = itemType.BaseType;
|
||||
} while (itemType != typeof(object));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using StreamWriter op = new StreamWriter("expandedItems.log", true);
|
||||
string[] names =
|
||||
{
|
||||
"Name",
|
||||
"Items",
|
||||
"Bounce",
|
||||
"Holder",
|
||||
"Blessed",
|
||||
"TempFlag",
|
||||
"SaveFlag",
|
||||
"Weight",
|
||||
"Spawner"
|
||||
};
|
||||
|
||||
List<KeyValuePair<Type, int[]>> list = typeTable.ToList();
|
||||
|
||||
list.Sort(new CountsSorter());
|
||||
|
||||
foreach (KeyValuePair<Type, int[]> kvp in list)
|
||||
{
|
||||
int[] countTable = kvp.Value;
|
||||
|
||||
op.WriteLine("# {0}", kvp.Key.FullName);
|
||||
|
||||
for (int i = 0; i < countTable.Length; ++i)
|
||||
if (countTable[i] > 0)
|
||||
op.WriteLine("{0}\t{1:N0}", names[i], countTable[i]);
|
||||
|
||||
op.WriteLine();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("TraceInternal")]
|
||||
[Description("Generates a log file describing all items in the 'internal' map.")]
|
||||
public static void TraceInternal_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
int totalCount = 0;
|
||||
Dictionary<Type, int[]> table = new Dictionary<Type, int[]>();
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
if (item.Parent != null || item.Map != Map.Internal)
|
||||
continue;
|
||||
|
||||
++totalCount;
|
||||
|
||||
Type type = item.GetType();
|
||||
|
||||
if (table.TryGetValue(type, out int[] parms))
|
||||
{
|
||||
parms[0]++;
|
||||
parms[1] += item.Amount;
|
||||
}
|
||||
else
|
||||
table[type] = new[] { 1, item.Amount };
|
||||
}
|
||||
|
||||
using StreamWriter op = new StreamWriter("internal.log");
|
||||
op.WriteLine("# {0} items found", totalCount);
|
||||
op.WriteLine("# {0} different types", table.Count);
|
||||
op.WriteLine();
|
||||
op.WriteLine();
|
||||
op.WriteLine("Type\t\tCount\t\tAmount\t\tAvg. Amount");
|
||||
|
||||
foreach (KeyValuePair<Type, int[]> de in table)
|
||||
{
|
||||
int[] parms = de.Value;
|
||||
|
||||
op.WriteLine("{0}\t\t{1}\t\t{2}\t\t{3:F2}", de.Key.Name, parms[0], parms[1], (double)parms[1] / parms[0]);
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("ProfileWorld")]
|
||||
[Description("Prints the amount of data serialized for every object type in your world file.")]
|
||||
public static void ProfileWorld_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
ProfileWorld("items", "worldprofile_items.log");
|
||||
ProfileWorld("mobiles", "worldprofile_mobiles.log");
|
||||
}
|
||||
|
||||
public static void ProfileWorld(string type, string opFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Type> types = new List<Type>();
|
||||
|
||||
using (BinaryReader bin = new BinaryReader(new FileStream(string.Format("Saves/{0}/{0}.tdb", type),
|
||||
FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
int count = bin.ReadInt32();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
types.Add(AssemblyHandler.FindFirstTypeForName(bin.ReadString()));
|
||||
}
|
||||
|
||||
long total = 0;
|
||||
|
||||
Dictionary<Type, int> table = new Dictionary<Type, int>();
|
||||
|
||||
using (BinaryReader bin = new BinaryReader(new FileStream(string.Format("Saves/{0}/{0}.idx", type),
|
||||
FileMode.Open, FileAccess.Read, FileShare.Read)))
|
||||
{
|
||||
int count = bin.ReadInt32();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int typeID = bin.ReadInt32();
|
||||
int serial = bin.ReadInt32();
|
||||
long pos = bin.ReadInt64();
|
||||
int length = bin.ReadInt32();
|
||||
Type objType = types[typeID];
|
||||
|
||||
while (objType != null && objType != typeof(object))
|
||||
{
|
||||
table[objType] = length + (table.TryGetValue(objType, out int value) ? value : 0);
|
||||
objType = objType.BaseType;
|
||||
total += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<KeyValuePair<Type, int>> list = table.ToList();
|
||||
|
||||
list.Sort(new CountSorter());
|
||||
|
||||
using StreamWriter op = new StreamWriter(opFile);
|
||||
op.WriteLine("# Profile of world {0}", type);
|
||||
op.WriteLine("# Generated on {0}", DateTime.UtcNow);
|
||||
op.WriteLine();
|
||||
op.WriteLine();
|
||||
|
||||
list.ForEach(kvp =>
|
||||
op.WriteLine("{0}\t{1:F2}%\t{2}", kvp.Value, 100.0 * kvp.Value / total, kvp.Key));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private class CountSorter : IComparer<KeyValuePair<Type, int>>
|
||||
{
|
||||
public int Compare(KeyValuePair<Type, int> x, KeyValuePair<Type, int> y)
|
||||
{
|
||||
int aCount = x.Value;
|
||||
int bCount = y.Value;
|
||||
|
||||
int v = -aCount.CompareTo(bCount);
|
||||
|
||||
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
private class CountsSorter : IComparer<KeyValuePair<Type, int[]>>
|
||||
{
|
||||
public int Compare(KeyValuePair<Type, int[]> x, KeyValuePair<Type, int[]> y)
|
||||
{
|
||||
int aCount = x.Value.Aggregate(0, (t, val) => t + val);
|
||||
int bCount = y.Value.Aggregate(0, (t, val) => t + val);
|
||||
|
||||
int v = -aCount.CompareTo(bCount);
|
||||
|
||||
return v != 0 ? v : x.Key.FullName.CompareTo(y.Key.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
751
Projects/UOContent/Commands/Properties.cs
Normal file
751
Projects/UOContent/Commands/Properties.cs
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using Server.Commands;
|
||||
using Server.Commands.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Targeting;
|
||||
using CPA = Server.CommandPropertyAttribute;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
[Flags]
|
||||
public enum PropertyAccess
|
||||
{
|
||||
Read = 0x01,
|
||||
Write = 0x02,
|
||||
ReadWrite = Read | Write
|
||||
}
|
||||
|
||||
public static class Properties
|
||||
{
|
||||
private static readonly Type typeofCPA = typeof(CPA);
|
||||
|
||||
private static readonly Type typeofSerial = typeof(Serial);
|
||||
|
||||
private static readonly Type typeofType = typeof(Type);
|
||||
|
||||
private static readonly Type typeofChar = typeof(char);
|
||||
|
||||
private static readonly Type typeofString = typeof(string);
|
||||
|
||||
private static readonly Type typeofText = typeof(TextDefinition);
|
||||
|
||||
private static readonly Type typeofTimeSpan = typeof(TimeSpan);
|
||||
private static readonly Type typeofParsable = typeof(ParsableAttribute);
|
||||
|
||||
private static readonly Type[] m_ParseTypes = { typeof(string) };
|
||||
private static readonly object[] m_ParseParams = new object[1];
|
||||
|
||||
private static readonly Type[] m_NumericTypes =
|
||||
{
|
||||
typeof(byte), typeof(sbyte),
|
||||
typeof(short), typeof(ushort),
|
||||
typeof(int), typeof(uint),
|
||||
typeof(long), typeof(ulong)
|
||||
};
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Props", AccessLevel.Counselor, Props_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Props [serial]")]
|
||||
[Description("Opens a menu where you can view and edit all properties of a targeted (or specified) object.")]
|
||||
private static void Props_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length == 1)
|
||||
{
|
||||
IEntity ent = World.FindEntity(e.GetUInt32(0));
|
||||
|
||||
if (ent == null)
|
||||
e.Mobile.SendMessage("No object with that serial was found.");
|
||||
else if (!BaseCommand.IsAccessible(e.Mobile, ent))
|
||||
e.Mobile.SendLocalizedMessage(500447); // That is not accessible.
|
||||
else
|
||||
e.Mobile.SendGump(new PropertiesGump(e.Mobile, ent));
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Mobile.Target = new PropsTarget();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CIEqual(string l, string r) => Insensitive.Equals(l, r);
|
||||
|
||||
public static CPA GetCPA(PropertyInfo p)
|
||||
{
|
||||
object[] attrs = p.GetCustomAttributes(typeofCPA, false);
|
||||
|
||||
if (attrs.Length == 0)
|
||||
return null;
|
||||
|
||||
return attrs[0] as CPA;
|
||||
}
|
||||
|
||||
public static PropertyInfo[] GetPropertyInfoChain(Mobile from, Type type, string propertyString,
|
||||
PropertyAccess endAccess, ref string failReason)
|
||||
{
|
||||
string[] split = propertyString.Split('.');
|
||||
|
||||
if (split.Length == 0)
|
||||
return null;
|
||||
|
||||
PropertyInfo[] info = new PropertyInfo[split.Length];
|
||||
|
||||
for (int i = 0; i < info.Length; ++i)
|
||||
{
|
||||
string propertyName = split[i];
|
||||
|
||||
if (CIEqual(propertyName, "current"))
|
||||
continue;
|
||||
|
||||
PropertyInfo[] props = type.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
bool isFinal = i == info.Length - 1;
|
||||
|
||||
PropertyAccess access = endAccess;
|
||||
|
||||
if (!isFinal)
|
||||
access |= PropertyAccess.Read;
|
||||
|
||||
for (int j = 0; j < props.Length; ++j)
|
||||
{
|
||||
PropertyInfo p = props[j];
|
||||
|
||||
if (CIEqual(p.Name, propertyName))
|
||||
{
|
||||
CPA attr = GetCPA(p);
|
||||
|
||||
if (attr == null)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' not found.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < attr.ReadLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.ReadLevel)} to get the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && from.AccessLevel < attr.WriteLevel)
|
||||
{
|
||||
failReason =
|
||||
$"You must be at least {Mobile.GetAccessLevelName(attr.WriteLevel)} to set the property '{propertyName}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && !p.CanRead)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is write only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && (!p.CanWrite || attr.ReadOnly) && isFinal)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' is read only.";
|
||||
return null;
|
||||
}
|
||||
|
||||
info[i] = p;
|
||||
type = p.PropertyType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (info[i] == null)
|
||||
{
|
||||
failReason = $"Property '{propertyName}' not found.";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static PropertyInfo GetPropertyInfo(Mobile from, ref object obj, string propertyName, PropertyAccess access,
|
||||
ref string failReason)
|
||||
{
|
||||
PropertyInfo[] chain = GetPropertyInfoChain(from, obj.GetType(), propertyName, access, ref failReason);
|
||||
|
||||
return chain == null ? null : GetPropertyInfo(ref obj, chain, ref failReason);
|
||||
}
|
||||
|
||||
public static PropertyInfo GetPropertyInfo(ref object obj, PropertyInfo[] chain, ref string failReason)
|
||||
{
|
||||
if (chain == null || chain.Length == 0)
|
||||
{
|
||||
failReason = "Property chain is empty.";
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chain.Length - 1; ++i)
|
||||
{
|
||||
if (chain[i] == null)
|
||||
continue;
|
||||
|
||||
obj = chain[i].GetValue(obj, null);
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
failReason = $"Property '{chain[i]}' is null.";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return chain[chain.Length - 1];
|
||||
}
|
||||
|
||||
public static string GetValue(Mobile from, object o, string name)
|
||||
{
|
||||
string failReason = "";
|
||||
|
||||
PropertyInfo[] chain = GetPropertyInfoChain(from, o.GetType(), name, PropertyAccess.Read, ref failReason);
|
||||
|
||||
if (chain == null || chain.Length == 0)
|
||||
return failReason;
|
||||
|
||||
PropertyInfo p = GetPropertyInfo(ref o, chain, ref failReason);
|
||||
|
||||
return p == null ? failReason : InternalGetValue(o, p, chain);
|
||||
}
|
||||
|
||||
public static string IncreaseValue(Mobile from, object o, string[] args)
|
||||
{
|
||||
// Type type = o.GetType();
|
||||
|
||||
object[] realObjs = new object[args.Length / 2];
|
||||
PropertyInfo[] realProps = new PropertyInfo[args.Length / 2];
|
||||
int[] realValues = new int[args.Length / 2];
|
||||
|
||||
bool positive = false;
|
||||
bool negative = false;
|
||||
|
||||
for (int i = 0; i < realProps.Length; ++i)
|
||||
{
|
||||
string name = args[i * 2];
|
||||
|
||||
try
|
||||
{
|
||||
string valueString = args[1 + i * 2];
|
||||
|
||||
if (valueString.StartsWith("0x"))
|
||||
realValues[i] = Convert.ToInt32(valueString.Substring(2), 16);
|
||||
else
|
||||
realValues[i] = Convert.ToInt32(valueString);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Offset value could not be parsed.";
|
||||
}
|
||||
|
||||
if (realValues[i] > 0)
|
||||
positive = true;
|
||||
else if (realValues[i] < 0)
|
||||
negative = true;
|
||||
else
|
||||
return "Zero is not a valid value to offset.";
|
||||
|
||||
string failReason = null;
|
||||
realObjs[i] = o;
|
||||
realProps[i] = GetPropertyInfo(from, ref realObjs[i], name, PropertyAccess.ReadWrite, ref failReason);
|
||||
|
||||
if (failReason != null)
|
||||
return failReason;
|
||||
|
||||
if (realProps[i] == null)
|
||||
return "Property not found.";
|
||||
}
|
||||
|
||||
for (int i = 0; i < realProps.Length; ++i)
|
||||
{
|
||||
object obj = realProps[i].GetValue(realObjs[i], null);
|
||||
|
||||
if (!(obj is IConvertible))
|
||||
return "Property is not IConvertable.";
|
||||
|
||||
try
|
||||
{
|
||||
long v = (long)Convert.ChangeType(obj, TypeCode.Int64);
|
||||
v += realValues[i];
|
||||
|
||||
realProps[i].SetValue(realObjs[i], Convert.ChangeType(v, realProps[i].PropertyType), null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Value could not be converted";
|
||||
}
|
||||
}
|
||||
|
||||
if (realProps.Length == 1)
|
||||
{
|
||||
if (positive)
|
||||
return "The property has been increased.";
|
||||
|
||||
return "The property has been decreased.";
|
||||
}
|
||||
|
||||
if (positive && negative)
|
||||
return "The properties have been changed.";
|
||||
|
||||
if (positive)
|
||||
return "The properties have been increased.";
|
||||
|
||||
return "The properties have been decreased.";
|
||||
}
|
||||
|
||||
private static string InternalGetValue(object o, PropertyInfo p, PropertyInfo[] chain = null)
|
||||
{
|
||||
Type type = p.PropertyType;
|
||||
|
||||
object value = p.GetValue(o, null);
|
||||
string toString;
|
||||
|
||||
if (value == null)
|
||||
toString = "null";
|
||||
else if (IsNumeric(type))
|
||||
toString = $"{value} (0x{value:X})";
|
||||
else if (IsChar(type))
|
||||
toString = $"'{value}' ({(int)value} [0x{(int)value:X}])";
|
||||
else if (IsString(type))
|
||||
toString = (string)value == "null" ? @"@""null""" : $"\"{value}\"";
|
||||
else if (IsText(type))
|
||||
toString = ((TextDefinition)value).Format(false);
|
||||
else
|
||||
toString = value.ToString();
|
||||
|
||||
if (chain == null)
|
||||
return $"{p.Name} = {toString}";
|
||||
|
||||
string[] concat = new string[chain.Length * 2 + 1];
|
||||
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
{
|
||||
concat[i * 2 + 0] = chain[i].Name;
|
||||
concat[i * 2 + 1] = i < chain.Length - 1 ? "." : " = ";
|
||||
}
|
||||
|
||||
concat[concat.Length - 1] = toString;
|
||||
|
||||
return string.Concat(concat);
|
||||
}
|
||||
|
||||
public static string SetValue(Mobile from, object o, string name, string value)
|
||||
{
|
||||
object logObject = o;
|
||||
|
||||
string failReason = "";
|
||||
PropertyInfo p = GetPropertyInfo(from, ref o, name, PropertyAccess.Write, ref failReason);
|
||||
|
||||
return p == null ? failReason : InternalSetValue(from, logObject, o, p, name, value, true);
|
||||
}
|
||||
|
||||
private static bool IsSerial(Type t) => t == typeofSerial;
|
||||
|
||||
private static bool IsType(Type t) => t == typeofType;
|
||||
|
||||
private static bool IsChar(Type t) => t == typeofChar;
|
||||
|
||||
private static bool IsString(Type t) => t == typeofString;
|
||||
|
||||
private static bool IsText(Type t) => t == typeofText;
|
||||
|
||||
private static bool IsEnum(Type t) => t.IsEnum;
|
||||
|
||||
private static bool IsParsable(Type t) => t == typeofTimeSpan || t.IsDefined(typeofParsable, false);
|
||||
|
||||
private static object Parse(object o, Type t, string value)
|
||||
{
|
||||
MethodInfo method = t.GetMethod("Parse", m_ParseTypes);
|
||||
|
||||
m_ParseParams[0] = value;
|
||||
|
||||
return method?.Invoke(o, m_ParseParams);
|
||||
}
|
||||
|
||||
private static bool IsNumeric(Type t) => Array.IndexOf(m_NumericTypes, t) >= 0;
|
||||
|
||||
public static string ConstructFromString(Type type, object obj, string value, ref object constructed)
|
||||
{
|
||||
object toSet;
|
||||
bool isSerial = IsSerial(type);
|
||||
|
||||
if (isSerial) // mutate into int32
|
||||
type = m_NumericTypes[4];
|
||||
|
||||
if (value == "(-null-)" && !type.IsValueType)
|
||||
value = null;
|
||||
|
||||
if (IsEnum(type))
|
||||
try
|
||||
{
|
||||
toSet = Enum.Parse(type, value ?? "", true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "That is not a valid enumeration member.";
|
||||
}
|
||||
else if (IsType(type))
|
||||
try
|
||||
{
|
||||
toSet = AssemblyHandler.FindFirstTypeForName(value);
|
||||
|
||||
if (toSet == null)
|
||||
return "No type with that name was found.";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "No type with that name was found.";
|
||||
}
|
||||
else if (IsParsable(type))
|
||||
try
|
||||
{
|
||||
toSet = Parse(obj, type, value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "That is not properly formatted.";
|
||||
}
|
||||
else if (value == null)
|
||||
toSet = null;
|
||||
else if (value.StartsWith("0x") && IsNumeric(type))
|
||||
try
|
||||
{
|
||||
toSet = Convert.ChangeType(Convert.ToUInt64(value.Substring(2), 16), type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "That is not properly formatted.";
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
toSet = Convert.ChangeType(value, type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "That is not properly formatted.";
|
||||
}
|
||||
|
||||
if (isSerial) // mutate back
|
||||
toSet = (Serial)(toSet ?? Serial.MinusOne);
|
||||
|
||||
constructed = toSet;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string SetDirect(Mobile from, object logObject, object obj, PropertyInfo prop, string givenName,
|
||||
object toSet, bool shouldLog)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (toSet is AccessLevel newLevel)
|
||||
{
|
||||
AccessLevel reqLevel = AccessLevel.Administrator;
|
||||
|
||||
if (newLevel == AccessLevel.Administrator)
|
||||
reqLevel = AccessLevel.Developer;
|
||||
else if (newLevel >= AccessLevel.Developer)
|
||||
reqLevel = AccessLevel.Owner;
|
||||
|
||||
if (from.AccessLevel < reqLevel)
|
||||
return "You do not have access to that level.";
|
||||
}
|
||||
|
||||
if (shouldLog)
|
||||
CommandLogging.LogChangeProperty(from, logObject, givenName,
|
||||
toSet?.ToString() ?? "(-null-)");
|
||||
|
||||
prop.SetValue(obj, toSet, null);
|
||||
return "Property has been set.";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "An exception was caught, the property may not be set.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string SetDirect(object obj, PropertyInfo prop, object toSet)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (toSet is AccessLevel) return "You do not have access to that level.";
|
||||
|
||||
prop.SetValue(obj, toSet, null);
|
||||
return "Property has been set.";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "An exception was caught, the property may not be set.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string InternalSetValue(Mobile from, object logobj, object o, PropertyInfo p, string pname,
|
||||
string value, bool shouldLog)
|
||||
{
|
||||
object toSet = null;
|
||||
string result = ConstructFromString(p.PropertyType, o, value, ref toSet);
|
||||
|
||||
return result ?? SetDirect(from, logobj, o, p, pname, toSet, shouldLog);
|
||||
}
|
||||
|
||||
public static string InternalSetValue(object o, PropertyInfo p, string value)
|
||||
{
|
||||
object toSet = null;
|
||||
string result = ConstructFromString(p.PropertyType, o, value, ref toSet);
|
||||
|
||||
return result ?? SetDirect(o, p, toSet);
|
||||
}
|
||||
|
||||
private class PropsTarget : Target
|
||||
{
|
||||
public PropsTarget() : base(-1, true, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object o)
|
||||
{
|
||||
if (!BaseCommand.IsAccessible(from, o))
|
||||
from.SendLocalizedMessage(500447); // That is not accessible.
|
||||
else
|
||||
from.SendGump(new PropertiesGump(from, o));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public abstract class PropertyException : ApplicationException
|
||||
{
|
||||
protected Property m_Property;
|
||||
|
||||
public PropertyException(Property property, string message)
|
||||
: base(message) =>
|
||||
m_Property = property;
|
||||
|
||||
public Property Property => m_Property;
|
||||
}
|
||||
|
||||
public abstract class BindingException : PropertyException
|
||||
{
|
||||
public BindingException(Property property, string message)
|
||||
: base(property, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NotYetBoundException : BindingException
|
||||
{
|
||||
public NotYetBoundException(Property property)
|
||||
: base(property, "Property has not yet been bound.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class AlreadyBoundException : BindingException
|
||||
{
|
||||
public AlreadyBoundException(Property property)
|
||||
: base(property, "Property has already been bound.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UnknownPropertyException : BindingException
|
||||
{
|
||||
public UnknownPropertyException(Property property, string current)
|
||||
: base(property, $"Property '{current}' not found.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ReadOnlyException : BindingException
|
||||
{
|
||||
public ReadOnlyException(Property property)
|
||||
: base(property, "Property is read-only.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WriteOnlyException : BindingException
|
||||
{
|
||||
public WriteOnlyException(Property property)
|
||||
: base(property, "Property is write-only.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AccessException : PropertyException
|
||||
{
|
||||
public AccessException(Property property, string message)
|
||||
: base(property, message)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InternalAccessException : AccessException
|
||||
{
|
||||
public InternalAccessException(Property property)
|
||||
: base(property, "Property is internal.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ClearanceException : AccessException
|
||||
{
|
||||
public ClearanceException(Property property, AccessLevel playerAccess, AccessLevel neededAccess, string accessType)
|
||||
: base(property,
|
||||
$"You must be at least {Mobile.GetAccessLevelName(neededAccess)} to {accessType} this property.")
|
||||
{
|
||||
}
|
||||
|
||||
public AccessLevel PlayerAccess { get; set; }
|
||||
public AccessLevel NeededAccess { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ReadAccessException : ClearanceException
|
||||
{
|
||||
public ReadAccessException(Property property, AccessLevel playerAccess, AccessLevel neededAccess)
|
||||
: base(property, playerAccess, neededAccess, "read")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WriteAccessException : ClearanceException
|
||||
{
|
||||
public WriteAccessException(Property property, AccessLevel playerAccess, AccessLevel neededAccess)
|
||||
: base(property, playerAccess, neededAccess, "write")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Property
|
||||
{
|
||||
private PropertyInfo[] m_Chain;
|
||||
|
||||
public Property(string binding) => Binding = binding;
|
||||
|
||||
public Property(PropertyInfo[] chain) => m_Chain = chain;
|
||||
|
||||
public string Binding { get; }
|
||||
|
||||
public bool IsBound => m_Chain != null;
|
||||
|
||||
public PropertyAccess Access { get; private set; }
|
||||
|
||||
public PropertyInfo[] Chain
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsBound)
|
||||
throw new NotYetBoundException(this);
|
||||
|
||||
return m_Chain;
|
||||
}
|
||||
}
|
||||
|
||||
public Type Type
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsBound)
|
||||
throw new NotYetBoundException(this);
|
||||
|
||||
return m_Chain[m_Chain.Length - 1].PropertyType;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckAccess(Mobile from)
|
||||
{
|
||||
if (!IsBound)
|
||||
throw new NotYetBoundException(this);
|
||||
|
||||
for (int i = 0; i < m_Chain.Length; ++i)
|
||||
{
|
||||
PropertyInfo prop = m_Chain[i];
|
||||
|
||||
bool isFinal = i == m_Chain.Length - 1;
|
||||
|
||||
PropertyAccess access = Access;
|
||||
|
||||
if (!isFinal)
|
||||
access |= PropertyAccess.Read;
|
||||
|
||||
CPA security = Properties.GetCPA(prop);
|
||||
|
||||
if (security == null)
|
||||
throw new InternalAccessException(this);
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && from.AccessLevel < security.ReadLevel)
|
||||
throw new ReadAccessException(this, from.AccessLevel, security.ReadLevel);
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && (from.AccessLevel < security.WriteLevel || security.ReadOnly))
|
||||
throw new WriteAccessException(this, from.AccessLevel, security.ReadLevel);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void BindTo(Type objectType, PropertyAccess desiredAccess)
|
||||
{
|
||||
if (IsBound)
|
||||
throw new AlreadyBoundException(this);
|
||||
|
||||
string[] split = Binding.Split('.');
|
||||
|
||||
PropertyInfo[] chain = new PropertyInfo[split.Length];
|
||||
|
||||
for (int i = 0; i < split.Length; ++i)
|
||||
{
|
||||
bool isFinal = i == chain.Length - 1;
|
||||
|
||||
chain[i] = objectType.GetProperty(split[i],
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
|
||||
|
||||
if (chain[i] == null)
|
||||
throw new UnknownPropertyException(this, split[i]);
|
||||
|
||||
objectType = chain[i].PropertyType;
|
||||
|
||||
PropertyAccess access = desiredAccess;
|
||||
|
||||
if (!isFinal)
|
||||
access |= PropertyAccess.Read;
|
||||
|
||||
if ((access & PropertyAccess.Read) != 0 && !chain[i].CanRead)
|
||||
throw new WriteOnlyException(this);
|
||||
|
||||
if ((access & PropertyAccess.Write) != 0 && !chain[i].CanWrite)
|
||||
throw new ReadOnlyException(this);
|
||||
}
|
||||
|
||||
Access = desiredAccess;
|
||||
m_Chain = chain;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (!IsBound)
|
||||
return Binding;
|
||||
|
||||
string[] toJoin = new string[m_Chain.Length];
|
||||
|
||||
for (int i = 0; i < toJoin.Length; ++i)
|
||||
toJoin[i] = m_Chain[i].Name;
|
||||
|
||||
return string.Join(".", toJoin);
|
||||
}
|
||||
|
||||
public static Property Parse(Type type, string binding, PropertyAccess access)
|
||||
{
|
||||
Property prop = new Property(binding);
|
||||
|
||||
prop.BindTo(type, access);
|
||||
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Projects/UOContent/Commands/ShardTime.cs
Normal file
19
Projects/UOContent/Commands/ShardTime.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class ShardTime
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Time", AccessLevel.Player, Time_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Time")]
|
||||
[Description("Returns the server's local time.")]
|
||||
private static void Time_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.SendMessage(DateTime.UtcNow.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Projects/UOContent/Commands/SignParser.cs
Normal file
136
Projects/UOContent/Commands/SignParser.cs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class SignParser
|
||||
{
|
||||
private static readonly Queue<Item> m_ToDelete = new Queue<Item>();
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("SignGen", AccessLevel.Administrator, SignGen_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("SignGen")]
|
||||
[Description("Generates world/shop signs on all facets.")]
|
||||
public static void SignGen_OnCommand(CommandEventArgs c)
|
||||
{
|
||||
Parse(c.Mobile);
|
||||
}
|
||||
|
||||
public static void Parse(Mobile from)
|
||||
{
|
||||
string cfg = Path.Combine(Core.BaseDirectory, "Data/signs.cfg");
|
||||
|
||||
if (File.Exists(cfg))
|
||||
{
|
||||
List<SignEntry> list = new List<SignEntry>();
|
||||
from.SendMessage("Generating signs, please wait.");
|
||||
|
||||
using (StreamReader ip = new StreamReader(cfg))
|
||||
{
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
string[] split = line.Split(' ');
|
||||
|
||||
SignEntry e = new SignEntry(
|
||||
line.Substring(split[0].Length + 1 + split[1].Length + 1 + split[2].Length + 1 +
|
||||
split[3].Length + 1 + split[4].Length + 1),
|
||||
new Point3D(Utility.ToInt32(split[2]), Utility.ToInt32(split[3]), Utility.ToInt32(split[4])),
|
||||
Utility.ToInt32(split[1]), Utility.ToInt32(split[0]));
|
||||
|
||||
list.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
Map[] brit = { Map.Felucca, Map.Trammel };
|
||||
Map[] fel = { Map.Felucca };
|
||||
Map[] tram = { Map.Trammel };
|
||||
Map[] ilsh = { Map.Ilshenar };
|
||||
Map[] malas = { Map.Malas };
|
||||
Map[] tokuno = { Map.Tokuno };
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
SignEntry e = list[i];
|
||||
|
||||
var maps = e.m_Map switch
|
||||
{
|
||||
0 => brit,
|
||||
1 => fel,
|
||||
2 => tram,
|
||||
3 => ilsh,
|
||||
4 => malas,
|
||||
5 => tokuno,
|
||||
_ => null
|
||||
};
|
||||
|
||||
for (int j = 0; maps?.Length > j; ++j)
|
||||
Add_Static(e.m_ItemID, e.m_Location, maps[j], e.m_Text);
|
||||
}
|
||||
|
||||
from.SendMessage("Sign generating complete.");
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("{0} not found!", cfg);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Add_Static(int itemID, Point3D location, Map map, string name)
|
||||
{
|
||||
IPooledEnumerable<Item> eable = map.GetItemsInRange(location, 0);
|
||||
|
||||
foreach (Item item in eable)
|
||||
if (item is Sign && item.Z == location.Z && item.ItemID == itemID)
|
||||
m_ToDelete.Enqueue(item);
|
||||
|
||||
eable.Free();
|
||||
|
||||
while (m_ToDelete.Count > 0)
|
||||
m_ToDelete.Dequeue().Delete();
|
||||
|
||||
Item sign;
|
||||
|
||||
if (name.StartsWith("#"))
|
||||
{
|
||||
sign = new LocalizedSign(itemID, Utility.ToInt32(name.Substring(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
sign = new Sign(itemID);
|
||||
sign.Name = name;
|
||||
}
|
||||
|
||||
if (map == Map.Malas)
|
||||
{
|
||||
if (location.X >= 965 && location.Y >= 502 && location.X <= 1012 && location.Y <= 537)
|
||||
sign.Hue = 0x47E;
|
||||
else if (location.X >= 1960 && location.Y >= 1278 && location.X < 2106 && location.Y < 1413)
|
||||
sign.Hue = 0x44E;
|
||||
}
|
||||
|
||||
sign.MoveToWorld(location, map);
|
||||
}
|
||||
|
||||
private class SignEntry
|
||||
{
|
||||
public readonly int m_ItemID;
|
||||
public readonly Point3D m_Location;
|
||||
public readonly int m_Map;
|
||||
public readonly string m_Text;
|
||||
|
||||
public SignEntry(string text, Point3D pt, int itemID, int mapLoc)
|
||||
{
|
||||
m_Text = text;
|
||||
m_Location = pt;
|
||||
m_ItemID = itemID;
|
||||
m_Map = mapLoc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Projects/UOContent/Commands/Skills.cs
Normal file
126
Projects/UOContent/Commands/Skills.cs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class SkillsCommand
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("SetSkill", AccessLevel.GameMaster, SetSkill_OnCommand);
|
||||
CommandSystem.Register("GetSkill", AccessLevel.GameMaster, GetSkill_OnCommand);
|
||||
CommandSystem.Register("SetAllSkills", AccessLevel.GameMaster, SetAllSkills_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("SetSkill <name> <value>")]
|
||||
[Description("Sets a skill value by name of a targeted mobile.")]
|
||||
public static void SetSkill_OnCommand(CommandEventArgs arg)
|
||||
{
|
||||
if (arg.Length != 2)
|
||||
{
|
||||
arg.Mobile.SendMessage("SetSkill <skill name> <value>");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Enum.TryParse(arg.GetString(0), true, out SkillName skill))
|
||||
arg.Mobile.Target = new SkillTarget(skill, arg.GetDouble(1));
|
||||
else
|
||||
arg.Mobile.SendLocalizedMessage(1005631); // You have specified an invalid skill to set.
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("SetAllSkills <name> <value>")]
|
||||
[Description("Sets all skill values of a targeted mobile.")]
|
||||
public static void SetAllSkills_OnCommand(CommandEventArgs arg)
|
||||
{
|
||||
if (arg.Length != 1)
|
||||
arg.Mobile.SendMessage("SetAllSkills <value>");
|
||||
else
|
||||
arg.Mobile.Target = new AllSkillsTarget(arg.GetDouble(0));
|
||||
}
|
||||
|
||||
[Usage("GetSkill <name>")]
|
||||
[Description("Gets a skill value by name of a targeted mobile.")]
|
||||
public static void GetSkill_OnCommand(CommandEventArgs arg)
|
||||
{
|
||||
if (arg.Length != 1)
|
||||
{
|
||||
arg.Mobile.SendMessage("GetSkill <skill name>");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Enum.TryParse(arg.GetString(0), true, out SkillName skill))
|
||||
arg.Mobile.Target = new SkillTarget(skill);
|
||||
else
|
||||
arg.Mobile.SendMessage("You have specified an invalid skill to get.");
|
||||
}
|
||||
}
|
||||
|
||||
public class AllSkillsTarget : Target
|
||||
{
|
||||
private readonly double m_Value;
|
||||
|
||||
public AllSkillsTarget(double value) : base(-1, false, TargetFlags.None) => m_Value = value;
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile targ)
|
||||
{
|
||||
Server.Skills skills = targ.Skills;
|
||||
|
||||
for (int i = 0; i < skills.Length; ++i)
|
||||
skills[i].Base = m_Value;
|
||||
|
||||
CommandLogging.LogChangeProperty(from, targ, "EverySkill.Base", m_Value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("That does not have skills!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SkillTarget : Target
|
||||
{
|
||||
private readonly bool m_Set;
|
||||
private readonly SkillName m_Skill;
|
||||
private readonly double m_Value;
|
||||
|
||||
public SkillTarget(SkillName skill, double value) : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
m_Set = true;
|
||||
m_Skill = skill;
|
||||
m_Value = value;
|
||||
}
|
||||
|
||||
public SkillTarget(SkillName skill) : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
m_Set = false;
|
||||
m_Skill = skill;
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Mobile targ)
|
||||
{
|
||||
Skill skill = targ.Skills[m_Skill];
|
||||
|
||||
if (skill == null)
|
||||
return;
|
||||
|
||||
if (m_Set)
|
||||
{
|
||||
skill.Base = m_Value;
|
||||
CommandLogging.LogChangeProperty(from, targ, $"{m_Skill}.Base", m_Value.ToString());
|
||||
}
|
||||
|
||||
from.SendMessage("{0} : {1} (Base: {2})", m_Skill, skill.Value, skill.Base);
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("That does not have skills!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Projects/UOContent/Commands/SkillsMenu.cs
Normal file
38
Projects/UOContent/Commands/SkillsMenu.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using Server.Gumps;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Skills
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
Register();
|
||||
}
|
||||
|
||||
public static void Register()
|
||||
{
|
||||
CommandSystem.Register("Skills", AccessLevel.Counselor, Skills_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Skills")]
|
||||
[Description("Opens a menu where you can view or edit skills of a targeted mobile.")]
|
||||
private static void Skills_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
e.Mobile.Target = new SkillsTarget();
|
||||
}
|
||||
|
||||
private class SkillsTarget : Target
|
||||
{
|
||||
public SkillsTarget() : base(-1, true, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object o)
|
||||
{
|
||||
if (o is Mobile mobile)
|
||||
from.SendGump(new SkillsGump(from, mobile));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
579
Projects/UOContent/Commands/Statics.cs
Normal file
579
Projects/UOContent/Commands/Statics.cs
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Commands;
|
||||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public class Statics
|
||||
{
|
||||
private const string BaseFreezeWarning = "{0} " +
|
||||
"Those items <u>will be removed from the world</u> and placed into the server data files. " +
|
||||
"Other players <u>will not see the changes</u> unless you distribute your data files to them.<br><br>" +
|
||||
"This operation may not complete unless the server and client are using different data files. " +
|
||||
"If you receive a message stating 'output data files could not be opened,' then you are probably sharing data files. " +
|
||||
"Create a new directory for the world data files (statics*.mul and staidx*.mul) and add that to Scritps/Misc/DataPath.cs.<br><br>" +
|
||||
"The change will be in effect immediately on the server, however, you must restart your client and update it's data files for the changes to become visible. " +
|
||||
"It is strongly recommended that you make backup of the data files mentioned above. " +
|
||||
"Do you wish to proceed?";
|
||||
|
||||
private const string BaseUnfreezeWarning = "{0} " +
|
||||
"Those items <u>will be removed from the static files</u> and exchanged with unmovable dynamic items. " +
|
||||
"Other players <u>will not see the changes</u> unless you distribute your data files to them.<br><br>" +
|
||||
"This operation may not complete unless the server and client are using different data files. " +
|
||||
"If you receive a message stating 'output data files could not be opened,' then you are probably sharing data files. " +
|
||||
"Create a new directory for the world data files (statics*.mul and staidx*.mul) and add that to Scritps/Misc/DataPath.cs.<br><br>" +
|
||||
"The change will be in effect immediately on the server, however, you must restart your client and update it's data files for the changes to become visible. " +
|
||||
"It is strongly recommended that you make backup of the data files mentioned above. " +
|
||||
"Do you wish to proceed?";
|
||||
|
||||
private static readonly Point3D NullP3D = new Point3D(int.MinValue, int.MinValue, int.MinValue);
|
||||
|
||||
private static byte[] m_Buffer;
|
||||
|
||||
private static StaticTile[] m_TileBuffer = new StaticTile[128];
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Freeze", AccessLevel.Administrator, Freeze_OnCommand);
|
||||
CommandSystem.Register("FreezeMap", AccessLevel.Administrator, FreezeMap_OnCommand);
|
||||
CommandSystem.Register("FreezeWorld", AccessLevel.Administrator, FreezeWorld_OnCommand);
|
||||
|
||||
CommandSystem.Register("Unfreeze", AccessLevel.Administrator, Unfreeze_OnCommand);
|
||||
CommandSystem.Register("UnfreezeMap", AccessLevel.Administrator, UnfreezeMap_OnCommand);
|
||||
CommandSystem.Register("UnfreezeWorld", AccessLevel.Administrator, UnfreezeWorld_OnCommand);
|
||||
}
|
||||
|
||||
public delegate void FreezeCallback(Mobile from, bool okay, StateInfo si);
|
||||
|
||||
[Usage("Freeze")]
|
||||
[Description("Makes a targeted area of dynamic items static.")]
|
||||
public static void Freeze_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) => FreezeBox_Callback(from, map, start, end));
|
||||
}
|
||||
|
||||
[Usage("FreezeMap")]
|
||||
[Description("Makes every dynamic item in your map static.")]
|
||||
public static void FreezeMap_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
Map map = from.Map;
|
||||
|
||||
if (map != null && map != Map.Internal)
|
||||
SendWarning(from, "You are about to freeze <u>all items in {0}</u>.", BaseFreezeWarning, map, NullP3D,
|
||||
NullP3D, FreezeWarning_Callback);
|
||||
}
|
||||
|
||||
[Usage("FreezeWorld")]
|
||||
[Description("Makes every dynamic item on all maps static.")]
|
||||
public static void FreezeWorld_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
SendWarning(e.Mobile, "You are about to freeze <u>every item on every map</u>.", BaseFreezeWarning, null,
|
||||
NullP3D, NullP3D, FreezeWarning_Callback);
|
||||
}
|
||||
|
||||
public static void SendWarning(Mobile m, string header, string baseWarning, Map map, Point3D start, Point3D end,
|
||||
FreezeCallback callback)
|
||||
{
|
||||
m.SendGump(new WarningGump(1060635, 30720, string.Format(baseWarning, string.Format(header, map)), 0xFFC000, 420,
|
||||
400, okay => callback(m, okay, new StateInfo(map, start, end))));
|
||||
}
|
||||
|
||||
private static void FreezeBox_Callback(Mobile from, Map map, Point3D start, Point3D end)
|
||||
{
|
||||
SendWarning(from, "You are about to freeze a section of items.", BaseFreezeWarning, map, start, end,
|
||||
FreezeWarning_Callback);
|
||||
}
|
||||
|
||||
private static void FreezeWarning_Callback(Mobile from, bool okay, StateInfo si)
|
||||
{
|
||||
if (!okay)
|
||||
return;
|
||||
|
||||
Freeze(from, si.m_Map, si.m_Start, si.m_End);
|
||||
}
|
||||
|
||||
public static void Freeze(Mobile from, Map targetMap, Point3D start3d, Point3D end3d)
|
||||
{
|
||||
Dictionary<Map, Dictionary<Point2D, DeltaState>> mapTable = new Dictionary<Map, Dictionary<Point2D, DeltaState>>();
|
||||
|
||||
if (start3d == NullP3D && end3d == NullP3D)
|
||||
{
|
||||
if (targetMap == null)
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking freeze for every item in every map", from.AccessLevel,
|
||||
CommandLogging.Format(from));
|
||||
else
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking freeze for every item in {0}", from.AccessLevel,
|
||||
CommandLogging.Format(from), targetMap);
|
||||
|
||||
foreach (Item item in World.Items.Values)
|
||||
{
|
||||
if (targetMap != null && item.Map != targetMap)
|
||||
continue;
|
||||
|
||||
if (item.Parent != null)
|
||||
continue;
|
||||
|
||||
if (item is Static || item is BaseFloor || item is BaseWall)
|
||||
{
|
||||
Map itemMap = item.Map;
|
||||
|
||||
if (itemMap == null || itemMap == Map.Internal)
|
||||
continue;
|
||||
|
||||
if (!mapTable.TryGetValue(itemMap, out Dictionary<Point2D, DeltaState> table))
|
||||
mapTable[itemMap] = table = new Dictionary<Point2D, DeltaState>();
|
||||
|
||||
Point2D p = new Point2D(item.X >> 3, item.Y >> 3);
|
||||
|
||||
if (!table.TryGetValue(p, out DeltaState state))
|
||||
table[p] = state = new DeltaState(p);
|
||||
|
||||
state.m_List.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (targetMap != null)
|
||||
{
|
||||
Point2D start = targetMap.Bound(new Point2D(start3d)), end = targetMap.Bound(new Point2D(end3d));
|
||||
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking freeze from {2} to {3} in {4}", from.AccessLevel,
|
||||
CommandLogging.Format(from), start, end, targetMap);
|
||||
|
||||
IPooledEnumerable<Item> eable =
|
||||
targetMap.GetItemsInBounds(new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1));
|
||||
|
||||
foreach (Item item in eable)
|
||||
if (item is Static || item is BaseFloor || item is BaseWall)
|
||||
{
|
||||
Map itemMap = item.Map;
|
||||
|
||||
if (itemMap == null || itemMap == Map.Internal)
|
||||
continue;
|
||||
|
||||
if (!mapTable.TryGetValue(itemMap, out Dictionary<Point2D, DeltaState> table))
|
||||
mapTable[itemMap] = table = new Dictionary<Point2D, DeltaState>();
|
||||
|
||||
Point2D p = new Point2D(item.X >> 3, item.Y >> 3);
|
||||
|
||||
if (!table.TryGetValue(p, out DeltaState state))
|
||||
table[p] = state = new DeltaState(p);
|
||||
|
||||
state.m_List.Add(item);
|
||||
}
|
||||
|
||||
eable.Free();
|
||||
}
|
||||
|
||||
if (mapTable.Count == 0)
|
||||
{
|
||||
from.SendGump(new NoticeGump(1060637, 30720,
|
||||
"No freezable items were found. Only the following item types are frozen:<br> - Static<br> - BaseFloor<br> - BaseWall",
|
||||
0xFFC000, 320, 240));
|
||||
return;
|
||||
}
|
||||
|
||||
bool badDataFile = false;
|
||||
|
||||
int totalFrozen = 0;
|
||||
|
||||
foreach (KeyValuePair<Map, Dictionary<Point2D, DeltaState>> de in mapTable)
|
||||
{
|
||||
Map map = de.Key;
|
||||
Dictionary<Point2D, DeltaState> table = de.Value;
|
||||
|
||||
TileMatrix matrix = map.Tiles;
|
||||
|
||||
using FileStream idxStream = OpenWrite(matrix.IndexStream);
|
||||
using FileStream mulStream = OpenWrite(matrix.DataStream);
|
||||
if (idxStream == null || mulStream == null)
|
||||
{
|
||||
badDataFile = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
BinaryReader idxReader = new BinaryReader(idxStream);
|
||||
|
||||
BinaryWriter idxWriter = new BinaryWriter(idxStream);
|
||||
BinaryWriter mulWriter = new BinaryWriter(mulStream);
|
||||
|
||||
foreach (DeltaState state in table.Values)
|
||||
{
|
||||
StaticTile[] oldTiles = ReadStaticBlock(idxReader, mulStream, state.m_X, state.m_Y,
|
||||
matrix.BlockWidth, matrix.BlockHeight, out int oldTileCount);
|
||||
|
||||
if (oldTileCount < 0)
|
||||
continue;
|
||||
|
||||
int newTileCount = 0;
|
||||
StaticTile[] newTiles = new StaticTile[state.m_List.Count];
|
||||
|
||||
for (int i = 0; i < state.m_List.Count; ++i)
|
||||
{
|
||||
Item item = state.m_List[i];
|
||||
|
||||
int xOffset = item.X - state.m_X * 8;
|
||||
int yOffset = item.Y - state.m_Y * 8;
|
||||
|
||||
if (xOffset < 0 || xOffset >= 8 || yOffset < 0 || yOffset >= 8)
|
||||
continue;
|
||||
|
||||
StaticTile newTile = new StaticTile((ushort)item.ItemID, (byte)xOffset, (byte)yOffset,
|
||||
(sbyte)item.Z, (short)item.Hue);
|
||||
|
||||
newTiles[newTileCount++] = newTile;
|
||||
|
||||
item.Delete();
|
||||
|
||||
++totalFrozen;
|
||||
}
|
||||
|
||||
int mulPos = -1;
|
||||
int length = -1;
|
||||
int extra = 0;
|
||||
|
||||
if (oldTileCount + newTileCount > 0)
|
||||
{
|
||||
mulWriter.Seek(0, SeekOrigin.End);
|
||||
|
||||
mulPos = (int)mulWriter.BaseStream.Position;
|
||||
length = (oldTileCount + newTileCount) * 7;
|
||||
extra = 1;
|
||||
|
||||
for (int i = 0; i < oldTileCount; ++i)
|
||||
{
|
||||
StaticTile toWrite = oldTiles[i];
|
||||
|
||||
mulWriter.Write((ushort)toWrite.ID);
|
||||
mulWriter.Write((byte)toWrite.X);
|
||||
mulWriter.Write((byte)toWrite.Y);
|
||||
mulWriter.Write((sbyte)toWrite.Z);
|
||||
mulWriter.Write((short)toWrite.Hue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < newTileCount; ++i)
|
||||
{
|
||||
StaticTile toWrite = newTiles[i];
|
||||
|
||||
mulWriter.Write((ushort)toWrite.ID);
|
||||
mulWriter.Write((byte)toWrite.X);
|
||||
mulWriter.Write((byte)toWrite.Y);
|
||||
mulWriter.Write((sbyte)toWrite.Z);
|
||||
mulWriter.Write((short)toWrite.Hue);
|
||||
}
|
||||
|
||||
mulWriter.Flush();
|
||||
}
|
||||
|
||||
int idxPos = (state.m_X * matrix.BlockHeight + state.m_Y) * 12;
|
||||
|
||||
idxWriter.Seek(idxPos, SeekOrigin.Begin);
|
||||
idxWriter.Write(mulPos);
|
||||
idxWriter.Write(length);
|
||||
idxWriter.Write(extra);
|
||||
|
||||
idxWriter.Flush();
|
||||
|
||||
matrix.SetStaticBlock(state.m_X, state.m_Y, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (totalFrozen == 0 && badDataFile)
|
||||
from.SendGump(new NoticeGump(1060637, 30720,
|
||||
"Output data files could not be opened and the freeze operation has been aborted.<br><br>This probably means your server and client are using the same data files. Instructions on how to resolve this can be found in the first warning window.",
|
||||
0xFFC000, 320, 240));
|
||||
else
|
||||
from.SendGump(new NoticeGump(1060637, 30720,
|
||||
$"Freeze operation completed successfully.<br><br>{totalFrozen} item{(totalFrozen != 1 ? "s were" : " was")} frozen.<br><br>You must restart your client and update it's data files to see the changes.",
|
||||
0xFFC000, 320, 240));
|
||||
}
|
||||
|
||||
[Usage("Unfreeze")]
|
||||
[Description("Makes a targeted area of static items dynamic.")]
|
||||
public static void Unfreeze_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Mobile from = e.Mobile;
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) => UnfreezeBox_Callback(from, map, start, end));
|
||||
}
|
||||
|
||||
[Usage("UnfreezeMap")]
|
||||
[Description("Makes every static item in your map dynamic.")]
|
||||
public static void UnfreezeMap_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
Map map = e.Mobile.Map;
|
||||
|
||||
if (map != null && map != Map.Internal)
|
||||
SendWarning(e.Mobile, "You are about to unfreeze <u>all items in {0}</u>.", BaseUnfreezeWarning, map,
|
||||
NullP3D, NullP3D, UnfreezeWarning_Callback);
|
||||
}
|
||||
|
||||
[Usage("UnfreezeWorld")]
|
||||
[Description("Makes every static item on all maps dynamic.")]
|
||||
public static void UnfreezeWorld_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
SendWarning(e.Mobile, "You are about to unfreeze <u>every item on every map</u>.", BaseUnfreezeWarning, null,
|
||||
NullP3D, NullP3D, UnfreezeWarning_Callback);
|
||||
}
|
||||
|
||||
private static void UnfreezeBox_Callback(Mobile from, Map map, Point3D start, Point3D end)
|
||||
{
|
||||
SendWarning(from, "You are about to unfreeze a section of items.", BaseUnfreezeWarning, map, start, end,
|
||||
UnfreezeWarning_Callback);
|
||||
}
|
||||
|
||||
private static void UnfreezeWarning_Callback(Mobile from, bool okay, StateInfo si)
|
||||
{
|
||||
if (!okay)
|
||||
return;
|
||||
|
||||
Unfreeze(from, si.m_Map, si.m_Start, si.m_End);
|
||||
}
|
||||
|
||||
private static void DoUnfreeze(Map map, Point2D start, Point2D end, ref bool badDataFile, ref int totalUnfrozen)
|
||||
{
|
||||
start = map.Bound(start);
|
||||
end = map.Bound(end);
|
||||
|
||||
int xStartBlock = start.X >> 3;
|
||||
int yStartBlock = start.Y >> 3;
|
||||
int xEndBlock = end.X >> 3;
|
||||
int yEndBlock = end.Y >> 3;
|
||||
|
||||
int xTileStart = start.X, yTileStart = start.Y;
|
||||
int xTileWidth = end.X - start.X + 1, yTileHeight = end.Y - start.Y + 1;
|
||||
|
||||
TileMatrix matrix = map.Tiles;
|
||||
|
||||
using FileStream idxStream = OpenWrite(matrix.IndexStream);
|
||||
using FileStream mulStream = OpenWrite(matrix.DataStream);
|
||||
if (idxStream == null || mulStream == null)
|
||||
{
|
||||
badDataFile = true;
|
||||
return;
|
||||
}
|
||||
|
||||
BinaryReader idxReader = new BinaryReader(idxStream);
|
||||
|
||||
BinaryWriter idxWriter = new BinaryWriter(idxStream);
|
||||
BinaryWriter mulWriter = new BinaryWriter(mulStream);
|
||||
|
||||
for (int x = xStartBlock; x <= xEndBlock; ++x)
|
||||
for (int y = yStartBlock; y <= yEndBlock; ++y)
|
||||
{
|
||||
StaticTile[] oldTiles = ReadStaticBlock(idxReader, mulStream, x, y, matrix.BlockWidth,
|
||||
matrix.BlockHeight, out int oldTileCount);
|
||||
|
||||
if (oldTileCount < 0)
|
||||
continue;
|
||||
|
||||
int newTileCount = 0;
|
||||
StaticTile[] newTiles = new StaticTile[oldTileCount];
|
||||
|
||||
int baseX = (x << 3) - xTileStart, baseY = (y << 3) - yTileStart;
|
||||
|
||||
for (int i = 0; i < oldTileCount; ++i)
|
||||
{
|
||||
StaticTile oldTile = oldTiles[i];
|
||||
|
||||
int px = baseX + oldTile.X;
|
||||
int py = baseY + oldTile.Y;
|
||||
|
||||
if (px < 0 || px >= xTileWidth || py < 0 || py >= yTileHeight)
|
||||
{
|
||||
newTiles[newTileCount++] = oldTile;
|
||||
}
|
||||
else
|
||||
{
|
||||
++totalUnfrozen;
|
||||
|
||||
Item item = new Static(oldTile.ID);
|
||||
|
||||
item.Hue = oldTile.Hue;
|
||||
|
||||
item.MoveToWorld(new Point3D(px + xTileStart, py + yTileStart, oldTile.Z), map);
|
||||
}
|
||||
}
|
||||
|
||||
int mulPos = -1;
|
||||
int length = -1;
|
||||
int extra = 0;
|
||||
|
||||
if (newTileCount > 0)
|
||||
{
|
||||
mulWriter.Seek(0, SeekOrigin.End);
|
||||
|
||||
mulPos = (int)mulWriter.BaseStream.Position;
|
||||
length = newTileCount * 7;
|
||||
extra = 1;
|
||||
|
||||
for (int i = 0; i < newTileCount; ++i)
|
||||
{
|
||||
StaticTile toWrite = newTiles[i];
|
||||
|
||||
mulWriter.Write((ushort)toWrite.ID);
|
||||
mulWriter.Write((byte)toWrite.X);
|
||||
mulWriter.Write((byte)toWrite.Y);
|
||||
mulWriter.Write((sbyte)toWrite.Z);
|
||||
mulWriter.Write((short)toWrite.Hue);
|
||||
}
|
||||
|
||||
mulWriter.Flush();
|
||||
}
|
||||
|
||||
int idxPos = (x * matrix.BlockHeight + y) * 12;
|
||||
|
||||
idxWriter.Seek(idxPos, SeekOrigin.Begin);
|
||||
idxWriter.Write(mulPos);
|
||||
idxWriter.Write(length);
|
||||
idxWriter.Write(extra);
|
||||
|
||||
idxWriter.Flush();
|
||||
|
||||
matrix.SetStaticBlock(x, y, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DoUnfreeze(Map map, ref bool badDataFile, ref int totalUnfrozen)
|
||||
{
|
||||
DoUnfreeze(map, Point2D.Zero, new Point2D(map.Width - 1, map.Height - 1), ref badDataFile, ref totalUnfrozen);
|
||||
}
|
||||
|
||||
public static void Unfreeze(Mobile from, Map map, Point3D start, Point3D end)
|
||||
{
|
||||
int totalUnfrozen = 0;
|
||||
bool badDataFile = false;
|
||||
|
||||
if (map == null)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze for every item in every map", from.AccessLevel,
|
||||
CommandLogging.Format(from));
|
||||
|
||||
DoUnfreeze(Map.Felucca, ref badDataFile, ref totalUnfrozen);
|
||||
DoUnfreeze(Map.Trammel, ref badDataFile, ref totalUnfrozen);
|
||||
DoUnfreeze(Map.Ilshenar, ref badDataFile, ref totalUnfrozen);
|
||||
DoUnfreeze(Map.Malas, ref badDataFile, ref totalUnfrozen);
|
||||
DoUnfreeze(Map.Tokuno, ref badDataFile, ref totalUnfrozen);
|
||||
}
|
||||
else if (start == NullP3D && end == NullP3D)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze for every item in {2}", from.AccessLevel,
|
||||
CommandLogging.Format(from), map);
|
||||
|
||||
DoUnfreeze(map, ref badDataFile, ref totalUnfrozen);
|
||||
}
|
||||
else
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} invoking unfreeze from {2} to {3} in {4}", from.AccessLevel,
|
||||
CommandLogging.Format(from), new Point2D(start), new Point2D(end), map);
|
||||
|
||||
DoUnfreeze(map, new Point2D(start), new Point2D(end), ref badDataFile, ref totalUnfrozen);
|
||||
}
|
||||
|
||||
if (totalUnfrozen == 0 && badDataFile)
|
||||
from.SendGump(new NoticeGump(1060637, 30720,
|
||||
"Output data files could not be opened and the unfreeze operation has been aborted.<br><br>This probably means your server and client are using the same data files. Instructions on how to resolve this can be found in the first warning window.",
|
||||
0xFFC000, 320, 240));
|
||||
else
|
||||
from.SendGump(new NoticeGump(1060637, 30720,
|
||||
$"Unfreeze operation completed successfully.<br><br>{totalUnfrozen} item{(totalUnfrozen != 1 ? "s were" : " was")} unfrozen.<br><br>You must restart your client and update it's data files to see the changes.",
|
||||
0xFFC000, 320, 240));
|
||||
}
|
||||
|
||||
private static FileStream OpenWrite(FileStream orig)
|
||||
{
|
||||
if (orig == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return new FileStream(orig.Name, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static StaticTile[] ReadStaticBlock(BinaryReader idxReader, FileStream mulStream, int x, int y, int width,
|
||||
int height, out int count)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||
{
|
||||
count = -1;
|
||||
return m_TileBuffer;
|
||||
}
|
||||
|
||||
idxReader.BaseStream.Seek((x * height + y) * 12, SeekOrigin.Begin);
|
||||
|
||||
int lookup = idxReader.ReadInt32();
|
||||
int length = idxReader.ReadInt32();
|
||||
|
||||
if (lookup < 0 || length <= 0)
|
||||
{
|
||||
count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = length / 7;
|
||||
|
||||
mulStream.Seek(lookup, SeekOrigin.Begin);
|
||||
|
||||
if (m_TileBuffer.Length < count)
|
||||
m_TileBuffer = new StaticTile[count];
|
||||
|
||||
StaticTile[] staTiles = m_TileBuffer;
|
||||
|
||||
if (m_Buffer == null || length > m_Buffer.Length)
|
||||
m_Buffer = new byte[length];
|
||||
|
||||
mulStream.Read(m_Buffer, 0, length);
|
||||
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
staTiles[i].Set((ushort)(m_Buffer[index++] | m_Buffer[index++] << 8),
|
||||
m_Buffer[index++], m_Buffer[index++], (sbyte)m_Buffer[index++],
|
||||
(short)(m_Buffer[index++] | m_Buffer[index++] << 8));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
count = -1;
|
||||
}
|
||||
|
||||
return m_TileBuffer;
|
||||
}
|
||||
|
||||
private class DeltaState
|
||||
{
|
||||
public readonly List<Item> m_List;
|
||||
public readonly int m_X;
|
||||
public readonly int m_Y;
|
||||
|
||||
public DeltaState(Point2D p)
|
||||
{
|
||||
m_X = p.X;
|
||||
m_Y = p.Y;
|
||||
m_List = new List<Item>();
|
||||
}
|
||||
}
|
||||
|
||||
public class StateInfo
|
||||
{
|
||||
public Map m_Map;
|
||||
public Point3D m_Start, m_End;
|
||||
|
||||
public StateInfo(Map map, Point3D start, Point3D end)
|
||||
{
|
||||
m_Map = map;
|
||||
m_Start = start;
|
||||
m_End = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
Projects/UOContent/Commands/VisibilityList.cs
Normal file
141
Projects/UOContent/Commands/VisibilityList.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System.Collections.Generic;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public static class VisibilityList
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
EventSink.Login += OnLogin;
|
||||
|
||||
CommandSystem.Register("Vis", AccessLevel.Counselor, Vis_OnCommand);
|
||||
CommandSystem.Register("VisList", AccessLevel.Counselor, VisList_OnCommand);
|
||||
CommandSystem.Register("VisClear", AccessLevel.Counselor, VisClear_OnCommand);
|
||||
}
|
||||
|
||||
public static void OnLogin(Mobile m)
|
||||
{
|
||||
(m as PlayerMobile)?.VisibilityList.Clear();
|
||||
}
|
||||
|
||||
[Usage("Vis")]
|
||||
[Description(
|
||||
"Adds or removes a targeted player from your visibility list. Anyone on your visibility list will be able to see you at all times, even when you're hidden.")]
|
||||
public static void Vis_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Mobile is PlayerMobile)
|
||||
{
|
||||
e.Mobile.Target = new VisTarget();
|
||||
e.Mobile.SendMessage("Select person to add or remove from your visibility list.");
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("VisList")]
|
||||
[Description("Shows the names of everyone in your visibility list.")]
|
||||
public static void VisList_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Mobile is PlayerMobile pm)
|
||||
{
|
||||
List<Mobile> list = pm.VisibilityList;
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
pm.SendMessage("You are visible to {0} mobile{1}:", list.Count, list.Count == 1 ? "" : "s");
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
pm.SendMessage("#{0}: {1}", i + 1, list[i].Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
pm.SendMessage("Your visibility list is empty.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Usage("VisClear")]
|
||||
[Description("Removes everyone from your visibility list.")]
|
||||
public static void VisClear_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
if (e.Mobile is PlayerMobile pm)
|
||||
{
|
||||
List<Mobile> list = new List<Mobile>(pm.VisibilityList);
|
||||
|
||||
pm.VisibilityList.Clear();
|
||||
pm.SendMessage("Your visibility list has been cleared.");
|
||||
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
Mobile m = list[i];
|
||||
|
||||
if (!m.CanSee(pm) && Utility.InUpdateRange(m, pm))
|
||||
m.Send(pm.RemovePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VisTarget : Target
|
||||
{
|
||||
public VisTarget() : base(-1, false, TargetFlags.None)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (from is PlayerMobile pm && targeted is Mobile targ)
|
||||
{
|
||||
if (targ.AccessLevel <= pm.AccessLevel)
|
||||
{
|
||||
List<Mobile> list = pm.VisibilityList;
|
||||
|
||||
if (list.Contains(targ))
|
||||
{
|
||||
list.Remove(targ);
|
||||
pm.SendMessage("{0} has been removed from your visibility list.", targ.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(targ);
|
||||
pm.SendMessage("{0} has been added to your visibility list.", targ.Name);
|
||||
}
|
||||
|
||||
if (Utility.InUpdateRange(targ, from))
|
||||
{
|
||||
NetState ns = targ.NetState;
|
||||
|
||||
if (ns != null)
|
||||
{
|
||||
if (targ.CanSee(pm))
|
||||
{
|
||||
ns.Send(MobileIncoming.Create(ns, targ, pm));
|
||||
|
||||
if (ObjectPropertyList.Enabled)
|
||||
{
|
||||
ns.Send(pm.OPLPacket);
|
||||
|
||||
foreach (Item item in pm.Items)
|
||||
ns.Send(item.OPLPacket);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ns.Send(pm.RemovePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pm.SendMessage("They can already see you!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("Add only mobiles to your visibility list.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Projects/UOContent/Commands/Wipe.cs
Normal file
94
Projects/UOContent/Commands/Wipe.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Items;
|
||||
using Server.Multis;
|
||||
|
||||
namespace Server.Commands
|
||||
{
|
||||
public class Wipe
|
||||
{
|
||||
[Flags]
|
||||
public enum WipeType
|
||||
{
|
||||
Items = 0x01,
|
||||
Mobiles = 0x02,
|
||||
Multis = 0x04,
|
||||
All = Items | Mobiles | Multis
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
CommandSystem.Register("Wipe", AccessLevel.GameMaster, WipeAll_OnCommand);
|
||||
CommandSystem.Register("WipeItems", AccessLevel.GameMaster, WipeItems_OnCommand);
|
||||
CommandSystem.Register("WipeNPCs", AccessLevel.GameMaster, WipeNPCs_OnCommand);
|
||||
CommandSystem.Register("WipeMultis", AccessLevel.GameMaster, WipeMultis_OnCommand);
|
||||
}
|
||||
|
||||
[Usage("Wipe")]
|
||||
[Description("Wipes all items and npcs in a targeted bounding box.")]
|
||||
private static void WipeAll_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BeginWipe(e.Mobile, WipeType.Items | WipeType.Mobiles);
|
||||
}
|
||||
|
||||
[Usage("WipeItems")]
|
||||
[Description("Wipes all items in a targeted bounding box.")]
|
||||
private static void WipeItems_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BeginWipe(e.Mobile, WipeType.Items);
|
||||
}
|
||||
|
||||
[Usage("WipeNPCs")]
|
||||
[Description("Wipes all npcs in a targeted bounding box.")]
|
||||
private static void WipeNPCs_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BeginWipe(e.Mobile, WipeType.Mobiles);
|
||||
}
|
||||
|
||||
[Usage("WipeMultis")]
|
||||
[Description("Wipes all multis in a targeted bounding box.")]
|
||||
private static void WipeMultis_OnCommand(CommandEventArgs e)
|
||||
{
|
||||
BeginWipe(e.Mobile, WipeType.Multis);
|
||||
}
|
||||
|
||||
public static void BeginWipe(Mobile from, WipeType type)
|
||||
{
|
||||
BoundingBoxPicker.Begin(from, (map, start, end) => DoWipe(from, map, start, end, type));
|
||||
}
|
||||
|
||||
public static void DoWipe(Mobile from, Map map, Point3D start, Point3D end, WipeType type)
|
||||
{
|
||||
CommandLogging.WriteLine(from, "{0} {1} wiping from {2} to {3} in {5} ({4})", from.AccessLevel,
|
||||
CommandLogging.Format(from), start, end, type, map);
|
||||
|
||||
bool mobiles = (type & WipeType.Mobiles) != 0;
|
||||
bool multis = (type & WipeType.Multis) != 0;
|
||||
bool items = (type & WipeType.Items) != 0;
|
||||
|
||||
List<IEntity> toDelete = new List<IEntity>();
|
||||
|
||||
Rectangle2D rect = new Rectangle2D(start.X, start.Y, end.X - start.X + 1, end.Y - start.Y + 1);
|
||||
|
||||
IPooledEnumerable<IEntity> eable;
|
||||
|
||||
if ((!items && !multis) || !mobiles)
|
||||
return;
|
||||
|
||||
eable = map.GetObjectsInBounds(rect);
|
||||
|
||||
foreach (IEntity obj in eable)
|
||||
if (items && obj is Item && !(obj is BaseMulti || obj is HouseSign))
|
||||
toDelete.Add(obj);
|
||||
else if (multis && obj is BaseMulti)
|
||||
toDelete.Add(obj);
|
||||
else if (obj is Mobile mobile && !mobile.Player)
|
||||
toDelete.Add(mobile);
|
||||
|
||||
eable.Free();
|
||||
|
||||
for (int i = 0; i < toDelete.Count; ++i)
|
||||
toDelete[i].Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Projects/UOContent/Configuration/EmailConfiguration.cs
Normal file
96
Projects/UOContent/Configuration/EmailConfiguration.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*************************************************************************
|
||||
* ModernUO *
|
||||
* Copyright (C) 2019-2020 - ModernUO Development Team *
|
||||
* Email: hi@modernuo.com *
|
||||
* File: EmailConfiguration.cs *
|
||||
* Created: 2019/10/04 - Updated: 2020/05/02 *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
*************************************************************************/
|
||||
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using MimeKit;
|
||||
using Server.Json;
|
||||
|
||||
namespace Server.Configurations
|
||||
{
|
||||
public static class EmailConfiguration
|
||||
{
|
||||
public static readonly bool EmailEnabled;
|
||||
public static readonly MailboxAddress FromAddress;
|
||||
public static readonly MailboxAddress CrashAddress;
|
||||
public static readonly MailboxAddress SpeechLogPageAddress;
|
||||
public static readonly string EmailServer;
|
||||
public static readonly int EmailPort;
|
||||
public static readonly string EmailServerUsername;
|
||||
public static readonly string EmailServerPassword;
|
||||
public static readonly int EmailSendRetryCount = 5; // seconds
|
||||
public static readonly int EmailSendRetryDelay = 2; // seconds
|
||||
|
||||
static EmailConfiguration()
|
||||
{
|
||||
string filePath = Path.Join(Core.BaseDirectory, "Configuration/email-settings.json");
|
||||
Settings settings = JsonConfig.Deserialize<Settings>(filePath) ?? new Settings();
|
||||
|
||||
if (settings.emailServer == null || settings.fromAddress == null)
|
||||
{
|
||||
JsonConfig.Serialize(filePath, settings);
|
||||
return;
|
||||
}
|
||||
|
||||
EmailEnabled = true;
|
||||
FromAddress = new MailboxAddress(settings.fromName, settings.fromAddress);
|
||||
CrashAddress = new MailboxAddress(settings.crashName, settings.crashAddress);
|
||||
SpeechLogPageAddress = new MailboxAddress(settings.speechLogPageName, settings.speechLogPageAddress);
|
||||
EmailServer = settings.emailServer;
|
||||
EmailPort = settings.emailPort;
|
||||
EmailServerUsername = settings.emailUsername;
|
||||
EmailServerPassword = settings.emailPassword;
|
||||
}
|
||||
|
||||
internal class Settings
|
||||
{
|
||||
[JsonPropertyName("fromAddress")]
|
||||
internal string fromAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("fromName")]
|
||||
internal string fromName { get; set; }
|
||||
|
||||
[JsonPropertyName("crashAddress")]
|
||||
internal string crashAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("crashName")]
|
||||
internal string crashName { get; set; }
|
||||
|
||||
[JsonPropertyName("speechLogPageAddress")]
|
||||
internal string speechLogPageAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("speechLogPageName")]
|
||||
internal string speechLogPageName { get; set; }
|
||||
|
||||
[JsonPropertyName("emailServer")]
|
||||
internal string emailServer { get; set; }
|
||||
|
||||
[JsonPropertyName("emailPort")]
|
||||
internal int emailPort { get; set; }
|
||||
|
||||
[JsonPropertyName("emailUsername")]
|
||||
internal string emailUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("emailPassword")]
|
||||
internal string emailPassword { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Projects/UOContent/Configuration/ExpansionConfiguration.cs
Normal file
39
Projects/UOContent/Configuration/ExpansionConfiguration.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using Server.Accounting;
|
||||
using Server.Items;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server
|
||||
{
|
||||
public static class ExpansionConfiguration
|
||||
{
|
||||
public static void Configure()
|
||||
{
|
||||
Core.Expansion = ServerConfiguration.GetOrUpdateSetting("currentExpansion", Expansion.TOL);
|
||||
|
||||
AccountGold.Enabled = ServerConfiguration.GetSetting("accountGold.enable", Core.TOL);
|
||||
AccountGold.ConvertOnBank = ServerConfiguration.GetSetting("accountGold.convertOnBank", true);
|
||||
AccountGold.ConvertOnTrade = ServerConfiguration.GetSetting("accountGold.convertOnTrade", false);
|
||||
VirtualCheck.UseEditGump = ServerConfiguration.GetSetting("virtualChecks.useEditGump", true);
|
||||
|
||||
Mobile.InsuranceEnabled = ServerConfiguration.GetSetting("insurance.enable", Core.AOS);
|
||||
ObjectPropertyList.Enabled = ServerConfiguration.GetSetting("opl.enable", Core.AOS);
|
||||
bool visibleDamage = ServerConfiguration.GetSetting("visibleDamage", Core.AOS);
|
||||
Mobile.VisibleDamageType = visibleDamage ? VisibleDamageType.Related : VisibleDamageType.None;
|
||||
Mobile.GuildClickMessage = ServerConfiguration.GetSetting("guildClickMessage", !Core.AOS);
|
||||
Mobile.AsciiClickMessage = ServerConfiguration.GetSetting("asciiClickMessage", !Core.AOS);
|
||||
|
||||
Mobile.ActionDelay = ServerConfiguration.GetSetting("actionDelay", Core.AOS ? 1000 : 500);
|
||||
|
||||
if (Core.AOS)
|
||||
{
|
||||
AOS.DisableStatInfluences();
|
||||
|
||||
if (ObjectPropertyList.Enabled)
|
||||
PacketHandlers.SingleClickProps = true; // single click for everything is overridden to check object property list
|
||||
|
||||
Mobile.AOSStatusHandler = AOS.GetStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Projects/UOContent/Context Menus/AddToParty.cs
Normal file
37
Projects/UOContent/Context Menus/AddToParty.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using Server.Engines.PartySystem;
|
||||
|
||||
namespace Server.ContextMenus
|
||||
{
|
||||
public class AddToPartyEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
private readonly Mobile m_Target;
|
||||
|
||||
public AddToPartyEntry(Mobile from, Mobile target) : base(0197, 12)
|
||||
{
|
||||
m_From = from;
|
||||
m_Target = target;
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
Party p = Party.Get(m_From);
|
||||
Party mp = Party.Get(m_Target);
|
||||
|
||||
if (m_From == m_Target)
|
||||
m_From.SendLocalizedMessage(1005439); // You cannot add yourself to a party.
|
||||
else if (p != null && p.Leader != m_From)
|
||||
m_From.SendLocalizedMessage(1005453); // You may only add members to the party if you are the leader.
|
||||
else if (p != null && p.Members.Count + p.Candidates.Count >= Party.Capacity)
|
||||
m_From.SendLocalizedMessage(1008095); // You may only have 10 in your party (this includes candidates).
|
||||
else if (!m_Target.Player)
|
||||
m_From.SendLocalizedMessage(1005444); // The creature ignores your offer.
|
||||
else if (mp != null && mp == p)
|
||||
m_From.SendLocalizedMessage(1005440); // This person is already in your party!
|
||||
else if (mp != null)
|
||||
m_From.SendLocalizedMessage(1005441); // This person is already in a party!
|
||||
else
|
||||
Party.Invite(m_From, m_Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Projects/UOContent/Context Menus/AddToSpellbookEntry.cs
Normal file
57
Projects/UOContent/Context Menus/AddToSpellbookEntry.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using Server.Items;
|
||||
using Server.Network;
|
||||
using Server.Targeting;
|
||||
|
||||
namespace Server.ContextMenus
|
||||
{
|
||||
public class AddToSpellbookEntry : ContextMenuEntry
|
||||
{
|
||||
public AddToSpellbookEntry() : base(6144, 3)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (Owner.From.CheckAlive() && Owner.Target is SpellScroll scroll)
|
||||
Owner.From.Target = new InternalTarget(scroll);
|
||||
}
|
||||
|
||||
private class InternalTarget : Target
|
||||
{
|
||||
private readonly SpellScroll m_Scroll;
|
||||
|
||||
public InternalTarget(SpellScroll scroll) : base(3, false, TargetFlags.None) => m_Scroll = scroll;
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (targeted is Spellbook book)
|
||||
if (from.CheckAlive() && !m_Scroll.Deleted && m_Scroll.Movable && m_Scroll.Amount >= 1 &&
|
||||
m_Scroll.CheckItemUse(from))
|
||||
{
|
||||
SpellbookType type = Spellbook.GetTypeForSpell(m_Scroll.SpellID);
|
||||
|
||||
if (type != book.SpellbookType)
|
||||
{
|
||||
}
|
||||
else if (book.HasSpell(m_Scroll.SpellID))
|
||||
{
|
||||
from.SendLocalizedMessage(500179); // That spell is already present in that spellbook.
|
||||
}
|
||||
else
|
||||
{
|
||||
int val = m_Scroll.SpellID - book.BookOffset;
|
||||
|
||||
if (val >= 0 && val < book.BookCount)
|
||||
{
|
||||
book.Content |= (ulong)1 << val;
|
||||
|
||||
m_Scroll.Consume();
|
||||
|
||||
from.Send(new PlaySound(0x249, book.GetWorldLocation()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Projects/UOContent/Context Menus/EatEntry.cs
Normal file
24
Projects/UOContent/Context Menus/EatEntry.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Server.Items;
|
||||
|
||||
namespace Server.ContextMenus
|
||||
{
|
||||
public class EatEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Food m_Food;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public EatEntry(Mobile from, Food food) : base(6135, 1)
|
||||
{
|
||||
m_From = from;
|
||||
m_Food = food;
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (m_Food?.Deleted != false || !m_Food.Movable || !m_From.CheckAlive() || !m_Food.CheckItemUse(m_From))
|
||||
return;
|
||||
|
||||
m_Food.Eat(m_From);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Projects/UOContent/Context Menus/EjectPlayer.cs
Normal file
26
Projects/UOContent/Context Menus/EjectPlayer.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using Server.Multis;
|
||||
|
||||
namespace Server.ContextMenus
|
||||
{
|
||||
public class EjectPlayerEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
private readonly Mobile m_Target;
|
||||
private readonly BaseHouse m_TargetHouse;
|
||||
|
||||
public EjectPlayerEntry(Mobile from, Mobile target) : base(6206, 12)
|
||||
{
|
||||
m_From = from;
|
||||
m_Target = target;
|
||||
m_TargetHouse = BaseHouse.FindHouseAt(m_Target);
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (!m_From.Alive || m_TargetHouse.Deleted || !m_TargetHouse.IsFriend(m_From))
|
||||
return;
|
||||
|
||||
m_TargetHouse.Kick(m_From, m_Target);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Projects/UOContent/Context Menus/OpenBankEntry.cs
Normal file
20
Projects/UOContent/Context Menus/OpenBankEntry.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace Server.ContextMenus
|
||||
{
|
||||
public class OpenBankEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Mobile m_Banker;
|
||||
|
||||
public OpenBankEntry(Mobile from, Mobile banker) : base(6105, 12) => m_Banker = banker;
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (!Owner.From.CheckAlive())
|
||||
return;
|
||||
|
||||
if (Owner.From.Criminal)
|
||||
m_Banker.Say(500378); // Thou art a criminal and cannot access thy bank box.
|
||||
else
|
||||
Owner.From.BankBox.Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Projects/UOContent/Context Menus/TeachEntry.cs
Normal file
30
Projects/UOContent/Context Menus/TeachEntry.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.ContextMenus
|
||||
{
|
||||
public class TeachEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
private readonly BaseCreature m_Mobile;
|
||||
private readonly SkillName m_Skill;
|
||||
|
||||
public TeachEntry(SkillName skill, BaseCreature m, Mobile from, bool enabled) : base(6000 + (int)skill)
|
||||
{
|
||||
m_Skill = skill;
|
||||
m_Mobile = m;
|
||||
m_From = from;
|
||||
|
||||
if (!enabled)
|
||||
Flags |= CMEFlags.Disabled;
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (!m_From.CheckAlive())
|
||||
return;
|
||||
|
||||
m_Mobile.Teach(m_Skill, m_From, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Projects/UOContent/Engines/BulkOrders/BODTarget.cs
Normal file
25
Projects/UOContent/Engines/BulkOrders/BODTarget.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Server.Targeting;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BODTarget : Target
|
||||
{
|
||||
private readonly BaseBOD m_Deed;
|
||||
|
||||
public BODTarget(BaseBOD deed) : base(18, false, TargetFlags.None) => m_Deed = deed;
|
||||
|
||||
protected override void OnTarget(Mobile from, object targeted)
|
||||
{
|
||||
if (m_Deed.Deleted || !m_Deed.IsChildOf(from.Backpack))
|
||||
return;
|
||||
|
||||
if (!(targeted is Item item && item.IsChildOf(from.Backpack)))
|
||||
{
|
||||
from.SendLocalizedMessage(1045158); // You must have the item in your backpack to target it.
|
||||
return;
|
||||
}
|
||||
|
||||
m_Deed.EndCombine(from, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Projects/UOContent/Engines/BulkOrders/BaseBOD.cs
Normal file
153
Projects/UOContent/Engines/BulkOrders/BaseBOD.cs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public abstract class BaseBOD : Item
|
||||
{
|
||||
private int m_AmountMax;
|
||||
private bool m_RequireExceptional;
|
||||
private BulkMaterialType m_Material;
|
||||
|
||||
public static BulkMaterialType GetRandomMaterial(BulkMaterialType start, double[] chances)
|
||||
{
|
||||
double random = Utility.RandomDouble();
|
||||
|
||||
for (int i = 0; i < chances.Length; ++i)
|
||||
{
|
||||
if (random < chances[i])
|
||||
return i == 0 ? BulkMaterialType.None : start + (i - 1);
|
||||
|
||||
random -= chances[i];
|
||||
}
|
||||
|
||||
return BulkMaterialType.None;
|
||||
}
|
||||
|
||||
public BaseBOD(int hue, int amountMax, bool requireExeptional, BulkMaterialType material) : this()
|
||||
{
|
||||
Hue = hue;
|
||||
AmountMax = amountMax;
|
||||
RequireExceptional = requireExeptional;
|
||||
Material = material;
|
||||
}
|
||||
|
||||
public BaseBOD() : base(Core.AOS ? 0x2258 : 0x14EF)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public BaseBOD(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract bool Complete { get; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public sealed override int Hue { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int AmountMax
|
||||
{
|
||||
get => m_AmountMax;
|
||||
set { m_AmountMax = value; InvalidateProperties(); }
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public bool RequireExceptional
|
||||
{
|
||||
get => m_RequireExceptional;
|
||||
set { m_RequireExceptional = value; InvalidateProperties(); }
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public BulkMaterialType Material
|
||||
{
|
||||
get => m_Material;
|
||||
set { m_Material = value; InvalidateProperties(); }
|
||||
}
|
||||
|
||||
public abstract RewardGroup GetRewardGroup();
|
||||
|
||||
public abstract int ComputeGold();
|
||||
public abstract int ComputeFame();
|
||||
public abstract void EndCombine(Mobile from, Item item);
|
||||
|
||||
public virtual void GetRewards(out Item reward, out int gold, out int fame)
|
||||
{
|
||||
gold = ComputeGold();
|
||||
fame = ComputeFame();
|
||||
|
||||
List<RewardItem> rewards = ComputeRewards(false);
|
||||
|
||||
reward = rewards.Count <= 0 ? null : rewards[Utility.Random(rewards.Count)].Construct();
|
||||
}
|
||||
|
||||
public virtual List<RewardItem> ComputeRewards(bool full)
|
||||
{
|
||||
RewardGroup rewardGroup = GetRewardGroup();
|
||||
|
||||
List<RewardItem> list = new List<RewardItem>();
|
||||
|
||||
if (full)
|
||||
{
|
||||
for (int i = 0; i < rewardGroup?.Items.Length; ++i)
|
||||
{
|
||||
RewardItem reward = rewardGroup.Items[i];
|
||||
|
||||
if (reward != null)
|
||||
list.Add(reward);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RewardItem reward = rewardGroup.AcquireItem();
|
||||
|
||||
if (reward != null)
|
||||
list.Add(reward);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public virtual void BeginCombine(Mobile from)
|
||||
{
|
||||
if (Complete)
|
||||
from.SendLocalizedMessage(1045166); // The maximum amount of requested items have already been combined to this deed.
|
||||
else
|
||||
from.Target = new BODTarget(this);
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
writer.Write(m_AmountMax);
|
||||
writer.Write(m_RequireExceptional);
|
||||
writer.Write((int)m_Material);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_AmountMax = reader.ReadInt();
|
||||
m_RequireExceptional = reader.ReadBool();
|
||||
m_Material = (BulkMaterialType)reader.ReadInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (Parent == null && Map == Map.Internal && Location == Point3D.Zero)
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Projects/UOContent/Engines/BulkOrders/Books/BOBFilter.cs
Normal file
62
Projects/UOContent/Engines/BulkOrders/Books/BOBFilter.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBFilter
|
||||
{
|
||||
public BOBFilter()
|
||||
{
|
||||
}
|
||||
|
||||
public BOBFilter(IGenericReader reader)
|
||||
{
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
Type = reader.ReadEncodedInt();
|
||||
Quality = reader.ReadEncodedInt();
|
||||
Material = reader.ReadEncodedInt();
|
||||
Quantity = reader.ReadEncodedInt();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDefault => Type == 0 && Quality == 0 && Material == 0 && Quantity == 0;
|
||||
|
||||
public int Type { get; set; }
|
||||
|
||||
public int Quality { get; set; }
|
||||
|
||||
public int Material { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Type = 0;
|
||||
Quality = 0;
|
||||
Material = 0;
|
||||
Quantity = 0;
|
||||
}
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
{
|
||||
if (IsDefault)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteEncodedInt(1); // version
|
||||
|
||||
writer.WriteEncodedInt(Type);
|
||||
writer.WriteEncodedInt(Quality);
|
||||
writer.WriteEncodedInt(Material);
|
||||
writer.WriteEncodedInt(Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
218
Projects/UOContent/Engines/BulkOrders/Books/BOBFilterGump.cs
Normal file
218
Projects/UOContent/Engines/BulkOrders/Books/BOBFilterGump.cs
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
using Server.Gumps;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBFilterGump : Gump
|
||||
{
|
||||
private const int LabelColor = 0x7FFF;
|
||||
|
||||
private static readonly int[,] m_MaterialFilters =
|
||||
{
|
||||
{ 1044067, 1 }, // Blacksmithy
|
||||
{ 1062226, 3 }, // Iron
|
||||
{ 1018332, 4 }, // Dull Copper
|
||||
{ 1018333, 5 }, // Shadow Iron
|
||||
{ 1018334, 6 }, // Copper
|
||||
{ 1018335, 7 }, // Bronze
|
||||
{ 0, 0 }, // --Blank--
|
||||
{ 1018336, 8 }, // Golden
|
||||
{ 1018337, 9 }, // Agapite
|
||||
{ 1018338, 10 }, // Verite
|
||||
{ 1018339, 11 }, // Valorite
|
||||
{ 0, 0 }, // --Blank--
|
||||
{ 1044094, 2 }, // Tailoring
|
||||
{ 1044286, 12 }, // Cloth
|
||||
{ 1062235, 13 }, // Leather
|
||||
{ 1062236, 14 }, // Spined
|
||||
{ 1062237, 15 }, // Horned
|
||||
{ 1062238, 16 } // Barbed
|
||||
};
|
||||
|
||||
private static readonly int[,] m_TypeFilters =
|
||||
{
|
||||
{ 1062229, 0 }, // All
|
||||
{ 1062224, 1 }, // Small
|
||||
{ 1062225, 2 } // Large
|
||||
};
|
||||
|
||||
private static readonly int[,] m_QualityFilters =
|
||||
{
|
||||
{ 1062229, 0 }, // All
|
||||
{ 1011542, 1 }, // Normal
|
||||
{ 1060636, 2 } // Exceptional
|
||||
};
|
||||
|
||||
private static readonly int[,] m_AmountFilters =
|
||||
{
|
||||
{ 1062229, 0 }, // All
|
||||
{ 1049706, 1 }, // 10
|
||||
{ 1016007, 2 }, // 15
|
||||
{ 1062239, 3 } // 20
|
||||
};
|
||||
|
||||
private static readonly int[][,] m_Filters =
|
||||
{
|
||||
m_TypeFilters,
|
||||
m_QualityFilters,
|
||||
m_MaterialFilters,
|
||||
m_AmountFilters
|
||||
};
|
||||
|
||||
private static readonly int[] m_XOffsets_Type = { 0, 75, 170 };
|
||||
private static readonly int[] m_XOffsets_Quality = { 0, 75, 170 };
|
||||
private static readonly int[] m_XOffsets_Amount = { 0, 75, 180, 275 };
|
||||
private static readonly int[] m_XOffsets_Material = { 0, 105, 210, 305, 390, 485 };
|
||||
|
||||
private static readonly int[] m_XWidths_Small = { 50, 50, 70, 50 };
|
||||
private static readonly int[] m_XWidths_Large = { 80, 50, 50, 50, 50, 50 };
|
||||
private readonly BulkOrderBook m_Book;
|
||||
private readonly PlayerMobile m_From;
|
||||
|
||||
public BOBFilterGump(PlayerMobile from, BulkOrderBook book) : base(12, 24)
|
||||
{
|
||||
from.CloseGump<BOBGump>();
|
||||
from.CloseGump<BOBFilterGump>();
|
||||
|
||||
m_From = from;
|
||||
m_Book = book;
|
||||
|
||||
BOBFilter f = from.UseOwnFilter ? from.BOBFilter : book.Filter;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(10, 10, 600, 439, 5054);
|
||||
|
||||
AddImageTiled(18, 20, 583, 420, 2624);
|
||||
AddAlphaRegion(18, 20, 583, 420);
|
||||
|
||||
AddImage(5, 5, 10460);
|
||||
AddImage(585, 5, 10460);
|
||||
AddImage(5, 424, 10460);
|
||||
AddImage(585, 424, 10460);
|
||||
|
||||
AddHtmlLocalized(270, 32, 200, 32, 1062223, LabelColor); // Filter Preference
|
||||
|
||||
AddHtmlLocalized(26, 64, 120, 32, 1062228, LabelColor); // Bulk Order Type
|
||||
AddFilterList(25, 96, m_XOffsets_Type, 40, m_TypeFilters, m_XWidths_Small, f.Type, 0);
|
||||
|
||||
AddHtmlLocalized(320, 64, 50, 32, 1062215, LabelColor); // Quality
|
||||
AddFilterList(320, 96, m_XOffsets_Quality, 40, m_QualityFilters, m_XWidths_Small, f.Quality, 1);
|
||||
|
||||
AddHtmlLocalized(26, 160, 120, 32, 1062232, LabelColor); // Material Type
|
||||
AddFilterList(25, 192, m_XOffsets_Material, 40, m_MaterialFilters, m_XWidths_Large, f.Material, 2);
|
||||
|
||||
AddHtmlLocalized(26, 320, 120, 32, 1062217, LabelColor); // Amount
|
||||
AddFilterList(25, 352, m_XOffsets_Amount, 40, m_AmountFilters, m_XWidths_Small, f.Quantity, 3);
|
||||
|
||||
AddHtmlLocalized(75, 416, 120, 32, 1062477, from.UseOwnFilter ? LabelColor : 16927); // Set Book Filter
|
||||
AddButton(40, 416, 4005, 4007, 1);
|
||||
|
||||
AddHtmlLocalized(235, 416, 120, 32, 1062478, from.UseOwnFilter ? 16927 : LabelColor); // Set Your Filter
|
||||
AddButton(200, 416, 4005, 4007, 2);
|
||||
|
||||
AddHtmlLocalized(405, 416, 120, 32, 1062231, LabelColor); // Clear Filter
|
||||
AddButton(370, 416, 4005, 4007, 3);
|
||||
|
||||
AddHtmlLocalized(540, 416, 50, 32, 1011046, LabelColor); // APPLY
|
||||
AddButton(505, 416, 4017, 4018, 0);
|
||||
}
|
||||
|
||||
private void AddFilterList(int x, int y, int[] xOffsets, int yOffset, int[,] filters, int[] xWidths, int filterValue,
|
||||
int filterIndex)
|
||||
{
|
||||
for (int i = 0; i < filters.GetLength(0); ++i)
|
||||
{
|
||||
int number = filters[i, 0];
|
||||
|
||||
if (number == 0)
|
||||
continue;
|
||||
|
||||
bool isSelected = filters[i, 1] == filterValue ||
|
||||
(i % xOffsets.Length == 0 && filterValue == 0);
|
||||
|
||||
AddHtmlLocalized(x + 35 + xOffsets[i % xOffsets.Length], y + i / xOffsets.Length * yOffset,
|
||||
xWidths[i % xOffsets.Length], 32, number, isSelected ? 16927 : LabelColor);
|
||||
AddButton(x + xOffsets[i % xOffsets.Length], y + i / xOffsets.Length * yOffset, 4005, 4007,
|
||||
4 + filterIndex + i * 4);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
BOBFilter f = m_From.UseOwnFilter ? m_From.BOBFilter : m_Book.Filter;
|
||||
|
||||
int index = info.ButtonID;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case 0: // Apply
|
||||
{
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book));
|
||||
|
||||
break;
|
||||
}
|
||||
case 1: // Set Book Filter
|
||||
{
|
||||
m_From.UseOwnFilter = false;
|
||||
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Set Your Filter
|
||||
{
|
||||
m_From.UseOwnFilter = true;
|
||||
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Clear Filter
|
||||
{
|
||||
f.Clear();
|
||||
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
index -= 4;
|
||||
|
||||
int type = index % 4;
|
||||
index /= 4;
|
||||
|
||||
if (type >= 0 && type < m_Filters.Length)
|
||||
{
|
||||
int[,] filters = m_Filters[type];
|
||||
|
||||
if (index >= 0 && index < filters.GetLength(0))
|
||||
{
|
||||
if (filters[index, 0] == 0)
|
||||
break;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 0:
|
||||
f.Type = filters[index, 1];
|
||||
break;
|
||||
case 1:
|
||||
f.Quality = filters[index, 1];
|
||||
break;
|
||||
case 2:
|
||||
f.Material = filters[index, 1];
|
||||
break;
|
||||
case 3:
|
||||
f.Quantity = filters[index, 1];
|
||||
break;
|
||||
}
|
||||
|
||||
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
637
Projects/UOContent/Engines/BulkOrders/Books/BOBGump.cs
Normal file
637
Projects/UOContent/Engines/BulkOrders/Books/BOBGump.cs
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
using Server.Prompts;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBGump : Gump
|
||||
{
|
||||
private const int LabelColor = 0x7FFF;
|
||||
private readonly BulkOrderBook m_Book;
|
||||
private readonly PlayerMobile m_From;
|
||||
private readonly List<IBOBEntry> m_List;
|
||||
|
||||
private int m_Page;
|
||||
|
||||
public BOBGump(PlayerMobile from, BulkOrderBook book, int page = 0, List<IBOBEntry> list = null) : base(12, 24)
|
||||
{
|
||||
from.CloseGump<BOBGump>();
|
||||
from.CloseGump<BOBFilterGump>();
|
||||
|
||||
m_From = from;
|
||||
m_Book = book;
|
||||
m_Page = page;
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<IBOBEntry>(book.Entries.Count);
|
||||
|
||||
for (int i = 0; i < book.Entries.Count; ++i)
|
||||
{
|
||||
IBOBEntry entry = book.Entries[i];
|
||||
|
||||
if (CheckFilter(entry))
|
||||
list.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
m_List = list;
|
||||
|
||||
int index = GetIndexForPage(page);
|
||||
int count = GetCountForIndex(index);
|
||||
|
||||
int tableIndex = 0;
|
||||
|
||||
PlayerVendor pv = book.RootParent as PlayerVendor;
|
||||
|
||||
bool canDrop = book.IsChildOf(from.Backpack);
|
||||
bool canBuy = pv != null;
|
||||
bool canPrice = canDrop || canBuy;
|
||||
|
||||
if (canBuy)
|
||||
{
|
||||
VendorItem vi = pv.GetVendorItem(book);
|
||||
|
||||
canBuy = vi?.IsForSale == false;
|
||||
}
|
||||
|
||||
int width = 600;
|
||||
|
||||
if (!canPrice)
|
||||
width = 516;
|
||||
|
||||
X = (624 - width) / 2;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(10, 10, width, 439, 5054);
|
||||
AddImageTiled(18, 20, width - 17, 420, 2624);
|
||||
|
||||
if (canPrice)
|
||||
{
|
||||
AddImageTiled(573, 64, 24, 352, 200);
|
||||
AddImageTiled(493, 64, 78, 352, 1416);
|
||||
}
|
||||
|
||||
if (canDrop)
|
||||
AddImageTiled(24, 64, 32, 352, 1416);
|
||||
|
||||
AddImageTiled(58, 64, 36, 352, 200);
|
||||
AddImageTiled(96, 64, 133, 352, 1416);
|
||||
AddImageTiled(231, 64, 80, 352, 200);
|
||||
AddImageTiled(313, 64, 100, 352, 1416);
|
||||
AddImageTiled(415, 64, 76, 352, 200);
|
||||
|
||||
for (int i = index; i < index + count && i >= 0 && i < list.Count; ++i)
|
||||
{
|
||||
IBOBEntry entry = list[i];
|
||||
|
||||
if (!CheckFilter(entry))
|
||||
continue;
|
||||
|
||||
AddImageTiled(24, 94 + tableIndex * 32, canPrice ? 573 : 489, 2, 2624);
|
||||
tableIndex += entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
|
||||
}
|
||||
|
||||
AddAlphaRegion(18, 20, width - 17, 420);
|
||||
AddImage(5, 5, 10460);
|
||||
AddImage(width - 15, 5, 10460);
|
||||
AddImage(5, 424, 10460);
|
||||
AddImage(width - 15, 424, 10460);
|
||||
|
||||
AddHtmlLocalized(canPrice ? 266 : 224, 32, 200, 32, 1062220, LabelColor); // Bulk Order Book
|
||||
AddHtmlLocalized(63, 64, 200, 32, 1062213, LabelColor); // Type
|
||||
AddHtmlLocalized(147, 64, 200, 32, 1062214, LabelColor); // Item
|
||||
AddHtmlLocalized(246, 64, 200, 32, 1062215, LabelColor); // Quality
|
||||
AddHtmlLocalized(336, 64, 200, 32, 1062216, LabelColor); // Material
|
||||
AddHtmlLocalized(429, 64, 200, 32, 1062217, LabelColor); // Amount
|
||||
|
||||
AddButton(35, 32, 4005, 4007, 1);
|
||||
AddHtmlLocalized(70, 32, 200, 32, 1062476, LabelColor); // Set Filter
|
||||
|
||||
BOBFilter f = from.UseOwnFilter ? from.BOBFilter : book.Filter;
|
||||
|
||||
if (f.IsDefault)
|
||||
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062475, 16927); // Using No Filter
|
||||
else if (from.UseOwnFilter)
|
||||
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062451, 16927); // Using Your Filter
|
||||
else
|
||||
AddHtmlLocalized(canPrice ? 470 : 386, 32, 120, 32, 1062230, 16927); // Using Book Filter
|
||||
|
||||
AddButton(375, 416, 4017, 4018, 0);
|
||||
AddHtmlLocalized(410, 416, 120, 20, 1011441, LabelColor); // EXIT
|
||||
|
||||
if (canDrop)
|
||||
AddHtmlLocalized(26, 64, 50, 32, 1062212, LabelColor); // Drop
|
||||
|
||||
if (canPrice)
|
||||
{
|
||||
AddHtmlLocalized(516, 64, 200, 32, 1062218, LabelColor); // Price
|
||||
|
||||
if (canBuy)
|
||||
{
|
||||
AddHtmlLocalized(576, 64, 200, 32, 1062219, LabelColor); // Buy
|
||||
}
|
||||
else
|
||||
{
|
||||
AddHtmlLocalized(576, 64, 200, 32, 1062227, LabelColor); // Set
|
||||
|
||||
AddButton(450, 416, 4005, 4007, 4);
|
||||
AddHtml(485, 416, 120, 20, "<BASEFONT COLOR=#FFFFFF>Price all</FONT>");
|
||||
}
|
||||
}
|
||||
|
||||
tableIndex = 0;
|
||||
|
||||
if (page > 0)
|
||||
{
|
||||
AddButton(75, 416, 4014, 4016, 2);
|
||||
AddHtmlLocalized(110, 416, 150, 20, 1011067, LabelColor); // Previous page
|
||||
}
|
||||
|
||||
if (GetIndexForPage(page + 1) < list.Count)
|
||||
{
|
||||
AddButton(225, 416, 4005, 4007, 3);
|
||||
AddHtmlLocalized(260, 416, 150, 20, 1011066, LabelColor); // Next page
|
||||
}
|
||||
|
||||
for (int i = index; i < index + count && i >= 0 && i < list.Count; ++i)
|
||||
{
|
||||
IBOBEntry entry = list[i];
|
||||
|
||||
if (!CheckFilter(entry))
|
||||
continue;
|
||||
|
||||
if (entry is BOBLargeEntry largeEntry)
|
||||
{
|
||||
int y = 96 + tableIndex * 32;
|
||||
|
||||
if (canDrop)
|
||||
AddButton(35, y + 2, 5602, 5606, 5 + i * 2);
|
||||
|
||||
if (canDrop || (canBuy && entry.Price > 0))
|
||||
{
|
||||
AddButton(579, y + 2, 2117, 2118, 6 + i * 2);
|
||||
AddLabel(495, y, 1152, entry.Price.ToString());
|
||||
}
|
||||
|
||||
AddHtmlLocalized(61, y, 50, 32, 1062225, LabelColor); // Large
|
||||
|
||||
for (int j = 0; j < largeEntry.Entries.Length; ++j)
|
||||
{
|
||||
BOBLargeSubEntry sub = largeEntry.Entries[j];
|
||||
|
||||
AddHtmlLocalized(103, y, 130, 32, sub.Number, LabelColor);
|
||||
|
||||
if (entry.RequireExceptional)
|
||||
AddHtmlLocalized(235, y, 80, 20, 1060636, LabelColor); // exceptional
|
||||
else
|
||||
AddHtmlLocalized(235, y, 80, 20, 1011542, LabelColor); // normal
|
||||
|
||||
object name = GetMaterialName(entry.Material, entry.DeedType, sub.ItemType);
|
||||
|
||||
if (name is int intName)
|
||||
AddHtmlLocalized(316, y, 100, 20, intName, LabelColor);
|
||||
else
|
||||
AddLabel(316, y, 1152, name.ToString());
|
||||
|
||||
AddLabel(421, y, 1152, $"{sub.AmountCur} / {entry.AmountMax}");
|
||||
|
||||
++tableIndex;
|
||||
y += 32;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
BOBSmallEntry smallEntry = (BOBSmallEntry)entry;
|
||||
|
||||
int y = 96 + tableIndex++ * 32;
|
||||
|
||||
if (canDrop)
|
||||
AddButton(35, y + 2, 5602, 5606, 5 + i * 2);
|
||||
|
||||
if (canDrop || (canBuy && smallEntry.Price > 0))
|
||||
{
|
||||
AddButton(579, y + 2, 2117, 2118, 6 + i * 2);
|
||||
AddLabel(495, y, 1152, smallEntry.Price.ToString());
|
||||
}
|
||||
|
||||
AddHtmlLocalized(61, y, 50, 32, 1062224, LabelColor); // Small
|
||||
|
||||
AddHtmlLocalized(103, y, 130, 32, smallEntry.Number, LabelColor);
|
||||
|
||||
if (smallEntry.RequireExceptional)
|
||||
AddHtmlLocalized(235, y, 80, 20, 1060636, LabelColor); // exceptional
|
||||
else
|
||||
AddHtmlLocalized(235, y, 80, 20, 1011542, LabelColor); // normal
|
||||
|
||||
object name = GetMaterialName(smallEntry.Material, smallEntry.DeedType, smallEntry.ItemType);
|
||||
|
||||
if (name is int intName)
|
||||
AddHtmlLocalized(316, y, 100, 20, intName, LabelColor);
|
||||
else
|
||||
AddLabel(316, y, 1152, name.ToString());
|
||||
|
||||
AddLabel(421, y, 1152, $"{smallEntry.AmountCur} / {smallEntry.AmountMax}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CheckFilter(IBOBEntry entry)
|
||||
{
|
||||
if (entry is BOBLargeEntry largeEntry)
|
||||
return CheckFilter(entry.Material, entry.AmountMax, true, entry.RequireExceptional, entry.DeedType,
|
||||
largeEntry.Entries.Length > 0 ? largeEntry.Entries[0].ItemType : null);
|
||||
|
||||
if (entry is BOBSmallEntry smallEntry)
|
||||
return CheckFilter(entry.Material, entry.AmountMax, false, entry.RequireExceptional,
|
||||
entry.DeedType, smallEntry.ItemType);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CheckFilter(BulkMaterialType mat, int amountMax, bool isLarge, bool reqExc, BODType deedType,
|
||||
Type itemType)
|
||||
{
|
||||
BOBFilter f = m_From.UseOwnFilter ? m_From.BOBFilter : m_Book.Filter;
|
||||
|
||||
if (f.IsDefault)
|
||||
return true;
|
||||
|
||||
if (f.Quality == 1 && reqExc)
|
||||
return false;
|
||||
if (f.Quality == 2 && !reqExc)
|
||||
return false;
|
||||
|
||||
if (f.Quantity == 1 && amountMax != 10)
|
||||
return false;
|
||||
if (f.Quantity == 2 && amountMax != 15)
|
||||
return false;
|
||||
if (f.Quantity == 3 && amountMax != 20)
|
||||
return false;
|
||||
|
||||
if (f.Type == 1 && isLarge)
|
||||
return false;
|
||||
if (f.Type == 2 && !isLarge)
|
||||
return false;
|
||||
|
||||
return f.Material switch
|
||||
{
|
||||
1 => deedType == BODType.Smith,
|
||||
2 => deedType == BODType.Tailor,
|
||||
3 => mat == BulkMaterialType.None && BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Iron,
|
||||
4 => mat == BulkMaterialType.DullCopper,
|
||||
5 => mat == BulkMaterialType.ShadowIron,
|
||||
6 => mat == BulkMaterialType.Copper,
|
||||
7 => mat == BulkMaterialType.Bronze,
|
||||
8 => mat == BulkMaterialType.Gold,
|
||||
9 => mat == BulkMaterialType.Agapite,
|
||||
10 => mat == BulkMaterialType.Verite,
|
||||
11 => mat == BulkMaterialType.Valorite,
|
||||
12 => mat == BulkMaterialType.None && BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Cloth,
|
||||
13 => mat == BulkMaterialType.None && BGTClassifier.Classify(deedType, itemType) == BulkGenericType.Leather,
|
||||
14 => mat == BulkMaterialType.Spined,
|
||||
15 => mat == BulkMaterialType.Horned,
|
||||
16 => mat == BulkMaterialType.Barbed,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
|
||||
public int GetIndexForPage(int page)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
while (page-- > 0)
|
||||
index += GetCountForIndex(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public int GetCountForIndex(int index)
|
||||
{
|
||||
int slots = 0;
|
||||
int count = 0;
|
||||
|
||||
List<IBOBEntry> list = m_List;
|
||||
|
||||
for (int i = index; i >= 0 && i < list.Count; ++i)
|
||||
{
|
||||
IBOBEntry entry = list[i];
|
||||
|
||||
if (CheckFilter(entry))
|
||||
{
|
||||
int add = entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
|
||||
|
||||
if (slots + add > 10)
|
||||
break;
|
||||
|
||||
slots += add;
|
||||
}
|
||||
|
||||
++count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public int GetPageForIndex(int index, int sizeDropped)
|
||||
{
|
||||
if (index <= 0)
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
int page = 0;
|
||||
int i;
|
||||
|
||||
List<IBOBEntry> list = m_List;
|
||||
for (i = 0; i < index && i < list.Count; i++)
|
||||
{
|
||||
IBOBEntry entry = list[i];
|
||||
if (!CheckFilter(entry))
|
||||
continue;
|
||||
|
||||
int add = entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
|
||||
count += add;
|
||||
if (count > 10)
|
||||
{
|
||||
page++;
|
||||
count = add;
|
||||
}
|
||||
}
|
||||
|
||||
/* now we are on the page of the bod preceding the dropped one.
|
||||
* next step: checking whether we have to remain where we are.
|
||||
* The counter i needs to be incremented as the bod to this very moment
|
||||
* has not yet been removed from m_List */
|
||||
i++;
|
||||
|
||||
/* if, for instance, a big bod of size 6 has been removed, smaller bods
|
||||
* might fall back into this page. Depending on their sizes, the page needs
|
||||
* to be adjusted accordingly. This is done now.
|
||||
*/
|
||||
if (count + sizeDropped > 10)
|
||||
{
|
||||
while (i < list.Count && count <= 10)
|
||||
{
|
||||
IBOBEntry entry = list[i];
|
||||
if (CheckFilter(entry))
|
||||
count += entry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (count > 10)
|
||||
page++;
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
public object GetMaterialName(BulkMaterialType mat, BODType type, Type itemType)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BODType.Smith:
|
||||
{
|
||||
switch (mat)
|
||||
{
|
||||
case BulkMaterialType.None: return 1062226;
|
||||
case BulkMaterialType.DullCopper: return 1018332;
|
||||
case BulkMaterialType.ShadowIron: return 1018333;
|
||||
case BulkMaterialType.Copper: return 1018334;
|
||||
case BulkMaterialType.Bronze: return 1018335;
|
||||
case BulkMaterialType.Gold: return 1018336;
|
||||
case BulkMaterialType.Agapite: return 1018337;
|
||||
case BulkMaterialType.Verite: return 1018338;
|
||||
case BulkMaterialType.Valorite: return 1018339;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case BODType.Tailor:
|
||||
{
|
||||
switch (mat)
|
||||
{
|
||||
case BulkMaterialType.None:
|
||||
{
|
||||
if (itemType.IsSubclassOf(typeof(BaseArmor)) || itemType.IsSubclassOf(typeof(BaseShoes)))
|
||||
return 1062235;
|
||||
|
||||
return 1044286;
|
||||
}
|
||||
case BulkMaterialType.Spined: return 1062236;
|
||||
case BulkMaterialType.Horned: return 1062237;
|
||||
case BulkMaterialType.Barbed: return 1062238;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return "Invalid";
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
int index = info.ButtonID;
|
||||
|
||||
switch (index)
|
||||
{
|
||||
case 0: // EXIT
|
||||
{
|
||||
break;
|
||||
}
|
||||
case 1: // Set Filter
|
||||
{
|
||||
m_From.SendGump(new BOBFilterGump(m_From, m_Book));
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Previous page
|
||||
{
|
||||
if (m_Page > 0)
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page - 1, m_List));
|
||||
|
||||
return;
|
||||
}
|
||||
case 3: // Next page
|
||||
{
|
||||
if (GetIndexForPage(m_Page + 1) < m_List.Count)
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page + 1, m_List));
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: // Price all
|
||||
{
|
||||
if (m_Book.IsChildOf(m_From.Backpack))
|
||||
{
|
||||
m_From.Prompt = new SetPricePrompt(m_Book, null, m_Page, m_List);
|
||||
m_From.SendMessage("Type in a price for all deeds in the book:");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
index -= 5;
|
||||
|
||||
int type = index % 2;
|
||||
index /= 2;
|
||||
|
||||
if (index < 0 || index >= m_List.Count)
|
||||
break;
|
||||
|
||||
IBOBEntry bobEntry = m_List[index];
|
||||
|
||||
if (!m_Book.Entries.Contains(bobEntry))
|
||||
{
|
||||
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
|
||||
break;
|
||||
}
|
||||
|
||||
if (type == 0) // Drop
|
||||
{
|
||||
if (m_Book.IsChildOf(m_From.Backpack))
|
||||
{
|
||||
Item item = bobEntry.Reconstruct();
|
||||
|
||||
Container pack = m_From.Backpack;
|
||||
if (pack?.CheckHold(m_From, item, true, true, 0,
|
||||
item.PileWeight + item.TotalWeight) != true)
|
||||
{
|
||||
m_From.SendLocalizedMessage(503204); // You do not have room in your backpack for this
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Book.IsChildOf(m_From.Backpack))
|
||||
{
|
||||
int sizeOfDroppedBod = bobEntry is BOBLargeEntry entry ? entry.Entries.Length : 1;
|
||||
|
||||
m_From.AddToBackpack(item);
|
||||
m_From.SendLocalizedMessage(
|
||||
1045152); // The bulk order deed has been placed in your backpack.
|
||||
m_Book.Entries.Remove(bobEntry);
|
||||
m_Book.InvalidateProperties();
|
||||
|
||||
if (m_Book.Entries.Count / 5 < m_Book.ItemCount)
|
||||
{
|
||||
m_Book.ItemCount--;
|
||||
m_Book.InvalidateItems();
|
||||
}
|
||||
|
||||
if (m_Book.Entries.Count > 0)
|
||||
{
|
||||
m_Page = GetPageForIndex(index, sizeOfDroppedBod);
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendLocalizedMessage(1062381); // The book is empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Set Price | Buy
|
||||
{
|
||||
if (m_Book.IsChildOf(m_From.Backpack))
|
||||
{
|
||||
m_From.Prompt = new SetPricePrompt(m_Book, bobEntry, m_Page, m_List);
|
||||
m_From.SendLocalizedMessage(1062383); // Type in a price for the deed:
|
||||
}
|
||||
else if (m_Book.RootParent is PlayerVendor pv)
|
||||
{
|
||||
VendorItem vi = pv.GetVendorItem(m_Book);
|
||||
|
||||
if (vi?.IsForSale != false)
|
||||
return;
|
||||
|
||||
int sizeOfDroppedBod = bobEntry is BOBLargeEntry largeEntry ? largeEntry.Entries.Length : 1;
|
||||
int price = bobEntry.Price;
|
||||
|
||||
if (price == 0)
|
||||
{
|
||||
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Book.Entries.Count > 0)
|
||||
{
|
||||
m_Page = GetPageForIndex(index, sizeOfDroppedBod);
|
||||
m_From.SendGump(new BODBuyGump(m_From, m_Book, bobEntry, m_Page, price));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendLocalizedMessage(1062381); // The book is emptz
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SetPricePrompt : Prompt
|
||||
{
|
||||
private readonly BulkOrderBook m_Book;
|
||||
private readonly List<IBOBEntry> m_List;
|
||||
private readonly IBOBEntry m_Entry;
|
||||
private readonly int m_Page;
|
||||
|
||||
public SetPricePrompt(BulkOrderBook book, IBOBEntry entry, int page, List<IBOBEntry> list)
|
||||
{
|
||||
m_Book = book;
|
||||
m_Entry = entry;
|
||||
m_Page = page;
|
||||
m_List = list;
|
||||
}
|
||||
|
||||
public override void OnResponse(Mobile from, string text)
|
||||
{
|
||||
if (m_Entry != null && !m_Book.Entries.Contains(m_Entry))
|
||||
{
|
||||
from.SendLocalizedMessage(1062382); // The deed selected is not available.
|
||||
return;
|
||||
}
|
||||
|
||||
int price = Utility.ToInt32(text);
|
||||
|
||||
if (price < 0 || price > 250000000)
|
||||
{
|
||||
from.SendLocalizedMessage(1062390); // The price you requested is outrageous!
|
||||
}
|
||||
else if (m_Entry == null)
|
||||
{
|
||||
for (int i = 0; i < m_List.Count; ++i)
|
||||
{
|
||||
IBOBEntry entry = m_List[i];
|
||||
|
||||
if (!m_Book.Entries.Contains(entry))
|
||||
continue;
|
||||
|
||||
entry.Price = price;
|
||||
}
|
||||
|
||||
from.SendMessage("Deed prices set.");
|
||||
|
||||
if (from is PlayerMobile mobile)
|
||||
mobile.SendGump(new BOBGump(mobile, m_Book, m_Page, m_List));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Entry.Price = price;
|
||||
from.SendLocalizedMessage(1062384); // Deed price set.
|
||||
if (from is PlayerMobile mobile)
|
||||
mobile.SendGump(new BOBGump(mobile, m_Book, m_Page, m_List));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
105
Projects/UOContent/Engines/BulkOrders/Books/BOBLargeEntry.cs
Normal file
105
Projects/UOContent/Engines/BulkOrders/Books/BOBLargeEntry.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBLargeEntry : IBOBEntry
|
||||
{
|
||||
public BOBLargeEntry(LargeBOD bod)
|
||||
{
|
||||
RequireExceptional = bod.RequireExceptional;
|
||||
|
||||
if (bod is LargeTailorBOD)
|
||||
DeedType = BODType.Tailor;
|
||||
else if (bod is LargeSmithBOD)
|
||||
DeedType = BODType.Smith;
|
||||
|
||||
Material = bod.Material;
|
||||
AmountMax = bod.AmountMax;
|
||||
|
||||
Entries = new BOBLargeSubEntry[bod.Entries.Length];
|
||||
|
||||
for (int i = 0; i < Entries.Length; ++i)
|
||||
Entries[i] = new BOBLargeSubEntry(bod.Entries[i]);
|
||||
}
|
||||
|
||||
public BOBLargeEntry(IGenericReader reader)
|
||||
{
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
RequireExceptional = reader.ReadBool();
|
||||
|
||||
DeedType = (BODType)reader.ReadEncodedInt();
|
||||
|
||||
Material = (BulkMaterialType)reader.ReadEncodedInt();
|
||||
AmountMax = reader.ReadEncodedInt();
|
||||
Price = reader.ReadEncodedInt();
|
||||
|
||||
Entries = new BOBLargeSubEntry[reader.ReadEncodedInt()];
|
||||
|
||||
for (int i = 0; i < Entries.Length; ++i)
|
||||
Entries[i] = new BOBLargeSubEntry(reader);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RequireExceptional { get; }
|
||||
|
||||
public BODType DeedType { get; }
|
||||
|
||||
public BulkMaterialType Material { get; }
|
||||
|
||||
public int AmountMax { get; }
|
||||
|
||||
public int Price { get; set; }
|
||||
|
||||
public BOBLargeSubEntry[] Entries { get; }
|
||||
|
||||
public Item Reconstruct()
|
||||
{
|
||||
LargeBOD bod = null;
|
||||
|
||||
if (DeedType == BODType.Smith)
|
||||
bod = new LargeSmithBOD(AmountMax, RequireExceptional, Material, ReconstructEntries());
|
||||
else if (DeedType == BODType.Tailor)
|
||||
bod = new LargeTailorBOD(AmountMax, RequireExceptional, Material, ReconstructEntries());
|
||||
|
||||
for (int i = 0; bod?.Entries.Length >= i; ++i)
|
||||
bod.Entries[i].Owner = bod;
|
||||
|
||||
return bod;
|
||||
}
|
||||
|
||||
private LargeBulkEntry[] ReconstructEntries()
|
||||
{
|
||||
LargeBulkEntry[] entries = new LargeBulkEntry[Entries.Length];
|
||||
|
||||
for (int i = 0; i < Entries.Length; ++i)
|
||||
entries[i] = new LargeBulkEntry(null,
|
||||
new SmallBulkEntry(Entries[i].ItemType, Entries[i].Number, Entries[i].Graphic))
|
||||
{ Amount = Entries[i].AmountCur };
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
writer.Write(RequireExceptional);
|
||||
|
||||
writer.WriteEncodedInt((int)DeedType);
|
||||
writer.WriteEncodedInt((int)Material);
|
||||
writer.WriteEncodedInt(AmountMax);
|
||||
writer.WriteEncodedInt(Price);
|
||||
|
||||
writer.WriteEncodedInt(Entries.Length);
|
||||
|
||||
for (int i = 0; i < Entries.Length; ++i)
|
||||
Entries[i].Serialize(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBLargeSubEntry
|
||||
{
|
||||
public BOBLargeSubEntry(LargeBulkEntry lbe)
|
||||
{
|
||||
ItemType = lbe.Details.Type;
|
||||
AmountCur = lbe.Amount;
|
||||
Number = lbe.Details.Number;
|
||||
Graphic = lbe.Details.Graphic;
|
||||
}
|
||||
|
||||
public BOBLargeSubEntry(IGenericReader reader)
|
||||
{
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
string type = reader.ReadString();
|
||||
|
||||
if (type != null)
|
||||
ItemType = AssemblyHandler.FindFirstTypeForName(type);
|
||||
|
||||
AmountCur = reader.ReadEncodedInt();
|
||||
Number = reader.ReadEncodedInt();
|
||||
Graphic = reader.ReadEncodedInt();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Type ItemType { get; }
|
||||
|
||||
public int AmountCur { get; }
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public int Graphic { get; }
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
writer.Write(ItemType?.FullName);
|
||||
|
||||
writer.WriteEncodedInt(AmountCur);
|
||||
writer.WriteEncodedInt(Number);
|
||||
writer.WriteEncodedInt(Graphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Projects/UOContent/Engines/BulkOrders/Books/BOBSmallEntry.cs
Normal file
100
Projects/UOContent/Engines/BulkOrders/Books/BOBSmallEntry.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BOBSmallEntry : IBOBEntry
|
||||
{
|
||||
public BOBSmallEntry(SmallBOD bod)
|
||||
{
|
||||
ItemType = bod.Type;
|
||||
RequireExceptional = bod.RequireExceptional;
|
||||
|
||||
if (bod is SmallTailorBOD)
|
||||
DeedType = BODType.Tailor;
|
||||
else if (bod is SmallSmithBOD)
|
||||
DeedType = BODType.Smith;
|
||||
|
||||
Material = bod.Material;
|
||||
AmountCur = bod.AmountCur;
|
||||
AmountMax = bod.AmountMax;
|
||||
Number = bod.Number;
|
||||
Graphic = bod.Graphic;
|
||||
}
|
||||
|
||||
public BOBSmallEntry(IGenericReader reader)
|
||||
{
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
string type = reader.ReadString();
|
||||
|
||||
if (type != null)
|
||||
ItemType = AssemblyHandler.FindFirstTypeForName(type);
|
||||
|
||||
RequireExceptional = reader.ReadBool();
|
||||
|
||||
DeedType = (BODType)reader.ReadEncodedInt();
|
||||
|
||||
Material = (BulkMaterialType)reader.ReadEncodedInt();
|
||||
AmountCur = reader.ReadEncodedInt();
|
||||
AmountMax = reader.ReadEncodedInt();
|
||||
Number = reader.ReadEncodedInt();
|
||||
Graphic = reader.ReadEncodedInt();
|
||||
Price = reader.ReadEncodedInt();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Type ItemType { get; }
|
||||
|
||||
public bool RequireExceptional { get; }
|
||||
|
||||
public BODType DeedType { get; }
|
||||
|
||||
public BulkMaterialType Material { get; }
|
||||
|
||||
public int AmountCur { get; }
|
||||
|
||||
public int AmountMax { get; }
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public int Graphic { get; }
|
||||
|
||||
public int Price { get; set; }
|
||||
|
||||
public Item Reconstruct()
|
||||
{
|
||||
SmallBOD bod = null;
|
||||
|
||||
if (DeedType == BODType.Smith)
|
||||
bod = new SmallSmithBOD(AmountCur, AmountMax, ItemType, Number, Graphic, RequireExceptional, Material);
|
||||
else if (DeedType == BODType.Tailor)
|
||||
bod = new SmallTailorBOD(AmountCur, AmountMax, ItemType, Number, Graphic, RequireExceptional, Material);
|
||||
|
||||
return bod;
|
||||
}
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
writer.Write(ItemType?.FullName);
|
||||
|
||||
writer.Write(RequireExceptional);
|
||||
|
||||
writer.WriteEncodedInt((int)DeedType);
|
||||
writer.WriteEncodedInt((int)Material);
|
||||
writer.WriteEncodedInt(AmountCur);
|
||||
writer.WriteEncodedInt(AmountMax);
|
||||
writer.WriteEncodedInt(Number);
|
||||
writer.WriteEncodedInt(Graphic);
|
||||
writer.WriteEncodedInt(Price);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Projects/UOContent/Engines/BulkOrders/Books/BODBuyGump.cs
Normal file
122
Projects/UOContent/Engines/BulkOrders/Books/BODBuyGump.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using Server.Gumps;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BODBuyGump : Gump
|
||||
{
|
||||
private readonly BulkOrderBook m_Book;
|
||||
private readonly PlayerMobile m_From;
|
||||
private readonly IBOBEntry m_Entry;
|
||||
private readonly int m_Page;
|
||||
private readonly int m_Price;
|
||||
|
||||
public BODBuyGump(PlayerMobile from, BulkOrderBook book, IBOBEntry entry, int page, int price) : base(100, 200)
|
||||
{
|
||||
m_From = from;
|
||||
m_Book = book;
|
||||
m_Entry = entry;
|
||||
m_Price = price;
|
||||
m_Page = page;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(100, 10, 300, 150, 5054);
|
||||
|
||||
AddHtmlLocalized(125, 20, 250, 24, 1019070); // You have agreed to purchase:
|
||||
AddHtmlLocalized(125, 45, 250, 24, 1045151); // a bulk order deed
|
||||
|
||||
AddHtmlLocalized(125, 70, 250, 24, 1019071); // for the amount of:
|
||||
AddLabel(125, 95, 0, price.ToString());
|
||||
|
||||
AddButton(250, 130, 4005, 4007, 1);
|
||||
AddHtmlLocalized(282, 130, 100, 24, 1011012); // CANCEL
|
||||
|
||||
AddButton(120, 130, 4005, 4007, 2);
|
||||
AddHtmlLocalized(152, 130, 100, 24, 1011036); // OKAY
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (info.ButtonID != 2)
|
||||
{
|
||||
m_From.SendLocalizedMessage(503207); // Cancelled purchase.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(m_Book.RootParent is PlayerVendor pv))
|
||||
{
|
||||
m_From.SendLocalizedMessage(1062382); // The deed selected is not available.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_Book.Entries.Contains(m_Entry))
|
||||
{
|
||||
pv.SayTo(m_From, 1062382); // The deed selected is not available.
|
||||
return;
|
||||
}
|
||||
|
||||
int price = 0;
|
||||
|
||||
if (pv.GetVendorItem(m_Book)?.IsForSale == false)
|
||||
price = m_Entry.Price;
|
||||
|
||||
if (price != m_Price)
|
||||
{
|
||||
pv.SayTo(m_From,
|
||||
"The price has been been changed. If you like, you may offer to purchase the item again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (price == 0)
|
||||
{
|
||||
pv.SayTo(m_From, 1062382); // The deed selected is not available.
|
||||
return;
|
||||
}
|
||||
|
||||
Item item = m_Entry.Reconstruct();
|
||||
|
||||
pv.Say(m_From.Name);
|
||||
|
||||
Container pack = m_From.Backpack;
|
||||
|
||||
if (pack?.CheckHold(m_From, item, true, true, 0,
|
||||
item.PileWeight + item.TotalWeight) != true)
|
||||
{
|
||||
pv.SayTo(m_From, 503204); // You do not have room in your backpack for this
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
|
||||
item.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pack.ConsumeTotal(typeof(Gold), price) || Banker.Withdraw(m_From, price))
|
||||
{
|
||||
m_Book.Entries.Remove(m_Entry);
|
||||
m_Book.InvalidateProperties();
|
||||
pv.HoldGold += price;
|
||||
m_From.AddToBackpack(item);
|
||||
m_From.SendLocalizedMessage(
|
||||
1045152); // The bulk order deed has been placed in your backpack.
|
||||
|
||||
if (m_Book.Entries.Count / 5 < m_Book.ItemCount)
|
||||
{
|
||||
m_Book.ItemCount--;
|
||||
m_Book.InvalidateItems();
|
||||
}
|
||||
|
||||
if (m_Book.Entries.Count > 0)
|
||||
m_From.SendGump(new BOBGump(m_From, m_Book, m_Page));
|
||||
else
|
||||
m_From.SendLocalizedMessage(1062381); // The book is empty.
|
||||
}
|
||||
else
|
||||
{
|
||||
pv.SayTo(m_From, 503205); // You cannot afford this item.
|
||||
item.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Projects/UOContent/Engines/BulkOrders/Books/BODType.cs
Normal file
8
Projects/UOContent/Engines/BulkOrders/Books/BODType.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public enum BODType
|
||||
{
|
||||
Smith,
|
||||
Tailor
|
||||
}
|
||||
}
|
||||
305
Projects/UOContent/Engines/BulkOrders/Books/BulkOrderBook.cs
Normal file
305
Projects/UOContent/Engines/BulkOrders/Books/BulkOrderBook.cs
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
using System.Collections.Generic;
|
||||
using Server.Gumps;
|
||||
using Server.Multis;
|
||||
using Server.Prompts;
|
||||
using Server.Mobiles;
|
||||
using Server.ContextMenus;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class BulkOrderBook : Item, ISecurable
|
||||
{
|
||||
private string m_BookName;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public string BookName
|
||||
{
|
||||
get => m_BookName;
|
||||
set { m_BookName = value; InvalidateProperties(); }
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public SecureLevel Level { get; set; }
|
||||
|
||||
public List<IBOBEntry> Entries { get; private set; }
|
||||
|
||||
public BOBFilter Filter { get; private set; }
|
||||
|
||||
public int ItemCount { get; set; }
|
||||
|
||||
[Constructible]
|
||||
public BulkOrderBook() : base(0x2259)
|
||||
{
|
||||
Weight = 1.0;
|
||||
LootType = LootType.Blessed;
|
||||
|
||||
Entries = new List<IBOBEntry>();
|
||||
Filter = new BOBFilter();
|
||||
|
||||
Level = SecureLevel.CoOwners;
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (!from.InRange(GetWorldLocation(), 2))
|
||||
from.LocalOverheadMessage(Network.MessageType.Regular, 0x3B2, 1019045); // I can't reach that.
|
||||
else if (Entries.Count == 0)
|
||||
from.SendLocalizedMessage(1062381); // The book is empty.
|
||||
else if (from is PlayerMobile mobile)
|
||||
mobile.SendGump(new BOBGump(mobile, this));
|
||||
}
|
||||
|
||||
public override void OnDoubleClickSecureTrade(Mobile from)
|
||||
{
|
||||
if (!from.InRange(GetWorldLocation(), 2))
|
||||
{
|
||||
from.SendLocalizedMessage(500446); // That is too far away.
|
||||
}
|
||||
else if (Entries.Count == 0)
|
||||
{
|
||||
from.SendLocalizedMessage(1062381); // The book is empty.
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendGump(new BOBGump((PlayerMobile)from, this));
|
||||
|
||||
SecureTrade trade = GetSecureTradeCont()?.Trade;
|
||||
|
||||
if (trade?.From.Mobile == from)
|
||||
trade.To.Mobile.SendGump(new BOBGump((PlayerMobile)trade.To.Mobile, this));
|
||||
else if (trade?.To.Mobile == from)
|
||||
trade.From.Mobile.SendGump(new BOBGump((PlayerMobile)trade.From.Mobile, this));
|
||||
}
|
||||
}
|
||||
|
||||
public override bool OnDragDrop(Mobile from, Item dropped)
|
||||
{
|
||||
if (dropped is BaseBOD)
|
||||
{
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1062385); // You must have the book in your backpack to add deeds to it.
|
||||
return false;
|
||||
}
|
||||
if (!from.Backpack.CheckHold(from, dropped, true, true))
|
||||
return false;
|
||||
if (Entries.Count < 500)
|
||||
{
|
||||
if (dropped is LargeBOD bod)
|
||||
Entries.Add(new BOBLargeEntry(bod));
|
||||
else
|
||||
Entries.Add(new BOBSmallEntry((SmallBOD)dropped));
|
||||
|
||||
InvalidateProperties();
|
||||
|
||||
if (Entries.Count / 5 > ItemCount)
|
||||
{
|
||||
ItemCount++;
|
||||
InvalidateItems();
|
||||
}
|
||||
|
||||
from.SendSound(0x42, GetWorldLocation());
|
||||
from.SendLocalizedMessage(1062386); // Deed added to book.
|
||||
|
||||
if (from is PlayerMobile pm)
|
||||
pm.SendGump(new BOBGump(pm, this));
|
||||
|
||||
dropped.Delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(1062387); // The book is full of deeds.
|
||||
return false;
|
||||
}
|
||||
|
||||
from.SendLocalizedMessage(1062388); // That is not a bulk order deed.
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetTotal(TotalType type)
|
||||
{
|
||||
int total = base.GetTotal(type);
|
||||
|
||||
if (type == TotalType.Items)
|
||||
total = ItemCount;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public void InvalidateItems()
|
||||
{
|
||||
if (RootParent is Mobile m)
|
||||
{
|
||||
m.UpdateTotals();
|
||||
InvalidateContainers(Parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void InvalidateContainers(IEntity parent)
|
||||
{
|
||||
if (parent is Container c)
|
||||
{
|
||||
c.InvalidateProperties();
|
||||
InvalidateContainers(c.Parent);
|
||||
}
|
||||
}
|
||||
|
||||
public BulkOrderBook(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(2); // version
|
||||
|
||||
writer.Write(ItemCount);
|
||||
|
||||
writer.Write((int)Level);
|
||||
|
||||
writer.Write(m_BookName);
|
||||
|
||||
Filter.Serialize(writer);
|
||||
|
||||
writer.WriteEncodedInt(Entries.Count);
|
||||
|
||||
for (int i = 0; i < Entries.Count; ++i)
|
||||
{
|
||||
object obj = Entries[i];
|
||||
|
||||
if (obj is BOBLargeEntry entry)
|
||||
{
|
||||
writer.WriteEncodedInt(0);
|
||||
entry.Serialize(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteEncodedInt(1);
|
||||
((BOBSmallEntry)obj).Serialize(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
ItemCount = reader.ReadInt();
|
||||
goto case 1;
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
Level = (SecureLevel)reader.ReadInt();
|
||||
goto case 0;
|
||||
}
|
||||
case 0:
|
||||
{
|
||||
m_BookName = reader.ReadString();
|
||||
|
||||
Filter = new BOBFilter(reader);
|
||||
|
||||
int count = reader.ReadEncodedInt();
|
||||
|
||||
Entries = new List<IBOBEntry>(count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
int v = reader.ReadEncodedInt();
|
||||
|
||||
switch (v)
|
||||
{
|
||||
case 0: Entries.Add(new BOBLargeEntry(reader)); break;
|
||||
case 1: Entries.Add(new BOBSmallEntry(reader)); break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1062344, Entries.Count.ToString()); // Deeds in book: ~1_val~
|
||||
|
||||
if (!string.IsNullOrEmpty(m_BookName))
|
||||
list.Add(1062481, m_BookName); // Book Name: ~1_val~
|
||||
}
|
||||
|
||||
public override void OnSingleClick(Mobile from)
|
||||
{
|
||||
base.OnSingleClick(from);
|
||||
|
||||
LabelTo(from, 1062344, Entries.Count.ToString()); // Deeds in book: ~1_val~
|
||||
|
||||
if (!string.IsNullOrEmpty(m_BookName))
|
||||
LabelTo(from, 1062481, m_BookName);
|
||||
}
|
||||
|
||||
public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
|
||||
{
|
||||
base.GetContextMenuEntries(from, list);
|
||||
|
||||
if (from.CheckAlive() && IsChildOf(from.Backpack))
|
||||
list.Add(new NameBookEntry(from, this));
|
||||
|
||||
SetSecureLevelEntry.AddTo(from, this, list);
|
||||
}
|
||||
|
||||
private class NameBookEntry : ContextMenuEntry
|
||||
{
|
||||
private readonly Mobile m_From;
|
||||
private readonly BulkOrderBook m_Book;
|
||||
|
||||
public NameBookEntry(Mobile from, BulkOrderBook book) : base(6216)
|
||||
{
|
||||
m_From = from;
|
||||
m_Book = book;
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
if (m_From.CheckAlive() && m_Book.IsChildOf(m_From.Backpack))
|
||||
{
|
||||
m_From.Prompt = new NameBookPrompt(m_Book);
|
||||
m_From.SendLocalizedMessage(1062479); // Type in the new name of the book:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NameBookPrompt : Prompt
|
||||
{
|
||||
private readonly BulkOrderBook m_Book;
|
||||
|
||||
public NameBookPrompt(BulkOrderBook book) => m_Book = book;
|
||||
|
||||
public override void OnResponse(Mobile from, string text)
|
||||
{
|
||||
if (text.Length > 40)
|
||||
text = text.Substring(0, 40);
|
||||
|
||||
if (from.CheckAlive() && m_Book.IsChildOf(from.Backpack))
|
||||
{
|
||||
m_Book.BookName = Utility.FixHtml(text.Trim());
|
||||
|
||||
from.SendLocalizedMessage(1062480); // The bulk order book's name has been changed.
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnCancel(Mobile from)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Projects/UOContent/Engines/BulkOrders/Books/IBOBEntry.cs
Normal file
12
Projects/UOContent/Engines/BulkOrders/Books/IBOBEntry.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public interface IBOBEntry
|
||||
{
|
||||
bool RequireExceptional { get; }
|
||||
BODType DeedType { get; }
|
||||
BulkMaterialType Material { get; }
|
||||
int AmountMax { get; }
|
||||
int Price { get; set; }
|
||||
Item Reconstruct();
|
||||
}
|
||||
}
|
||||
40
Projects/UOContent/Engines/BulkOrders/BulkMaterialType.cs
Normal file
40
Projects/UOContent/Engines/BulkOrders/BulkMaterialType.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public enum BulkMaterialType
|
||||
{
|
||||
None,
|
||||
DullCopper,
|
||||
ShadowIron,
|
||||
Copper,
|
||||
Bronze,
|
||||
Gold,
|
||||
Agapite,
|
||||
Verite,
|
||||
Valorite,
|
||||
Spined,
|
||||
Horned,
|
||||
Barbed
|
||||
}
|
||||
|
||||
public enum BulkGenericType
|
||||
{
|
||||
Iron,
|
||||
Cloth,
|
||||
Leather
|
||||
}
|
||||
|
||||
public class BGTClassifier
|
||||
{
|
||||
public static BulkGenericType Classify(BODType deedType, Type itemType)
|
||||
{
|
||||
if (deedType != BODType.Tailor)
|
||||
return BulkGenericType.Iron;
|
||||
|
||||
return itemType == null || itemType.IsSubclassOf(typeof(BaseArmor)) || itemType.IsSubclassOf(typeof(BaseShoes))
|
||||
? BulkGenericType.Leather : BulkGenericType.Cloth;
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Projects/UOContent/Engines/BulkOrders/LargeBOD.cs
Normal file
170
Projects/UOContent/Engines/BulkOrders/LargeBOD.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public abstract class LargeBOD : BaseBOD
|
||||
{
|
||||
private LargeBulkEntry[] m_Entries;
|
||||
|
||||
public LargeBulkEntry[] Entries
|
||||
{
|
||||
get => m_Entries;
|
||||
set { m_Entries = value; InvalidateProperties(); }
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public override bool Complete
|
||||
{
|
||||
get
|
||||
{
|
||||
for (int i = 0; i < m_Entries.Length; ++i)
|
||||
if (m_Entries[i].Amount < AmountMax)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override int LabelNumber => 1045151; // a bulk order deed
|
||||
|
||||
public LargeBOD(int hue, int amountMax, bool requireExeptional, BulkMaterialType material, LargeBulkEntry[] entries) : base(hue, amountMax, requireExeptional, material) =>
|
||||
m_Entries = entries;
|
||||
|
||||
public LargeBOD()
|
||||
{
|
||||
}
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1060655); // large bulk order
|
||||
|
||||
if (RequireExceptional)
|
||||
list.Add(1045141); // All items must be exceptional.
|
||||
|
||||
if (Material != BulkMaterialType.None)
|
||||
list.Add(LargeBODGump.GetMaterialNumberFor(Material)); // All items must be made with x material.
|
||||
|
||||
list.Add(1060656, AmountMax.ToString()); // amount to make: ~1_val~
|
||||
|
||||
for (int i = 0; i < m_Entries.Length; ++i)
|
||||
list.Add(1060658 + i, "#{0}\t{1}", m_Entries[i].Details.Number, m_Entries[i].Amount); // ~1_val~: ~2_val~
|
||||
}
|
||||
|
||||
public override void OnDoubleClickNotAccessible(Mobile from)
|
||||
{
|
||||
OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public override void OnDoubleClickSecureTrade(Mobile from)
|
||||
{
|
||||
OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (IsChildOf(from.Backpack) || InSecureTrade || RootParent is PlayerVendor)
|
||||
from.SendGump(new LargeBODGump(from, this));
|
||||
else
|
||||
from.SendLocalizedMessage(1045156); // You must have the deed in your backpack to use it.
|
||||
}
|
||||
|
||||
public override void EndCombine(Mobile from, Item item)
|
||||
{
|
||||
if (!(item is SmallBOD small))
|
||||
{
|
||||
from.SendLocalizedMessage(1045159); // That is not a bulk order.
|
||||
return;
|
||||
}
|
||||
|
||||
LargeBulkEntry entry = null;
|
||||
|
||||
for (int i = 0; i < m_Entries.Length; ++i)
|
||||
if (m_Entries[i].Details.Type == small.Type)
|
||||
{
|
||||
entry = m_Entries[i];
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
from.SendLocalizedMessage(1045160); // That is not a bulk order for this large request.
|
||||
}
|
||||
else if (RequireExceptional && !small.RequireExceptional)
|
||||
{
|
||||
from.SendLocalizedMessage(1045161); // Both orders must be of exceptional quality.
|
||||
}
|
||||
else if (Material >= BulkMaterialType.DullCopper && Material <= BulkMaterialType.Valorite &&
|
||||
small.Material != Material)
|
||||
{
|
||||
from.SendLocalizedMessage(1045162); // Both orders must use the same ore type.
|
||||
}
|
||||
else if (Material >= BulkMaterialType.Spined && Material <= BulkMaterialType.Barbed &&
|
||||
small.Material != Material)
|
||||
{
|
||||
from.SendLocalizedMessage(1049351); // Both orders must use the same leather type.
|
||||
}
|
||||
else if (AmountMax != small.AmountMax)
|
||||
{
|
||||
from.SendLocalizedMessage(1045163); // The two orders have different requested amounts and cannot be combined.
|
||||
}
|
||||
else if (small.AmountCur < small.AmountMax)
|
||||
{
|
||||
from.SendLocalizedMessage(1045164); // The order to combine with is not completed.
|
||||
}
|
||||
else if (entry.Amount >= AmountMax)
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1045166); // The maximum amount of requested items have already been combined to this deed.
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.Amount += small.AmountCur;
|
||||
small.Delete();
|
||||
|
||||
from.SendLocalizedMessage(1045165); // The orders have been combined.
|
||||
from.SendGump(new LargeBODGump(from, this));
|
||||
|
||||
if (!Complete)
|
||||
BeginCombine(from);
|
||||
}
|
||||
}
|
||||
|
||||
public LargeBOD(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.WriteEncodedInt(0); // version
|
||||
|
||||
writer.Write(m_Entries.Length);
|
||||
|
||||
for (int i = 0; i < m_Entries.Length; ++i)
|
||||
m_Entries[i].Serialize(writer);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadEncodedInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_Entries = new LargeBulkEntry[reader.ReadInt()];
|
||||
|
||||
for (int i = 0; i < m_Entries.Length; ++i)
|
||||
m_Entries[i] = new LargeBulkEntry(this, reader);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Projects/UOContent/Engines/BulkOrders/LargeBODAcceptGump.cs
Normal file
110
Projects/UOContent/Engines/BulkOrders/LargeBODAcceptGump.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class LargeBODAcceptGump : Gump
|
||||
{
|
||||
private readonly LargeBOD m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public LargeBODAcceptGump(Mobile from, LargeBOD deed) : base(50, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Deed = deed;
|
||||
|
||||
m_From.CloseGump<LargeBODAcceptGump>();
|
||||
m_From.CloseGump<SmallBODAcceptGump>();
|
||||
|
||||
LargeBulkEntry[] entries = deed.Entries;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(25, 10, 430, 240 + entries.Length * 24, 5054);
|
||||
|
||||
AddImageTiled(33, 20, 413, 221 + entries.Length * 24, 2624);
|
||||
AddAlphaRegion(33, 20, 413, 221 + entries.Length * 24);
|
||||
|
||||
AddImage(20, 5, 10460);
|
||||
AddImage(430, 5, 10460);
|
||||
AddImage(20, 225 + entries.Length * 24, 10460);
|
||||
AddImage(430, 225 + entries.Length * 24, 10460);
|
||||
|
||||
AddHtmlLocalized(180, 25, 120, 20, 1045134, 0x7FFF); // A large bulk order
|
||||
|
||||
AddHtmlLocalized(40, 48, 350, 20, 1045135, 0x7FFF); // Ah! Thanks for the goods! Would you help me out?
|
||||
|
||||
AddHtmlLocalized(40, 72, 210, 20, 1045138, 0x7FFF); // Amount to make:
|
||||
AddLabel(250, 72, 1152, deed.AmountMax.ToString());
|
||||
|
||||
AddHtmlLocalized(40, 96, 120, 20, 1045137, 0x7FFF); // Items requested:
|
||||
|
||||
int y = 120;
|
||||
|
||||
for (int i = 0; i < entries.Length; ++i, y += 24)
|
||||
AddHtmlLocalized(40, y, 210, 20, entries[i].Details.Number, 0x7FFF);
|
||||
|
||||
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
|
||||
{
|
||||
AddHtmlLocalized(40, y, 210, 20, 1045140, 0x7FFF); // Special requirements to meet:
|
||||
y += 24;
|
||||
|
||||
if (deed.RequireExceptional)
|
||||
{
|
||||
AddHtmlLocalized(40, y, 350, 20, 1045141, 0x7FFF); // All items must be exceptional.
|
||||
y += 24;
|
||||
}
|
||||
|
||||
if (deed.Material != BulkMaterialType.None)
|
||||
{
|
||||
AddHtmlLocalized(40, y, 350, 20, GetMaterialNumberFor(deed.Material), 0x7FFF); // All items must be made with x material.
|
||||
y += 24;
|
||||
}
|
||||
}
|
||||
|
||||
AddHtmlLocalized(40, 192 + entries.Length * 24, 350, 20, 1045139, 0x7FFF); // Do you want to accept this order?
|
||||
|
||||
AddButton(100, 216 + entries.Length * 24, 4005, 4007, 1);
|
||||
AddHtmlLocalized(135, 216 + entries.Length * 24, 120, 20, 1006044, 0x7FFF); // Ok
|
||||
|
||||
AddButton(275, 216 + entries.Length * 24, 4005, 4007, 0);
|
||||
AddHtmlLocalized(310, 216 + entries.Length * 24, 120, 20, 1011012, 0x7FFF); // CANCEL
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (info.ButtonID == 1) // Ok
|
||||
{
|
||||
if (m_From.PlaceInBackpack(m_Deed))
|
||||
{
|
||||
m_From.SendLocalizedMessage(1045152); // The bulk order deed has been placed in your backpack.
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendLocalizedMessage(1045150); // There is not enough room in your backpack for the deed.
|
||||
m_Deed.Delete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Deed.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnServerClose(NetState owner)
|
||||
{
|
||||
if (m_Deed?.Deleted == false)
|
||||
m_Deed.Delete();
|
||||
}
|
||||
|
||||
public static int GetMaterialNumberFor(BulkMaterialType material)
|
||||
{
|
||||
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
|
||||
return 1045142 + (material - BulkMaterialType.DullCopper);
|
||||
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
|
||||
return 1049348 + (material - BulkMaterialType.Spined);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Projects/UOContent/Engines/BulkOrders/LargeBODGump.cs
Normal file
98
Projects/UOContent/Engines/BulkOrders/LargeBODGump.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class LargeBODGump : Gump
|
||||
{
|
||||
private readonly LargeBOD m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public LargeBODGump(Mobile from, LargeBOD deed) : base(25, 25)
|
||||
{
|
||||
m_From = from;
|
||||
m_Deed = deed;
|
||||
|
||||
m_From.CloseGump<LargeBODGump>();
|
||||
m_From.CloseGump<SmallBODGump>();
|
||||
|
||||
LargeBulkEntry[] entries = deed.Entries;
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(50, 10, 455, 236 + entries.Length * 24, 5054);
|
||||
|
||||
AddImageTiled(58, 20, 438, 217 + entries.Length * 24, 2624);
|
||||
AddAlphaRegion(58, 20, 438, 217 + entries.Length * 24);
|
||||
|
||||
AddImage(45, 5, 10460);
|
||||
AddImage(480, 5, 10460);
|
||||
AddImage(45, 221 + entries.Length * 24, 10460);
|
||||
AddImage(480, 221 + entries.Length * 24, 10460);
|
||||
|
||||
AddHtmlLocalized(225, 25, 120, 20, 1045134, 0x7FFF); // A large bulk order
|
||||
|
||||
AddHtmlLocalized(75, 48, 250, 20, 1045138, 0x7FFF); // Amount to make:
|
||||
AddLabel(275, 48, 1152, deed.AmountMax.ToString());
|
||||
|
||||
AddHtmlLocalized(75, 72, 120, 20, 1045137, 0x7FFF); // Items requested:
|
||||
AddHtmlLocalized(275, 76, 200, 20, 1045153, 0x7FFF); // Amount finished:
|
||||
|
||||
int y = 96;
|
||||
|
||||
for (int i = 0; i < entries.Length; ++i)
|
||||
{
|
||||
LargeBulkEntry entry = entries[i];
|
||||
SmallBulkEntry details = entry.Details;
|
||||
|
||||
AddHtmlLocalized(75, y, 210, 20, details.Number, 0x7FFF);
|
||||
AddLabel(275, y, 0x480, entry.Amount.ToString());
|
||||
|
||||
y += 24;
|
||||
}
|
||||
|
||||
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
|
||||
{
|
||||
AddHtmlLocalized(75, y, 200, 20, 1045140, 0x7FFF); // Special requirements to meet:
|
||||
y += 24;
|
||||
}
|
||||
|
||||
if (deed.RequireExceptional)
|
||||
{
|
||||
AddHtmlLocalized(75, y, 300, 20, 1045141, 0x7FFF); // All items must be exceptional.
|
||||
y += 24;
|
||||
}
|
||||
|
||||
if (deed.Material != BulkMaterialType.None)
|
||||
AddHtmlLocalized(75, y, 300, 20, GetMaterialNumberFor(deed.Material), 0x7FFF); // All items must be made with x material.
|
||||
|
||||
AddButton(125, 168 + entries.Length * 24, 4005, 4007, 2);
|
||||
AddHtmlLocalized(160, 168 + entries.Length * 24, 300, 20, 1045155, 0x7FFF); // Combine this deed with another deed.
|
||||
|
||||
AddButton(125, 192 + entries.Length * 24, 4005, 4007, 1);
|
||||
AddHtmlLocalized(160, 192 + entries.Length * 24, 120, 20, 1011441, 0x7FFF); // EXIT
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Deed.Deleted || !m_Deed.IsChildOf(m_From.Backpack))
|
||||
return;
|
||||
|
||||
if (info.ButtonID == 2) // Combine
|
||||
{
|
||||
m_From.SendGump(new LargeBODGump(m_From, m_Deed));
|
||||
m_Deed.BeginCombine(m_From);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetMaterialNumberFor(BulkMaterialType material)
|
||||
{
|
||||
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
|
||||
return 1045142 + (material - BulkMaterialType.DullCopper);
|
||||
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
|
||||
return 1049348 + (material - BulkMaterialType.Spined);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Projects/UOContent/Engines/BulkOrders/LargeBulkEntry.cs
Normal file
117
Projects/UOContent/Engines/BulkOrders/LargeBulkEntry.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class LargeBulkEntry
|
||||
{
|
||||
private int m_Amount;
|
||||
|
||||
public LargeBOD Owner { get; set; }
|
||||
|
||||
public int Amount
|
||||
{
|
||||
get => m_Amount;
|
||||
set { m_Amount = value; Owner?.InvalidateProperties(); }
|
||||
}
|
||||
public SmallBulkEntry Details { get; }
|
||||
|
||||
public static SmallBulkEntry[] LargeRing => GetEntries("Blacksmith", "largering");
|
||||
|
||||
public static SmallBulkEntry[] LargePlate => GetEntries("Blacksmith", "largeplate");
|
||||
|
||||
public static SmallBulkEntry[] LargeChain => GetEntries("Blacksmith", "largechain");
|
||||
|
||||
public static SmallBulkEntry[] LargeAxes => GetEntries("Blacksmith", "largeaxes");
|
||||
|
||||
public static SmallBulkEntry[] LargeFencing => GetEntries("Blacksmith", "largefencing");
|
||||
|
||||
public static SmallBulkEntry[] LargeMaces => GetEntries("Blacksmith", "largemaces");
|
||||
|
||||
public static SmallBulkEntry[] LargePolearms => GetEntries("Blacksmith", "largepolearms");
|
||||
|
||||
public static SmallBulkEntry[] LargeSwords => GetEntries("Blacksmith", "largeswords");
|
||||
|
||||
public static SmallBulkEntry[] BoneSet => GetEntries("Tailoring", "boneset");
|
||||
|
||||
public static SmallBulkEntry[] Farmer => GetEntries("Tailoring", "farmer");
|
||||
|
||||
public static SmallBulkEntry[] FemaleLeatherSet => GetEntries("Tailoring", "femaleleatherset");
|
||||
|
||||
public static SmallBulkEntry[] FisherGirl => GetEntries("Tailoring", "fishergirl");
|
||||
|
||||
public static SmallBulkEntry[] Gypsy => GetEntries("Tailoring", "gypsy");
|
||||
|
||||
public static SmallBulkEntry[] HatSet => GetEntries("Tailoring", "hatset");
|
||||
|
||||
public static SmallBulkEntry[] Jester => GetEntries("Tailoring", "jester");
|
||||
|
||||
public static SmallBulkEntry[] Lady => GetEntries("Tailoring", "lady");
|
||||
|
||||
public static SmallBulkEntry[] MaleLeatherSet => GetEntries("Tailoring", "maleleatherset");
|
||||
|
||||
public static SmallBulkEntry[] Pirate => GetEntries("Tailoring", "pirate");
|
||||
|
||||
public static SmallBulkEntry[] ShoeSet => GetEntries("Tailoring", "shoeset");
|
||||
|
||||
public static SmallBulkEntry[] StuddedSet => GetEntries("Tailoring", "studdedset");
|
||||
|
||||
public static SmallBulkEntry[] TownCrier => GetEntries("Tailoring", "towncrier");
|
||||
|
||||
public static SmallBulkEntry[] Wizard => GetEntries("Tailoring", "wizard");
|
||||
|
||||
private static Dictionary<string, Dictionary<string, SmallBulkEntry[]>> m_Cache;
|
||||
|
||||
public static SmallBulkEntry[] GetEntries(string type, string name)
|
||||
{
|
||||
m_Cache ??= new Dictionary<string, Dictionary<string, SmallBulkEntry[]>>();
|
||||
|
||||
if (!m_Cache.TryGetValue(type, out Dictionary<string, SmallBulkEntry[]> table))
|
||||
m_Cache[type] = table = new Dictionary<string, SmallBulkEntry[]>();
|
||||
|
||||
if (!table.TryGetValue(name, out SmallBulkEntry[] entries))
|
||||
table[name] = entries = SmallBulkEntry.LoadEntries(type, name);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static LargeBulkEntry[] ConvertEntries(LargeBOD owner, SmallBulkEntry[] small)
|
||||
{
|
||||
LargeBulkEntry[] large = new LargeBulkEntry[small.Length];
|
||||
|
||||
for (int i = 0; i < small.Length; ++i)
|
||||
large[i] = new LargeBulkEntry(owner, small[i]);
|
||||
|
||||
return large;
|
||||
}
|
||||
|
||||
public LargeBulkEntry(LargeBOD owner, SmallBulkEntry details)
|
||||
{
|
||||
Owner = owner;
|
||||
Details = details;
|
||||
}
|
||||
|
||||
public LargeBulkEntry(LargeBOD owner, IGenericReader reader)
|
||||
{
|
||||
Owner = owner;
|
||||
m_Amount = reader.ReadInt();
|
||||
|
||||
Type realType = null;
|
||||
|
||||
string type = reader.ReadString();
|
||||
|
||||
if (type != null)
|
||||
realType = AssemblyHandler.FindFirstTypeForName(type);
|
||||
|
||||
Details = new SmallBulkEntry(realType, reader.ReadInt(), reader.ReadInt());
|
||||
}
|
||||
|
||||
public void Serialize(IGenericWriter writer)
|
||||
{
|
||||
writer.Write(m_Amount);
|
||||
writer.Write(Details.Type?.FullName);
|
||||
writer.Write(Details.Number);
|
||||
writer.Write(Details.Graphic);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Projects/UOContent/Engines/BulkOrders/LargeSmithBOD.cs
Normal file
86
Projects/UOContent/Engines/BulkOrders/LargeSmithBOD.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class LargeSmithBOD : LargeBOD
|
||||
{
|
||||
public static double[] m_BlacksmithMaterialChances =
|
||||
{
|
||||
0.501953125, // None
|
||||
0.250000000, // Dull Copper
|
||||
0.125000000, // Shadow Iron
|
||||
0.062500000, // Copper
|
||||
0.031250000, // Bronze
|
||||
0.015625000, // Gold
|
||||
0.007812500, // Agapite
|
||||
0.003906250, // Verite
|
||||
0.001953125 // Valorite
|
||||
};
|
||||
|
||||
[Constructible]
|
||||
public LargeSmithBOD()
|
||||
{
|
||||
LargeBulkEntry[] entries;
|
||||
bool useMaterials = true;
|
||||
|
||||
int rand = Utility.Random(8);
|
||||
|
||||
entries = rand switch
|
||||
{
|
||||
0 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeRing),
|
||||
1 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargePlate),
|
||||
2 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeChain),
|
||||
3 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeAxes),
|
||||
4 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeFencing),
|
||||
5 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeMaces),
|
||||
6 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargePolearms),
|
||||
7 => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeSwords),
|
||||
_ => LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.LargeRing)
|
||||
};
|
||||
|
||||
if (rand > 2 && rand < 8)
|
||||
useMaterials = false;
|
||||
|
||||
int hue = 0x44E;
|
||||
int amountMax = Utility.RandomList(10, 15, 20, 20);
|
||||
bool reqExceptional = Utility.RandomDouble() < 0.825;
|
||||
|
||||
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.DullCopper, m_BlacksmithMaterialChances)
|
||||
: BulkMaterialType.None;
|
||||
|
||||
Hue = hue;
|
||||
AmountMax = amountMax;
|
||||
Entries = entries;
|
||||
RequireExceptional = reqExceptional;
|
||||
Material = material;
|
||||
}
|
||||
|
||||
public LargeSmithBOD(int amountMax, bool reqExceptional, BulkMaterialType mat, LargeBulkEntry[] entries)
|
||||
: base(0x44E, amountMax, reqExceptional, mat, entries)
|
||||
{
|
||||
}
|
||||
|
||||
public LargeSmithBOD(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int ComputeFame() => SmithRewardCalculator.Instance.ComputeFame(this);
|
||||
|
||||
public override int ComputeGold() => SmithRewardCalculator.Instance.ComputeGold(this);
|
||||
|
||||
public override RewardGroup GetRewardGroup() =>
|
||||
SmithRewardCalculator.Instance.LookupRewards(SmithRewardCalculator.Instance.ComputePoints(this));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Projects/UOContent/Engines/BulkOrders/LargeTailorBOD.cs
Normal file
114
Projects/UOContent/Engines/BulkOrders/LargeTailorBOD.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class LargeTailorBOD : LargeBOD
|
||||
{
|
||||
public static double[] m_TailoringMaterialChances =
|
||||
{
|
||||
0.857421875, // None
|
||||
0.125000000, // Spined
|
||||
0.015625000, // Horned
|
||||
0.001953125 // Barbed
|
||||
};
|
||||
|
||||
[Constructible]
|
||||
public LargeTailorBOD()
|
||||
{
|
||||
LargeBulkEntry[] entries;
|
||||
bool useMaterials = false;
|
||||
|
||||
switch (Utility.Random(14))
|
||||
{
|
||||
default:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Farmer);
|
||||
break;
|
||||
case 1:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.FemaleLeatherSet);
|
||||
useMaterials = true;
|
||||
break;
|
||||
case 2:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.FisherGirl);
|
||||
break;
|
||||
case 3:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Gypsy);
|
||||
break;
|
||||
case 4:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.HatSet);
|
||||
break;
|
||||
case 5:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Jester);
|
||||
break;
|
||||
case 6:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Lady);
|
||||
break;
|
||||
case 7:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.MaleLeatherSet);
|
||||
useMaterials = true;
|
||||
break;
|
||||
case 8:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Pirate);
|
||||
break;
|
||||
case 9:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.ShoeSet);
|
||||
useMaterials = Core.ML;
|
||||
break;
|
||||
case 10:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.StuddedSet);
|
||||
useMaterials = true;
|
||||
break;
|
||||
case 11:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.TownCrier);
|
||||
break;
|
||||
case 12:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.Wizard);
|
||||
break;
|
||||
case 13:
|
||||
entries = LargeBulkEntry.ConvertEntries(this, LargeBulkEntry.BoneSet);
|
||||
useMaterials = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int hue = 0x483;
|
||||
int amountMax = Utility.RandomList(10, 15, 20, 20);
|
||||
bool reqExceptional = Utility.RandomDouble() < 0.825;
|
||||
|
||||
BulkMaterialType material = useMaterials ? GetRandomMaterial(BulkMaterialType.Spined, m_TailoringMaterialChances)
|
||||
: BulkMaterialType.None;
|
||||
|
||||
Hue = hue;
|
||||
AmountMax = amountMax;
|
||||
Entries = entries;
|
||||
RequireExceptional = reqExceptional;
|
||||
Material = material;
|
||||
}
|
||||
|
||||
public LargeTailorBOD(int amountMax, bool reqExceptional, BulkMaterialType mat, LargeBulkEntry[] entries)
|
||||
: base(0x483, amountMax, reqExceptional, mat, entries)
|
||||
{
|
||||
}
|
||||
|
||||
public LargeTailorBOD(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
public override int ComputeFame() => TailorRewardCalculator.Instance.ComputeFame(this);
|
||||
|
||||
public override int ComputeGold() => TailorRewardCalculator.Instance.ComputeGold(this);
|
||||
|
||||
public override RewardGroup GetRewardGroup() =>
|
||||
TailorRewardCalculator.Instance.LookupRewards(TailorRewardCalculator.Instance.ComputePoints(this));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
716
Projects/UOContent/Engines/BulkOrders/Rewards.cs
Normal file
716
Projects/UOContent/Engines/BulkOrders/Rewards.cs
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
using System;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public delegate Item ConstructCallback(int type);
|
||||
|
||||
public sealed class RewardType
|
||||
{
|
||||
public RewardType(int points, params Type[] types)
|
||||
{
|
||||
Points = points;
|
||||
Types = types;
|
||||
}
|
||||
|
||||
public int Points { get; }
|
||||
|
||||
public Type[] Types { get; }
|
||||
|
||||
public bool Contains(Type type)
|
||||
{
|
||||
for (int i = 0; i < Types.Length; ++i)
|
||||
if (Types[i] == type)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RewardItem
|
||||
{
|
||||
public RewardItem(int weight, ConstructCallback constructor, int type = 0)
|
||||
{
|
||||
Weight = weight;
|
||||
Constructor = constructor;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public int Weight { get; }
|
||||
|
||||
public ConstructCallback Constructor { get; }
|
||||
|
||||
public int Type { get; }
|
||||
|
||||
public Item Construct()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Constructor(Type);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RewardGroup
|
||||
{
|
||||
public RewardGroup(int points, params RewardItem[] items)
|
||||
{
|
||||
Points = points;
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public int Points { get; }
|
||||
|
||||
public RewardItem[] Items { get; }
|
||||
|
||||
public RewardItem AcquireItem()
|
||||
{
|
||||
if (Items.Length == 0)
|
||||
return null;
|
||||
if (Items.Length == 1)
|
||||
return Items[0];
|
||||
|
||||
int totalWeight = 0;
|
||||
|
||||
for (int i = 0; i < Items.Length; ++i)
|
||||
totalWeight += Items[i].Weight;
|
||||
|
||||
int randomWeight = Utility.Random(totalWeight);
|
||||
|
||||
for (int i = 0; i < Items.Length; ++i)
|
||||
{
|
||||
RewardItem item = Items[i];
|
||||
|
||||
if (randomWeight < item.Weight)
|
||||
return item;
|
||||
|
||||
randomWeight -= item.Weight;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class RewardCalculator
|
||||
{
|
||||
public RewardGroup[] Groups { get; set; }
|
||||
|
||||
public abstract int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
|
||||
Type type);
|
||||
|
||||
public abstract int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type);
|
||||
|
||||
public virtual int ComputeFame(SmallBOD bod)
|
||||
{
|
||||
int points = ComputePoints(bod) / 50;
|
||||
return points * points;
|
||||
}
|
||||
|
||||
public virtual int ComputeFame(LargeBOD bod)
|
||||
{
|
||||
int points = ComputePoints(bod) / 50;
|
||||
return points * points;
|
||||
}
|
||||
|
||||
public virtual int ComputePoints(SmallBOD bod) => ComputePoints(bod.AmountMax, bod.RequireExceptional, bod.Material, 1, bod.Type);
|
||||
|
||||
public virtual int ComputePoints(LargeBOD bod) =>
|
||||
ComputePoints(bod.AmountMax, bod.RequireExceptional, bod.Material, bod.Entries.Length,
|
||||
bod.Entries[0].Details.Type);
|
||||
|
||||
public virtual int ComputeGold(SmallBOD bod) => ComputeGold(bod.AmountMax, bod.RequireExceptional, bod.Material, 1, bod.Type);
|
||||
|
||||
public virtual int ComputeGold(LargeBOD bod) =>
|
||||
ComputeGold(bod.AmountMax, bod.RequireExceptional, bod.Material, bod.Entries.Length,
|
||||
bod.Entries[0].Details.Type);
|
||||
|
||||
public virtual RewardGroup LookupRewards(int points)
|
||||
{
|
||||
for (int i = Groups.Length - 1; i >= 1; --i)
|
||||
{
|
||||
RewardGroup group = Groups[i];
|
||||
|
||||
if (points >= group.Points)
|
||||
return group;
|
||||
}
|
||||
|
||||
return Groups[0];
|
||||
}
|
||||
|
||||
public virtual int LookupTypePoints(RewardType[] types, Type type)
|
||||
{
|
||||
for (int i = 0; i < types.Length; ++i)
|
||||
if (types[i].Contains(type))
|
||||
return types[i].Points;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SmithRewardCalculator : RewardCalculator
|
||||
{
|
||||
private static readonly ConstructCallback SturdyShovel = CreateSturdyShovel;
|
||||
private static readonly ConstructCallback SturdyPickaxe = CreateSturdyPickaxe;
|
||||
private static readonly ConstructCallback MiningGloves = CreateMiningGloves;
|
||||
private static readonly ConstructCallback GargoylesPickaxe = CreateGargoylesPickaxe;
|
||||
private static readonly ConstructCallback ProspectorsTool = CreateProspectorsTool;
|
||||
private static readonly ConstructCallback PowderOfTemperament = CreatePowderOfTemperament;
|
||||
private static readonly ConstructCallback RunicHammer = CreateRunicHammer;
|
||||
private static readonly ConstructCallback PowerScroll = CreatePowerScroll;
|
||||
private static readonly ConstructCallback ColoredAnvil = CreateColoredAnvil;
|
||||
private static readonly ConstructCallback AncientHammer = CreateAncientHammer;
|
||||
public static readonly SmithRewardCalculator Instance = new SmithRewardCalculator();
|
||||
|
||||
private static readonly int[][][] m_GoldTable =
|
||||
{
|
||||
new[] // 1-part (regular)
|
||||
{
|
||||
new[] { 150, 250, 250, 400, 400, 750, 750, 1200, 1200 },
|
||||
new[] { 225, 375, 375, 600, 600, 1125, 1125, 1800, 1800 },
|
||||
new[] { 300, 500, 750, 800, 1050, 1500, 2250, 2400, 4000 }
|
||||
},
|
||||
new[] // 1-part (exceptional)
|
||||
{
|
||||
new[] { 250, 400, 400, 750, 750, 1500, 1500, 3000, 3000 },
|
||||
new[] { 375, 600, 600, 1125, 1125, 2250, 2250, 4500, 4500 },
|
||||
new[] { 500, 800, 1200, 1500, 2500, 3000, 6000, 6000, 12000 }
|
||||
},
|
||||
new[] // Ringmail (regular)
|
||||
{
|
||||
new[] { 3000, 5000, 5000, 7500, 7500, 10000, 10000, 15000, 15000 },
|
||||
new[] { 4500, 7500, 7500, 11250, 11500, 15000, 15000, 22500, 22500 },
|
||||
new[] { 6000, 10000, 15000, 15000, 20000, 20000, 30000, 30000, 50000 }
|
||||
},
|
||||
new[] // Ringmail (exceptional)
|
||||
{
|
||||
new[] { 5000, 10000, 10000, 15000, 15000, 25000, 25000, 50000, 50000 },
|
||||
new[] { 7500, 15000, 15000, 22500, 22500, 37500, 37500, 75000, 75000 },
|
||||
new[] { 10000, 20000, 30000, 30000, 50000, 50000, 100000, 100000, 200000 }
|
||||
},
|
||||
new[] // Chainmail (regular)
|
||||
{
|
||||
new[] { 4000, 7500, 7500, 10000, 10000, 15000, 15000, 25000, 25000 },
|
||||
new[] { 6000, 11250, 11250, 15000, 15000, 22500, 22500, 37500, 37500 },
|
||||
new[] { 8000, 15000, 20000, 20000, 30000, 30000, 50000, 50000, 100000 }
|
||||
},
|
||||
new[] // Chainmail (exceptional)
|
||||
{
|
||||
new[] { 7500, 15000, 15000, 25000, 25000, 50000, 50000, 100000, 100000 },
|
||||
new[] { 11250, 22500, 22500, 37500, 37500, 75000, 75000, 150000, 150000 },
|
||||
new[] { 15000, 30000, 50000, 50000, 100000, 100000, 200000, 200000, 200000 }
|
||||
},
|
||||
new[] // Platemail (regular)
|
||||
{
|
||||
new[] { 5000, 10000, 10000, 15000, 15000, 25000, 25000, 50000, 50000 },
|
||||
new[] { 7500, 15000, 15000, 22500, 22500, 37500, 37500, 75000, 75000 },
|
||||
new[] { 10000, 20000, 30000, 30000, 50000, 50000, 100000, 100000, 200000 }
|
||||
},
|
||||
new[] // Platemail (exceptional)
|
||||
{
|
||||
new[] { 10000, 25000, 25000, 50000, 50000, 100000, 100000, 100000, 100000 },
|
||||
new[] { 15000, 37500, 37500, 75000, 75000, 150000, 150000, 150000, 150000 },
|
||||
new[] { 20000, 50000, 100000, 100000, 200000, 200000, 200000, 200000, 200000 }
|
||||
},
|
||||
new[] // 2-part weapons (regular)
|
||||
{
|
||||
new[] { 3000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 4500, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
},
|
||||
new[] // 2-part weapons (exceptional)
|
||||
{
|
||||
new[] { 5000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 10000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
},
|
||||
new[] // 5-part weapons (regular)
|
||||
{
|
||||
new[] { 4000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 8000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
},
|
||||
new[] // 5-part weapons (exceptional)
|
||||
{
|
||||
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 11250, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 15000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
},
|
||||
new[] // 6-part weapons (regular)
|
||||
{
|
||||
new[] { 4000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 6000, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 10000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
},
|
||||
new[] // 6-part weapons (exceptional)
|
||||
{
|
||||
new[] { 7500, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 11250, 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
new[] { 15000, 0, 0, 0, 0, 0, 0, 0, 0 }
|
||||
}
|
||||
};
|
||||
|
||||
private readonly RewardType[] m_Types =
|
||||
{
|
||||
// Armors
|
||||
new RewardType(200, typeof(RingmailGloves), typeof(RingmailChest), typeof(RingmailArms), typeof(RingmailLegs)),
|
||||
new RewardType(300, typeof(ChainCoif), typeof(ChainLegs), typeof(ChainChest)),
|
||||
new RewardType(400, typeof(PlateArms), typeof(PlateLegs), typeof(PlateHelm), typeof(PlateGorget),
|
||||
typeof(PlateGloves), typeof(PlateChest)),
|
||||
|
||||
// Weapons
|
||||
new RewardType(200, typeof(Bardiche), typeof(Halberd)),
|
||||
new RewardType(300, typeof(Dagger), typeof(ShortSpear), typeof(Spear), typeof(WarFork),
|
||||
typeof(Kryss)), // OSI put the dagger in there. Odd, ain't it.
|
||||
new RewardType(350, typeof(Axe), typeof(BattleAxe), typeof(DoubleAxe), typeof(ExecutionersAxe),
|
||||
typeof(LargeBattleAxe), typeof(TwoHandedAxe)),
|
||||
new RewardType(350, typeof(Broadsword), typeof(Cutlass), typeof(Katana), typeof(Longsword),
|
||||
typeof(Scimitar), /*typeof( ThinLongsword ),*/ typeof(VikingSword)),
|
||||
new RewardType(350, typeof(WarAxe), typeof(HammerPick), typeof(Mace), typeof(Maul), typeof(WarHammer),
|
||||
typeof(WarMace))
|
||||
};
|
||||
|
||||
public SmithRewardCalculator()
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new RewardGroup(0, new RewardItem(1, SturdyShovel)),
|
||||
new RewardGroup(25, new RewardItem(1, SturdyPickaxe)),
|
||||
new RewardGroup(50, new RewardItem(45, SturdyShovel), new RewardItem(45, SturdyPickaxe),
|
||||
new RewardItem(10, MiningGloves, 1)),
|
||||
new RewardGroup(200, new RewardItem(45, GargoylesPickaxe), new RewardItem(45, ProspectorsTool),
|
||||
new RewardItem(10, MiningGloves, 3)),
|
||||
new RewardGroup(400, new RewardItem(2, GargoylesPickaxe), new RewardItem(2, ProspectorsTool),
|
||||
new RewardItem(1, PowderOfTemperament)),
|
||||
new RewardGroup(450, new RewardItem(9, PowderOfTemperament), new RewardItem(1, MiningGloves, 5)),
|
||||
new RewardGroup(500, new RewardItem(1, RunicHammer, 1)),
|
||||
new RewardGroup(550, new RewardItem(3, RunicHammer, 1), new RewardItem(2, RunicHammer, 2)),
|
||||
new RewardGroup(600, new RewardItem(1, RunicHammer, 2)),
|
||||
new RewardGroup(625, new RewardItem(3, RunicHammer, 2), new RewardItem(6, PowerScroll, 5),
|
||||
new RewardItem(1, ColoredAnvil)),
|
||||
new RewardGroup(650, new RewardItem(1, RunicHammer, 3)),
|
||||
new RewardGroup(675, new RewardItem(1, ColoredAnvil), new RewardItem(6, PowerScroll, 10),
|
||||
new RewardItem(3, RunicHammer, 3)),
|
||||
new RewardGroup(700, new RewardItem(1, RunicHammer, 4)),
|
||||
new RewardGroup(750, new RewardItem(1, AncientHammer, 10)),
|
||||
new RewardGroup(800, new RewardItem(1, PowerScroll, 15)),
|
||||
new RewardGroup(850, new RewardItem(1, AncientHammer, 15)),
|
||||
new RewardGroup(900, new RewardItem(1, PowerScroll, 20)),
|
||||
new RewardGroup(950, new RewardItem(1, RunicHammer, 5)),
|
||||
new RewardGroup(1000, new RewardItem(1, AncientHammer, 30)),
|
||||
new RewardGroup(1050, new RewardItem(1, RunicHammer, 6)),
|
||||
new RewardGroup(1100, new RewardItem(1, AncientHammer, 60)),
|
||||
new RewardGroup(1150, new RewardItem(1, RunicHammer, 7)),
|
||||
new RewardGroup(1200, new RewardItem(1, RunicHammer, 8))
|
||||
};
|
||||
}
|
||||
|
||||
public override int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
|
||||
Type type)
|
||||
{
|
||||
int points = 0;
|
||||
|
||||
if (quantity == 10)
|
||||
points += 10;
|
||||
else if (quantity == 15)
|
||||
points += 25;
|
||||
else if (quantity == 20)
|
||||
points += 50;
|
||||
|
||||
if (exceptional)
|
||||
points += 200;
|
||||
|
||||
if (itemCount > 1)
|
||||
points += LookupTypePoints(m_Types, type);
|
||||
|
||||
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
|
||||
points += 200 + 50 * (material - BulkMaterialType.DullCopper);
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private int ComputeType(Type type, int itemCount)
|
||||
{
|
||||
// Item count of 1 means it's a small BOD.
|
||||
if (itemCount == 1)
|
||||
return 0;
|
||||
|
||||
int typeIdx = 0;
|
||||
|
||||
// Loop through the RewardTypes defined earlier and find the correct one.
|
||||
for (; typeIdx < 7; ++typeIdx)
|
||||
if (m_Types[typeIdx].Contains(type))
|
||||
break;
|
||||
|
||||
// Types 5, 6 and 7 are Large Weapon BODs with the same rewards.
|
||||
if (typeIdx > 5)
|
||||
typeIdx = 5;
|
||||
|
||||
return (typeIdx + 1) * 2;
|
||||
}
|
||||
|
||||
public override int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type)
|
||||
{
|
||||
int[][][] goldTable = m_GoldTable;
|
||||
|
||||
int typeIndex = ComputeType(type, itemCount);
|
||||
int quanIndex = quantity switch
|
||||
{
|
||||
20 => 2,
|
||||
15 => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
int mtrlIndex = material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite
|
||||
? 1 + (material - BulkMaterialType.DullCopper)
|
||||
: 0;
|
||||
|
||||
if (exceptional)
|
||||
typeIndex++;
|
||||
|
||||
int gold = goldTable[typeIndex][quanIndex][mtrlIndex];
|
||||
|
||||
int min = gold * 9 / 10;
|
||||
int max = gold * 10 / 9;
|
||||
|
||||
return Utility.RandomMinMax(min, max);
|
||||
}
|
||||
|
||||
private static Item CreateSturdyShovel(int type) => new SturdyShovel();
|
||||
|
||||
private static Item CreateSturdyPickaxe(int type) => new SturdyPickaxe();
|
||||
|
||||
private static Item CreateMiningGloves(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
1 => (Item)new LeatherGlovesOfMining(1),
|
||||
3 => new StuddedGlovesOfMining(3),
|
||||
5 => new RingmailGlovesOfMining(5),
|
||||
_ => throw new InvalidOperationException()
|
||||
};
|
||||
}
|
||||
|
||||
private static Item CreateGargoylesPickaxe(int type) => new GargoylesPickaxe();
|
||||
|
||||
private static Item CreateProspectorsTool(int type) => new ProspectorsTool();
|
||||
|
||||
private static Item CreatePowderOfTemperament(int type) => new PowderOfTemperament();
|
||||
|
||||
private static Item CreateRunicHammer(int type)
|
||||
{
|
||||
if (type >= 1 && type <= 8)
|
||||
return new RunicHammer(CraftResource.Iron + type, Core.AOS ? 55 - type * 5 : 50);
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static Item CreatePowerScroll(int type)
|
||||
{
|
||||
if (type == 5 || type == 10 || type == 15 || type == 20)
|
||||
return new PowerScroll(SkillName.Blacksmith, 100 + type);
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static Item CreateColoredAnvil(int type) => new ColoredAnvil();
|
||||
|
||||
private static Item CreateAncientHammer(int type)
|
||||
{
|
||||
if (type == 10 || type == 15 || type == 30 || type == 60)
|
||||
return new AncientSmithyHammer(type);
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TailorRewardCalculator : RewardCalculator
|
||||
{
|
||||
private static readonly ConstructCallback Cloth = CreateCloth;
|
||||
private static readonly ConstructCallback Sandals = CreateSandals;
|
||||
private static readonly ConstructCallback StretchedHide = CreateStretchedHide;
|
||||
private static readonly ConstructCallback RunicKit = CreateRunicKit;
|
||||
private static readonly ConstructCallback Tapestry = CreateTapestry;
|
||||
private static readonly ConstructCallback PowerScroll = CreatePowerScroll;
|
||||
private static readonly ConstructCallback BearRug = CreateBearRug;
|
||||
private static readonly ConstructCallback ClothingBlessDeed = CreateCBD;
|
||||
public static readonly TailorRewardCalculator Instance = new TailorRewardCalculator();
|
||||
|
||||
private static readonly int[][][] m_AosGoldTable =
|
||||
{
|
||||
new[] // 1-part (regular)
|
||||
{
|
||||
new[] { 150, 150, 300, 300 },
|
||||
new[] { 225, 225, 450, 450 },
|
||||
new[] { 300, 400, 600, 750 }
|
||||
},
|
||||
new[] // 1-part (exceptional)
|
||||
{
|
||||
new[] { 300, 300, 600, 600 },
|
||||
new[] { 450, 450, 900, 900 },
|
||||
new[] { 600, 750, 1200, 1800 }
|
||||
},
|
||||
new[] // 4-part (regular)
|
||||
{
|
||||
new[] { 4000, 4000, 5000, 5000 },
|
||||
new[] { 6000, 6000, 7500, 7500 },
|
||||
new[] { 8000, 10000, 10000, 15000 }
|
||||
},
|
||||
new[] // 4-part (exceptional)
|
||||
{
|
||||
new[] { 5000, 5000, 7500, 7500 },
|
||||
new[] { 7500, 7500, 11250, 11250 },
|
||||
new[] { 10000, 15000, 15000, 20000 }
|
||||
},
|
||||
new[] // 5-part (regular)
|
||||
{
|
||||
new[] { 5000, 5000, 7500, 7500 },
|
||||
new[] { 7500, 7500, 11250, 11250 },
|
||||
new[] { 10000, 15000, 15000, 20000 }
|
||||
},
|
||||
new[] // 5-part (exceptional)
|
||||
{
|
||||
new[] { 7500, 7500, 10000, 10000 },
|
||||
new[] { 11250, 11250, 15000, 15000 },
|
||||
new[] { 15000, 20000, 20000, 30000 }
|
||||
},
|
||||
new[] // 6-part (regular)
|
||||
{
|
||||
new[] { 7500, 7500, 10000, 10000 },
|
||||
new[] { 11250, 11250, 15000, 15000 },
|
||||
new[] { 15000, 20000, 20000, 30000 }
|
||||
},
|
||||
new[] // 6-part (exceptional)
|
||||
{
|
||||
new[] { 10000, 10000, 15000, 15000 },
|
||||
new[] { 15000, 15000, 22500, 22500 },
|
||||
new[] { 20000, 30000, 30000, 50000 }
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly int[][][] m_OldGoldTable =
|
||||
{
|
||||
new[] // 1-part (regular)
|
||||
{
|
||||
new[] { 150, 150, 300, 300 },
|
||||
new[] { 225, 225, 450, 450 },
|
||||
new[] { 300, 400, 600, 750 }
|
||||
},
|
||||
new[] // 1-part (exceptional)
|
||||
{
|
||||
new[] { 300, 300, 600, 600 },
|
||||
new[] { 450, 450, 900, 900 },
|
||||
new[] { 600, 750, 1200, 1800 }
|
||||
},
|
||||
new[] // 4-part (regular)
|
||||
{
|
||||
new[] { 3000, 3000, 4000, 4000 },
|
||||
new[] { 4500, 4500, 6000, 6000 },
|
||||
new[] { 6000, 8000, 8000, 10000 }
|
||||
},
|
||||
new[] // 4-part (exceptional)
|
||||
{
|
||||
new[] { 4000, 4000, 5000, 5000 },
|
||||
new[] { 6000, 6000, 7500, 7500 },
|
||||
new[] { 8000, 10000, 10000, 15000 }
|
||||
},
|
||||
new[] // 5-part (regular)
|
||||
{
|
||||
new[] { 4000, 4000, 5000, 5000 },
|
||||
new[] { 6000, 6000, 7500, 7500 },
|
||||
new[] { 8000, 10000, 10000, 15000 }
|
||||
},
|
||||
new[] // 5-part (exceptional)
|
||||
{
|
||||
new[] { 5000, 5000, 7500, 7500 },
|
||||
new[] { 7500, 7500, 11250, 11250 },
|
||||
new[] { 10000, 15000, 15000, 20000 }
|
||||
},
|
||||
new[] // 6-part (regular)
|
||||
{
|
||||
new[] { 5000, 5000, 7500, 7500 },
|
||||
new[] { 7500, 7500, 11250, 11250 },
|
||||
new[] { 10000, 15000, 15000, 20000 }
|
||||
},
|
||||
new[] // 6-part (exceptional)
|
||||
{
|
||||
new[] { 7500, 7500, 10000, 10000 },
|
||||
new[] { 11250, 11250, 15000, 15000 },
|
||||
new[] { 15000, 20000, 20000, 30000 }
|
||||
}
|
||||
};
|
||||
|
||||
public TailorRewardCalculator()
|
||||
{
|
||||
Groups = new[]
|
||||
{
|
||||
new RewardGroup(0, new RewardItem(1, Cloth)),
|
||||
new RewardGroup(50, new RewardItem(1, Cloth, 1)),
|
||||
new RewardGroup(100, new RewardItem(1, Cloth, 2)),
|
||||
new RewardGroup(150, new RewardItem(9, Cloth, 3), new RewardItem(1, Sandals)),
|
||||
new RewardGroup(200, new RewardItem(4, Cloth, 4), new RewardItem(1, Sandals)),
|
||||
new RewardGroup(300, new RewardItem(1, StretchedHide)),
|
||||
new RewardGroup(350, new RewardItem(1, RunicKit, 1)),
|
||||
new RewardGroup(400, new RewardItem(2, PowerScroll, 5), new RewardItem(3, Tapestry)),
|
||||
new RewardGroup(450, new RewardItem(1, BearRug)),
|
||||
new RewardGroup(500, new RewardItem(1, PowerScroll, 10)),
|
||||
new RewardGroup(550, new RewardItem(1, ClothingBlessDeed)),
|
||||
new RewardGroup(575, new RewardItem(1, PowerScroll, 15)),
|
||||
new RewardGroup(600, new RewardItem(1, RunicKit, 2)),
|
||||
new RewardGroup(650, new RewardItem(1, PowerScroll, 20)),
|
||||
new RewardGroup(700, new RewardItem(1, RunicKit, 3))
|
||||
};
|
||||
}
|
||||
|
||||
public override int ComputePoints(int quantity, bool exceptional, BulkMaterialType material, int itemCount,
|
||||
Type type)
|
||||
{
|
||||
int points = 0;
|
||||
|
||||
if (quantity == 10)
|
||||
points += 10;
|
||||
else if (quantity == 15)
|
||||
points += 25;
|
||||
else if (quantity == 20)
|
||||
points += 50;
|
||||
|
||||
if (exceptional)
|
||||
points += 100;
|
||||
|
||||
if (itemCount == 4)
|
||||
points += 300;
|
||||
else if (itemCount == 5)
|
||||
points += 400;
|
||||
else if (itemCount == 6)
|
||||
points += 500;
|
||||
|
||||
if (material == BulkMaterialType.Spined)
|
||||
points += 50;
|
||||
else if (material == BulkMaterialType.Horned)
|
||||
points += 100;
|
||||
else if (material == BulkMaterialType.Barbed)
|
||||
points += 150;
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
public override int ComputeGold(int quantity, bool exceptional, BulkMaterialType material, int itemCount, Type type)
|
||||
{
|
||||
int[][][] goldTable = Core.AOS ? m_AosGoldTable : m_OldGoldTable;
|
||||
|
||||
int typeIndex = itemCount switch
|
||||
{
|
||||
6 => 3,
|
||||
5 => 2,
|
||||
4 => 1,
|
||||
_ => 0
|
||||
} * 2 + (exceptional ? 1 : 0);
|
||||
|
||||
int quanIndex = quantity switch
|
||||
{
|
||||
20 => 2,
|
||||
15 => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
int mtrlIndex = material switch
|
||||
{
|
||||
BulkMaterialType.Barbed => 3,
|
||||
BulkMaterialType.Horned => 2,
|
||||
BulkMaterialType.Spined => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
int gold = goldTable[typeIndex][quanIndex][mtrlIndex];
|
||||
|
||||
int min = gold * 9 / 10;
|
||||
int max = gold * 10 / 9;
|
||||
|
||||
return Utility.RandomMinMax(min, max);
|
||||
}
|
||||
|
||||
private static readonly int[][] m_ClothHues =
|
||||
{
|
||||
new[] { 0x483, 0x48C, 0x488, 0x48A },
|
||||
new[] { 0x495, 0x48B, 0x486, 0x485 },
|
||||
new[] { 0x48D, 0x490, 0x48E, 0x491 },
|
||||
new[] { 0x48F, 0x494, 0x484, 0x497 },
|
||||
new[] { 0x489, 0x47F, 0x482, 0x47E }
|
||||
};
|
||||
|
||||
private static Item CreateCloth(int type)
|
||||
{
|
||||
if (type >= 0 && type < m_ClothHues.Length)
|
||||
{
|
||||
UncutCloth cloth = new UncutCloth(100);
|
||||
cloth.Hue = m_ClothHues[type][Utility.Random(m_ClothHues[type].Length)];
|
||||
return cloth;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static readonly int[] m_SandalHues =
|
||||
{
|
||||
0x489, 0x47F, 0x482,
|
||||
0x47E, 0x48F, 0x494,
|
||||
0x484, 0x497
|
||||
};
|
||||
|
||||
private static Item CreateSandals(int type) => new Sandals(m_SandalHues[Utility.Random(m_SandalHues.Length)]);
|
||||
|
||||
private static Item CreateStretchedHide(int type)
|
||||
{
|
||||
return Utility.Random(4) switch
|
||||
{
|
||||
1 => (Item)new SmallStretchedHideSouthDeed(),
|
||||
2 => new MediumStretchedHideEastDeed(),
|
||||
3 => new MediumStretchedHideSouthDeed(),
|
||||
_ => new SmallStretchedHideEastDeed()
|
||||
};
|
||||
}
|
||||
|
||||
private static Item CreateTapestry(int type)
|
||||
{
|
||||
return Utility.Random(4) switch
|
||||
{
|
||||
1 => (Item)new LightFlowerTapestrySouthDeed(),
|
||||
2 => new DarkFlowerTapestryEastDeed(),
|
||||
3 => new DarkFlowerTapestrySouthDeed(),
|
||||
_ => new LightFlowerTapestryEastDeed()
|
||||
};
|
||||
}
|
||||
|
||||
private static Item CreateBearRug(int type)
|
||||
{
|
||||
return Utility.Random(4) switch
|
||||
{
|
||||
1 => (Item)new BrownBearRugSouthDeed(),
|
||||
2 => new PolarBearRugEastDeed(),
|
||||
3 => new PolarBearRugSouthDeed(),
|
||||
_ => new BrownBearRugEastDeed()
|
||||
};
|
||||
}
|
||||
|
||||
private static Item CreateRunicKit(int type)
|
||||
{
|
||||
if (type >= 1 && type <= 3)
|
||||
return new RunicSewingKit(CraftResource.RegularLeather + type, 60 - type * 15);
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static Item CreatePowerScroll(int type)
|
||||
{
|
||||
if (type == 5 || type == 10 || type == 15 || type == 20)
|
||||
return new PowerScroll(SkillName.Tailoring, 100 + type);
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
private static Item CreateCBD(int type) => new ClothingBlessDeed();
|
||||
}
|
||||
}
|
||||
212
Projects/UOContent/Engines/BulkOrders/SmallBOD.cs
Normal file
212
Projects/UOContent/Engines/BulkOrders/SmallBOD.cs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
using System;
|
||||
using Server.Items;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public abstract class SmallBOD : BaseBOD
|
||||
{
|
||||
private int m_AmountCur;
|
||||
private int m_Number;
|
||||
|
||||
public SmallBOD(int hue, int amountCur, int amountMax, Type type, int number, int graphic, bool requireExeptional,
|
||||
BulkMaterialType material) : base(hue, amountMax, requireExeptional, material)
|
||||
{
|
||||
Type = type;
|
||||
Graphic = graphic;
|
||||
m_AmountCur = amountCur;
|
||||
m_Number = number;
|
||||
}
|
||||
|
||||
public SmallBOD()
|
||||
{
|
||||
}
|
||||
|
||||
public SmallBOD(Serial serial) : base(serial)
|
||||
{
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int AmountCur
|
||||
{
|
||||
get => m_AmountCur;
|
||||
set
|
||||
{
|
||||
m_AmountCur = value;
|
||||
InvalidateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Type Type { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int Number
|
||||
{
|
||||
get => m_Number;
|
||||
set
|
||||
{
|
||||
m_Number = value;
|
||||
InvalidateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int Graphic { get; set; }
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public override bool Complete => m_AmountCur == AmountMax;
|
||||
|
||||
public override int LabelNumber => 1045151; // a bulk order deed
|
||||
|
||||
public override void GetProperties(ObjectPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
|
||||
list.Add(1060654); // small bulk order
|
||||
|
||||
if (RequireExceptional)
|
||||
list.Add(1045141); // All items must be exceptional.
|
||||
|
||||
if (Material != BulkMaterialType.None)
|
||||
list.Add(SmallBODGump.GetMaterialNumberFor(Material)); // All items must be made with x material.
|
||||
|
||||
list.Add(1060656, AmountMax.ToString()); // amount to make: ~1_val~
|
||||
list.Add(1060658, "#{0}\t{1}", m_Number, m_AmountCur); // ~1_val~: ~2_val~
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from)
|
||||
{
|
||||
if (IsChildOf(from.Backpack) || InSecureTrade || RootParent is PlayerVendor)
|
||||
from.SendGump(new SmallBODGump(from, this));
|
||||
else
|
||||
from.SendLocalizedMessage(1045156); // You must have the deed in your backpack to use it.
|
||||
}
|
||||
|
||||
public override void OnDoubleClickNotAccessible(Mobile from)
|
||||
{
|
||||
OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public override void OnDoubleClickSecureTrade(Mobile from)
|
||||
{
|
||||
OnDoubleClick(from);
|
||||
}
|
||||
|
||||
public static BulkMaterialType GetMaterial(CraftResource resource)
|
||||
{
|
||||
return resource switch
|
||||
{
|
||||
CraftResource.DullCopper => BulkMaterialType.DullCopper,
|
||||
CraftResource.ShadowIron => BulkMaterialType.ShadowIron,
|
||||
CraftResource.Copper => BulkMaterialType.Copper,
|
||||
CraftResource.Bronze => BulkMaterialType.Bronze,
|
||||
CraftResource.Gold => BulkMaterialType.Gold,
|
||||
CraftResource.Agapite => BulkMaterialType.Agapite,
|
||||
CraftResource.Verite => BulkMaterialType.Verite,
|
||||
CraftResource.Valorite => BulkMaterialType.Valorite,
|
||||
CraftResource.SpinedLeather => BulkMaterialType.Spined,
|
||||
CraftResource.HornedLeather => BulkMaterialType.Horned,
|
||||
CraftResource.BarbedLeather => BulkMaterialType.Barbed,
|
||||
_ => BulkMaterialType.None
|
||||
};
|
||||
}
|
||||
|
||||
public override void EndCombine(Mobile from, Item item)
|
||||
{
|
||||
Type objectType = item.GetType();
|
||||
|
||||
if (m_AmountCur >= AmountMax)
|
||||
{
|
||||
from.SendLocalizedMessage(
|
||||
1045166); // The maximum amount of requested items have already been combined to this deed.
|
||||
}
|
||||
else if (Type == null || (objectType != Type && !objectType.IsSubclassOf(Type)) ||
|
||||
(!(item is BaseWeapon) && !(item is BaseArmor) && !(item is BaseClothing)))
|
||||
{
|
||||
from.SendLocalizedMessage(1045169); // The item is not in the request.
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseArmor armor = item as BaseArmor;
|
||||
BaseClothing clothing = item as BaseClothing;
|
||||
|
||||
BulkMaterialType material = GetMaterial(armor?.Resource ?? clothing?.Resource ?? CraftResource.None);
|
||||
|
||||
if (Material >= BulkMaterialType.DullCopper && Material <= BulkMaterialType.Valorite &&
|
||||
material != Material)
|
||||
{
|
||||
from.SendLocalizedMessage(1045168); // The item is not made from the requested ore.
|
||||
}
|
||||
else if (Material >= BulkMaterialType.Spined && Material <= BulkMaterialType.Barbed &&
|
||||
material != Material)
|
||||
{
|
||||
from.SendLocalizedMessage(1049352); // The item is not made from the requested leather type.
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isExceptional;
|
||||
|
||||
if (item is BaseWeapon weapon)
|
||||
isExceptional = weapon.Quality == WeaponQuality.Exceptional;
|
||||
else if (armor != null)
|
||||
isExceptional = armor.Quality == ArmorQuality.Exceptional;
|
||||
else
|
||||
isExceptional = clothing.Quality == ClothingQuality.Exceptional;
|
||||
|
||||
if (RequireExceptional && !isExceptional)
|
||||
{
|
||||
from.SendLocalizedMessage(1045167); // The item must be exceptional.
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Delete();
|
||||
++AmountCur;
|
||||
|
||||
from.SendLocalizedMessage(1045170); // The item has been combined with the deed.
|
||||
from.SendGump(new SmallBODGump(from, this));
|
||||
|
||||
if (m_AmountCur < AmountMax)
|
||||
BeginCombine(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Serialize(IGenericWriter writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
|
||||
writer.Write(0); // version
|
||||
|
||||
writer.Write(m_AmountCur);
|
||||
writer.Write(Type?.FullName);
|
||||
writer.Write(m_Number);
|
||||
writer.Write(Graphic);
|
||||
}
|
||||
|
||||
public override void Deserialize(IGenericReader reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
|
||||
int version = reader.ReadInt();
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_AmountCur = reader.ReadInt();
|
||||
|
||||
string type = reader.ReadString();
|
||||
|
||||
if (type != null)
|
||||
Type = AssemblyHandler.FindFirstTypeForName(type);
|
||||
|
||||
m_Number = reader.ReadInt();
|
||||
Graphic = reader.ReadInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Projects/UOContent/Engines/BulkOrders/SmallBODAcceptGump.cs
Normal file
98
Projects/UOContent/Engines/BulkOrders/SmallBODAcceptGump.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class SmallBODAcceptGump : Gump
|
||||
{
|
||||
private readonly SmallBOD m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public SmallBODAcceptGump(Mobile from, SmallBOD deed) : base(50, 50)
|
||||
{
|
||||
m_From = from;
|
||||
m_Deed = deed;
|
||||
|
||||
m_From.CloseGump<LargeBODAcceptGump>();
|
||||
m_From.CloseGump<SmallBODAcceptGump>();
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(25, 10, 430, 264, 5054);
|
||||
|
||||
AddImageTiled(33, 20, 413, 245, 2624);
|
||||
AddAlphaRegion(33, 20, 413, 245);
|
||||
|
||||
AddImage(20, 5, 10460);
|
||||
AddImage(430, 5, 10460);
|
||||
AddImage(20, 249, 10460);
|
||||
AddImage(430, 249, 10460);
|
||||
|
||||
AddHtmlLocalized(190, 25, 120, 20, 1045133, 0x7FFF); // A bulk order
|
||||
AddHtmlLocalized(40, 48, 350, 20, 1045135, 0x7FFF); // Ah! Thanks for the goods! Would you help me out?
|
||||
|
||||
AddHtmlLocalized(40, 72, 210, 20, 1045138, 0x7FFF); // Amount to make:
|
||||
AddLabel(250, 72, 1152, deed.AmountMax.ToString());
|
||||
|
||||
AddHtmlLocalized(40, 96, 120, 20, 1045136, 0x7FFF); // Item requested:
|
||||
AddItem(385, 96, deed.Graphic);
|
||||
AddHtmlLocalized(40, 120, 210, 20, deed.Number, 0xFFFFFF);
|
||||
|
||||
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
|
||||
{
|
||||
AddHtmlLocalized(40, 144, 210, 20, 1045140, 0x7FFF); // Special requirements to meet:
|
||||
|
||||
if (deed.RequireExceptional)
|
||||
AddHtmlLocalized(40, 168, 350, 20, 1045141, 0x7FFF); // All items must be exceptional.
|
||||
|
||||
if (deed.Material != BulkMaterialType.None)
|
||||
AddHtmlLocalized(40, deed.RequireExceptional ? 192 : 168, 350, 20, GetMaterialNumberFor(deed.Material),
|
||||
0x7FFF); // All items must be made with x material.
|
||||
}
|
||||
|
||||
AddHtmlLocalized(40, 216, 350, 20, 1045139, 0x7FFF); // Do you want to accept this order?
|
||||
|
||||
AddButton(100, 240, 4005, 4007, 1);
|
||||
AddHtmlLocalized(135, 240, 120, 20, 1006044, 0x7FFF); // Ok
|
||||
|
||||
AddButton(275, 240, 4005, 4007, 0);
|
||||
AddHtmlLocalized(310, 240, 120, 20, 1011012, 0x7FFF); // CANCEL
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (info.ButtonID == 1) // Ok
|
||||
{
|
||||
if (m_From.PlaceInBackpack(m_Deed))
|
||||
{
|
||||
m_From.SendLocalizedMessage(1045152); // The bulk order deed has been placed in your backpack.
|
||||
}
|
||||
else
|
||||
{
|
||||
m_From.SendLocalizedMessage(1045150); // There is not enough room in your backpack for the deed.
|
||||
m_Deed.Delete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Deed.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnServerClose(NetState owner)
|
||||
{
|
||||
if (m_Deed?.Deleted == false)
|
||||
m_Deed.Delete();
|
||||
}
|
||||
|
||||
public static int GetMaterialNumberFor(BulkMaterialType material)
|
||||
{
|
||||
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
|
||||
return 1045142 + (material - BulkMaterialType.DullCopper);
|
||||
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
|
||||
return 1049348 + (material - BulkMaterialType.Spined);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Projects/UOContent/Engines/BulkOrders/SmallBODGump.cs
Normal file
82
Projects/UOContent/Engines/BulkOrders/SmallBODGump.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
using Server.Gumps;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class SmallBODGump : Gump
|
||||
{
|
||||
private readonly SmallBOD m_Deed;
|
||||
private readonly Mobile m_From;
|
||||
|
||||
public SmallBODGump(Mobile from, SmallBOD deed) : base(25, 25)
|
||||
{
|
||||
m_From = from;
|
||||
m_Deed = deed;
|
||||
|
||||
m_From.CloseGump<LargeBODGump>();
|
||||
m_From.CloseGump<SmallBODGump>();
|
||||
|
||||
AddPage(0);
|
||||
|
||||
AddBackground(50, 10, 455, 260, 5054);
|
||||
AddImageTiled(58, 20, 438, 241, 2624);
|
||||
AddAlphaRegion(58, 20, 438, 241);
|
||||
|
||||
AddImage(45, 5, 10460);
|
||||
AddImage(480, 5, 10460);
|
||||
AddImage(45, 245, 10460);
|
||||
AddImage(480, 245, 10460);
|
||||
|
||||
AddHtmlLocalized(225, 25, 120, 20, 1045133, 0x7FFF); // A bulk order
|
||||
|
||||
AddHtmlLocalized(75, 48, 250, 20, 1045138, 0x7FFF); // Amount to make:
|
||||
AddLabel(275, 48, 1152, deed.AmountMax.ToString());
|
||||
|
||||
AddHtmlLocalized(275, 76, 200, 20, 1045153, 0x7FFF); // Amount finished:
|
||||
AddHtmlLocalized(75, 72, 120, 20, 1045136, 0x7FFF); // Item requested:
|
||||
|
||||
AddItem(410, 72, deed.Graphic);
|
||||
|
||||
AddHtmlLocalized(75, 96, 210, 20, deed.Number, 0x7FFF);
|
||||
AddLabel(275, 96, 0x480, deed.AmountCur.ToString());
|
||||
|
||||
if (deed.RequireExceptional || deed.Material != BulkMaterialType.None)
|
||||
AddHtmlLocalized(75, 120, 200, 20, 1045140, 0x7FFF); // Special requirements to meet:
|
||||
|
||||
if (deed.RequireExceptional)
|
||||
AddHtmlLocalized(75, 144, 300, 20, 1045141, 0x7FFF); // All items must be exceptional.
|
||||
|
||||
if (deed.Material != BulkMaterialType.None)
|
||||
AddHtmlLocalized(75, deed.RequireExceptional ? 168 : 144, 300, 20, GetMaterialNumberFor(deed.Material),
|
||||
0x7FFF); // All items must be made with x material.
|
||||
|
||||
AddButton(125, 192, 4005, 4007, 2);
|
||||
AddHtmlLocalized(160, 192, 300, 20, 1045154, 0x7FFF); // Combine this deed with the item requested.
|
||||
|
||||
AddButton(125, 216, 4005, 4007, 1);
|
||||
AddHtmlLocalized(160, 216, 120, 20, 1011441, 0x7FFF); // EXIT
|
||||
}
|
||||
|
||||
public override void OnResponse(NetState sender, RelayInfo info)
|
||||
{
|
||||
if (m_Deed.Deleted || !m_Deed.IsChildOf(m_From.Backpack))
|
||||
return;
|
||||
|
||||
if (info.ButtonID == 2) // Combine
|
||||
{
|
||||
m_From.SendGump(new SmallBODGump(m_From, m_Deed));
|
||||
m_Deed.BeginCombine(m_From);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetMaterialNumberFor(BulkMaterialType material)
|
||||
{
|
||||
if (material >= BulkMaterialType.DullCopper && material <= BulkMaterialType.Valorite)
|
||||
return 1045142 + (material - BulkMaterialType.DullCopper);
|
||||
if (material >= BulkMaterialType.Spined && material <= BulkMaterialType.Barbed)
|
||||
return 1049348 + (material - BulkMaterialType.Spined);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Projects/UOContent/Engines/BulkOrders/SmallBulkEntry.cs
Normal file
87
Projects/UOContent/Engines/BulkOrders/SmallBulkEntry.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Server.Engines.BulkOrders
|
||||
{
|
||||
public class SmallBulkEntry
|
||||
{
|
||||
public Type Type { get; }
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public int Graphic { get; }
|
||||
|
||||
public SmallBulkEntry(Type type, int number, int graphic)
|
||||
{
|
||||
Type = type;
|
||||
Number = number;
|
||||
Graphic = graphic;
|
||||
}
|
||||
|
||||
public static SmallBulkEntry[] BlacksmithWeapons => GetEntries("Blacksmith", "weapons");
|
||||
|
||||
public static SmallBulkEntry[] BlacksmithArmor => GetEntries("Blacksmith", "armor");
|
||||
|
||||
public static SmallBulkEntry[] TailorCloth => GetEntries("Tailoring", "cloth");
|
||||
|
||||
public static SmallBulkEntry[] TailorLeather => GetEntries("Tailoring", "leather");
|
||||
|
||||
private static Dictionary<string, Dictionary<string, SmallBulkEntry[]>> m_Cache;
|
||||
|
||||
public static SmallBulkEntry[] GetEntries(string type, string name)
|
||||
{
|
||||
if (m_Cache == null)
|
||||
m_Cache = new Dictionary<string, Dictionary<string, SmallBulkEntry[]>>();
|
||||
|
||||
if (!m_Cache.TryGetValue(type, out Dictionary<string, SmallBulkEntry[]> table))
|
||||
m_Cache[type] = table = new Dictionary<string, SmallBulkEntry[]>();
|
||||
|
||||
if (!table.TryGetValue(name, out SmallBulkEntry[] entries))
|
||||
table[name] = entries = LoadEntries(type, name);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static SmallBulkEntry[] LoadEntries(string type, string name) => LoadEntries($"Data/Bulk Orders/{type}/{name}.cfg");
|
||||
|
||||
public static SmallBulkEntry[] LoadEntries(string path)
|
||||
{
|
||||
path = Path.Combine(Core.BaseDirectory, path);
|
||||
|
||||
List<SmallBulkEntry> list = new List<SmallBulkEntry>();
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
using StreamReader ip = new StreamReader(path);
|
||||
string line;
|
||||
|
||||
while ((line = ip.ReadLine()) != null)
|
||||
{
|
||||
if (line.Length == 0 || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
string[] split = line.Split('\t');
|
||||
|
||||
if (split.Length >= 2)
|
||||
{
|
||||
Type type = AssemblyHandler.FindFirstTypeForName(split[0]);
|
||||
int graphic = Utility.ToInt32(split[split.Length - 1]);
|
||||
|
||||
if (type != null && graphic > 0)
|
||||
list.Add(new SmallBulkEntry(type, graphic < 0x4000 ? 1020000 + graphic : 1078872 + graphic, graphic));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue