Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,291 @@
using System.Collections.Generic;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
using Server.Gumps;
using Server.Mobiles;
namespace Server.Engines.MLQuests.Gumps
{
public enum ButtonPosition : byte
{
Left,
Right
}
public enum ButtonGraphic : ushort
{
Invalid,
Accept = 0x2EE0,
Clear = 0x2EE3,
Close = 0x2EE6,
Continue = 0x2EE9,
Okay = 0x2EEC,
Previous = 0x2EEF,
Refuse = 0x2EF2,
Resign = 0x2EF5
}
public abstract class BaseQuestGump : Gump
{
private struct ButtonInfo
{
public ButtonPosition Position{ get; }
public ButtonGraphic Graphic{ get; }
public int ButtonID{ get; }
public ButtonInfo(ButtonPosition position, ButtonGraphic graphic, int buttonID)
{
Position = position;
Graphic = graphic;
ButtonID = buttonID;
}
}
private int m_Page;
private int m_MaxPages;
private int m_Label;
private string m_Title;
private List<ButtonInfo> m_Buttons;
#if false
// OSI clone, inefficient layout
public BaseQuestGump( int label ) : base( 75, 25 )
{
m_Page = 0;
m_MaxPages = 0;
m_Label = label;
m_Title = null;
m_Buttons = new List<ButtonInfo>( 2 );
}
public void BuildPage()
{
AddPage( ++m_Page );
Closable = false;
AddImageTiled( 50, 20, 400, 460, 0x1404 );
AddImageTiled( 50, 29, 30, 450, 0x28DC );
AddImageTiled( 34, 140, 17, 339, 0x242F );
AddImage( 48, 135, 0x28AB );
AddImage( -16, 285, 0x28A2 );
AddImage( 0, 10, 0x28B5 );
AddImage( 25, 0, 0x28B4 );
AddImageTiled( 83, 15, 350, 15, 0x280A );
AddImage( 34, 479, 0x2842 );
AddImage( 442, 479, 0x2840 );
AddImageTiled( 51, 479, 392, 17, 0x2775 );
AddImageTiled( 415, 29, 44, 450, 0xA2D );
AddImageTiled( 415, 29, 30, 450, 0x28DC );
AddLabel( 100, 50, 0x481, "" );
AddImage( 370, 50, 0x589 );
AddImage( 379, 60, 0x15A9 );
AddImage( 425, 0, 0x28C9 );
AddImage( 90, 33, 0x232D );
AddHtmlLocalized( 130, 45, 270, 16, m_Label, 0xFFFFFF, false, false );
AddImageTiled( 130, 65, 175, 1, 0x238D );
if ( m_Page > 1 )
AddButton( 130, 430, (int)ButtonGraphic.Previous, (int)ButtonGraphic.Previous + 2, 0, GumpButtonType.Page, m_Page - 1 );
if ( m_Page < m_MaxPages )
AddButton( 275, 430, (int)ButtonGraphic.Continue, (int)ButtonGraphic.Continue + 2, 0, GumpButtonType.Page, m_Page + 1 );
foreach ( ButtonInfo button in m_Buttons )
AddButton( button.Position == ButtonPosition.Left ? 95 : 313, 455, (int)button.Graphic, (int)button.Graphic + 2, button.ButtonID, );
if ( m_Title != null )
AddHtmlLocalized( 130, 68, 220, 48, 1114513, m_Title, 0x2710, false, false ); // <DIV ALIGN=CENTER>~1_TOKEN~</DIV>
}
#else
// RunUO optimized version
public BaseQuestGump(int label)
: base(75, 25)
{
m_Page = 0;
m_MaxPages = 0;
m_Label = label;
m_Title = null;
m_Buttons = new List<ButtonInfo>(2);
Closable = false;
AddPage(0);
AddImageTiled(50, 20, 400, 460, 0x1404);
AddImageTiled(50, 29, 30, 450, 0x28DC);
AddImageTiled(34, 140, 17, 339, 0x242F);
AddImage(48, 135, 0x28AB);
AddImage(-16, 285, 0x28A2);
AddImage(0, 10, 0x28B5);
AddImage(25, 0, 0x28B4);
AddImageTiled(83, 15, 350, 15, 0x280A);
AddImage(34, 479, 0x2842);
AddImage(442, 479, 0x2840);
AddImageTiled(51, 479, 392, 17, 0x2775);
AddImageTiled(415, 29, 44, 450, 0xA2D);
AddImageTiled(415, 29, 30, 450, 0x28DC);
//AddLabel( 100, 50, 0x481, "" );
AddImage(370, 50, 0x589);
AddImage(379, 60, 0x15A9);
AddImage(425, 0, 0x28C9);
AddImage(90, 33, 0x232D);
AddHtmlLocalized(130, 45, 270, 16, label, 0xFFFFFF);
AddImageTiled(130, 65, 175, 1, 0x238D);
}
public void BuildPage()
{
AddPage(++m_Page);
if (m_Page > 1)
AddButton(130, 430, (int)ButtonGraphic.Previous, (int)ButtonGraphic.Previous + 2, 0, GumpButtonType.Page,
m_Page - 1);
if (m_Page < m_MaxPages)
AddButton(275, 430, (int)ButtonGraphic.Continue, (int)ButtonGraphic.Continue + 2, 0, GumpButtonType.Page,
m_Page + 1);
foreach (ButtonInfo button in m_Buttons)
AddButton(button.Position == ButtonPosition.Left ? 95 : 313, 455, (int)button.Graphic,
(int)button.Graphic + 2, button.ButtonID);
if (m_Title != null)
AddHtmlLocalized(130, 68, 220, 48, 1114513, m_Title, 0x2710); // <DIV ALIGN=CENTER>~1_TOKEN~</DIV>
}
#endif
public void SetPageCount(int maxPages)
{
m_MaxPages = maxPages;
}
public void SetTitle(TextDefinition def)
{
if (def.Number > 0)
m_Title = $"#{def.Number}"; // OSI does "@@#{0}" instead, why? KR client related?
else
m_Title = def.String;
}
public void RegisterButton(ButtonPosition position, ButtonGraphic graphic, int buttonID)
{
m_Buttons.Add(new ButtonInfo(position, graphic, buttonID));
}
#region Elaborate Formatting Shortcuts
public void AddDescription(MLQuest quest)
{
AddHtmlLocalized(98, 140, 312, 16, quest.IsChainTriggered || quest.NextQuest != null ? 1075024 : 1072202, 0x2710); // Description [(quest chain)]
TextDefinition.AddHtmlText(this, 98, 156, 312, 240, quest.Description, false, true, 0x15F90, 0xBDE784);
}
public void AddObjectives(MLQuest quest)
{
AddHtmlLocalized(98, 140, 312, 16, 1049073, 0x2710); // Objective:
AddHtmlLocalized(98, 156, 312, 16, quest.ObjectiveType == ObjectiveType.All ? 1072208 : 1072209, 0x2710); // All of the following / Only one of the following
int y = 172;
foreach (BaseObjective objective in quest.Objectives)
{
objective.WriteToGump(this, ref y);
if (objective.IsTimed)
{
if (objective is CollectObjective)
y -= 16;
BaseObjectiveInstance.WriteTimeRemaining(this, ref y, objective.Duration);
}
}
}
public void AddObjectivesProgress(MLQuestInstance instance)
{
MLQuest quest = instance.Quest;
AddHtmlLocalized(98, 140, 312, 16, 1049073, 0x2710); // Objective:
AddHtmlLocalized(98, 156, 312, 16, quest.ObjectiveType == ObjectiveType.All ? 1072208 : 1072209, 0x2710); // All of the following / Only one of the following
int y = 172;
foreach (BaseObjectiveInstance objInstance in instance.Objectives)
objInstance.WriteToGump(this, ref y);
}
public void AddRewardsPage(MLQuest quest) // For the quest log/offer gumps
{
AddHtmlLocalized(98, 140, 312, 16, 1072201, 0x2710); // Reward
int y = 162;
if (quest.Rewards.Count > 1)
{
// TODO: Is this what this is for? Does "Only one of the following" occur?
AddHtmlLocalized(98, 156, 312, 16, 1072208, 0x2710); // All of the following
y += 16;
}
AddRewards(quest, 105, y, 16);
}
public void AddRewards(MLQuest quest) // For the claim rewards gump
{
int y = 146;
if (quest.Rewards.Count > 1)
{
// TODO: Is this what this is for? Does "Only one of the following" occur?
AddHtmlLocalized(100, 140, 312, 16, 1072208, 0x2710); // All of the following
y += 16;
}
AddRewards(quest, 107, y, 26);
}
public void AddRewards(MLQuest quest, int x, int y, int spacing)
{
int xReward = x + 28;
foreach (BaseReward reward in quest.Rewards)
{
AddImage(x, y + 1, 0x4B9);
reward.WriteToGump(this, xReward, ref y);
y += spacing;
}
}
public void AddConversation(TextDefinition text)
{
TextDefinition.AddHtmlText(this, 98, 140, 312, 180, text, false, true, 0x15F90, 0xBDE784);
}
#endregion
/* OSI gump IDs:
* 800 - QuestOfferGump
* 801 - QuestCancelConfirmGump
* 802 - ?? (gets closed by Toggle Quest Item)
* 803 - QuestRewardGump
* 804 - ?? (gets closed by Toggle Quest Item)
* 805 - QuestLogGump
* 806 - QuestConversationGump (refuse / in progress)
* 807 - ?? (gets closed by Toggle Quest Item and most quest gumps)
* 808 - InfoNPCGump
* 809 - QuestLogDetailedGump
* 810 - QuestReportBackGump
*/
public static void CloseOtherGumps(PlayerMobile pm)
{
pm.CloseGump<InfoNPCGump>();
pm.CloseGump<QuestRewardGump>();
pm.CloseGump<QuestConversationGump>();
pm.CloseGump<QuestReportBackGump>();
//pm.CloseGump( typeof( UnknownGump807 ) );
pm.CloseGump<QuestCancelConfirmGump>();
}
}
}

View file

@ -0,0 +1,17 @@
namespace Server.Engines.MLQuests.Gumps
{
public class InfoNPCGump : BaseQuestGump
{
public InfoNPCGump(TextDefinition title, TextDefinition message)
: base(1060668) // INFORMATION
{
RegisterButton(ButtonPosition.Left, ButtonGraphic.Close, 3);
SetPageCount(1);
BuildPage();
TextDefinition.AddHtmlText(this, 160, 108, 250, 16, title, false, false, 0x2710, 0x4AC684);
TextDefinition.AddHtmlText(this, 98, 156, 312, 180, message, false, true, 0x15F90, 0xBDE784);
}
}
}

View file

@ -0,0 +1,97 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestCancelConfirmGump : Gump
{
private bool m_CloseGumps;
private MLQuestInstance m_Instance;
public QuestCancelConfirmGump(MLQuestInstance instance, bool closeGumps = true)
: base(120, 50)
{
m_Instance = instance;
m_CloseGumps = closeGumps;
if (closeGumps)
BaseQuestGump.CloseOtherGumps(instance.Player);
AddPage(0);
Closable = false;
AddImageTiled(0, 0, 348, 262, 0xA8E);
AddAlphaRegion(0, 0, 348, 262);
AddImage(0, 15, 0x27A8);
AddImageTiled(0, 30, 17, 200, 0x27A7);
AddImage(0, 230, 0x27AA);
AddImage(15, 0, 0x280C);
AddImageTiled(30, 0, 300, 17, 0x280A);
AddImage(315, 0, 0x280E);
AddImage(15, 244, 0x280C);
AddImageTiled(30, 244, 300, 17, 0x280A);
AddImage(315, 244, 0x280E);
AddImage(330, 15, 0x27A8);
AddImageTiled(330, 30, 17, 200, 0x27A7);
AddImage(330, 230, 0x27AA);
AddImage(333, 2, 0x2716);
AddImage(333, 248, 0x2716);
AddImage(2, 248, 0x2716);
AddImage(2, 2, 0x2716);
AddHtmlLocalized(25, 22, 200, 20, 1049000, 0x7D00); // Confirm Quest Cancellation
AddImage(25, 40, 0xBBF);
/*
* This quest will give you valuable information, skills
* and equipment that will help you advance in the
* game at a quicker pace.<BR>
* <BR>
* Are you certain you wish to cancel at this time?
*/
AddHtmlLocalized(25, 55, 300, 120, 1060836, 0xFFFFFF);
MLQuest quest = instance.Quest;
if (quest.IsChainTriggered || quest.NextQuest != null)
{
AddRadio(25, 145, 0x25F8, 0x25FB, false, 2);
AddHtmlLocalized(60, 150, 280, 20, 1075023, 0xFFFFFF); // Yes, I want to quit this entire chain!
}
AddRadio(25, 180, 0x25F8, 0x25FB, true, 1);
AddHtmlLocalized(60, 185, 280, 20, 1049005, 0xFFFFFF); // Yes, I really want to quit this quest!
AddRadio(25, 215, 0x25F8, 0x25FB, false, 0);
AddHtmlLocalized(60, 220, 280, 20, 1049006, 0xFFFFFF); // No, I don't want to quit.
AddButton(265, 220, 0xF7, 0xF8, 7);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Instance.Removed)
return;
switch (info.ButtonID)
{
case 7: // Okay
{
if (info.IsSwitched(2))
m_Instance.Cancel(true);
else if (info.IsSwitched(1))
m_Instance.Cancel(false);
sender.Mobile.SendGump(new QuestLogGump(m_Instance.Player, m_CloseGumps));
break;
}
}
}
}
}

View file

@ -0,0 +1,21 @@
using Server.Mobiles;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestConversationGump : BaseQuestGump
{
public QuestConversationGump(MLQuest quest, PlayerMobile pm, TextDefinition text)
: base(3006156) // Quest Conversation
{
CloseOtherGumps(pm);
SetTitle(quest.Title);
RegisterButton(ButtonPosition.Right, ButtonGraphic.Close, 3);
SetPageCount(1);
BuildPage();
AddConversation(text);
}
}
}

View file

@ -0,0 +1,73 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestLogDetailedGump : BaseQuestGump
{
private bool m_CloseGumps;
private MLQuestInstance m_Instance;
public QuestLogDetailedGump(MLQuestInstance instance, bool closeGumps = true)
: base(1046026) // Quest Log
{
m_Instance = instance;
m_CloseGumps = closeGumps;
PlayerMobile pm = instance.Player;
MLQuest quest = instance.Quest;
if (closeGumps)
{
CloseOtherGumps(pm);
pm.CloseGump<QuestLogDetailedGump>();
}
SetTitle(quest.Title);
RegisterButton(ButtonPosition.Left, ButtonGraphic.Resign, 1);
RegisterButton(ButtonPosition.Right, ButtonGraphic.Okay, 2);
SetPageCount(3);
BuildPage();
AddDescription(quest);
if (instance.Failed) // only displayed on the first page
AddHtmlLocalized(160, 80, 250, 16, 500039, 0x3C00); // Failed!
BuildPage();
AddObjectivesProgress(instance);
BuildPage();
AddRewardsPage(quest);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_Instance.Removed)
return;
switch (info.ButtonID)
{
case 1: // Resign
{
// TODO: Custom reward loss protection? OSI doesn't have this
//if ( m_Instance.ClaimReward )
// pm.SendMessage( "You cannot cancel a quest with rewards pending." );
//else
sender.Mobile.SendGump(new QuestCancelConfirmGump(m_Instance, m_CloseGumps));
break;
}
case 2: // Okay
{
sender.Mobile.SendGump(new QuestLogGump(m_Instance.Player, m_CloseGumps));
break;
}
}
}
}
}

View file

@ -0,0 +1,77 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestLogGump : BaseQuestGump
{
private bool m_CloseGumps;
private PlayerMobile m_Owner;
public QuestLogGump(PlayerMobile pm, bool closeGumps = true)
: base(1046026) // Quest Log
{
m_Owner = pm;
m_CloseGumps = closeGumps;
if (closeGumps)
{
pm.CloseGump<QuestLogGump>();
pm.CloseGump<QuestLogDetailedGump>();
}
RegisterButton(ButtonPosition.Right, ButtonGraphic.Okay, 3);
SetPageCount(1);
BuildPage();
int numberColor, stringColor;
MLQuestContext context = MLQuestSystem.GetContext(pm);
if (context != null)
{
List<MLQuestInstance> instances = context.QuestInstances;
for (int i = 0; i < instances.Count; ++i)
{
if (instances[i].Failed)
{
numberColor = 0x3C00;
stringColor = 0x7B0000;
}
else
{
numberColor = stringColor = 0xFFFFFF;
}
TextDefinition.AddHtmlText(this, 98, 140 + 21 * i, 270, 21, instances[i].Quest.Title, false, false,
numberColor, stringColor);
AddButton(368, 140 + 21 * i, 0x26B0, 0x26B1, 6 + i, GumpButtonType.Reply, 1);
}
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID < 6)
return;
MLQuestContext context = MLQuestSystem.GetContext(m_Owner);
if (context == null)
return;
List<MLQuestInstance> instances = context.QuestInstances;
int index = info.ButtonID - 6;
if (index >= instances.Count)
return;
sender.Mobile.SendGump(new QuestLogDetailedGump(instances[index], m_CloseGumps));
}
}
}

View file

@ -0,0 +1,57 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestOfferGump : BaseQuestGump
{
private MLQuest m_Quest;
private IQuestGiver m_Quester;
public QuestOfferGump(MLQuest quest, IQuestGiver quester, PlayerMobile pm)
: base(1049010) // Quest Offer
{
m_Quest = quest;
m_Quester = quester;
CloseOtherGumps(pm);
pm.CloseGump<QuestOfferGump>();
SetTitle(quest.Title);
RegisterButton(ButtonPosition.Left, ButtonGraphic.Accept, 1);
RegisterButton(ButtonPosition.Right, ButtonGraphic.Refuse, 2);
SetPageCount(3);
BuildPage();
AddDescription(quest);
BuildPage();
AddObjectives(quest);
BuildPage();
AddRewardsPage(quest);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (!(sender.Mobile is PlayerMobile pm))
return;
switch (info.ButtonID)
{
case 1: // Accept
{
m_Quest.OnAccept(m_Quester, pm);
break;
}
case 2: // Refuse
{
m_Quest.OnRefuse(m_Quester, pm);
break;
}
}
}
}
}

View file

@ -0,0 +1,38 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestReportBackGump : BaseQuestGump
{
private MLQuestInstance m_Instance;
public QuestReportBackGump(MLQuestInstance instance)
: base(3006156) // Quest Conversation
{
m_Instance = instance;
MLQuest quest = instance.Quest;
PlayerMobile pm = instance.Player;
// TODO: Check close sequence
CloseOtherGumps(pm);
SetTitle(quest.Title);
RegisterButton(ButtonPosition.Left, ButtonGraphic.Continue, 4);
RegisterButton(ButtonPosition.Right, ButtonGraphic.Close, 3);
SetPageCount(1);
BuildPage();
AddConversation(quest.CompletionMessage);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 4)
m_Instance.ContinueReportBack(true);
}
}
}

View file

@ -0,0 +1,36 @@
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestRewardGump : BaseQuestGump
{
private MLQuestInstance m_Instance;
public QuestRewardGump(MLQuestInstance instance)
: base(1072201) // Reward
{
m_Instance = instance;
MLQuest quest = instance.Quest;
PlayerMobile pm = instance.Player;
CloseOtherGumps(pm);
SetTitle(quest.Title);
RegisterButton(ButtonPosition.Left, ButtonGraphic.Accept, 1);
SetPageCount(1);
BuildPage();
AddRewards(quest);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1)
m_Instance.ClaimRewards();
}
}
}

View file

@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Spells.Fifth;
using Server.Spells.Ninjitsu;
using Server.Spells.Seventh;
namespace Server.Engines.MLQuests.Gumps
{
public interface IRaceChanger
{
bool CheckComplete(PlayerMobile from);
void ConsumeNeeded(PlayerMobile from);
void OnCancel(PlayerMobile from);
}
public class RaceChangeConfirmGump : Gump
{
private static Dictionary<NetState, RaceChangeState> m_Pending;
private PlayerMobile m_From;
private IRaceChanger m_Owner;
private Race m_Race;
public RaceChangeConfirmGump(IRaceChanger owner, PlayerMobile from, Race targetRace)
: base(50, 50)
{
from.CloseGump<RaceChangeConfirmGump>();
m_Owner = owner;
m_From = from;
m_Race = targetRace;
AddPage(0);
AddBackground(0, 0, 240, 135, 0x2422);
if (targetRace == Race.Human)
AddHtmlLocalized(15, 15, 210, 75, 1073643, 0); // Are you sure you wish to embrace your humanity?
else if (targetRace == Race.Elf)
AddHtmlLocalized(15, 15, 210, 75, 1073642, 0); // Are you sure you want to follow the elven ways?
else
AddHtml(15, 15, 210, 75, $"Are you sure you want to change your race to {targetRace.Name}?");
AddButton(160, 95, 0xF7, 0xF8, 1);
AddButton(90, 95, 0xF2, 0xF1, 0);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
switch (info.ButtonID)
{
case 0: // Cancel
{
m_Owner?.OnCancel(m_From);
break;
}
case 1: // Okay
{
if (m_Owner == null || m_Owner.CheckComplete(m_From))
Offer(m_Owner, m_From, m_Race);
break;
}
}
}
public static void Initialize()
{
m_Pending = new Dictionary<NetState, RaceChangeState>();
PacketHandlers.RegisterExtended(0x2A, true, RaceChangeReply);
}
public static bool IsPending(NetState state)
{
return state != null && m_Pending.ContainsKey(state);
}
private static void Offer(IRaceChanger owner, PlayerMobile from, Race targetRace)
{
NetState ns = from.NetState;
if (ns == null || !CanChange(from, targetRace))
return;
CloseCurrent(ns);
m_Pending[ns] = new RaceChangeState(owner, ns, targetRace);
ns.Send(new RaceChanger(from.Female, targetRace));
}
private static void CloseCurrent(NetState ns)
{
if (m_Pending.TryGetValue(ns, out RaceChangeState state))
{
state.m_Timeout.Stop();
m_Pending.Remove(ns);
}
ns.Send(CloseRaceChanger.Instance);
}
private static void Timeout(NetState ns)
{
if (IsPending(ns))
{
m_Pending.Remove(ns);
ns.Send(CloseRaceChanger.Instance);
}
}
public static bool IsWearingEquipment(Mobile from)
{
foreach (Item item in from.Items)
switch (item.Layer)
{
case Layer.Hair:
case Layer.FacialHair:
case Layer.Backpack:
case Layer.Mount:
case Layer.Bank:
{
continue; // ignore
}
default:
{
return true;
}
}
return false;
}
private static bool CanChange(PlayerMobile from, Race targetRace)
{
if (from.Deleted)
return false;
if (from.Race == targetRace)
from.SendLocalizedMessage(1111918); // You are already that race.
else if (!MondainsLegacy.CheckML(from, false))
from.SendLocalizedMessage(1073651); // You must have Mondain's Legacy before proceeding...
else if (!from.Alive)
from.SendLocalizedMessage(1073646); // Only the living may proceed...
else if (from.Mounted)
from.SendLocalizedMessage(1073647); // You may not continue while mounted...
else if (!from.CanBeginAction<PolymorphSpell>() || DisguiseTimers.IsDisguised(from) ||
AnimalForm.UnderTransformation(from) || !from.CanBeginAction<IncognitoSpell>() ||
from.IsBodyMod) // TODO: Does this cover everything?
from.SendLocalizedMessage(1073648); // You may only proceed while in your original state...
else if (from.Spell != null && from.Spell.IsCasting)
from.SendLocalizedMessage(1073649); // One may not proceed while embracing magic...
else if (from.Poisoned)
from.SendLocalizedMessage(1073652); // You must be healthy to proceed...
else if (IsWearingEquipment(from))
from.SendLocalizedMessage(1073650); // To proceed you must be unburdened by equipment...
else
return true;
return false;
}
private static void RaceChangeReply(NetState state, PacketReader pvSrc)
{
if (!m_Pending.TryGetValue(state, out RaceChangeState raceChangeState))
return;
CloseCurrent(state);
if (!(state.Mobile is PlayerMobile pm))
return;
IRaceChanger owner = raceChangeState.m_Owner;
Race targetRace = raceChangeState.m_TargetRace;
if (pvSrc.Length == 5)
{
owner?.OnCancel(pm);
return;
}
if (!CanChange(pm, targetRace) || owner?.CheckComplete(pm) == false)
return;
int hue = pvSrc.ReadUInt16();
int hairItemId = pvSrc.ReadUInt16();
int hairHue = pvSrc.ReadUInt16();
int facialHairItemId = pvSrc.ReadUInt16();
int facialHairHue = pvSrc.ReadUInt16();
pm.Race = targetRace;
pm.Hue = targetRace.ClipSkinHue(hue) | 0x8000;
if (targetRace.ValidateHair(pm, hairItemId))
{
pm.HairItemID = hairItemId;
pm.HairHue = targetRace.ClipHairHue(hairHue);
}
else
{
pm.HairItemID = 0;
}
if (targetRace.ValidateFacialHair(pm, facialHairItemId))
{
pm.FacialHairItemID = facialHairItemId;
pm.FacialHairHue = targetRace.ClipHairHue(facialHairHue);
}
else
{
pm.FacialHairItemID = 0;
}
if (targetRace == Race.Human)
pm.SendLocalizedMessage(1073654); // You are now fully human.
else if (targetRace == Race.Elf)
pm.SendLocalizedMessage(1073653); // You are now fully initiated into the Elven culture.
else
pm.SendMessage("You have fully changed your race to {0}.", targetRace.Name);
owner?.ConsumeNeeded(pm);
}
private class RaceChangeState
{
private static readonly TimeSpan m_TimeoutDelay = TimeSpan.FromMinutes(1);
public IRaceChanger m_Owner;
public Race m_TargetRace;
public Timer m_Timeout;
public RaceChangeState(IRaceChanger owner, NetState ns, Race targetRace)
{
m_Owner = owner;
m_TargetRace = targetRace;
m_Timeout = Timer.DelayCall(m_TimeoutDelay, Timeout, ns);
}
}
}
public sealed class RaceChanger : Packet
{
public RaceChanger(bool female, Race targetRace)
: base(0xBF)
{
EnsureCapacity(7);
m_Stream.Write((short)0x2A);
m_Stream.Write((byte)(female ? 1 : 0));
m_Stream.Write((byte)(targetRace.RaceID + 1));
}
}
public sealed class CloseRaceChanger : Packet
{
public static readonly Packet Instance = SetStatic(new CloseRaceChanger());
private CloseRaceChanger()
: base(0xBF)
{
EnsureCapacity(7);
m_Stream.Write((short)0x2A);
m_Stream.Write((byte)0);
m_Stream.Write((byte)0xFF);
}
}
#region For testing
public class RaceChangeDeed : Item, IRaceChanger
{
[Constructible]
public RaceChangeDeed()
: base(0x14F0)
{
LootType = LootType.Blessed;
}
public RaceChangeDeed(Serial serial)
: base(serial)
{
}
public override string DefaultName => "a race change deed";
public bool CheckComplete(PlayerMobile pm)
{
if (Deleted)
return false;
if (!IsChildOf(pm.Backpack))
{
pm.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
return false;
}
return true;
}
public void ConsumeNeeded(PlayerMobile pm)
{
Consume();
}
public void OnCancel(PlayerMobile pm)
{
}
public override void OnDoubleClick(Mobile from)
{
if (!(from is PlayerMobile pm))
return;
if (CheckComplete(pm))
pm.SendGump(new RaceChangeConfirmGump(this, pm, pm.Race == Race.Human ? Race.Elf : Race.Human));
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write(0); // version
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
#endregion
}