ML quest system; first SVN release.

Secondary changes:
- Region fixes, mainly for New Haven.
- Recipe system changes.
- Enabled ML craftables.
- Dyeing/cutting restrictions for quest items.
- Moved IsInvulnerable from BaseVendor to BaseCreature.
- Moved RandomBrightHue to Utility.
- Items no longer decay when attached to a spawner.
- Quest item hue is now faked through packets, instead of overriding Hue.
- Re-enabled item drag effects for SA clients.
- Fixed AssignRandomFacialHair bug in Utility.
- Added random hue comments to Utility.
This commit is contained in:
eos 2013-02-03 01:05:17 +00:00
parent 5c7af18dfb
commit 35fbffdb51
167 changed files with 23218 additions and 234 deletions

View file

@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Engines.MLQuests.Objectives;
using Server.Engines.MLQuests.Rewards;
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
{
private ButtonPosition m_Position;
private ButtonGraphic m_Graphic;
private int m_ButtonID;
public ButtonPosition Position { get { return m_Position; } }
public ButtonGraphic Graphic { get { return m_Graphic; } }
public int ButtonID { get { return m_ButtonID; } }
public ButtonInfo( ButtonPosition position, ButtonGraphic graphic, int buttonID )
{
m_Position = position;
m_Graphic = graphic;
m_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, GumpButtonType.Reply, 0 );
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, false, false );
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, GumpButtonType.Reply, 0 );
if ( m_Title != null )
AddHtmlLocalized( 130, 68, 220, 48, 1114513, m_Title, 0x2710, false, false ); // <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 = String.Format( "#{0}", 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, false, false ); // 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, false, false ); // Objective:
AddHtmlLocalized( 98, 156, 312, 16, ( quest.ObjectiveType == ObjectiveType.All ) ? 1072208 : 1072209, 0x2710, false, false ); // 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, false, false ); // Objective:
AddHtmlLocalized( 98, 156, 312, 16, ( quest.ObjectiveType == ObjectiveType.All ) ? 1072208 : 1072209, 0x2710, false, false ); // 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, false, false ); // 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, false, false ); // 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, false, false ); // 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( typeof( InfoNPCGump ) );
pm.CloseGump( typeof( QuestRewardGump ) );
pm.CloseGump( typeof( QuestConversationGump ) );
pm.CloseGump( typeof( QuestReportBackGump ) );
//pm.CloseGump( typeof( UnknownGump807 ) );
pm.CloseGump( typeof( QuestCancelConfirmGump ) );
}
}
}

View file

@ -0,0 +1,22 @@
using System;
using Server;
using Server.Gumps;
using Server.Network;
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,105 @@
using System;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestCancelConfirmGump : Gump
{
private MLQuestInstance m_Instance;
private bool m_CloseGumps;
public QuestCancelConfirmGump( MLQuestInstance instance )
: this( instance, true )
{
}
public QuestCancelConfirmGump( MLQuestInstance instance, bool closeGumps )
: 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, false, false ); // 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, false, false );
MLQuest quest = instance.Quest;
if ( quest.IsChainTriggered || quest.NextQuest != null )
{
AddRadio( 25, 145, 0x25F8, 0x25FB, false, 2 );
AddHtmlLocalized( 60, 150, 280, 20, 1075023, 0xFFFFFF, false, false ); // Yes, I want to quit this entire chain!
}
AddRadio( 25, 180, 0x25F8, 0x25FB, true, 1 );
AddHtmlLocalized( 60, 185, 280, 20, 1049005, 0xFFFFFF, false, false ); // Yes, I really want to quit this quest!
AddRadio( 25, 215, 0x25F8, 0x25FB, false, 0 );
AddHtmlLocalized( 60, 220, 280, 20, 1049006, 0xFFFFFF, false, false ); // No, I don't want to quit.
AddButton( 265, 220, 0xF7, 0xF8, 7, GumpButtonType.Reply, 0 );
}
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,25 @@
using System;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
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,81 @@
using System;
using Server;
using Server.Engines.MLQuests;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestLogDetailedGump : BaseQuestGump
{
private MLQuestInstance m_Instance;
private bool m_CloseGumps;
public QuestLogDetailedGump( MLQuestInstance instance )
: this( instance, true )
{
}
public QuestLogDetailedGump( MLQuestInstance instance, bool closeGumps )
: base( 1046026 ) // Quest Log
{
m_Instance = instance;
m_CloseGumps = closeGumps;
PlayerMobile pm = instance.Player;
MLQuest quest = instance.Quest;
if ( closeGumps )
{
CloseOtherGumps( pm );
pm.CloseGump( typeof( 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, false, false ); // 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,83 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestLogGump : BaseQuestGump
{
private PlayerMobile m_Owner;
private bool m_CloseGumps;
public QuestLogGump( PlayerMobile pm )
: this( pm, true )
{
}
public QuestLogGump( PlayerMobile pm, bool closeGumps )
: base( 1046026 ) // Quest Log
{
m_Owner = pm;
m_CloseGumps = closeGumps;
if ( closeGumps )
{
pm.CloseGump( typeof( QuestLogGump ) );
pm.CloseGump( typeof( 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,62 @@
using System;
using Server;
using Server.Engines.MLQuests;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.MLQuests.Gumps
{
public class QuestOfferGump : BaseQuestGump
{
private MLQuest m_Quest;
private BaseCreature m_Quester;
public QuestOfferGump( MLQuest quest, BaseCreature quester, PlayerMobile pm )
: base( 1049010 ) // Quest Offer
{
m_Quest = quest;
m_Quester = quester;
CloseOtherGumps( pm );
pm.CloseGump( typeof( 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 )
{
PlayerMobile pm = sender.Mobile as PlayerMobile;
if ( pm == null )
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,40 @@
using System;
using Server;
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();
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using Server;
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,356 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Engines.MLQuests.Mobiles;
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
{
public static readonly Type Type = typeof( RaceChangeConfirmGump );
private IRaceChanger m_Owner;
private PlayerMobile m_From;
private Race m_Race;
public RaceChangeConfirmGump( IRaceChanger owner, PlayerMobile from, Race targetRace )
: base( 50, 50 )
{
from.CloseGump( Type );
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, false, false ); // Are you sure you wish to embrace your humanity?
else if ( targetRace == Race.Elf )
AddHtmlLocalized( 15, 15, 210, 75, 1073642, 0, false, false ); // Are you sure you want to follow the elven ways?
else
AddHtml( 15, 15, 210, 75, String.Format( "Are you sure you want to change your race to {0}?", targetRace.Name ), false, false );
AddButton( 160, 95, 0xF7, 0xF8, 1, GumpButtonType.Reply, 0 );
AddButton( 90, 95, 0xF2, 0xF1, 0, GumpButtonType.Reply, 0 );
}
public override void OnResponse( NetState sender, RelayInfo info )
{
switch ( info.ButtonID )
{
case 0: // Cancel
{
if ( m_Owner != null )
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;
}
}
}
private class RaceChangeState
{
private static readonly TimeSpan m_TimeoutDelay = TimeSpan.FromMinutes( 1 );
private static readonly TimerStateCallback<NetState> m_TimeoutCallback = new TimerStateCallback<NetState>( Timeout );
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<NetState>( m_TimeoutDelay, m_TimeoutCallback, ns );
}
}
private static Dictionary<NetState, RaceChangeState> m_Pending;
public static void Initialize()
{
m_Pending = new Dictionary<NetState, RaceChangeState>();
PacketHandlers.RegisterExtended( 0x2A, true, new OnPacketReceive( 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 )
{
RaceChangeState state;
if ( m_Pending.TryGetValue( ns, out state ) )
{
state.m_Timeout.Stop();
m_Pending.Remove( ns );
}
ns.Send( CloseRaceChanger.Instance );
}
private static void Timeout( NetState ns )
{
if ( m_Pending.ContainsKey( 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( typeof( PolymorphSpell ) ) || DisguiseTimers.IsDisguised( from ) || AnimalForm.UnderTransformation( from ) || !from.CanBeginAction( typeof( 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 )
{
RaceChangeState raceChangeState;
if ( !m_Pending.TryGetValue( state, out raceChangeState ) )
return;
CloseCurrent( state );
PlayerMobile pm = state.Mobile as PlayerMobile;
if ( pm == null )
return;
IRaceChanger owner = raceChangeState.m_Owner;
Race targetRace = raceChangeState.m_TargetRace;
if ( pvSrc.Size == 5 )
{
if ( owner != null )
owner.OnCancel( pm );
return;
}
if ( !CanChange( pm, targetRace ) || ( owner != null && !owner.CheckComplete( pm ) ) )
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 );
if ( owner != null )
owner.ConsumeNeeded( pm );
}
}
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 = Packet.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
{
public override string DefaultName { get { return "a race change deed"; } }
[Constructable]
public RaceChangeDeed()
: base( 0x14F0 )
{
LootType = LootType.Blessed;
}
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 )
{
PlayerMobile pm = from as PlayerMobile;
if ( pm == null )
return;
if ( CheckComplete( pm ) )
pm.SendGump( new RaceChangeConfirmGump( this, pm, ( pm.Race == Race.Human ) ? Race.Elf : Race.Human ) );
}
public RaceChangeDeed( Serial serial )
: base( serial )
{
}
public override void Serialize( GenericWriter writer )
{
base.Serialize( writer );
writer.Write( (int)0 ); // version
}
public override void Deserialize( GenericReader reader )
{
base.Deserialize( reader );
int version = reader.ReadInt();
}
}
#endregion
}