runuo/Scripts/Engines/MLQuests/MLQuestEntry.cs
Mark Sturgill d2c670f5c3 Implement Core.TickCount
Convert movement, actions (lift/use), combat, and spells to use Core.TickCount and avoid DateTime caveats (performance, system time dependency, etc)
Refactor DateTime.Now to DateTime.UtcNow
Add LOS check for Iron Maiden addon
2013-10-06 03:21:18 -07:00

534 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using Server;
using Server.Engines.MLQuests.Objectives;
using Server.Mobiles;
using Server.Network;
using Server.Engines.MLQuests.Gumps;
using Server.Engines.MLQuests.Rewards;
namespace Server.Engines.MLQuests
{
[Flags]
public enum MLQuestInstanceFlags : byte
{
None = 0x00,
ClaimReward = 0x01,
Removed = 0x02,
Failed = 0x04
}
public class MLQuestInstance
{
private MLQuest m_Quest;
private IQuestGiver m_Quester;
private Type m_QuesterType;
private PlayerMobile m_Player;
private DateTime m_Accepted;
private MLQuestInstanceFlags m_Flags;
private BaseObjectiveInstance[] m_ObjectiveInstances;
private Timer m_Timer;
public MLQuestInstance( MLQuest quest, IQuestGiver quester, PlayerMobile player )
{
m_Quest = quest;
m_Quester = quester;
m_QuesterType = ( quester == null ) ? null : quester.GetType();
m_Player = player;
m_Accepted = DateTime.UtcNow;
m_Flags = MLQuestInstanceFlags.None;
m_ObjectiveInstances = new BaseObjectiveInstance[quest.Objectives.Count];
BaseObjectiveInstance obj;
bool timed = false;
for ( int i = 0; i < quest.Objectives.Count; ++i )
{
m_ObjectiveInstances[i] = obj = quest.Objectives[i].CreateInstance( this );
if ( obj.IsTimed )
timed = true;
}
Register();
if ( timed )
m_Timer = Timer.DelayCall( TimeSpan.FromSeconds( 5 ), TimeSpan.FromSeconds( 5 ), Slice );
}
private void Register()
{
if ( m_Quest != null && m_Quest.Instances != null )
m_Quest.Instances.Add( this );
if ( m_Player != null )
PlayerContext.QuestInstances.Add( this );
}
private void Unregister()
{
if ( m_Quest != null && m_Quest.Instances != null )
m_Quest.Instances.Remove( this );
if ( m_Player != null )
PlayerContext.QuestInstances.Remove( this );
Removed = true;
}
public MLQuest Quest
{
get { return m_Quest; }
set { m_Quest = value; }
}
public IQuestGiver Quester
{
get { return m_Quester; }
set
{
m_Quester = value;
m_QuesterType = ( value == null ) ? null : value.GetType();
}
}
public Type QuesterType
{
get { return m_QuesterType; }
}
public PlayerMobile Player
{
get { return m_Player; }
set { m_Player = value; }
}
public MLQuestContext PlayerContext
{
get { return MLQuestSystem.GetOrCreateContext( m_Player ); }
}
public DateTime Accepted
{
get { return m_Accepted; }
set { m_Accepted = value; }
}
public bool ClaimReward
{
get { return GetFlag( MLQuestInstanceFlags.ClaimReward ); }
set { SetFlag( MLQuestInstanceFlags.ClaimReward, value ); }
}
public bool Removed
{
get { return GetFlag( MLQuestInstanceFlags.Removed ); }
set { SetFlag( MLQuestInstanceFlags.Removed, value ); }
}
public bool Failed
{
get { return GetFlag( MLQuestInstanceFlags.Failed ); }
set { SetFlag( MLQuestInstanceFlags.Failed, value ); }
}
public BaseObjectiveInstance[] Objectives
{
get { return m_ObjectiveInstances; }
set { m_ObjectiveInstances = value; }
}
public bool AllowsQuestItem( Item item, Type type )
{
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
{
if ( !objective.Expired && objective.AllowsQuestItem( item, type ) )
return true;
}
return false;
}
public bool IsCompleted()
{
bool requiresAll = ( m_Quest.ObjectiveType == ObjectiveType.All );
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
{
bool complete = obj.IsCompleted();
if ( complete && !requiresAll )
return true;
else if ( !complete && requiresAll )
return false;
}
return requiresAll;
}
public void CheckComplete()
{
if ( IsCompleted() )
{
m_Player.PlaySound( 0x5B5 ); // public sound
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
obj.OnQuestCompleted();
TextDefinition.SendMessageTo( m_Player, m_Quest.CompletionNotice, 0x23 );
/*
* Advance to the ClaimReward=true stage if this quest has no
* completion message to show anyway. This suppresses further
* triggers of CheckComplete.
*
* For quests that require collections, this is done later when
* the player double clicks the quester.
*/
if ( !Removed && SkipReportBack && !m_Quest.RequiresCollection ) // An OnQuestCompleted can potentially have removed this instance already
ContinueReportBack( false );
}
}
public void Fail()
{
Failed = true;
}
private void Slice()
{
if ( ClaimReward || Removed )
{
StopTimer();
return;
}
bool hasAnyFails = false;
bool hasAnyLeft = false;
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
{
if ( !obj.Expired )
{
if ( obj.IsTimed && obj.EndTime <= DateTime.UtcNow )
{
m_Player.SendLocalizedMessage( 1072258 ); // You failed to complete an objective in time!
obj.Expired = true;
obj.OnExpire();
hasAnyFails = true;
}
else
{
hasAnyLeft = true;
}
}
}
if ( ( m_Quest.ObjectiveType == ObjectiveType.All && hasAnyFails ) || !hasAnyLeft )
Fail();
if ( !hasAnyLeft )
StopTimer();
}
public void SendProgressGump()
{
m_Player.SendGump( new QuestConversationGump( m_Quest, m_Player, m_Quest.InProgressMessage ) );
}
public void SendRewardOffer()
{
m_Quest.GetRewards( this );
}
// TODO: Split next quest stuff from SendRewardGump stuff?
public void SendRewardGump()
{
Type nextQuestType = m_Quest.NextQuest;
if ( nextQuestType != null )
{
ClaimRewards(); // skip reward gump
if ( Removed ) // rewards were claimed successfully
{
MLQuest nextQuest = MLQuestSystem.FindQuest( nextQuestType );
if ( nextQuest != null )
nextQuest.SendOffer( m_Quester, m_Player );
}
}
else
{
m_Player.SendGump( new QuestRewardGump( this ) );
}
}
public bool SkipReportBack
{
get { return TextDefinition.IsNullOrEmpty( m_Quest.CompletionMessage ); }
}
public void SendReportBackGump()
{
if ( SkipReportBack )
ContinueReportBack( true ); // skip ahead
else
m_Player.SendGump( new QuestReportBackGump( this ) );
}
public void ContinueReportBack( bool sendRewardGump )
{
// There is a backpack check here on OSI for the rewards as well (even though it's not needed...)
if ( m_Quest.ObjectiveType == ObjectiveType.All )
{
// TODO: 1115877 - You no longer have the required items to complete this quest.
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
{
if ( !objective.IsCompleted() )
return;
}
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
{
if ( !objective.OnBeforeClaimReward() )
return;
}
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
objective.OnClaimReward();
}
else
{
/* The following behavior is unverified, as OSI (currently) has no collect quest requiring
* only one objective to be completed. It is assumed that only one objective is claimed
* (the first completed one), even when multiple are complete.
*/
bool complete = false;
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
{
if ( objective.IsCompleted() )
{
if ( objective.OnBeforeClaimReward() )
{
complete = true;
objective.OnClaimReward();
}
break;
}
}
if ( !complete )
return;
}
ClaimReward = true;
if ( m_Quest.HasRestartDelay )
PlayerContext.SetDoneQuest( m_Quest, DateTime.UtcNow + m_Quest.GetRestartDelay() );
// This is correct for ObjectiveType.Any as well
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
objective.OnAfterClaimReward();
if ( sendRewardGump )
SendRewardOffer();
}
public void ClaimRewards()
{
if ( m_Quest == null || m_Player == null || m_Player.Deleted || !ClaimReward || Removed )
return;
List<Item> rewards = new List<Item>();
foreach ( BaseReward reward in m_Quest.Rewards )
reward.AddRewardItems( m_Player, rewards );
if ( rewards.Count != 0 )
{
// On OSI a more naive method of checking is used.
// For containers, only the actual container item counts.
bool canFit = true;
foreach ( Item rewardItem in rewards )
{
if ( !m_Player.AddToBackpack( rewardItem ) )
{
canFit = false;
break;
}
}
if ( !canFit )
{
foreach ( Item rewardItem in rewards )
rewardItem.Delete();
m_Player.SendLocalizedMessage( 1078524 ); // Your backpack is full. You cannot complete the quest and receive your reward.
return;
}
foreach ( Item rewardItem in rewards )
{
string rewardName = ( rewardItem.Name != null ) ? rewardItem.Name : String.Concat( "#", rewardItem.LabelNumber );
if ( rewardItem.Stackable )
m_Player.SendLocalizedMessage( 1115917, String.Concat( rewardItem.Amount, "\t", rewardName ) ); // You receive a reward: ~1_QUANTITY~ ~2_ITEM~
else
m_Player.SendLocalizedMessage( 1074360, rewardName ); // You receive a reward: ~1_REWARD~
}
}
foreach ( BaseObjectiveInstance objective in m_ObjectiveInstances )
objective.OnRewardClaimed();
m_Quest.OnRewardClaimed( this );
MLQuestContext context = PlayerContext;
if ( m_Quest.RecordCompletion && !m_Quest.HasRestartDelay ) // Quests with restart delays are logged earlier as per OSI
context.SetDoneQuest( m_Quest );
if ( m_Quest.IsChainTriggered )
context.ChainOffers.Remove( m_Quest );
Type nextQuestType = m_Quest.NextQuest;
if ( nextQuestType != null )
{
MLQuest nextQuest = MLQuestSystem.FindQuest( nextQuestType );
if ( nextQuest != null && !context.ChainOffers.Contains( nextQuest ) )
context.ChainOffers.Add( nextQuest );
}
Remove();
}
public void Cancel()
{
Cancel( false );
}
public void Cancel( bool removeChain )
{
Remove();
m_Player.SendSound( 0x5B3 ); // private sound
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
obj.OnQuestCancelled();
m_Quest.OnCancel( this );
if ( removeChain )
PlayerContext.ChainOffers.Remove( m_Quest );
}
public void Remove()
{
Unregister();
StopTimer();
}
private void StopTimer()
{
if ( m_Timer != null )
{
m_Timer.Stop();
m_Timer = null;
}
}
public void OnQuesterDeleted()
{
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
obj.OnQuesterDeleted();
m_Quest.OnQuesterDeleted( this );
}
public void OnPlayerDeath()
{
foreach ( BaseObjectiveInstance obj in m_ObjectiveInstances )
obj.OnPlayerDeath();
m_Quest.OnPlayerDeath( this );
}
private bool GetFlag( MLQuestInstanceFlags flag )
{
return ( ( m_Flags & flag ) != 0 );
}
private void SetFlag( MLQuestInstanceFlags flag, bool value )
{
if ( value )
m_Flags |= flag;
else
m_Flags &= ~flag;
}
public void Serialize( GenericWriter writer )
{
// Version info is written in MLQuestPersistence.Serialize
MLQuestSystem.WriteQuestRef( writer, m_Quest );
if ( m_Quester == null || m_Quester.Deleted )
writer.Write( Serial.MinusOne );
else
writer.Write( m_Quester.Serial );
writer.Write( ClaimReward );
writer.Write( m_ObjectiveInstances.Length );
foreach ( BaseObjectiveInstance objInstance in m_ObjectiveInstances )
objInstance.Serialize( writer );
}
public static MLQuestInstance Deserialize( GenericReader reader, int version, PlayerMobile pm )
{
MLQuest quest = MLQuestSystem.ReadQuestRef( reader );
// TODO: Serialize quester TYPE too, the quest giver reference then becomes optional (only for escorts)
IQuestGiver quester = World.FindEntity( reader.ReadInt() ) as IQuestGiver;
bool claimReward = reader.ReadBool();
int objectives = reader.ReadInt();
MLQuestInstance instance;
if ( quest != null && quester != null && pm != null )
{
instance = quest.CreateInstance( quester, pm );
instance.ClaimReward = claimReward;
}
else
{
instance = null;
}
for ( int i = 0; i < objectives; ++i )
BaseObjectiveInstance.Deserialize( reader, version, ( instance != null && i < instance.Objectives.Length ) ? instance.Objectives[i] : null );
if ( instance != null )
instance.Slice();
return instance;
}
}
}