From 0a07109cc16a79501b0e9ea3139afec1671cee6d Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Mon, 22 Jul 2024 20:15:24 -0700 Subject: [PATCH] fix: Fixes invalid professions. (#1882) --- Projects/Server/Utilities/Utility.cs | 10 + Projects/UOContent/Misc/CharacterCreation.cs | 230 +++++++++---------- Projects/UOContent/Misc/ProfessionInfo.cs | 22 +- Projects/UOContent/Mobiles/PlayerMobile.cs | 2 +- 4 files changed, 133 insertions(+), 131 deletions(-) diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs index 24ab56991..8796823b3 100644 --- a/Projects/Server/Utilities/Utility.cs +++ b/Projects/Server/Utilities/Utility.cs @@ -745,6 +745,12 @@ public static class Utility [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomList(params T[] list) => list.RandomElement(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomElement(this ReadOnlySpan list) => list.Length == 0 ? (T)default : list[Random(list.Length)]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomElement(this T[] list) => list.RandomElement(default); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this IList list) => list.RandomElement(default); @@ -858,6 +864,10 @@ public static class Utility return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T RandomElement(this T[] list, T valueIfZero) => + list.Length == 0 ? valueIfZero : list[Random(list.Length)]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T RandomElement(this IList list, T valueIfZero) => list.Count == 0 ? valueIfZero : list[Random(list.Count)]; diff --git a/Projects/UOContent/Misc/CharacterCreation.cs b/Projects/UOContent/Misc/CharacterCreation.cs index e5e68d24b..ea9046768 100644 --- a/Projects/UOContent/Misc/CharacterCreation.cs +++ b/Projects/UOContent/Misc/CharacterCreation.cs @@ -199,7 +199,7 @@ public static class CharacterCreation private static void EventSink_CharacterCreated(CharacterCreatedEventArgs args) { - if (!VerifyProfession(args.Profession)) + if (!ProfessionInfo.GetProfession(args.Profession, out var profession)) { args.Profession = 0; } @@ -237,8 +237,6 @@ public static class CharacterCreation if (newChar is PlayerMobile pm) { - pm.Profession = args.Profession; - if (((Account)pm.Account).Young) { pm.Young = true; @@ -250,8 +248,9 @@ public static class CharacterCreation } } - SetStats(newChar, state, args.Stats, args.Profession); - SetSkills(newChar, args.Skills, args.Profession, args.ShirtHue, args.PantsHue); + SetStats(newChar, state, profession?.Stats ?? args.Stats); + SetSkills(newChar, profession?.Skills ?? args.Skills); + GiveProfessionItems(newChar, profession, args.ShirtHue, args.PantsHue); if (race.ValidateHair(newChar, args.HairID)) { @@ -302,9 +301,6 @@ public static class CharacterCreation new WelcomeTimer(newChar).Start(); } - public static bool VerifyProfession(int profession) => - profession >= 0 && profession < ProfessionInfo.Professions.Length; - private static CityInfo GetStartLocation(CharacterCreatedEventArgs args) { var availableMaps = ExpansionInfo.CoreExpansion.MapSelectionFlags; @@ -325,81 +321,82 @@ public static class CharacterCreation } var flags = args.State?.Flags ?? ClientFlags.None; - var profession = ProfessionInfo.Professions[args.Profession]; - - switch (profession?.Name.ToLowerInvariant()) + if (ProfessionInfo.GetProfession(args.Profession, out var profession)) { - case "necromancer": - { - if ((flags & ClientFlags.Malas) != 0 && availableMaps.Includes(MapSelectionFlags.Malas)) + switch (profession.Name.ToLowerInvariant()) + { + case "necromancer": { - return new CityInfo("Umbra", "Mardoth's Tower", 2114, 1301, -50, Map.Malas); + if ((flags & ClientFlags.Malas) != 0 && availableMaps.Includes(MapSelectionFlags.Malas)) + { + return new CityInfo("Umbra", "Mardoth's Tower", 2114, 1301, -50, Map.Malas); + } + + /* + * 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)); + return GetStartingCities()[0]; } - - /* - * 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)); - return GetStartingCities()[0]; - } - case "paladin": - { - return GetStartingCities()[0]; - } - case "samurai": - { - bool haotisAndTokunoAccessible = - (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && - (flags & ClientFlags.Malas) == ClientFlags.Malas && - availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); - - if (haotisAndTokunoAccessible) + case "paladin": { - return new CityInfo("Samurai DE", "Haoti's Grounds", 368, 780, -1, Map.Malas); + return GetStartingCities()[0]; } - - /* - * 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)); - return GetStartingCities()[0]; - } - case "ninja": - { - bool enimosAndTokunoAccessible = - (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && - (flags & ClientFlags.Malas) == ClientFlags.Malas && - availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); - - if (enimosAndTokunoAccessible) + case "samurai": { - return new CityInfo("Ninja DE", "Enimo's Residence", 414, 823, -1, Map.Malas); - } + bool haotisAndTokunoAccessible = + (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && + (flags & ClientFlags.Malas) == ClientFlags.Malas && + availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); - /* - * 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)); - return GetStartingCities()[0]; - } + if (haotisAndTokunoAccessible) + { + return new CityInfo("Samurai DE", "Haoti's Grounds", 368, 780, -1, Map.Malas); + } + + /* + * 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)); + return GetStartingCities()[0]; + } + case "ninja": + { + bool enimosAndTokunoAccessible = + (flags & ClientFlags.Tokuno) == ClientFlags.Tokuno && + (flags & ClientFlags.Malas) == ClientFlags.Malas && + availableMaps.Includes(MapSelectionFlags.Malas | MapSelectionFlags.Tokuno); + + if (enimosAndTokunoAccessible) + { + return new CityInfo("Ninja DE", "Enimo's Residence", 414, 823, -1, Map.Malas); + } + + /* + * 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)); + return GetStartingCities()[0]; + } + } } return args.City; } - private static void SetStats(Mobile m, NetState state, StatNameValue[] stats, int prof) + private static void SetStats(Mobile m, NetState state, StatNameValue[] stats) { var maxStats = state.NewCharacterCreation ? 90 : 80; @@ -407,11 +404,6 @@ public static class CharacterCreation 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]; @@ -480,20 +472,33 @@ public static class CharacterCreation return total is 100 or 120; } - private static void SetSkills(Mobile m, SkillNameValue[] skills, int prof, int shirtHue, int pantsHue) + private static void SetSkills(Mobile m, SkillNameValue[] skills) { - 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 + if (!ValidateSkills(m.Race.RaceFlag, skills)) { return; } - var addSkillItems = true; + for (var i = 0; i < skills.Length; ++i) + { + var (name, value) = skills[i]; + if (value <= 0) + { + continue; + } + + var skill = m.Skills[name]; + + if (skill != null) + { + skill.BaseFixedPoint = value * 10; + m.AddSkillItems(name); + } + } + } + + private static void GiveProfessionItems(Mobile m, ProfessionInfo profession, int shirtHue, int pantsHue) + { var elf = m.Race == Race.Elf; var gargoyle = m.Race == Race.Gargoyle; @@ -547,10 +552,7 @@ public static class CharacterCreation // animate dead, evil omen, pain spike, summon familiar, wraith form m.PackItem(new NecromancerSpellbook(0x8981ul) { LootType = LootType.Blessed }); - - addSkillItems = false; - - break; + return; } case "paladin": { @@ -588,15 +590,10 @@ public static class CharacterCreation } m.PackItem(new BookOfChivalry { LootType = LootType.Blessed }); - - addSkillItems = false; - - break; + return; } case "samurai": { - addSkillItems = false; - if (elf) { EquipItem(m, new RavenHelm()); @@ -628,13 +625,11 @@ public static class CharacterCreation m.PackItem(new Bandage(50)); m.PackItem(new BookOfBushido()); - break; + return; } case "ninja": { - addSkillItems = false; - - int[] hues = [0x1A8, 0xEC, 0x99, 0x90, 0xB5, 0x336, 0x89]; + ReadOnlySpan hues = [0x1A8, 0xEC, 0x99, 0x90, 0xB5, 0x336, 0x89]; // TODO: Verify that's ALL the hues for that above. if (elf) @@ -668,7 +663,8 @@ public static class CharacterCreation m.PackItem(new SmokeBomb()); m.PackItem(new SmokeBomb()); m.PackItem(new BookOfNinjitsu()); - break; + + return; } case "swordsman": case "fencer": @@ -706,34 +702,14 @@ public static class CharacterCreation } } - if (addSkillItems) + m.AddShirt(shirtHue); + m.AddPants(pantsHue); + m.AddShoes(); + + // All elves get a wild staff + if (elf) { - m.AddShirt(shirtHue); - m.AddPants(pantsHue); - m.AddShoes(); - - // All elves get a wild staff - if (elf) - { - EquipItem(m, new WildStaff()); - } - } - - for (var i = 0; i < skills.Length; ++i) - { - var (name, value) = skills[i]; - if (value <= 0) - { - continue; - } - - var skill = m.Skills[name]; - - if (skill != null) - { - skill.BaseFixedPoint = value * 10; - m.AddSkillItems(name); - } + EquipItem(m, new WildStaff()); } } diff --git a/Projects/UOContent/Misc/ProfessionInfo.cs b/Projects/UOContent/Misc/ProfessionInfo.cs index 86c5b137d..a6feed5df 100644 --- a/Projects/UOContent/Misc/ProfessionInfo.cs +++ b/Projects/UOContent/Misc/ProfessionInfo.cs @@ -1,12 +1,28 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; namespace Server; public class ProfessionInfo { - public static ProfessionInfo[] Professions { get; } + private static readonly ProfessionInfo[] _professions; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool VerifyProfession(int profIndex) => + profIndex >= 0 && profIndex < _professions.Length; + + public static bool GetProfession(int profIndex, out ProfessionInfo profession) + { + if (!VerifyProfession(profIndex)) + { + profession = null; + return false; + } + + return (profession = _professions[profIndex - 1]) != null; + } private static bool TryGetSkillName(string name, out SkillName skillName) { @@ -177,11 +193,11 @@ public class ProfessionInfo } } - Professions = new ProfessionInfo[1 + maxProf]; + _professions = new ProfessionInfo[maxProf]; foreach (var p in profs) { - Professions[p.ID] = p; + _professions[p.ID - 1] = p; } profs.Clear(); diff --git a/Projects/UOContent/Mobiles/PlayerMobile.cs b/Projects/UOContent/Mobiles/PlayerMobile.cs index 92dc09ae9..6c9123179 100644 --- a/Projects/UOContent/Mobiles/PlayerMobile.cs +++ b/Projects/UOContent/Mobiles/PlayerMobile.cs @@ -3178,7 +3178,7 @@ namespace Server.Mobiles } } - if (!CharacterCreation.VerifyProfession(Profession)) + if (!ProfessionInfo.VerifyProfession(Profession)) { Profession = 0; }