From 2d00f6d27a1d946eb59a1b6cccc19aa6e28ff9dd Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Wed, 29 Jun 2022 01:23:57 -0700 Subject: [PATCH] fix: Fixes old haven and new haven starting cities (#1097) Adds OldHaven cities for shards that use clients older than 6.x --- .../Configuration/ServerConfiguration.cs | 2 +- Projects/Server/TileMatrix/TileMatrix.cs | 2 +- .../UOContent/Accounting/AccountHandler.cs | 836 +++--- Projects/UOContent/Misc/CharacterCreation.cs | 2373 +++++++++-------- 4 files changed, 1604 insertions(+), 1609 deletions(-) diff --git a/Projects/Server/Configuration/ServerConfiguration.cs b/Projects/Server/Configuration/ServerConfiguration.cs index 718540c23..1045a55ea 100644 --- a/Projects/Server/Configuration/ServerConfiguration.cs +++ b/Projects/Server/Configuration/ServerConfiguration.cs @@ -276,7 +276,7 @@ public static class ServerConfiguration isPre60000 = ServerConfigurationPrompts.GetIsClientPre6000(); if (isPre60000 == true) { - SetSetting("maps.enablePre6000Trammel", true.ToString()); + SetSetting("maps.enablePre6000Trammel", true); } } diff --git a/Projects/Server/TileMatrix/TileMatrix.cs b/Projects/Server/TileMatrix/TileMatrix.cs index 79f61bd3d..90d13c5e1 100644 --- a/Projects/Server/TileMatrix/TileMatrix.cs +++ b/Projects/Server/TileMatrix/TileMatrix.cs @@ -31,7 +31,7 @@ namespace Server public FileStream DataStream { get; } public BinaryReader IndexReader { get; } - private static bool Pre6000ClientSupport; + public static bool Pre6000ClientSupport { get; private set; } public static void Configure() { diff --git a/Projects/UOContent/Accounting/AccountHandler.cs b/Projects/UOContent/Accounting/AccountHandler.cs index 6594fc667..f93022830 100644 --- a/Projects/UOContent/Accounting/AccountHandler.cs +++ b/Projects/UOContent/Accounting/AccountHandler.cs @@ -8,463 +8,457 @@ using Server.Logging; using Server.Network; using Server.Regions; -namespace Server.Misc +namespace Server.Misc; + +public static class AccountHandler { - public static class AccountHandler + private static readonly ILogger logger = LogFactory.GetLogger(typeof(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; + + private static CityInfo[] OldHavenStartingCities = { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(AccountHandler)); + new("Yew", "The Empath Abbey", 633, 858, 0), + new("Minoc", "The Barnacle", 2476, 413, 15), + new("Britain", "Sweet Dreams Inn", 1496, 1628, 10), + new("Moonglow", "The Scholars Inn", 4408, 1168, 0), + new("Trinsic", "The Traveler's Inn", 1845, 2745, 0), + new("Magincia", "The Great Horns Tavern", 3734, 2222, 20), + new("Jhelom", "The Mercenary Inn", 1374, 3826, 0), + new("Skara Brae", "The Falconer's Inn", 618, 2234, 0), + new("Vesper", "The Ironwood Inn", 2771, 976, 0), + new("Haven", "Buckler's Hideaway", 3667, 2625, 0) + }; - private static int MaxAccountsPerIP; - private static bool AutoAccountCreation; - private static readonly bool RestrictDeletion = !TestCenter.Enabled; - private static readonly TimeSpan DeleteDelay = TimeSpan.FromDays(7.0); - private static bool PasswordCommandEnabled; + private static CityInfo[] StartingCities = + { + new("New Haven", "New Haven Bank", 1150168, 3667, 2625, 0), + new("Yew", "The Empath Abbey", 1075072, 633, 858, 0), + new("Minoc", "The Barnacle", 1075073, 2476, 413, 15), + new("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20), + new("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0), + new("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0), + new("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0), + new("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0), + new("Vesper", "The Ironwood Inn", 1075080, 2771, 976, 0) + }; - private static readonly CityInfo[] StartingCities = + private static Dictionary m_IPTable; + + private static char[] m_ForbiddenChars = { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }; + + public static AccessLevel LockdownLevel { get; set; } + + public static Dictionary IPTable + { + get { - new("New Haven", "New Haven Bank", 1150168, 3667, 2625, 0), - new("Yew", "The Empath Abbey", 1075072, 633, 858, 0), - new("Minoc", "The Barnacle", 1075073, 2476, 413, 15), - new("Britain", "The Wayfarer's Inn", 1075074, 1602, 1591, 20), - new("Moonglow", "The Scholars Inn", 1075075, 4408, 1168, 0), - new("Trinsic", "The Traveler's Inn", 1075076, 1845, 2745, 0), - new("Jhelom", "The Mercenary Inn", 1075078, 1374, 3826, 0), - new("Skara Brae", "The Falconer's Inn", 1075079, 618, 2234, 0), - new("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 m_IPTable; - - private static readonly char[] m_ForbiddenChars = - { - '<', '>', ':', '"', '/', '\\', '|', '?', '*' - }; - - public static AccessLevel LockdownLevel { get; set; } - - public static Dictionary IPTable - { - get + if (m_IPTable == null) { - if (m_IPTable == null) + m_IPTable = new Dictionary(); + + foreach (Account a in Accounts.GetAccounts()) { - m_IPTable = new Dictionary(); - - foreach (Account a in Accounts.GetAccounts()) + if (a.LoginIPs.Length > 0) { - if (a.LoginIPs.Length > 0) - { - var ip = a.LoginIPs[0]; - m_IPTable[ip] = (m_IPTable.TryGetValue(ip, out var value) ? value : 0) + 1; - } - } - } - - return m_IPTable; - } - } - - public static void Configure() - { - MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1); - AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true); - PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting( - "accountHandler.enablePlayerPasswordCommand", - false - ); - } - - 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 "), 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) - { - var from = e.Mobile; - - if (from.Account is not Account acct) - { - return; - } - - var accessList = acct.LoginIPs; - - if (accessList.Length == 0) - { - return; - } - - var 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; - } - - var pass = e.GetString(0); - var pass2 = e.GetString(1); - - if (pass != pass2) - { - from.SendMessage("The passwords do not match."); - return; - } - - var isSafe = true; - - for (var 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 - { - var ipAddress = ns.Address; - - if (Utility.IPMatchClassC(accessList[0], ipAddress)) - { - acct.SetPassword(pass); - from.SendMessage("The password to your account has changed."); - } - else - { - var entry = PageQueue.GetEntry(from); - - if (entry != null) - { - if (entry.Message.StartsWithOrdinal("[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]
Desired password: {pass}
Current IP address: {ipAddress}
Account IP address: {accessList[0]}", - PageType.Account - ) - ); + var ip = a.LoginIPs[0]; + m_IPTable[ip] = (m_IPTable.TryGetValue(ip, out var value) ? value : 0) + 1; } } } - catch - { - // ignored - } + + return m_IPTable; + } + } + + public static void Configure() + { + MaxAccountsPerIP = ServerConfiguration.GetOrUpdateSetting("accountHandler.maxAccountsPerIP", 1); + AutoAccountCreation = ServerConfiguration.GetOrUpdateSetting("accountHandler.enableAutoAccountCreation", true); + PasswordCommandEnabled = ServerConfiguration.GetOrUpdateSetting( + "accountHandler.enablePlayerPasswordCommand", + false + ); + } + + 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 ")] + [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) + { + var from = e.Mobile; + + if (from.Account is not Account acct) + { + return; } - private static void EventSink_DeleteRequest(NetState state, int index) + var accessList = acct.LoginIPs; + + if (accessList.Length == 0) { - if (state.Account is not Account acct) + return; + } + + var 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; + } + + var pass = e.GetString(0); + var pass2 = e.GetString(1); + + if (pass != pass2) + { + from.SendMessage("The passwords do not match."); + return; + } + + var isSafe = true; + + for (var 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 + { + var ipAddress = ns.Address; + + if (Utility.IPMatchClassC(accessList[0], ipAddress)) { - state.Disconnect("Attempted to delete a character but the account could not be found."); - return; + acct.SetPassword(pass); + from.SendMessage("The password to your account has changed."); } + else + { + var entry = PageQueue.GetEntry(from); - DeleteResultType res; + if (entry != null) + { + if (entry.Message.StartsWithOrdinal("[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." + ); - if (index < 0 || index >= acct.Length) + /* 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]
Desired password: {pass}
Current IP address: {ipAddress}
Account IP address: {accessList[0]}", + PageType.Account + ) + ); + } + } + } + catch + { + // ignored + } + } + + private static void EventSink_DeleteRequest(NetState state, int index) + { + if (state.Account is not Account acct) + { + state.Disconnect("Attempted to delete a character but the account could not be found."); + return; + } + + DeleteResultType res; + + if (index < 0 || index >= acct.Length) + { + res = DeleteResultType.BadRequest; + } + else + { + var m = acct[index]; + + if (m == null) + { + res = DeleteResultType.CharNotExist; + } + else if (m.NetState != null) + { + res = DeleteResultType.CharBeingPlayed; + } + else if (acct.AccessLevel == AccessLevel.Player && RestrictDeletion && Core.Now < m.Created + DeleteDelay) + { + res = DeleteResultType.CharTooYoung; + } + // Don't need to check current location, if netstate is null, they're logged out + else if ( + m.AccessLevel == AccessLevel.Player && + Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf() + ) { res = DeleteResultType.BadRequest; } else { - var m = acct[index]; + state.LogInfo($"Deleting character {index} ({m.Serial})"); - if (m == null) - { - res = DeleteResultType.CharNotExist; - } - else if (m.NetState != null) - { - res = DeleteResultType.CharBeingPlayed; - } - else if (acct.AccessLevel == AccessLevel.Player && RestrictDeletion && Core.Now < m.Created + DeleteDelay) - { - res = DeleteResultType.CharTooYoung; - } - // Don't need to check current location, if netstate is null, they're logged out - else if ( - m.AccessLevel == AccessLevel.Player && - Region.Find(m.LogoutLocation, m.LogoutMap).IsPartOf() - ) - { - res = DeleteResultType.BadRequest; - } - else - { - state.LogInfo($"Deleting character {index} ({m.Serial})"); + acct.Comments.Add(new AccountComment("System", $"Character #{index + 1} {m} deleted by {state}")); - acct.Comments.Add(new AccountComment("System", $"Character #{index + 1} {m} deleted by {state}")); - - m.Delete(); - state.SendCharacterListUpdate(acct); - return; - } + m.Delete(); + state.SendCharacterListUpdate(acct); + return; } - - state.SendCharacterDeleteResult(res); - state.SendCharacterListUpdate(acct); } - public static bool CanCreate(IPAddress ip) => - !IPTable.TryGetValue(ip, out var result) || result < MaxAccountsPerIP; + state.SendCharacterDeleteResult(res); + state.SendCharacterListUpdate(acct); + } - private static bool IsForbiddenChar(char c) + public static bool CanCreate(IPAddress ip) => + !IPTable.TryGetValue(ip, out var result) || result < MaxAccountsPerIP; + + private static bool IsForbiddenChar(char c) + { + for (var i = 0; i < m_ForbiddenChars.Length; ++i) { - for (var i = 0; i < m_ForbiddenChars.Length; ++i) + if (c == m_ForbiddenChars[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; + } + + var isSafe = !(un.StartsWithOrdinal(" ") || + un.EndsWithOrdinal(" ") || + un.EndsWithOrdinal(".")); + + for (var i = 0; isSafe && i < un.Length; ++i) + { + isSafe = un[i] >= 0x20 && un[i] < 0x7F && !IsForbiddenChar(un[i]); + } + + for (var i = 0; isSafe && i < pw.Length; ++i) + { + isSafe = pw[i] >= 0x20 && pw[i] < 0x7F; + } + + if (!isSafe) + { + return null; + } + + if (!CanCreate(state.Address)) + { + logger.Information( + $"Login: {{NetState}} Account '{{Username}}' not created, ip already has {{AccountCount}} account{(MaxAccountsPerIP == 1 ? "" : "s")}.", + state, + un, + MaxAccountsPerIP + ); + + return null; + } + + logger.Information("Login: {NetState}: Creating new account '{Username}'", state, un); + + var 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; + + logger.Information("Login: {NetState}: Past IP limit threshold", e.State); + + using var op = new StreamWriter("ipLimits.log", true); + op.WriteLine($"{e.State}\tPast IP limit threshold\t{Core.Now}"); + + return; + } + + var un = e.Username; + var pw = e.Password; + + e.Accepted = false; + + if (Accounts.GetAccount(un) is not 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 + { + logger.Information("Login: {NetState} Invalid username '{Username}'", e.State, un); + e.RejectReason = ALRReason.Invalid; + } + } + else if (!acct.HasAccess(e.State)) + { + logger.Information("Login: {NetState} Access denied for '{Username}'", e.State, un); + e.RejectReason = LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass; + } + else if (!acct.CheckPassword(pw)) + { + logger.Information("Login: {NetState} Invalid password for '{Username}'", e.State, un); + e.RejectReason = ALRReason.BadPass; + } + else if (acct.Banned) + { + logger.Information("Login: {NetState} Banned account '{Username}'", e.State, un); + e.RejectReason = ALRReason.Blocked; + } + else + { + logger.Information("Login: {NetState} Valid credentials for '{Username}'", 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; + + logger.Warning("Login: {NetState} Past IP limit threshold", e.State); + + using var op = new StreamWriter("ipLimits.log", true); + op.WriteLine($"{e.State}\tPast IP limit threshold\t{Core.Now}"); + + return; + } + + var un = e.Username; + var pw = e.Password; + + if (Accounts.GetAccount(un) is not Account acct) + { + e.Accepted = false; + } + else if (!acct.HasAccess(e.State)) + { + logger.Information("Login: {NetState} Access denied for '{Username}'", e.State, un); + e.Accepted = false; + } + else if (!acct.CheckPassword(pw)) + { + logger.Information("Login: {NetState} Invalid password for '{Username}'", e.State, un); + e.Accepted = false; + } + else if (acct.Banned) + { + logger.Information("Login: {NetState} Banned account '{Username}'", e.State, un); + e.Accepted = false; + } + else + { + acct.LogAccess(e.State); + + logger.Information("Login: {NetState} Account '{Username}' at character list", e.State, un); + e.State.Account = acct; + e.Accepted = true; + e.CityInfo = TileMatrix.Pre6000ClientSupport ? OldHavenStartingCities : StartingCities; + } + + if (!e.Accepted) + { + AccountAttackLimiter.RegisterInvalidAccess(e.State); + } + } + + public static bool CheckAccount(Mobile mobCheck, Mobile accCheck) + { + if (accCheck?.Account is Account a) + { + for (var i = 0; i < a.Length; ++i) + { + if (a[i] == mobCheck) { return true; } } - - return false; } - private static Account CreateAccount(NetState state, string un, string pw) - { - if (un.Length == 0 || pw.Length == 0) - { - return null; - } - - var isSafe = !(un.StartsWithOrdinal(" ") || - un.EndsWithOrdinal(" ") || - un.EndsWithOrdinal(".")); - - for (var i = 0; isSafe && i < un.Length; ++i) - { - isSafe = un[i] >= 0x20 && un[i] < 0x7F && !IsForbiddenChar(un[i]); - } - - for (var i = 0; isSafe && i < pw.Length; ++i) - { - isSafe = pw[i] >= 0x20 && pw[i] < 0x7F; - } - - if (!isSafe) - { - return null; - } - - if (!CanCreate(state.Address)) - { - logger.Information( - $"Login: {{NetState}} Account '{{Username}}' not created, ip already has {{AccountCount}} account{(MaxAccountsPerIP == 1 ? "" : "s")}.", - state, - un, - MaxAccountsPerIP - ); - - return null; - } - - logger.Information("Login: {NetState}: Creating new account '{Username}'", state, un); - - var 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; - - logger.Information("Login: {NetState}: Past IP limit threshold", e.State); - - using var op = new StreamWriter("ipLimits.log", true); - op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, Core.Now); - - return; - } - - var un = e.Username; - var pw = e.Password; - - e.Accepted = false; - - if (Accounts.GetAccount(un) is not 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 - { - logger.Information("Login: {NetState} Invalid username '{Username}'", e.State, un); - e.RejectReason = ALRReason.Invalid; - } - } - else if (!acct.HasAccess(e.State)) - { - logger.Information("Login: {NetState} Access denied for '{Username}'", e.State, un); - e.RejectReason = LockdownLevel > AccessLevel.Player ? ALRReason.BadComm : ALRReason.BadPass; - } - else if (!acct.CheckPassword(pw)) - { - logger.Information("Login: {NetState} Invalid password for '{Username}'", e.State, un); - e.RejectReason = ALRReason.BadPass; - } - else if (acct.Banned) - { - logger.Information("Login: {NetState} Banned account '{Username}'", e.State, un); - e.RejectReason = ALRReason.Blocked; - } - else - { - logger.Information("Login: {NetState} Valid credentials for '{Username}'", 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; - - logger.Warning("Login: {NetState} Past IP limit threshold", e.State); - - using var op = new StreamWriter("ipLimits.log", true); - op.WriteLine("{0}\tPast IP limit threshold\t{1}", e.State, Core.Now); - - return; - } - - var un = e.Username; - var pw = e.Password; - - if (Accounts.GetAccount(un) is not Account acct) - { - e.Accepted = false; - } - else if (!acct.HasAccess(e.State)) - { - logger.Information("Login: {NetState} Access denied for '{Username}'", e.State, un); - e.Accepted = false; - } - else if (!acct.CheckPassword(pw)) - { - logger.Information("Login: {NetState} Invalid password for '{Username}'", e.State, un); - e.Accepted = false; - } - else if (acct.Banned) - { - logger.Information("Login: {NetState} Banned account '{Username}'", e.State, un); - e.Accepted = false; - } - else - { - acct.LogAccess(e.State); - - logger.Information("Login: {NetState} Account '{Username}' 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 (var i = 0; i < a.Length; ++i) - { - if (a[i] == mobCheck) - { - return true; - } - } - } - - return false; - } + return false; } } diff --git a/Projects/UOContent/Misc/CharacterCreation.cs b/Projects/UOContent/Misc/CharacterCreation.cs index 3f20279ff..de115562c 100644 --- a/Projects/UOContent/Misc/CharacterCreation.cs +++ b/Projects/UOContent/Misc/CharacterCreation.cs @@ -7,1272 +7,1273 @@ using Server.Logging; using Server.Mobiles; using Server.Network; -namespace Server.Misc +namespace Server.Misc; + +public static class CharacterCreation { - public static class CharacterCreation + private static readonly ILogger logger = LogFactory.GetLogger(typeof(CharacterCreation)); + + // Allowed skills that are not race or era specific + private static readonly HashSet _allowedStartingSkills = new() { - private static readonly ILogger logger = LogFactory.GetLogger(typeof(CharacterCreation)); + SkillName.Alchemy, + SkillName.Anatomy, + SkillName.AnimalLore, + SkillName.AnimalTaming, + SkillName.Archery, + SkillName.ArmsLore, + SkillName.Begging, + SkillName.Blacksmith, + SkillName.Fletching, + SkillName.Bushido, + SkillName.Camping, + SkillName.Carpentry, + SkillName.Cartography, + SkillName.Chivalry, + SkillName.Cooking, + SkillName.DetectHidden, + SkillName.Discordance, + SkillName.EvalInt, + SkillName.Fencing, + SkillName.Fishing, + SkillName.Focus, + SkillName.Forensics, + SkillName.Healing, + SkillName.Herding, + SkillName.Hiding, + SkillName.Imbuing, + SkillName.Inscribe, + SkillName.ItemID, + SkillName.Lockpicking, + SkillName.Lumberjacking, + SkillName.Macing, + SkillName.Magery, + SkillName.Meditation, + SkillName.Mining, + SkillName.Musicianship, + SkillName.Mysticism, + SkillName.Necromancy, + SkillName.Ninjitsu, + SkillName.Parry, + SkillName.Peacemaking, + SkillName.Poisoning, + SkillName.Provocation, + SkillName.MagicResist, + SkillName.Snooping, + SkillName.SpiritSpeak, + SkillName.Stealing, + SkillName.Swords, + SkillName.Tactics, + SkillName.Tailoring, + SkillName.TasteID, + SkillName.Throwing, + SkillName.Tinkering, + SkillName.Tracking, + SkillName.Veterinary, + SkillName.Wrestling + }; - // Allowed skills that are not race or era specific - private static readonly HashSet _allowedStartingSkills = new() + private static readonly TimeSpan BadStartMessageDelay = TimeSpan.FromSeconds(3.5); + + private static readonly CityInfo _newHavenInfo = + new("New Haven", "The Bountiful Harvest Inn", 3503, 2574, 14, Map.Trammel); + + public static void Initialize() + { + // Register our event handler + EventSink.CharacterCreated += EventSink_CharacterCreated; + } + + private static void AddBackpack(this Mobile m) + { + var pack = m.Backpack; + + if (pack == null) { - SkillName.Alchemy, - SkillName.Anatomy, - SkillName.AnimalLore, - SkillName.AnimalTaming, - SkillName.Archery, - SkillName.ArmsLore, - SkillName.Begging, - SkillName.Blacksmith, - SkillName.Fletching, - SkillName.Bushido, - SkillName.Camping, - SkillName.Carpentry, - SkillName.Cartography, - SkillName.Chivalry, - SkillName.Cooking, - SkillName.DetectHidden, - SkillName.Discordance, - SkillName.EvalInt, - SkillName.Fencing, - SkillName.Fishing, - SkillName.Focus, - SkillName.Forensics, - SkillName.Healing, - SkillName.Herding, - SkillName.Hiding, - SkillName.Imbuing, - SkillName.Inscribe, - SkillName.ItemID, - SkillName.Lockpicking, - SkillName.Lumberjacking, - SkillName.Macing, - SkillName.Magery, - SkillName.Meditation, - SkillName.Mining, - SkillName.Musicianship, - SkillName.Mysticism, - SkillName.Necromancy, - SkillName.Ninjitsu, - SkillName.Parry, - SkillName.Peacemaking, - SkillName.Poisoning, - SkillName.Provocation, - SkillName.MagicResist, - SkillName.Snooping, - SkillName.SpiritSpeak, - SkillName.Stealing, - SkillName.Swords, - SkillName.Tactics, - SkillName.Tailoring, - SkillName.TasteID, - SkillName.Throwing, - SkillName.Tinkering, - SkillName.Tracking, - SkillName.Veterinary, - SkillName.Wrestling - }; + pack = new Backpack(); + pack.Movable = false; - private static readonly TimeSpan BadStartMessageDelay = TimeSpan.FromSeconds(3.5); - - private static readonly CityInfo _newHavenInfo = - new("New Haven", "The Bountiful Harvest Inn", 3503, 2574, 14, Map.Trammel); - - public static void Initialize() - { - // Register our event handler - EventSink.CharacterCreated += EventSink_CharacterCreated; + m.AddItem(pack); } - private static void AddBackpack(this Mobile m) + m.PackItem(new RedBook("a book", m.Name, 20, true)); + m.PackItem(new Gold(1000)); // Starting gold can be customized here + m.PackItem(new Dagger()); + m.PackItem(new Candle()); + } + + private static Mobile CreateMobile(Account a) + { + if (a.Count >= a.Limit) { - var pack = m.Backpack; - - if (pack == null) - { - pack = new Backpack(); - pack.Movable = false; - - m.AddItem(pack); - } - - m.PackItem(new RedBook("a book", m.Name, 20, true)); - m.PackItem(new Gold(1000)); // Starting gold can be customized here - m.PackItem(new Dagger()); - m.PackItem(new Candle()); - } - - private static Mobile CreateMobile(Account a) - { - if (a.Count >= a.Limit) - { - return null; - } - - for (var i = 0; i < a.Length; ++i) - { - if (a[i] == null) - { - return a[i] = new PlayerMobile(); - } - } - return null; } - private static void EventSink_CharacterCreated(CharacterCreatedEventArgs args) + for (var i = 0; i < a.Length; ++i) { - if (!VerifyProfession(args.Profession)) + if (a[i] == null) { - args.Profession = 0; + return a[i] = new PlayerMobile(); } - - var state = args.State; - - if (state == null) - { - return; - } - - var newChar = CreateMobile(args.Account as Account); - - if (newChar == null) - { - logger.Information("Login: {NetState}: Character creation failed, account full", state); - return; - } - - args.Mobile = newChar; - - newChar.Player = true; - newChar.AccessLevel = args.Account.AccessLevel; - newChar.Female = args.Female; - newChar.Race = Core.Expansion >= args.Race.RequiredExpansion ? args.Race : Race.DefaultRace; - newChar.Hue = newChar.Race.ClipSkinHue(args.Hue & 0x3FFF) | 0x8000; - newChar.Hunger = 20; - - var young = false; - - if (newChar is PlayerMobile pm) - { - pm.Profession = args.Profession; - - if (pm.AccessLevel == AccessLevel.Player && ((Account)pm.Account).Young) - { - young = pm.Young = true; - } - } - - SetName(newChar, args.Name); - - newChar.AddBackpack(); - - SetStats(newChar, state, args.Stats, args.Profession); - SetSkills(newChar, args.Skills, args.Profession, args.ShirtHue, args.PantsHue); - - var race = newChar.Race; - - if (race.ValidateHair(newChar, args.HairID)) - { - newChar.HairItemID = args.HairID; - newChar.HairHue = race.ClipHairHue(args.HairHue & 0x3FFF); - } - - if (race.ValidateFacialHair(newChar, args.BeardID)) - { - newChar.FacialHairItemID = args.BeardID; - newChar.FacialHairHue = race.ClipHairHue(args.BeardHue & 0x3FFF); - } - - if (TestCenter.Enabled) - { - TestCenter.FillBankbox(newChar); - } - - if (young) - { - var ticket = new NewPlayerTicket(); - ticket.Owner = newChar; - newChar.BankBox.DropItem(ticket); - } - - var city = GetStartLocation(args, young); - - newChar.MoveToWorld(city.Location, city.Map); - - logger.Information( - "Login: {0}: New character being created (account={1}, character={2}, serial={3}, started.city={4}, started.location={5}, started.map={6})", - state, - args.Account.Username, - newChar.Name, - newChar.Serial, - city.City, - city.Location, - city.Map); - - new WelcomeTimer(newChar).Start(); } - public static bool VerifyProfession(int profession) => - profession >= 0 && profession < ProfessionInfo.Professions.Length; + return null; + } - private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool isYoung) + private static void EventSink_CharacterCreated(CharacterCreatedEventArgs args) + { + if (!VerifyProfession(args.Profession)) { - if (Core.ML) - { - return _newHavenInfo; // We don't get the client Version until AFTER Character creation - } - - var useHaven = isYoung; - - var flags = args.State?.Flags ?? ClientFlags.None; - var m = args.Mobile; - - var profession = ProfessionInfo.Professions[args.Profession]; - - switch (profession?.Name.ToLowerInvariant()) - { - case "necromancer": - { - if ((flags & ClientFlags.Malas) != 0) - { - return new CityInfo("Umbra", "Mardoth's Tower", 2114, 1301, -50, Map.Malas); - } - - useHaven = true; - - /* - * Unfortunately you are playing on a *NON-Age-Of-Shadows* game - * installation and cannot be transported to Malas. - * You will not be able to take your new player quest in Malas - * without an AOS client. You are now being taken to the city of - * Haven on the Trammel facet. - */ - Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1062205)); - - break; - } - case "paladin": - { - return _newHavenInfo; - } - case "samurai": - { - if ((flags & ClientFlags.Tokuno) != 0) - { - return new CityInfo("Samurai DE", "Haoti's Grounds", 368, 780, -1, Map.Malas); - } - - useHaven = true; - - /* - * Unfortunately you are playing on a *NON-Samurai-Empire* game - * installation and cannot be transported to Tokuno. - * You will not be able to take your new player quest in Tokuno - * without an SE client. You are now being taken to the city of - * Haven on the Trammel facet. - */ - Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1063487)); - - break; - } - case "ninja": - { - if ((flags & ClientFlags.Tokuno) != 0) - { - return new CityInfo("Ninja DE", "Enimo's Residence", 414, 823, -1, Map.Malas); - } - - useHaven = true; - - /* - * Unfortunately you are playing on a *NON-Samurai-Empire* game - * installation and cannot be transported to Tokuno. - * You will not be able to take your new player quest in Tokuno - * without an SE client. You are now being taken to the city of - * Haven on the Trammel facet. - */ - Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1063487)); - - break; - } - } - - return useHaven ? _newHavenInfo : args.City; + args.Profession = 0; } - private static void SetStats(Mobile m, NetState state, StatNameValue[] stats, int prof) + var state = args.State; + + if (state == null) { - var maxStats = state.NewCharacterCreation ? 90 : 80; - - var str = 0; - var dex = 0; - var intel = 0; - - if (prof > 0) - { - stats = ProfessionInfo.Professions[prof]?.Stats ?? stats; - } - - for (var i = 0; i < stats.Length; i++) - { - var (statType, value) = stats[i]; - switch (statType) - { - case StatType.Str: str = value; break; - case StatType.Dex: dex = value; break; - case StatType.Int: intel = value; break; - } - } - - if (str is < 10 or > 60 || dex is < 10 or > 60 || intel is < 10 or > 60 || str + dex + intel != maxStats) - { - str = 10; - dex = 10; - intel = 10; - } - - m.InitStats(str, dex, intel); + return; } - private static void SetName(Mobile m, string name) + var newChar = CreateMobile(args.Account as Account); + + if (newChar == null) { - name = name.Trim(); - - if (!NameVerification.Validate(name, 2, 16, true, false, true, 1, NameVerification.SpaceDashPeriodQuote)) - { - name = "Generic Player"; - } - - m.Name = name; + logger.Information("Login: {NetState}: Character creation failed, account full", state); + return; } - private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills) + args.Mobile = newChar; + + newChar.Player = true; + newChar.AccessLevel = args.Account.AccessLevel; + newChar.Female = args.Female; + newChar.Race = Core.Expansion >= args.Race.RequiredExpansion ? args.Race : Race.DefaultRace; + newChar.Hue = newChar.Race.ClipSkinHue(args.Hue & 0x3FFF) | 0x8000; + newChar.Hunger = 20; + + var young = false; + + if (newChar is PlayerMobile pm) { - var total = 0; + pm.Profession = args.Profession; - for (var i = 0; i < skills.Length; ++i) + if (pm.AccessLevel == AccessLevel.Player && ((Account)pm.Account).Young) { - var (name, value) = skills[i]; - var notValid = value is < 0 or > 50 || !_allowedStartingSkills.Contains(name) || - !Core.AOS && name is SkillName.Necromancy or SkillName.Chivalry || - !Core.SE && name is SkillName.Ninjitsu or SkillName.Bushido || - Core.SA && (raceFlag == Race.AllowGargoylesOnly && name == SkillName.Archery || - raceFlag != Race.AllowGargoylesOnly && name == SkillName.Throwing) || - !Core.SA && name is SkillName.Throwing or SkillName.Imbuing; - - if (notValid) - { - skills[i] = default; - continue; - } - - total += value; - - for (var j = i + 1; j < skills.Length; ++j) - { - var (nameCheck, valueCheck) = skills[j]; - if (valueCheck > 0 && nameCheck == name) - { - return false; - } - } + young = pm.Young = true; } - - return total is 100 or 120; } - private static void SetSkills(Mobile m, SkillNameValue[] skills, int prof, int shirtHue, int pantsHue) + SetName(newChar, args.Name); + + newChar.AddBackpack(); + + SetStats(newChar, state, args.Stats, args.Profession); + SetSkills(newChar, args.Skills, args.Profession, args.ShirtHue, args.PantsHue); + + var race = newChar.Race; + + if (race.ValidateHair(newChar, args.HairID)) { - ProfessionInfo profession = null; - if (prof > 0) - { - profession = ProfessionInfo.Professions[prof]; - skills = ProfessionInfo.Professions[prof]?.Skills ?? skills; - } - else if (!ValidateSkills(m.Race.RaceFlag, skills)) // This does not check for skills that are not allowed by expansion - { - return; - } + newChar.HairItemID = args.HairID; + newChar.HairHue = race.ClipHairHue(args.HairHue & 0x3FFF); + } - var addSkillItems = true; - var elf = m.Race == Race.Elf; - var gargoyle = m.Race == Race.Gargoyle; + if (race.ValidateFacialHair(newChar, args.BeardID)) + { + newChar.FacialHairItemID = args.BeardID; + newChar.FacialHairHue = race.ClipHairHue(args.BeardHue & 0x3FFF); + } - switch (profession?.Name.ToLowerInvariant()) - { - case "necromancer": - { - Container regs = new BagOfNecroReagents { LootType = LootType.Regular }; + if (TestCenter.Enabled) + { + TestCenter.FillBankbox(newChar); + } - if (!Core.AOS) - { - foreach (var item in regs.Items) - { - item.LootType = LootType.Newbied; - } - } + if (young) + { + var ticket = new NewPlayerTicket(); + ticket.Owner = newChar; + newChar.BankBox.DropItem(ticket); + } - m.PackItem(regs); + var city = GetStartLocation(args, young); - EquipItem(m, new BoneHelm()); + newChar.MoveToWorld(city.Location, city.Map); - if (elf) - { - EquipItem(m, new ElvenMachete()); - EquipItem(m, NecroHue(new LeafChest())); - EquipItem(m, NecroHue(new LeafArms())); - EquipItem(m, NecroHue(new LeafGloves())); - EquipItem(m, NecroHue(new LeafGorget())); - EquipItem(m, NecroHue(new LeafLegs())); - EquipItem(m, new ElvenBoots()); - } - else if (gargoyle) - { - EquipItem(m, new GlassSword()); - EquipItem(m, NecroHue(m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1())); - EquipItem(m, NecroHue(m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1())); - EquipItem(m, NecroHue(m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1())); - EquipItem(m, NecroHue(m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1())); - } - else - { - EquipItem(m, new BoneHarvester()); - EquipItem(m, NecroHue(new LeatherChest())); - EquipItem(m, NecroHue(new LeatherArms())); - EquipItem(m, NecroHue(new LeatherGloves())); - EquipItem(m, NecroHue(new LeatherGorget())); - EquipItem(m, NecroHue(new LeatherLegs())); - EquipItem(m, NecroHue(new Skirt())); - EquipItem(m, new Sandals(0x8FD)); - } + logger.Information( + "Login: {0}: New character being created (account={1}, character={2}, serial={3}, started.city={4}, started.location={5}, started.map={6})", + state, + args.Account.Username, + newChar.Name, + newChar.Serial, + city.City, + city.Location, + city.Map); - // animate dead, evil omen, pain spike, summon familiar, wraith form - m.PackItem(new NecromancerSpellbook(0x8981ul) { LootType = LootType.Blessed }); + new WelcomeTimer(newChar).Start(); + } - addSkillItems = false; + public static bool VerifyProfession(int profession) => + profession >= 0 && profession < ProfessionInfo.Professions.Length; - break; - } - case "paladin": - { - if (elf) - { - EquipItem(m, new ElvenMachete()); - EquipItem(m, new WingedHelm()); - EquipItem(m, new LeafGorget()); - EquipItem(m, new LeafArms()); - EquipItem(m, new LeafChest()); - EquipItem(m, new LeafLegs()); - EquipItem(m, new LeafGloves()); - EquipItem(m, new ElvenBoots()); // Verify hue - } - else if (gargoyle) - { - EquipItem(m, new GlassSword()); - EquipItem(m, m.Female ? new GargishStoneChestType2() : new GargishStoneChestType1()); - EquipItem(m, m.Female ? new GargishStoneArmsType2() : new GargishStoneArmsType1()); - EquipItem(m, m.Female ? new GargishStoneKiltType2() : new GargishStoneKiltType1()); - EquipItem(m, m.Female ? new GargishStoneLegsType2() : new GargishStoneLegsType1()); - } - else - { - EquipItem(m, new Broadsword()); - EquipItem(m, new Helmet()); - EquipItem(m, new PlateGorget()); - EquipItem(m, new RingmailArms()); - EquipItem(m, new RingmailChest()); - EquipItem(m, new RingmailLegs()); - EquipItem(m, new RingmailGloves()); - EquipItem(m, new ThighBoots(0x748)); - EquipItem(m, new Cloak(0xCF)); - EquipItem(m, new BodySash(0xCF)); - } + private static CityInfo GetStartLocation(CharacterCreatedEventArgs args, bool isYoung) + { + var post6000Supported = !TileMatrix.Pre6000ClientSupport; - m.PackItem(new BookOfChivalry { LootType = LootType.Blessed }); + if (Core.ML && post6000Supported) + { + return _newHavenInfo; // We don't get the client Version until AFTER Character creation + } - addSkillItems = false; + var useHaven = isYoung; - break; - } - case "samurai": - { - addSkillItems = false; + var flags = args.State?.Flags ?? ClientFlags.None; + var m = args.Mobile; - if (elf) - { - EquipItem(m, new RavenHelm()); - EquipItem(m, new HakamaShita(0x2C3)); - EquipItem(m, new Hakama(0x2C3)); - EquipItem(m, new SamuraiTabi(0x2C3)); - EquipItem(m, new TattsukeHakama(0x22D)); - EquipItem(m, new Bokuto()); - } - else if (gargoyle) - { - EquipItem(m, new GargishTalwar()); - EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); - EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); - EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); - EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); - } - else - { - EquipItem(m, new LeatherJingasa()); - EquipItem(m, new HakamaShita(0x2C3)); - EquipItem(m, new Hakama(0x2C3)); - EquipItem(m, new SamuraiTabi(0x2C3)); - EquipItem(m, new TattsukeHakama(0x22D)); - EquipItem(m, new Bokuto()); - } + var profession = ProfessionInfo.Professions[args.Profession]; - m.PackItem(new Scissors()); - m.PackItem(new Bandage(50)); - m.PackItem(new BookOfBushido()); - - break; - } - case "ninja": - { - addSkillItems = false; - - int[] hues = { 0x1A8, 0xEC, 0x99, 0x90, 0xB5, 0x336, 0x89 }; - // TODO: Verify that's ALL the hues for that above. - - if (elf) - { - EquipItem(m, new AssassinSpike()); - EquipItem(m, new TattsukeHakama(hues.RandomElement())); - EquipItem(m, new HakamaShita(0x2C3)); - EquipItem(m, new NinjaTabi(0x2C3)); - EquipItem(m, new Kasa()); - } - else if (gargoyle) - { - EquipItem(m, new DualPointedSpear()); - EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); - EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); - EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); - EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); - } - else - { - EquipItem(m, new Tekagi()); - EquipItem(m, new TattsukeHakama(hues.RandomElement())); - EquipItem(m, new HakamaShita(0x2C3)); - EquipItem(m, new NinjaTabi(0x2C3)); - EquipItem(m, new Kasa()); - } - - m.PackItem(new SmokeBomb()); - m.PackItem(new SmokeBomb()); - m.PackItem(new SmokeBomb()); - m.PackItem(new SmokeBomb()); - m.PackItem(new SmokeBomb()); - m.PackItem(new BookOfNinjitsu()); - break; - } - case "swordsman": - case "fencer": - case "warrior": - case "mace fighter": - { - if (elf) - { - EquipItem(m, new Circlet()); - EquipItem(m, new HideGorget()); - EquipItem(m, new HideChest()); - EquipItem(m, new HidePauldrons()); - EquipItem(m, new HideGloves()); - EquipItem(m, new HidePants()); - EquipItem(m, new ElvenBoots()); - } - else if (gargoyle) - { - EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); - EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); - EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); - EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); - } - else - { - EquipItem(m, new Bascinet()); - EquipItem(m, new StuddedGorget()); - EquipItem(m, new StuddedChest()); - EquipItem(m, new StuddedArms()); - EquipItem(m, new StuddedGloves()); - EquipItem(m, new StuddedLegs()); - EquipItem(m, new ThighBoots()); - } - break; - } - } - - if (addSkillItems) - { - m.AddShirt(shirtHue); - m.AddPants(pantsHue); - m.AddShoes(); - - // All elves get a wild staff - if (elf) + switch (profession?.Name.ToLowerInvariant()) + { + case "necromancer": { - EquipItem(m, new WildStaff()); + if ((flags & ClientFlags.Malas) != 0) + { + return new CityInfo("Umbra", "Mardoth's Tower", 2114, 1301, -50, Map.Malas); + } + + useHaven = true; + + /* + * Unfortunately you are playing on a *NON-Age-Of-Shadows* game + * installation and cannot be transported to Malas. + * You will not be able to take your new player quest in Malas + * without an AOS client. You are now being taken to the city of + * Haven on the Trammel facet. + */ + Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1062205)); + + break; } + case "paladin": + { + return _newHavenInfo; + } + case "samurai": + { + if ((flags & ClientFlags.Tokuno) != 0) + { + return new CityInfo("Samurai DE", "Haoti's Grounds", 368, 780, -1, Map.Malas); + } + + useHaven = true; + + /* + * Unfortunately you are playing on a *NON-Samurai-Empire* game + * installation and cannot be transported to Tokuno. + * You will not be able to take your new player quest in Tokuno + * without an SE client. You are now being taken to the city of + * Haven on the Trammel facet. + */ + Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1063487)); + + break; + } + case "ninja": + { + if ((flags & ClientFlags.Tokuno) != 0) + { + return new CityInfo("Ninja DE", "Enimo's Residence", 414, 823, -1, Map.Malas); + } + + useHaven = true; + + /* + * Unfortunately you are playing on a *NON-Samurai-Empire* game + * installation and cannot be transported to Tokuno. + * You will not be able to take your new player quest in Tokuno + * without an SE client. You are now being taken to the city of + * Haven on the Trammel facet. + */ + Timer.StartTimer(BadStartMessageDelay, () => m.SendLocalizedMessage(1063487)); + + break; + } + } + + return post6000Supported && useHaven ? _newHavenInfo : args.City; + } + + private static void SetStats(Mobile m, NetState state, StatNameValue[] stats, int prof) + { + var maxStats = state.NewCharacterCreation ? 90 : 80; + + var str = 0; + var dex = 0; + var intel = 0; + + if (prof > 0) + { + stats = ProfessionInfo.Professions[prof]?.Stats ?? stats; + } + + for (var i = 0; i < stats.Length; i++) + { + var (statType, value) = stats[i]; + switch (statType) + { + case StatType.Str: str = value; break; + case StatType.Dex: dex = value; break; + case StatType.Int: intel = value; break; + } + } + + if (str is < 10 or > 60 || dex is < 10 or > 60 || intel is < 10 or > 60 || str + dex + intel != maxStats) + { + str = 10; + dex = 10; + intel = 10; + } + + m.InitStats(str, dex, intel); + } + + private static void SetName(Mobile m, string name) + { + name = name.Trim(); + + if (!NameVerification.Validate(name, 2, 16, true, false, true, 1, NameVerification.SpaceDashPeriodQuote)) + { + name = "Generic Player"; + } + + m.Name = name; + } + + private static bool ValidateSkills(int raceFlag, SkillNameValue[] skills) + { + var total = 0; + + for (var i = 0; i < skills.Length; ++i) + { + var (name, value) = skills[i]; + var notValid = value is < 0 or > 50 || !_allowedStartingSkills.Contains(name) || + !Core.AOS && name is SkillName.Necromancy or SkillName.Chivalry || + !Core.SE && name is SkillName.Ninjitsu or SkillName.Bushido || + Core.SA && (raceFlag == Race.AllowGargoylesOnly && name == SkillName.Archery || + raceFlag != Race.AllowGargoylesOnly && name == SkillName.Throwing) || + !Core.SA && name is SkillName.Throwing or SkillName.Imbuing; + + if (notValid) + { + skills[i] = default; + continue; } - for (var i = 0; i < skills.Length; ++i) + total += value; + + for (var j = i + 1; j < skills.Length; ++j) { - var (name, value) = skills[i]; - if (value <= 0) + var (nameCheck, valueCheck) = skills[j]; + if (valueCheck > 0 && nameCheck == name) { - continue; - } - - var skill = m.Skills[name]; - - if (skill != null) - { - skill.BaseFixedPoint = value * 10; - m.AddSkillItems(name); + return false; } } } - private static void EquipItem(Mobile m, Item item, bool mustEquip = false) + return total is 100 or 120; + } + + private static void SetSkills(Mobile m, SkillNameValue[] skills, int prof, int shirtHue, int pantsHue) + { + ProfessionInfo profession = null; + if (prof > 0) { - if (item == null) - { - return; - } - - if (!Core.AOS) - { - item.LootType = LootType.Newbied; - } - - if (m?.EquipItem(item) == true) - { - return; - } - - var pack = m?.Backpack; - - if (!mustEquip && pack != null) - { - pack.DropItem(item); - } - else - { - item.Delete(); - } + profession = ProfessionInfo.Professions[prof]; + skills = ProfessionInfo.Professions[prof]?.Skills ?? skills; + } + else if (!ValidateSkills(m.Race.RaceFlag, skills)) // This does not check for skills that are not allowed by expansion + { + return; } - private static void PackItem(this Mobile m, Item item) + var addSkillItems = true; + var elf = m.Race == Race.Elf; + var gargoyle = m.Race == Race.Gargoyle; + + switch (profession?.Name.ToLowerInvariant()) { - if (!Core.AOS) - { - item.LootType = LootType.Newbied; - } - - var pack = m.Backpack; - - if (pack != null) - { - pack.DropItem(item); - } - else - { - item.Delete(); - } - } - - private static void AddShirt(this Mobile m, int shirtHue) - { - var hue = Utility.ClipDyedHue(shirtHue & 0x3FFF); - var raceFlag = m.Race.RaceFlag; - - var shirt = raceFlag switch - { - Race.AllowElvesOnly => new ElvenShirt(hue), - Race.AllowGargoylesOnly when m.Female => new GargishClothChestType2 { Hue = hue }, - Race.AllowGargoylesOnly => new GargishClothChestType1 { Hue = hue }, - // Humans - _ => (Item)(Utility.Random(3) switch + case "necromancer": { - 0 => new Shirt(hue), - 1 => new FancyShirt(hue), - _ => new Doublet(hue) - }) - }; + Container regs = new BagOfNecroReagents { LootType = LootType.Regular }; - EquipItem(m, shirt); - } + if (!Core.AOS) + { + foreach (var item in regs.Items) + { + item.LootType = LootType.Newbied; + } + } - private static void AddPants(this Mobile m, int pantsHue) - { - var hue = Utility.ClipDyedHue(pantsHue & 0x3FFF); - var raceFlag = m.Race.RaceFlag; - var female = m.Female; + m.PackItem(regs); - var pants = raceFlag switch - { - Race.AllowElvesOnly => new ElvenPants(hue), - Race.AllowGargoylesOnly when female => new GargishClothLegsType2 { Hue = hue }, - Race.AllowGargoylesOnly => new GargishClothLegsType1 { Hue = hue }, - // Humans - _ => (Item)(Utility.RandomBool() switch + EquipItem(m, new BoneHelm()); + + if (elf) + { + EquipItem(m, new ElvenMachete()); + EquipItem(m, NecroHue(new LeafChest())); + EquipItem(m, NecroHue(new LeafArms())); + EquipItem(m, NecroHue(new LeafGloves())); + EquipItem(m, NecroHue(new LeafGorget())); + EquipItem(m, NecroHue(new LeafLegs())); + EquipItem(m, new ElvenBoots()); + } + else if (gargoyle) + { + EquipItem(m, new GlassSword()); + EquipItem(m, NecroHue(m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1())); + EquipItem(m, NecroHue(m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1())); + EquipItem(m, NecroHue(m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1())); + EquipItem(m, NecroHue(m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1())); + } + else + { + EquipItem(m, new BoneHarvester()); + EquipItem(m, NecroHue(new LeatherChest())); + EquipItem(m, NecroHue(new LeatherArms())); + EquipItem(m, NecroHue(new LeatherGloves())); + EquipItem(m, NecroHue(new LeatherGorget())); + EquipItem(m, NecroHue(new LeatherLegs())); + EquipItem(m, NecroHue(new Skirt())); + EquipItem(m, new Sandals(0x8FD)); + } + + // animate dead, evil omen, pain spike, summon familiar, wraith form + m.PackItem(new NecromancerSpellbook(0x8981ul) { LootType = LootType.Blessed }); + + addSkillItems = false; + + break; + } + case "paladin": { - true when female => new Skirt(hue), - true => new LongPants(hue), - false when female => new Kilt(hue), - false => new ShortPants(hue) - }) - }; + if (elf) + { + EquipItem(m, new ElvenMachete()); + EquipItem(m, new WingedHelm()); + EquipItem(m, new LeafGorget()); + EquipItem(m, new LeafArms()); + EquipItem(m, new LeafChest()); + EquipItem(m, new LeafLegs()); + EquipItem(m, new LeafGloves()); + EquipItem(m, new ElvenBoots()); // Verify hue + } + else if (gargoyle) + { + EquipItem(m, new GlassSword()); + EquipItem(m, m.Female ? new GargishStoneChestType2() : new GargishStoneChestType1()); + EquipItem(m, m.Female ? new GargishStoneArmsType2() : new GargishStoneArmsType1()); + EquipItem(m, m.Female ? new GargishStoneKiltType2() : new GargishStoneKiltType1()); + EquipItem(m, m.Female ? new GargishStoneLegsType2() : new GargishStoneLegsType1()); + } + else + { + EquipItem(m, new Broadsword()); + EquipItem(m, new Helmet()); + EquipItem(m, new PlateGorget()); + EquipItem(m, new RingmailArms()); + EquipItem(m, new RingmailChest()); + EquipItem(m, new RingmailLegs()); + EquipItem(m, new RingmailGloves()); + EquipItem(m, new ThighBoots(0x748)); + EquipItem(m, new Cloak(0xCF)); + EquipItem(m, new BodySash(0xCF)); + } - EquipItem(m, pants); + m.PackItem(new BookOfChivalry { LootType = LootType.Blessed }); + + addSkillItems = false; + + break; + } + case "samurai": + { + addSkillItems = false; + + if (elf) + { + EquipItem(m, new RavenHelm()); + EquipItem(m, new HakamaShita(0x2C3)); + EquipItem(m, new Hakama(0x2C3)); + EquipItem(m, new SamuraiTabi(0x2C3)); + EquipItem(m, new TattsukeHakama(0x22D)); + EquipItem(m, new Bokuto()); + } + else if (gargoyle) + { + EquipItem(m, new GargishTalwar()); + EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); + EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); + EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); + EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); + } + else + { + EquipItem(m, new LeatherJingasa()); + EquipItem(m, new HakamaShita(0x2C3)); + EquipItem(m, new Hakama(0x2C3)); + EquipItem(m, new SamuraiTabi(0x2C3)); + EquipItem(m, new TattsukeHakama(0x22D)); + EquipItem(m, new Bokuto()); + } + + m.PackItem(new Scissors()); + m.PackItem(new Bandage(50)); + m.PackItem(new BookOfBushido()); + + break; + } + case "ninja": + { + addSkillItems = false; + + int[] hues = { 0x1A8, 0xEC, 0x99, 0x90, 0xB5, 0x336, 0x89 }; + // TODO: Verify that's ALL the hues for that above. + + if (elf) + { + EquipItem(m, new AssassinSpike()); + EquipItem(m, new TattsukeHakama(hues.RandomElement())); + EquipItem(m, new HakamaShita(0x2C3)); + EquipItem(m, new NinjaTabi(0x2C3)); + EquipItem(m, new Kasa()); + } + else if (gargoyle) + { + EquipItem(m, new DualPointedSpear()); + EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); + EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); + EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); + EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); + } + else + { + EquipItem(m, new Tekagi()); + EquipItem(m, new TattsukeHakama(hues.RandomElement())); + EquipItem(m, new HakamaShita(0x2C3)); + EquipItem(m, new NinjaTabi(0x2C3)); + EquipItem(m, new Kasa()); + } + + m.PackItem(new SmokeBomb()); + m.PackItem(new SmokeBomb()); + m.PackItem(new SmokeBomb()); + m.PackItem(new SmokeBomb()); + m.PackItem(new SmokeBomb()); + m.PackItem(new BookOfNinjitsu()); + break; + } + case "swordsman": + case "fencer": + case "warrior": + case "mace fighter": + { + if (elf) + { + EquipItem(m, new Circlet()); + EquipItem(m, new HideGorget()); + EquipItem(m, new HideChest()); + EquipItem(m, new HidePauldrons()); + EquipItem(m, new HideGloves()); + EquipItem(m, new HidePants()); + EquipItem(m, new ElvenBoots()); + } + else if (gargoyle) + { + EquipItem(m, m.Female ? new GargishLeatherChestType2() : new GargishLeatherChestType1()); + EquipItem(m, m.Female ? new GargishLeatherArmsType2() : new GargishLeatherArmsType1()); + EquipItem(m, m.Female ? new GargishLeatherKiltType2() : new GargishLeatherKiltType1()); + EquipItem(m, m.Female ? new GargishLeatherLegsType2() : new GargishLeatherLegsType1()); + } + else + { + EquipItem(m, new Bascinet()); + EquipItem(m, new StuddedGorget()); + EquipItem(m, new StuddedChest()); + EquipItem(m, new StuddedArms()); + EquipItem(m, new StuddedGloves()); + EquipItem(m, new StuddedLegs()); + EquipItem(m, new ThighBoots()); + } + break; + } } - private static void AddShoes(this Mobile m) + if (addSkillItems) { - if (m.Race == Race.Elf) + m.AddShirt(shirtHue); + m.AddPants(pantsHue); + m.AddShoes(); + + // All elves get a wild staff + if (elf) { - EquipItem(m, new ElvenBoots()); - } - else if (m.Race == Race.Human) - { - EquipItem(m, new Shoes(Utility.RandomYellowHue())); + EquipItem(m, new WildStaff()); } } - private static void PackInstrument(this Mobile m) + for (var i = 0; i < skills.Length; ++i) { - Item instrument = Utility.Random(6) switch + var (name, value) = skills[i]; + if (value <= 0) { - 0 => new Drums(), - 1 => new Harp(), - 2 => new LapHarp(), - 3 => new Lute(), - 4 => new Tambourine(), - _ => new TambourineTassel() - }; + continue; + } - m.PackItem(instrument); - } + var skill = m.Skills[name]; - private static void PackScroll(this Mobile m, int circle) - { - Item item = (Utility.Random(8) * (circle + 1)) switch + if (skill != null) { - 0 => new ClumsyScroll(), - 1 => new CreateFoodScroll(), - 2 => new FeeblemindScroll(), - 3 => new HealScroll(), - 4 => new MagicArrowScroll(), - 5 => new NightSightScroll(), - 6 => new ReactiveArmorScroll(), - 7 => new WeakenScroll(), - 8 => new AgilityScroll(), - 9 => new CunningScroll(), - 10 => new CureScroll(), - 11 => new HarmScroll(), - 12 => new MagicTrapScroll(), - 13 => new MagicUnTrapScroll(), - 14 => new ProtectionScroll(), - 15 => new StrengthScroll(), - 16 => new BlessScroll(), - 17 => new FireballScroll(), - 18 => new MagicLockScroll(), - 19 => new PoisonScroll(), - 20 => new TelekinesisScroll(), - 21 => new TeleportScroll(), - 22 => new UnlockScroll(), - _ => new WallOfStoneScroll() - }; - - m.PackItem(item); - } - - private static void PackTinkerPart(this Mobile m) - { - Item item = Utility.Random(4) switch - { - 0 => new Axle(), - 1 => new Gears(), - 2 => new Hinge(), - 3 => new Springs() - }; - - m.PackItem(item); - } - - private static Item NecroHue(Item item) - { - item.Hue = 0x2C3; - - return item; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Item Robe(int raceFlag, bool female, int hue) => - raceFlag switch - { - Race.AllowElvesOnly when female => new FemaleElvenRobe(hue), - Race.AllowElvesOnly => new MaleElvenRobe(hue), - _ => new Robe(hue) - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Item SwordsWeapon(int raceFlag) => - raceFlag switch - { - Race.AllowElvesOnly => new RuneBlade(), - Race.AllowGargoylesOnly => new DreadSword(), - _ => new Katana() - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Item MacingWeapon(int raceFlag) => - raceFlag switch - { - Race.AllowElvesOnly => new DiamondMace(), - Race.AllowGargoylesOnly => new DiscMace(), - _ => new Club() - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Item FencingWeapon(int raceFlag) => - raceFlag switch - { - Race.AllowElvesOnly => new Leafblade(), - Race.AllowGargoylesOnly => new BloodBlade(), - _ => new Kryss() - }; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Item RangedWeapon(int raceFlag) => - raceFlag switch - { - Race.AllowElvesOnly => new ElvenCompositeLongbow(), - Race.AllowGargoylesOnly => new SerpentstoneStaff(), - _ => new Bow() - }; - - private static void AddSkillItems(this Mobile m, SkillName skill) - { - var raceFlag = m.Race.RaceFlag; - var elf = m.Race == Race.Elf; - var human = m.Race == Race.Human; - var gargoyle = m.Race == Race.Gargoyle; - var elfOrHuman = Race.IsAllowedRace(m.Race, Race.AllowHumanOrElves); - var female = m.Female; - - switch (skill) - { - case SkillName.Alchemy: - { - m.PackItem(new Bottle(4)); - m.PackItem(new MortarPestle()); - EquipItem(m, Robe(raceFlag, female, Utility.RandomPinkHue())); - - break; - } - case SkillName.Anatomy: - { - m.PackItem(new Bandage(3)); - EquipItem(m, Robe(raceFlag, female, Utility.RandomYellowHue())); - - break; - } - case SkillName.AnimalLore: - { - if (elf) - { - EquipItem(m, new WildStaff()); - } - else - { - EquipItem(m, new ShepherdsCrook()); - } - - EquipItem(m, Robe(raceFlag, female, Utility.RandomGreenHue())); - - break; - } - case SkillName.AnimalTaming: - { - if (human) - { - EquipItem(m, new ShepherdsCrook()); - } - - break; - } - case SkillName.Archery: - { - m.PackItem(new Arrow(25)); - - EquipItem(m, RangedWeapon(raceFlag)); - - break; - } - case SkillName.ArmsLore: - { - Item item = Utility.Random(3) switch - { - 0 => FencingWeapon(raceFlag), - 1 => MacingWeapon(raceFlag), - _ => SwordsWeapon(raceFlag) - }; - EquipItem(m, item); - - break; - } - case SkillName.Begging: - { - Item staff = raceFlag switch - { - Race.AllowElvesOnly => new WildStaff(), - Race.AllowGargoylesOnly => new GlassStaff(), - _ => new GnarledStaff() - }; - - EquipItem(m, staff); - break; - } - case SkillName.Blacksmith: - { - m.PackItem(new Tongs()); - m.PackItem(new Pickaxe()); - m.PackItem(new Pickaxe()); - m.PackItem(new IronIngot(50)); - EquipItem(m, new HalfApron(Utility.RandomYellowHue())); - break; - } - case SkillName.Bushido: - { - if (elfOrHuman) - { - // Delete pants - m?.FindItemOnLayer(Layer.OuterLegs)?.Delete(); - - EquipItem(m, new Hakama()); - EquipItem(m, new Kasa()); - } - - EquipItem(m, new BookOfBushido()); - break; - } - case SkillName.Fletching: - { - m.PackItem(new Board(14)); - m.PackItem(new Feather(5)); - m.PackItem(new Shaft(5)); - break; - } - case SkillName.Camping: - { - m.PackItem(new Bedroll()); - m.PackItem(new Kindling(5)); - break; - } - case SkillName.Carpentry: - { - m.PackItem(new Board(10)); - m.PackItem(new Saw()); - - if (elfOrHuman) - { - EquipItem(m, new HalfApron(Utility.RandomYellowHue())); - } - - break; - } - case SkillName.Cartography: - { - m.PackItem(new BlankMap()); - m.PackItem(new BlankMap()); - m.PackItem(new BlankMap()); - m.PackItem(new BlankMap()); - m.PackItem(new Sextant()); - break; - } - case SkillName.Cooking: - { - m.PackItem(new Kindling(2)); - m.PackItem(new RawLambLeg()); - m.PackItem(new RawChickenLeg()); - m.PackItem(new RawFishSteak()); - m.PackItem(new SackFlour()); - m.PackItem(new Pitcher(BeverageType.Water)); - break; - } - case SkillName.Chivalry: - { - m.PackItem(new BookOfChivalry()); - break; - } - case SkillName.DetectHidden: - { - if (elfOrHuman) - { - EquipItem(m, new Cloak(0x455)); - } - - break; - } - case SkillName.Discordance: - { - m.PackInstrument(); - break; - } - case SkillName.Fencing: - { - EquipItem(m, FencingWeapon(raceFlag)); - break; - } - case SkillName.Fishing: - { - EquipItem(m, new FishingPole()); - - var hue = Utility.RandomYellowHue(); - if (elf) - { - EquipItem(m, new Circlet { Hue = hue }); - } - else if (human) - { - EquipItem(m, new FloppyHat(hue)); - } - - break; - } - case SkillName.Healing: - { - m.PackItem(new Bandage(50)); - m.PackItem(new Scissors()); - break; - } - case SkillName.Herding: - { - EquipItem(m, new ShepherdsCrook()); - break; - } - case SkillName.Hiding: - { - if (elfOrHuman) - { - EquipItem(m, new Cloak(0x455)); - } - - break; - } - case SkillName.Inscribe: - { - m.PackItem(new BlankScroll(2)); - m.PackItem(new BlueBook()); - break; - } - case SkillName.ItemID: - { - Item staff = raceFlag switch - { - Race.AllowElvesOnly => new WildStaff(), - Race.AllowGargoylesOnly => new SerpentstoneStaff(), - _ => new GnarledStaff() - }; - - EquipItem(m, staff); - break; - } - case SkillName.Lockpicking: - { - m.PackItem(new Lockpick(20)); - break; - } - case SkillName.Lumberjacking: - { - EquipItem(m, elfOrHuman ? new Hatchet() : new DualShortAxes()); - break; - } - case SkillName.Macing: - { - EquipItem(m, MacingWeapon(raceFlag)); - break; - } - case SkillName.Magery: - { - var regs = new BagOfReagents(30) { LootType = LootType.Regular }; - - if (!Core.AOS) - { - foreach (var item in regs.Items) - { - item.LootType = LootType.Newbied; - } - } - - m.PackItem(regs); - m.PackScroll(0); - m.PackScroll(1); - m.PackScroll(2); - - EquipItem(m, new Spellbook(0x382A8C38ul) { LootType = LootType.Blessed }); - EquipItem(m, Robe(raceFlag, female, Utility.RandomBlueHue())); - - if (elf) - { - EquipItem(m, new Circlet()); - } - else if (human) - { - EquipItem(m, new WizardsHat()); - } - - break; - } - case SkillName.Mining: - { - m.PackItem(new Pickaxe()); - break; - } - case SkillName.Musicianship: - { - m.PackInstrument(); - break; - } - case SkillName.Necromancy: - { - if (Core.ML) - { - m.PackItem(new BagOfNecroReagents { LootType = LootType.Regular }); - } - - break; - } - case SkillName.Ninjitsu: - { - if (elfOrHuman) - { - // Delete pants - m?.FindItemOnLayer(Layer.OuterLegs)?.Delete(); - - EquipItem(m, new Hakama(0x2C3)); // Only ninjas get the hued one. - EquipItem(m, new Kasa()); - } - - EquipItem(m, new BookOfNinjitsu()); - break; - } - case SkillName.Parry: - { - Item shield = raceFlag switch - { - Race.AllowGargoylesOnly => new GargishWoodenShield(), - _ => new WoodenShield() - }; - EquipItem(m, shield); - - break; - } - case SkillName.Peacemaking: - { - m.PackInstrument(); - break; - } - case SkillName.Poisoning: - { - m.PackItem(new LesserPoisonPotion()); - m.PackItem(new LesserPoisonPotion()); - break; - } - case SkillName.Provocation: - { - m.PackInstrument(); - break; - } - case SkillName.Stealing: - case SkillName.Snooping: - { - m.PackItem(new Lockpick(20)); - break; - } - case SkillName.SpiritSpeak: - { - EquipItem(m, new Cloak(0x455)); - break; - } - case SkillName.Tactics: - case SkillName.Swords: - { - EquipItem(m, SwordsWeapon(raceFlag)); - - break; - } - case SkillName.Tailoring: - { - m.PackItem(new BoltOfCloth()); - m.PackItem(new SewingKit()); - break; - } - case SkillName.Tinkering: - { - if (!Core.AOS) - { - m.PackTinkerPart(); - m.PackTinkerPart(); - m.PackTinkerPart(); - } - m.PackItem(new TinkerTools()); - break; - } - case SkillName.Tracking: - { - if (elfOrHuman) - { - // Delete shoes - m?.FindItemOnLayer(Layer.Shoes)?.Delete(); - - var hue = Utility.RandomYellowHue(); - EquipItem(m, elf ? new ElvenBoots(hue) : new Boots(hue)); - } - - EquipItem(m, new SkinningKnife()); - break; - } - case SkillName.Veterinary: - { - m.PackItem(new Bandage(5)); - m.PackItem(new Scissors()); - break; - } - case SkillName.Wrestling: - { - Item item = raceFlag switch - { - Race.AllowElvesOnly => new LeafGloves(), - Race.AllowGargoylesOnly when m.Female => new GargishLeatherArmsType2(), - Race.AllowGargoylesOnly => new GargishLeatherArmsType1(), - _ => new LeatherGloves() - }; - EquipItem(m, item); - break; - } - case SkillName.Throwing: - { - if (gargoyle) - { - // EquipItem(m, new Boomerang()); - } - - break; - } - case SkillName.Mysticism: - { - // PackItem(new MysticBook(0xAB)); - break; - } + skill.BaseFixedPoint = value * 10; + m.AddSkillItems(name); } } } + + private static void EquipItem(Mobile m, Item item, bool mustEquip = false) + { + if (item == null) + { + return; + } + + if (!Core.AOS) + { + item.LootType = LootType.Newbied; + } + + if (m?.EquipItem(item) == true) + { + return; + } + + var pack = m?.Backpack; + + if (!mustEquip && pack != null) + { + pack.DropItem(item); + } + else + { + item.Delete(); + } + } + + private static void PackItem(this Mobile m, Item item) + { + if (!Core.AOS) + { + item.LootType = LootType.Newbied; + } + + var pack = m.Backpack; + + if (pack != null) + { + pack.DropItem(item); + } + else + { + item.Delete(); + } + } + + private static void AddShirt(this Mobile m, int shirtHue) + { + var hue = Utility.ClipDyedHue(shirtHue & 0x3FFF); + var raceFlag = m.Race.RaceFlag; + + var shirt = raceFlag switch + { + Race.AllowElvesOnly => new ElvenShirt(hue), + Race.AllowGargoylesOnly when m.Female => new GargishClothChestType2 { Hue = hue }, + Race.AllowGargoylesOnly => new GargishClothChestType1 { Hue = hue }, + // Humans + _ => (Item)(Utility.Random(3) switch + { + 0 => new Shirt(hue), + 1 => new FancyShirt(hue), + _ => new Doublet(hue) + }) + }; + + EquipItem(m, shirt); + } + + private static void AddPants(this Mobile m, int pantsHue) + { + var hue = Utility.ClipDyedHue(pantsHue & 0x3FFF); + var raceFlag = m.Race.RaceFlag; + var female = m.Female; + + var pants = raceFlag switch + { + Race.AllowElvesOnly => new ElvenPants(hue), + Race.AllowGargoylesOnly when female => new GargishClothLegsType2 { Hue = hue }, + Race.AllowGargoylesOnly => new GargishClothLegsType1 { Hue = hue }, + // Humans + _ => (Item)(Utility.RandomBool() switch + { + true when female => new Skirt(hue), + true => new LongPants(hue), + false when female => new Kilt(hue), + false => new ShortPants(hue) + }) + }; + + EquipItem(m, pants); + } + + private static void AddShoes(this Mobile m) + { + if (m.Race == Race.Elf) + { + EquipItem(m, new ElvenBoots()); + } + else if (m.Race == Race.Human) + { + EquipItem(m, new Shoes(Utility.RandomYellowHue())); + } + } + + private static void PackInstrument(this Mobile m) + { + Item instrument = Utility.Random(6) switch + { + 0 => new Drums(), + 1 => new Harp(), + 2 => new LapHarp(), + 3 => new Lute(), + 4 => new Tambourine(), + _ => new TambourineTassel() + }; + + m.PackItem(instrument); + } + + private static void PackScroll(this Mobile m, int circle) + { + Item item = (Utility.Random(8) * (circle + 1)) switch + { + 0 => new ClumsyScroll(), + 1 => new CreateFoodScroll(), + 2 => new FeeblemindScroll(), + 3 => new HealScroll(), + 4 => new MagicArrowScroll(), + 5 => new NightSightScroll(), + 6 => new ReactiveArmorScroll(), + 7 => new WeakenScroll(), + 8 => new AgilityScroll(), + 9 => new CunningScroll(), + 10 => new CureScroll(), + 11 => new HarmScroll(), + 12 => new MagicTrapScroll(), + 13 => new MagicUnTrapScroll(), + 14 => new ProtectionScroll(), + 15 => new StrengthScroll(), + 16 => new BlessScroll(), + 17 => new FireballScroll(), + 18 => new MagicLockScroll(), + 19 => new PoisonScroll(), + 20 => new TelekinesisScroll(), + 21 => new TeleportScroll(), + 22 => new UnlockScroll(), + _ => new WallOfStoneScroll() + }; + + m.PackItem(item); + } + + private static void PackTinkerPart(this Mobile m) + { + Item item = Utility.Random(4) switch + { + 0 => new Axle(), + 1 => new Gears(), + 2 => new Hinge(), + 3 => new Springs() + }; + + m.PackItem(item); + } + + private static Item NecroHue(Item item) + { + item.Hue = 0x2C3; + + return item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Item Robe(int raceFlag, bool female, int hue) => + raceFlag switch + { + Race.AllowElvesOnly when female => new FemaleElvenRobe(hue), + Race.AllowElvesOnly => new MaleElvenRobe(hue), + _ => new Robe(hue) + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Item SwordsWeapon(int raceFlag) => + raceFlag switch + { + Race.AllowElvesOnly => new RuneBlade(), + Race.AllowGargoylesOnly => new DreadSword(), + _ => new Katana() + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Item MacingWeapon(int raceFlag) => + raceFlag switch + { + Race.AllowElvesOnly => new DiamondMace(), + Race.AllowGargoylesOnly => new DiscMace(), + _ => new Club() + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Item FencingWeapon(int raceFlag) => + raceFlag switch + { + Race.AllowElvesOnly => new Leafblade(), + Race.AllowGargoylesOnly => new BloodBlade(), + _ => new Kryss() + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Item RangedWeapon(int raceFlag) => + raceFlag switch + { + Race.AllowElvesOnly => new ElvenCompositeLongbow(), + Race.AllowGargoylesOnly => new SerpentstoneStaff(), + _ => new Bow() + }; + + private static void AddSkillItems(this Mobile m, SkillName skill) + { + var raceFlag = m.Race.RaceFlag; + var elf = m.Race == Race.Elf; + var human = m.Race == Race.Human; + var gargoyle = m.Race == Race.Gargoyle; + var elfOrHuman = Race.IsAllowedRace(m.Race, Race.AllowHumanOrElves); + var female = m.Female; + + switch (skill) + { + case SkillName.Alchemy: + { + m.PackItem(new Bottle(4)); + m.PackItem(new MortarPestle()); + EquipItem(m, Robe(raceFlag, female, Utility.RandomPinkHue())); + + break; + } + case SkillName.Anatomy: + { + m.PackItem(new Bandage(3)); + EquipItem(m, Robe(raceFlag, female, Utility.RandomYellowHue())); + + break; + } + case SkillName.AnimalLore: + { + if (elf) + { + EquipItem(m, new WildStaff()); + } + else + { + EquipItem(m, new ShepherdsCrook()); + } + + EquipItem(m, Robe(raceFlag, female, Utility.RandomGreenHue())); + + break; + } + case SkillName.AnimalTaming: + { + if (human) + { + EquipItem(m, new ShepherdsCrook()); + } + + break; + } + case SkillName.Archery: + { + m.PackItem(new Arrow(25)); + + EquipItem(m, RangedWeapon(raceFlag)); + + break; + } + case SkillName.ArmsLore: + { + Item item = Utility.Random(3) switch + { + 0 => FencingWeapon(raceFlag), + 1 => MacingWeapon(raceFlag), + _ => SwordsWeapon(raceFlag) + }; + EquipItem(m, item); + + break; + } + case SkillName.Begging: + { + Item staff = raceFlag switch + { + Race.AllowElvesOnly => new WildStaff(), + Race.AllowGargoylesOnly => new GlassStaff(), + _ => new GnarledStaff() + }; + + EquipItem(m, staff); + break; + } + case SkillName.Blacksmith: + { + m.PackItem(new Tongs()); + m.PackItem(new Pickaxe()); + m.PackItem(new Pickaxe()); + m.PackItem(new IronIngot(50)); + EquipItem(m, new HalfApron(Utility.RandomYellowHue())); + break; + } + case SkillName.Bushido: + { + if (elfOrHuman) + { + // Delete pants + m.FindItemOnLayer(Layer.OuterLegs)?.Delete(); + + EquipItem(m, new Hakama()); + EquipItem(m, new Kasa()); + } + + EquipItem(m, new BookOfBushido()); + break; + } + case SkillName.Fletching: + { + m.PackItem(new Board(14)); + m.PackItem(new Feather(5)); + m.PackItem(new Shaft(5)); + break; + } + case SkillName.Camping: + { + m.PackItem(new Bedroll()); + m.PackItem(new Kindling(5)); + break; + } + case SkillName.Carpentry: + { + m.PackItem(new Board(10)); + m.PackItem(new Saw()); + + if (elfOrHuman) + { + EquipItem(m, new HalfApron(Utility.RandomYellowHue())); + } + + break; + } + case SkillName.Cartography: + { + m.PackItem(new BlankMap()); + m.PackItem(new BlankMap()); + m.PackItem(new BlankMap()); + m.PackItem(new BlankMap()); + m.PackItem(new Sextant()); + break; + } + case SkillName.Cooking: + { + m.PackItem(new Kindling(2)); + m.PackItem(new RawLambLeg()); + m.PackItem(new RawChickenLeg()); + m.PackItem(new RawFishSteak()); + m.PackItem(new SackFlour()); + m.PackItem(new Pitcher(BeverageType.Water)); + break; + } + case SkillName.Chivalry: + { + m.PackItem(new BookOfChivalry()); + break; + } + case SkillName.DetectHidden: + { + if (elfOrHuman) + { + EquipItem(m, new Cloak(0x455)); + } + + break; + } + case SkillName.Discordance: + { + m.PackInstrument(); + break; + } + case SkillName.Fencing: + { + EquipItem(m, FencingWeapon(raceFlag)); + break; + } + case SkillName.Fishing: + { + EquipItem(m, new FishingPole()); + + var hue = Utility.RandomYellowHue(); + if (elf) + { + EquipItem(m, new Circlet { Hue = hue }); + } + else if (human) + { + EquipItem(m, new FloppyHat(hue)); + } + + break; + } + case SkillName.Healing: + { + m.PackItem(new Bandage(50)); + m.PackItem(new Scissors()); + break; + } + case SkillName.Herding: + { + EquipItem(m, new ShepherdsCrook()); + break; + } + case SkillName.Hiding: + { + if (elfOrHuman) + { + EquipItem(m, new Cloak(0x455)); + } + + break; + } + case SkillName.Inscribe: + { + m.PackItem(new BlankScroll(2)); + m.PackItem(new BlueBook()); + break; + } + case SkillName.ItemID: + { + Item staff = raceFlag switch + { + Race.AllowElvesOnly => new WildStaff(), + Race.AllowGargoylesOnly => new SerpentstoneStaff(), + _ => new GnarledStaff() + }; + + EquipItem(m, staff); + break; + } + case SkillName.Lockpicking: + { + m.PackItem(new Lockpick(20)); + break; + } + case SkillName.Lumberjacking: + { + EquipItem(m, elfOrHuman ? new Hatchet() : new DualShortAxes()); + break; + } + case SkillName.Macing: + { + EquipItem(m, MacingWeapon(raceFlag)); + break; + } + case SkillName.Magery: + { + var regs = new BagOfReagents(30) { LootType = LootType.Regular }; + + if (!Core.AOS) + { + foreach (var item in regs.Items) + { + item.LootType = LootType.Newbied; + } + } + + m.PackItem(regs); + m.PackScroll(0); + m.PackScroll(1); + m.PackScroll(2); + + EquipItem(m, new Spellbook(0x382A8C38ul) { LootType = LootType.Blessed }); + EquipItem(m, Robe(raceFlag, female, Utility.RandomBlueHue())); + + if (elf) + { + EquipItem(m, new Circlet()); + } + else if (human) + { + EquipItem(m, new WizardsHat()); + } + + break; + } + case SkillName.Mining: + { + m.PackItem(new Pickaxe()); + break; + } + case SkillName.Musicianship: + { + m.PackInstrument(); + break; + } + case SkillName.Necromancy: + { + if (Core.ML) + { + m.PackItem(new BagOfNecroReagents { LootType = LootType.Regular }); + } + + break; + } + case SkillName.Ninjitsu: + { + if (elfOrHuman) + { + // Delete pants + m.FindItemOnLayer(Layer.OuterLegs)?.Delete(); + + EquipItem(m, new Hakama(0x2C3)); // Only ninjas get the hued one. + EquipItem(m, new Kasa()); + } + + EquipItem(m, new BookOfNinjitsu()); + break; + } + case SkillName.Parry: + { + Item shield = raceFlag switch + { + Race.AllowGargoylesOnly => new GargishWoodenShield(), + _ => new WoodenShield() + }; + EquipItem(m, shield); + + break; + } + case SkillName.Peacemaking: + { + m.PackInstrument(); + break; + } + case SkillName.Poisoning: + { + m.PackItem(new LesserPoisonPotion()); + m.PackItem(new LesserPoisonPotion()); + break; + } + case SkillName.Provocation: + { + m.PackInstrument(); + break; + } + case SkillName.Stealing: + case SkillName.Snooping: + { + m.PackItem(new Lockpick(20)); + break; + } + case SkillName.SpiritSpeak: + { + EquipItem(m, new Cloak(0x455)); + break; + } + case SkillName.Tactics: + case SkillName.Swords: + { + EquipItem(m, SwordsWeapon(raceFlag)); + + break; + } + case SkillName.Tailoring: + { + m.PackItem(new BoltOfCloth()); + m.PackItem(new SewingKit()); + break; + } + case SkillName.Tinkering: + { + if (!Core.AOS) + { + m.PackTinkerPart(); + m.PackTinkerPart(); + m.PackTinkerPart(); + } + m.PackItem(new TinkerTools()); + break; + } + case SkillName.Tracking: + { + if (elfOrHuman) + { + // Delete shoes + m.FindItemOnLayer(Layer.Shoes)?.Delete(); + + var hue = Utility.RandomYellowHue(); + EquipItem(m, elf ? new ElvenBoots(hue) : new Boots(hue)); + } + + EquipItem(m, new SkinningKnife()); + break; + } + case SkillName.Veterinary: + { + m.PackItem(new Bandage(5)); + m.PackItem(new Scissors()); + break; + } + case SkillName.Wrestling: + { + Item item = raceFlag switch + { + Race.AllowElvesOnly => new LeafGloves(), + Race.AllowGargoylesOnly when m.Female => new GargishLeatherArmsType2(), + Race.AllowGargoylesOnly => new GargishLeatherArmsType1(), + _ => new LeatherGloves() + }; + EquipItem(m, item); + break; + } + case SkillName.Throwing: + { + if (gargoyle) + { + // EquipItem(m, new Boomerang()); + } + + break; + } + case SkillName.Mysticism: + { + // PackItem(new MysticBook(0xAB)); + break; + } + } + } }