4127 lines
No EOL
97 KiB
C#
4127 lines
No EOL
97 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server.Regions;
|
|
using Server.Targeting;
|
|
using Server.Network;
|
|
using Server.Multis;
|
|
using Server.Spells;
|
|
using Server.Misc;
|
|
using Server.Items;
|
|
using Server.ContextMenus;
|
|
using Server.Engines.PartySystem;
|
|
|
|
namespace Server.Mobiles
|
|
{
|
|
#region Enums
|
|
/// <summary>
|
|
/// Summary description for MobileAI.
|
|
/// </summary>
|
|
///
|
|
public enum FightMode
|
|
{
|
|
None, // Never focus on others
|
|
Aggressor, // Only attack aggressors
|
|
Strongest, // Attack the strongest
|
|
Weakest, // Attack the weakest
|
|
Closest, // Attack the closest
|
|
Evil // Only attack aggressor -or- negative karma
|
|
}
|
|
|
|
public enum Clan
|
|
{
|
|
Monster,
|
|
Undead,
|
|
Demonic,
|
|
Wizard,
|
|
Humanoid,
|
|
Citizen
|
|
}
|
|
|
|
public enum OrderType
|
|
{
|
|
None, //When no order, let's roam
|
|
Come, //"(All/Name) come" Summons all or one pet to your location.
|
|
Drop, //"(Name) drop" Drops its loot to the ground (if it carries any).
|
|
Follow, //"(Name) follow" Follows targeted being.
|
|
//"(All/Name) follow me" Makes all or one pet follow you.
|
|
Friend, //"(Name) friend" Allows targeted player to confirm resurrection.
|
|
Unfriend, // Remove a friend
|
|
Guard, //"(Name) guard" Makes the specified pet guard you. Pets can only guard their owner.
|
|
//"(All/Name) guard me" Makes all or one pet guard you.
|
|
Attack, //"(All/Name) kill",
|
|
//"(All/Name) attack" All or the specified pet(s) currently under your control attack the target.
|
|
Release, //"(Name) release" Releases pet back into the wild (removes "tame" status).
|
|
Stay, //"(All/Name) stay" All or the specified pet(s) will stop and stay in current spot.
|
|
Stop, //"(All/Name) stop Cancels any current orders to attack, guard or follow.
|
|
Transfer //"(Name) transfer" Transfers complete ownership to targeted player.
|
|
}
|
|
|
|
public enum MeatType
|
|
{
|
|
Ribs,
|
|
Bird,
|
|
Lamb,
|
|
Fish,
|
|
Ham,
|
|
BigBird
|
|
}
|
|
|
|
#endregion
|
|
|
|
public class DamageStore : IComparable
|
|
{
|
|
public Mobile m_Mobile;
|
|
public int m_Damage;
|
|
public bool m_HasRight;
|
|
|
|
public DamageStore( Mobile m, int damage )
|
|
{
|
|
m_Mobile = m;
|
|
m_Damage = damage;
|
|
}
|
|
|
|
public int CompareTo( object obj )
|
|
{
|
|
DamageStore ds = (DamageStore)obj;
|
|
|
|
return ds.m_Damage - m_Damage;
|
|
}
|
|
}
|
|
|
|
[AttributeUsage( AttributeTargets.Class )]
|
|
public class FriendlyNameAttribute : Attribute
|
|
{
|
|
private TextDefinition m_FriendlyName;
|
|
|
|
public TextDefinition FriendlyName
|
|
{
|
|
get
|
|
{
|
|
return m_FriendlyName;
|
|
}
|
|
}
|
|
|
|
public FriendlyNameAttribute( TextDefinition friendlyName )
|
|
{
|
|
m_FriendlyName = friendlyName;
|
|
}
|
|
|
|
public static TextDefinition GetFriendlyNameFor( Type t )
|
|
{
|
|
if( t.IsDefined( typeof( FriendlyNameAttribute ), false ) )
|
|
{
|
|
object[] objs = t.GetCustomAttributes( typeof( FriendlyNameAttribute ), false );
|
|
|
|
if( objs != null && objs.Length > 0 )
|
|
{
|
|
FriendlyNameAttribute friendly = objs[0] as FriendlyNameAttribute;
|
|
|
|
return friendly.FriendlyName;
|
|
}
|
|
}
|
|
|
|
return t.Name;
|
|
}
|
|
}
|
|
|
|
public class BaseCreature : Mobile
|
|
{
|
|
#region Var declarations
|
|
private BaseAI m_AI; // THE AI
|
|
|
|
private AIType m_CurrentAI; // The current AI
|
|
private AIType m_DefaultAI; // The default AI
|
|
|
|
private Mobile m_FocusMob; // Use focus mob instead of combatant, maybe we don't whan to fight
|
|
private FightMode m_FightMode; // The style the mob uses
|
|
private Clan m_Clan; // The general group or race they belong to
|
|
|
|
private int m_iRangePerception; // The view area
|
|
private int m_iRangeFight; // The fight distance
|
|
|
|
private bool m_bDebugAI; // Show debug AI messages
|
|
|
|
private int m_iTeam; // Monster Team
|
|
|
|
private double m_dActiveSpeed; // Timer speed when active
|
|
private double m_dPassiveSpeed; // Timer speed when not active
|
|
private double m_dCurrentSpeed; // The current speed, lets say it could be changed by something;
|
|
|
|
private Point3D m_pHome; // The home position of the creature, used by some AI
|
|
private int m_iRangeHome = 10; // The home range of the creature
|
|
|
|
List<Type> m_arSpellAttack; // List of attack spell/power
|
|
List<Type> m_arSpellDefense; // List of defensive spell/power
|
|
|
|
private bool m_bControlled; // Is controlled
|
|
private Mobile m_ControlMaster; // My master
|
|
private Mobile m_ControlTarget; // My target mobile
|
|
private OrderType m_ControlOrder; // My order
|
|
|
|
private bool m_bSummoned = false;
|
|
private DateTime m_SummonEnd;
|
|
private int m_iControlSlots = 1;
|
|
|
|
private bool m_bBardProvoked = false;
|
|
private bool m_bBardPacified = false;
|
|
private Mobile m_bBardMaster = null;
|
|
private Mobile m_bBardTarget = null;
|
|
private DateTime m_timeBardEnd;
|
|
private WayPoint m_CurrentWayPoint = null;
|
|
private IPoint2D m_TargetLocation = null;
|
|
|
|
private Mobile m_SummonMaster;
|
|
|
|
private int m_HitsMax = -1;
|
|
private int m_StamMax = -1;
|
|
private int m_ManaMax = -1;
|
|
private int m_DamageMin = -1;
|
|
private int m_DamageMax = -1;
|
|
|
|
private List<Mobile> m_Owners;
|
|
private List<Mobile> m_Friends;
|
|
|
|
private bool m_IsStabled;
|
|
|
|
private bool m_HasGeneratedLoot; // have we generated our loot yet?
|
|
|
|
private bool m_SeaCreature = false;
|
|
|
|
#endregion
|
|
|
|
public virtual InhumanSpeech SpeechType{ get{ return null; } }
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public bool IsStabled
|
|
{
|
|
get{ return m_IsStabled; }
|
|
set
|
|
{
|
|
m_IsStabled = value;
|
|
if ( m_IsStabled )
|
|
StopDeleteTimer();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster, AccessLevel.Administrator )]
|
|
public bool SeaCreature
|
|
{
|
|
get { return m_SeaCreature; }
|
|
set { m_SeaCreature = value; }
|
|
}
|
|
|
|
protected DateTime SummonEnd
|
|
{
|
|
get { return m_SummonEnd; }
|
|
set { m_SummonEnd = value; }
|
|
}
|
|
|
|
#region Bonding
|
|
public const bool BondingEnabled = true;
|
|
|
|
public virtual bool IsBondable{ get{ return ( BondingEnabled && !Summoned ); } }
|
|
public virtual TimeSpan BondingDelay{ get{ return TimeSpan.FromDays( 7.0 ); } }
|
|
public virtual TimeSpan BondingAbandonDelay{ get{ return TimeSpan.FromDays( 1.0 ); } }
|
|
|
|
public override bool CanRegenHits{ get{ return !m_IsDeadPet && base.CanRegenHits; } }
|
|
public override bool CanRegenStam{ get{ return !m_IsDeadPet && base.CanRegenStam; } }
|
|
public override bool CanRegenMana{ get{ return !m_IsDeadPet && base.CanRegenMana; } }
|
|
|
|
public override bool IsDeadBondedPet{ get{ return m_IsDeadPet; } }
|
|
|
|
private bool m_IsBonded;
|
|
private bool m_IsDeadPet;
|
|
private DateTime m_BondingBegin;
|
|
private DateTime m_OwnerAbandonTime;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile LastOwner
|
|
{
|
|
get
|
|
{
|
|
if ( m_Owners == null || m_Owners.Count == 0 )
|
|
return null;
|
|
|
|
return m_Owners[m_Owners.Count - 1];
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool IsBonded
|
|
{
|
|
get{ return m_IsBonded; }
|
|
set{ m_IsBonded = value; InvalidateProperties(); }
|
|
}
|
|
|
|
public bool IsDeadPet
|
|
{
|
|
get{ return m_IsDeadPet; }
|
|
set{ m_IsDeadPet = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public DateTime BondingBegin
|
|
{
|
|
get{ return m_BondingBegin; }
|
|
set{ m_BondingBegin = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public DateTime OwnerAbandonTime
|
|
{
|
|
get{ return m_OwnerAbandonTime; }
|
|
set{ m_OwnerAbandonTime = value; }
|
|
}
|
|
#endregion
|
|
|
|
#region Delete Previously Tamed Timer
|
|
private DeleteTimer m_DeleteTimer;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public TimeSpan DeleteTimeLeft
|
|
{
|
|
get
|
|
{
|
|
if ( m_DeleteTimer != null && m_DeleteTimer.Running )
|
|
return m_DeleteTimer.Next - DateTime.Now;
|
|
|
|
return TimeSpan.Zero;
|
|
}
|
|
}
|
|
|
|
private class DeleteTimer : Timer
|
|
{
|
|
private Mobile m;
|
|
|
|
public DeleteTimer( Mobile creature, TimeSpan delay ) : base( delay )
|
|
{
|
|
m = creature;
|
|
Priority = TimerPriority.OneMinute;
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
m.Delete();
|
|
}
|
|
}
|
|
|
|
public void BeginDeleteTimer()
|
|
{
|
|
if ( !Summoned && !Deleted && !IsStabled )
|
|
{
|
|
StopDeleteTimer();
|
|
m_DeleteTimer = new DeleteTimer( this, TimeSpan.FromDays( 3.0 ) );
|
|
m_DeleteTimer.Start();
|
|
}
|
|
}
|
|
|
|
public void StopDeleteTimer()
|
|
{
|
|
if ( m_DeleteTimer != null )
|
|
{
|
|
m_DeleteTimer.Stop();
|
|
m_DeleteTimer = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public List<Mobile> Owners { get { return m_Owners; } }
|
|
|
|
public virtual bool Commandable{ get{ return true; } }
|
|
|
|
public virtual Poison HitPoison{ get{ return null; } }
|
|
public virtual double HitPoisonChance{ get{ return 0.5; } }
|
|
public virtual Poison PoisonImmune{ get{ return null; } }
|
|
|
|
public virtual bool BardImmune{ get{ return false; } }
|
|
public virtual bool Unprovokable{ get{ return BardImmune || m_IsDeadPet; } }
|
|
public virtual bool Uncalmable{ get{ return BardImmune || m_IsDeadPet; } }
|
|
public virtual bool AreaPeaceImmune { get { return BardImmune || m_IsDeadPet; } }
|
|
|
|
public virtual bool BleedImmune
|
|
{
|
|
get
|
|
{
|
|
if ( (SlayerGroup.GetEntryByName( SlayerName.SeaSlaughter )).Slays(this) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
public virtual double BonusPetDamageScalar{ get{ return 1.0; } }
|
|
|
|
//TODO: Find the pub 31 tweaks to the DispelDifficulty and apply them of course.
|
|
public virtual double DispelDifficulty{ get{ return 0.0; } } // at this skill level we dispel 50% chance
|
|
public virtual double DispelFocus{ get{ return 20.0; } } // at difficulty - focus we have 0%, at difficulty + focus we have 100%
|
|
public virtual bool DisplayWeight{ get{ return Backpack is StrongBackpack; } }
|
|
|
|
#region Breath ability, like dragon fire breath
|
|
private DateTime m_NextBreathTime;
|
|
|
|
// Must be overriden in subclass to enable
|
|
public virtual bool HasBreath{ get{ return false; } }
|
|
|
|
// Base damage given is: CurrentHitPoints * BreathDamageScalar
|
|
public virtual double BreathDamageScalar{ get{ return 0.05; } }
|
|
|
|
// Min/max seconds until next breath
|
|
public virtual double BreathMinDelay{ get{ return 10.0; } }
|
|
public virtual double BreathMaxDelay{ get{ return 15.0; } }
|
|
|
|
// Creature stops moving for 1.0 seconds while breathing
|
|
public virtual double BreathStallTime{ get{ return 1.0; } }
|
|
|
|
// Effect is sent 1.3 seconds after BreathAngerSound and BreathAngerAnimation is played
|
|
public virtual double BreathEffectDelay{ get{ return 1.3; } }
|
|
|
|
// Damage is given 1.0 seconds after effect is sent
|
|
public virtual double BreathDamageDelay{ get{ return 1.0; } }
|
|
|
|
public virtual int BreathRange{ get{ return RangePerception; } }
|
|
|
|
// Damage types
|
|
public virtual int BreathPhysicalDamage{ get{ return 0; } }
|
|
public virtual int BreathFireDamage{ get{ return 100; } }
|
|
public virtual int BreathColdDamage{ get{ return 0; } }
|
|
public virtual int BreathPoisonDamage{ get{ return 0; } }
|
|
public virtual int BreathEnergyDamage{ get{ return 0; } }
|
|
|
|
// Is immune to breath damages
|
|
public virtual bool BreathImmune{ get{ return false; } }
|
|
|
|
// Effect details and sound
|
|
public virtual int BreathEffectItemID{ get{ return 0x36D4; } }
|
|
public virtual int BreathEffectSpeed{ get{ return 5; } }
|
|
public virtual int BreathEffectDuration{ get{ return 0; } }
|
|
public virtual bool BreathEffectExplodes{ get{ return false; } }
|
|
public virtual bool BreathEffectFixedDir{ get{ return false; } }
|
|
public virtual int BreathEffectHue{ get{ return 0; } }
|
|
public virtual int BreathEffectRenderMode{ get{ return 0; } }
|
|
|
|
public virtual int BreathEffectSound{ get{ return 0x227; } }
|
|
|
|
// Anger sound/animations
|
|
public virtual int BreathAngerSound{ get{ return GetAngerSound(); } }
|
|
public virtual int BreathAngerAnimation{ get{ return 12; } }
|
|
|
|
public virtual void BreathStart( Mobile target )
|
|
{
|
|
BreathStallMovement();
|
|
BreathPlayAngerSound();
|
|
BreathPlayAngerAnimation();
|
|
|
|
this.Direction = this.GetDirectionTo( target );
|
|
|
|
Timer.DelayCall( TimeSpan.FromSeconds( BreathEffectDelay ), new TimerStateCallback( BreathEffect_Callback ), target );
|
|
}
|
|
|
|
public virtual void BreathStallMovement()
|
|
{
|
|
if ( m_AI != null )
|
|
m_AI.NextMove = DateTime.Now + TimeSpan.FromSeconds( BreathStallTime );
|
|
}
|
|
|
|
public virtual void BreathPlayAngerSound()
|
|
{
|
|
PlaySound( BreathAngerSound );
|
|
}
|
|
|
|
public virtual void BreathPlayAngerAnimation()
|
|
{
|
|
Animate( BreathAngerAnimation, 5, 1, true, false, 0 );
|
|
}
|
|
|
|
public virtual void BreathEffect_Callback( object state )
|
|
{
|
|
Mobile target = (Mobile)state;
|
|
|
|
if ( !target.Alive || !CanBeHarmful( target ) )
|
|
return;
|
|
|
|
BreathPlayEffectSound();
|
|
BreathPlayEffect( target );
|
|
|
|
Timer.DelayCall( TimeSpan.FromSeconds( BreathDamageDelay ), new TimerStateCallback( BreathDamage_Callback ), target );
|
|
}
|
|
|
|
public virtual void BreathPlayEffectSound()
|
|
{
|
|
PlaySound( BreathEffectSound );
|
|
}
|
|
|
|
public virtual void BreathPlayEffect( Mobile target )
|
|
{
|
|
Effects.SendMovingEffect( this, target, BreathEffectItemID,
|
|
BreathEffectSpeed, BreathEffectDuration, BreathEffectFixedDir,
|
|
BreathEffectExplodes, BreathEffectHue, BreathEffectRenderMode );
|
|
}
|
|
|
|
public virtual void BreathDamage_Callback( object state )
|
|
{
|
|
Mobile target = (Mobile)state;
|
|
|
|
if ( target is BaseCreature && ((BaseCreature)target).BreathImmune )
|
|
return;
|
|
|
|
if ( CanBeHarmful( target ) )
|
|
{
|
|
DoHarmful( target );
|
|
BreathDealDamage( target );
|
|
}
|
|
}
|
|
|
|
public virtual void BreathDealDamage( Mobile target )
|
|
{
|
|
target.Damage( BreathComputeDamage(), this );
|
|
}
|
|
|
|
public virtual int BreathComputeDamage()
|
|
{
|
|
int damage = (int)(Hits * BreathDamageScalar);
|
|
|
|
return damage;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Spill Acid
|
|
|
|
public void SpillAcid( int Amount )
|
|
{
|
|
SpillAcid( null, Amount );
|
|
}
|
|
|
|
public void SpillAcid( Mobile target, int Amount )
|
|
{
|
|
if ( (target != null && target.Map == null) || this.Map == null )
|
|
return;
|
|
|
|
for ( int i = 0; i < Amount; ++i )
|
|
{
|
|
Point3D loc = this.Location;
|
|
Map map = this.Map;
|
|
Item acid = NewHarmfulItem();
|
|
|
|
if ( target != null && target.Map != null && Amount == 1 )
|
|
{
|
|
loc = target.Location;
|
|
map = target.Map;
|
|
}
|
|
else
|
|
{
|
|
bool validLocation = false;
|
|
for ( int j = 0; !validLocation && j < 10; ++j )
|
|
{
|
|
loc = new Point3D(
|
|
loc.X+(Utility.Random(0,3)-2),
|
|
loc.Y+(Utility.Random(0,3)-2),
|
|
loc.Z );
|
|
loc.Z = map.GetAverageZ( loc.X, loc.Y );
|
|
validLocation = map.CanFit( loc, 16, false, false ) ;
|
|
}
|
|
}
|
|
acid.MoveToWorld( loc, map );
|
|
}
|
|
}
|
|
|
|
public virtual Item NewHarmfulItem()
|
|
{
|
|
return new PoolOfAcid( TimeSpan.FromSeconds(10), 30, 30 );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Flee!!!
|
|
private DateTime m_EndFlee;
|
|
|
|
public DateTime EndFleeTime
|
|
{
|
|
get{ return m_EndFlee; }
|
|
set{ m_EndFlee = value; }
|
|
}
|
|
|
|
public virtual void StopFlee()
|
|
{
|
|
m_EndFlee = DateTime.MinValue;
|
|
}
|
|
|
|
public virtual bool CheckFlee()
|
|
{
|
|
if ( m_EndFlee == DateTime.MinValue )
|
|
return false;
|
|
|
|
if ( DateTime.Now >= m_EndFlee )
|
|
{
|
|
StopFlee();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public virtual void BeginFlee( TimeSpan maxDuration )
|
|
{
|
|
m_EndFlee = DateTime.Now + maxDuration;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public BaseAI AIObject{ get{ return m_AI; } }
|
|
|
|
public const int MaxOwners = 5;
|
|
|
|
#region Friends
|
|
public List<Mobile> Friends { get { return m_Friends; } }
|
|
|
|
public virtual bool AllowNewPetFriend
|
|
{
|
|
get{ return ( m_Friends == null || m_Friends.Count < 5 ); }
|
|
}
|
|
|
|
public virtual bool IsPetFriend( Mobile m )
|
|
{
|
|
return ( m_Friends != null && m_Friends.Contains( m ) );
|
|
}
|
|
|
|
public virtual void AddPetFriend( Mobile m )
|
|
{
|
|
if ( m_Friends == null )
|
|
m_Friends = new List<Mobile>();
|
|
|
|
m_Friends.Add( m );
|
|
}
|
|
|
|
public virtual void RemovePetFriend( Mobile m )
|
|
{
|
|
if ( m_Friends != null )
|
|
m_Friends.Remove( m );
|
|
}
|
|
|
|
public virtual bool IsFriend( Mobile m )
|
|
{
|
|
if ( !(m is BaseCreature) )
|
|
return false;
|
|
|
|
BaseCreature c = (BaseCreature)m;
|
|
|
|
return ( m_iTeam == c.m_iTeam && ( (m_bSummoned || m_bControlled) == (c.m_bSummoned || c.m_bControlled) ) );
|
|
}
|
|
|
|
#endregion
|
|
|
|
public virtual bool IsEnemy( Mobile m )
|
|
{
|
|
if ( SeaCreature && !WontWalk && Hidden && m is PlayerMobile ) // SURFACE FROM WATER AND ATTACK
|
|
{
|
|
this.Home = this.Location; // SO THEY KNOW WHERE TO GO BACK TO
|
|
|
|
if ( m.Z < 0 ) // JUMP NEAR A BOAT
|
|
{
|
|
Point3D loc = BaseRegion.GetBoatWater( m.X, m.Y, m.Map, 4 );
|
|
if ( loc.X == 0 && loc.Y == 0 && loc.Z == 0 )
|
|
loc = m.Location;
|
|
|
|
this.Location = loc;
|
|
this.PlaySound( 0x026 );
|
|
Effects.SendLocationEffect( this.Location, this.Map, 0x35B2, 16 );
|
|
}
|
|
else if ( WontWalk ) // JUMP OUT OF WATER AND WALK TO TARGET
|
|
{
|
|
this.PlaySound( 0x026 );
|
|
Effects.SendLocationEffect( this.Location, this.Map, 0x35B2, 16 );
|
|
}
|
|
this.Hidden = false;
|
|
this.Warmode = true;
|
|
this.Combatant = m;
|
|
this.CantWalk = this.WontWalk;
|
|
this.CanSwim = this.WillSwim;
|
|
return true;
|
|
}
|
|
|
|
if ( !(m is BaseCreature) )
|
|
return true;
|
|
|
|
BaseCreature c = (BaseCreature)m;
|
|
|
|
if ( !m_bSummoned && !m_bControlled && !c.m_bSummoned && !c.m_bControlled )
|
|
{
|
|
if ( Clan == Clan.Citizen && c.Clan == Clan.Citizen )
|
|
return false;
|
|
|
|
if ( Clan == Clan.Humanoid && ( c.Clan == Clan.Undead || c.Clan == Clan.Demonic || c.Clan == Clan.Monster ) )
|
|
return true;
|
|
|
|
if ( ( Clan == Clan.Undead || Clan == Clan.Demonic || Clan == Clan.Monster ) && c.Clan == Clan.Humanoid )
|
|
return true;
|
|
|
|
if ( ( Clan == Clan.Undead || Clan == Clan.Demonic || Clan == Clan.Wizard ) && ( c.Clan == Clan.Undead || c.Clan == Clan.Demonic || c.Clan == Clan.Wizard ) )
|
|
return false;
|
|
|
|
|
|
if ( ( Clan == Clan.Undead || Clan == Clan.Demonic || Clan == Clan.Wizard ) && ( c.Clan == Clan.Undead || c.Clan == Clan.Demonic || c.Clan == Clan.Wizard ) )
|
|
return false;
|
|
}
|
|
|
|
return ( m_iTeam != c.m_iTeam || ( (m_bSummoned || m_bControlled) != (c.m_bSummoned || c.m_bControlled) ) );
|
|
}
|
|
|
|
public override string ApplyNameSuffix( string suffix )
|
|
{
|
|
return base.ApplyNameSuffix( suffix );
|
|
}
|
|
|
|
public virtual bool CheckControlChance( Mobile m )
|
|
{
|
|
if ( GetControlChance( m ) > Utility.RandomDouble() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
PlaySound( GetAngerSound() );
|
|
|
|
if ( Body.IsAnimal )
|
|
Animate( 10, 5, 1, true, false, 0 );
|
|
else if ( Body.IsMonster )
|
|
Animate( 18, 5, 1, true, false, 0 );
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual bool CanBeControlledBy( Mobile m )
|
|
{
|
|
return ( GetControlChance( m ) > 0.0 );
|
|
}
|
|
|
|
public double GetControlChance( Mobile m )
|
|
{
|
|
return GetControlChance( m, false );
|
|
}
|
|
|
|
public virtual double GetControlChance( Mobile m, bool useBaseSkill )
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
public override void Damage( int amount, Mobile from )
|
|
{
|
|
int oldHits = this.Hits;
|
|
|
|
base.Damage( amount, from );
|
|
}
|
|
|
|
public virtual bool DeleteCorpseOnDeath
|
|
{
|
|
get
|
|
{
|
|
return m_bSummoned;
|
|
}
|
|
}
|
|
|
|
public override void SetLocation( Point3D newLocation, bool isTeleport )
|
|
{
|
|
base.SetLocation( newLocation, isTeleport );
|
|
|
|
if ( isTeleport && m_AI != null )
|
|
m_AI.OnTeleported();
|
|
}
|
|
|
|
public override void OnBeforeSpawn( Point3D location, Map m )
|
|
{
|
|
base.OnBeforeSpawn( location, m );
|
|
}
|
|
|
|
public override ApplyPoisonResult ApplyPoison( Mobile from, Poison poison )
|
|
{
|
|
if ( !Alive || IsDeadPet )
|
|
return ApplyPoisonResult.Immune;
|
|
|
|
ApplyPoisonResult result = base.ApplyPoison( from, poison );
|
|
|
|
if ( from != null && result == ApplyPoisonResult.Poisoned && PoisonTimer is PoisonImpl.PoisonTimer )
|
|
(PoisonTimer as PoisonImpl.PoisonTimer).From = from;
|
|
|
|
return result;
|
|
}
|
|
|
|
public override bool CheckPoisonImmunity( Mobile from, Poison poison )
|
|
{
|
|
if ( base.CheckPoisonImmunity( from, poison ) )
|
|
return true;
|
|
|
|
Poison p = this.PoisonImmune;
|
|
|
|
return ( p != null && p.Level >= poison.Level );
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public WayPoint CurrentWayPoint
|
|
{
|
|
get
|
|
{
|
|
return m_CurrentWayPoint;
|
|
}
|
|
set
|
|
{
|
|
m_CurrentWayPoint = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public IPoint2D TargetLocation
|
|
{
|
|
get
|
|
{
|
|
return m_TargetLocation;
|
|
}
|
|
set
|
|
{
|
|
m_TargetLocation = value;
|
|
}
|
|
}
|
|
|
|
public virtual Mobile ConstantFocus{ get{ return null; } }
|
|
|
|
public virtual bool DisallowAllMoves
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public virtual bool InitialInnocent
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public virtual bool AlwaysMurderer
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public virtual bool AlwaysAttackable
|
|
{
|
|
get
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public virtual int DamageMin{ get{ return m_DamageMin; } set{ m_DamageMin = value; } }
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public virtual int DamageMax{ get{ return m_DamageMax; } set{ m_DamageMax = value; } }
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public override int HitsMax
|
|
{
|
|
get
|
|
{
|
|
if ( m_HitsMax > 0 ) {
|
|
int value = m_HitsMax + GetStatOffset( StatType.Str );
|
|
|
|
if( value < 1 )
|
|
value = 1;
|
|
else if( value > 65000 )
|
|
value = 65000;
|
|
|
|
return value;
|
|
}
|
|
|
|
return Str;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int HitsMaxSeed
|
|
{
|
|
get{ return m_HitsMax; }
|
|
set{ m_HitsMax = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public override int StamMax
|
|
{
|
|
get
|
|
{
|
|
if ( m_StamMax > 0 ) {
|
|
int value = m_StamMax + GetStatOffset( StatType.Dex );
|
|
|
|
if( value < 1 )
|
|
value = 1;
|
|
else if( value > 65000 )
|
|
value = 65000;
|
|
|
|
return value;
|
|
}
|
|
|
|
return Dex;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int StamMaxSeed
|
|
{
|
|
get{ return m_StamMax; }
|
|
set{ m_StamMax = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public override int ManaMax
|
|
{
|
|
get
|
|
{
|
|
if ( m_ManaMax > 0 ) {
|
|
int value = m_ManaMax + GetStatOffset( StatType.Int );
|
|
|
|
if( value < 1 )
|
|
value = 1;
|
|
else if( value > 65000 )
|
|
value = 65000;
|
|
|
|
return value;
|
|
}
|
|
|
|
return Int;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int ManaMaxSeed
|
|
{
|
|
get{ return m_ManaMax; }
|
|
set{ m_ManaMax = value; }
|
|
}
|
|
|
|
public virtual bool CanOpenDoors
|
|
{
|
|
get
|
|
{
|
|
return !this.Body.IsAnimal && !this.Body.IsSea;
|
|
}
|
|
}
|
|
|
|
public virtual bool CanMoveOverObstacles
|
|
{
|
|
get
|
|
{
|
|
return this.Body.IsMonster;
|
|
}
|
|
}
|
|
|
|
public virtual bool CanDestroyObstacles
|
|
{
|
|
get
|
|
{
|
|
// to enable breaking of furniture, 'return CanMoveOverObstacles;'
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Unpacify()
|
|
{
|
|
BardEndTime = DateTime.Now;
|
|
BardPacified = false;
|
|
}
|
|
|
|
public override void OnDamage( int amount, Mobile from, bool willKill )
|
|
{
|
|
if ( BardPacified && (HitsMax - Hits) * 0.001 > Utility.RandomDouble() )
|
|
Unpacify();
|
|
|
|
int disruptThreshold = 0;
|
|
|
|
if( amount > disruptThreshold )
|
|
{
|
|
BandageContext c = BandageContext.GetContext( this );
|
|
|
|
if( c != null )
|
|
c.Slip();
|
|
}
|
|
|
|
WeightOverloading.FatigueOnDamage( this, amount );
|
|
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null && !willKill )
|
|
speechType.OnDamage( this, amount );
|
|
|
|
if ( willKill && from is PlayerMobile )
|
|
Timer.DelayCall( TimeSpan.FromSeconds( 10 ), new TimerCallback( ((PlayerMobile) from).RecoverAmmo ) );
|
|
|
|
base.OnDamage( amount, from, willKill );
|
|
}
|
|
|
|
public virtual void OnDamagedBySpell( Mobile from )
|
|
{
|
|
}
|
|
|
|
public virtual void OnHarmfulSpell( Mobile from )
|
|
{
|
|
}
|
|
|
|
#region Alter[...]Damage From/To
|
|
|
|
public virtual void AlterDamageScalarFrom( Mobile caster, ref double scalar )
|
|
{
|
|
}
|
|
|
|
public virtual void AlterDamageScalarTo( Mobile target, ref double scalar )
|
|
{
|
|
}
|
|
|
|
public virtual void AlterSpellDamageFrom( Mobile from, ref int damage )
|
|
{
|
|
}
|
|
|
|
public virtual void AlterSpellDamageTo( Mobile to, ref int damage )
|
|
{
|
|
}
|
|
|
|
public virtual void AlterMeleeDamageFrom( Mobile from, ref int damage )
|
|
{
|
|
}
|
|
|
|
public virtual void AlterMeleeDamageTo( Mobile to, ref int damage )
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
public virtual void CheckReflect( Mobile caster, ref bool reflect )
|
|
{
|
|
}
|
|
|
|
public virtual void OnCarve( Mobile from, Corpse corpse, Item with )
|
|
{
|
|
int feathers = Feathers;
|
|
int wool = Wool;
|
|
int meat = Meat;
|
|
int hides = Hides;
|
|
|
|
if ( (feathers == 0 && wool == 0 && meat == 0 && hides == 0) || Summoned || IsBonded )
|
|
{
|
|
from.SendLocalizedMessage( 500485 ); // You see nothing useful to carve from the corpse.
|
|
}
|
|
else
|
|
{
|
|
new Blood( 0x122D ).MoveToWorld( corpse.Location, corpse.Map );
|
|
|
|
if ( feathers != 0 )
|
|
{
|
|
corpse.DropItem( new Feather( feathers ) );
|
|
from.SendLocalizedMessage( 500479 ); // You pluck the bird. The feathers are now on the corpse.
|
|
}
|
|
|
|
if ( wool != 0 )
|
|
{
|
|
corpse.DropItem( new TaintedWool( wool ) );
|
|
from.SendLocalizedMessage( 500483 ); // You shear it, and the wool is now on the corpse.
|
|
}
|
|
|
|
if ( meat != 0 )
|
|
{
|
|
if ( MeatType == MeatType.Ribs )
|
|
corpse.DropItem( new RawRibs( meat ) );
|
|
else if ( MeatType == MeatType.BigBird )
|
|
{
|
|
corpse.DropItem( new RawChickenLeg( 1 ) ); meat--;
|
|
if ( meat > 0 ){ corpse.DropItem( new RawChickenLeg( 1 ) ); meat--; }
|
|
if ( meat > 0 ){ corpse.DropItem( new RawBird( meat ) ); }
|
|
}
|
|
else if ( MeatType == MeatType.Bird )
|
|
corpse.DropItem( new RawBird( meat ) );
|
|
else if ( MeatType == MeatType.Ham )
|
|
{
|
|
int bacon = Utility.RandomMinMax( 1, meat );
|
|
if ( Utility.RandomBool() ){ bacon = 0; }
|
|
if ( bacon > 0 ){ corpse.DropItem( new RawSlabOfBacon( bacon ) ); meat = meat - bacon; }
|
|
if ( meat > 0 ){ corpse.DropItem( new RawHam( meat ) ); }
|
|
}
|
|
else if ( MeatType == MeatType.Fish )
|
|
corpse.DropItem( new RawFishSteak( meat ) );
|
|
else if ( MeatType == MeatType.Lamb )
|
|
{
|
|
corpse.DropItem( new RawLambLeg( 1 ) ); meat--;
|
|
if ( meat > 0 ){ corpse.DropItem( new RawLambLeg( 1 ) ); meat--; }
|
|
if ( meat > 0 ){ corpse.DropItem( new RawLambLeg( 1 ) ); meat--; }
|
|
if ( meat > 0 ){ corpse.DropItem( new RawLambLeg( 1 ) ); meat--; }
|
|
if ( meat > 0 ){ corpse.DropItem( new RawRibs( meat ) ); }
|
|
}
|
|
|
|
from.SendLocalizedMessage( 500467 ); // You carve some meat, which remains on the corpse.
|
|
}
|
|
|
|
if ( hides != 0 )
|
|
{
|
|
corpse.DropItem( new Hides( hides ) );
|
|
from.SendLocalizedMessage( 500471 ); // You skin it, and the hides are now in the corpse.
|
|
}
|
|
|
|
corpse.Carved = true;
|
|
|
|
if ( corpse.IsCriminalAction( from ) )
|
|
from.CriminalAction( true );
|
|
}
|
|
}
|
|
|
|
public const int DefaultRangePerception = 16;
|
|
public const int OldRangePerception = 10;
|
|
|
|
public BaseCreature(AIType ai,
|
|
FightMode mode,
|
|
int iRangePerception,
|
|
int iRangeFight,
|
|
double dActiveSpeed,
|
|
double dPassiveSpeed)
|
|
{
|
|
if ( iRangePerception == OldRangePerception )
|
|
iRangePerception = DefaultRangePerception;
|
|
|
|
m_CurrentAI = ai;
|
|
m_DefaultAI = ai;
|
|
|
|
m_iRangePerception = iRangePerception;
|
|
m_iRangeFight = iRangeFight;
|
|
|
|
m_FightMode = mode;
|
|
|
|
m_iTeam = 0;
|
|
|
|
SpeedInfo.GetSpeeds( this, ref dActiveSpeed, ref dPassiveSpeed );
|
|
|
|
m_dActiveSpeed = dActiveSpeed;
|
|
m_dPassiveSpeed = dPassiveSpeed;
|
|
m_dCurrentSpeed = dPassiveSpeed;
|
|
|
|
m_bDebugAI = false;
|
|
|
|
m_arSpellAttack = new List<Type>();
|
|
m_arSpellDefense = new List<Type>();
|
|
|
|
m_bControlled = false;
|
|
m_ControlMaster = null;
|
|
m_ControlTarget = null;
|
|
m_ControlOrder = OrderType.None;
|
|
|
|
m_Owners = new List<Mobile>();
|
|
|
|
m_NextReacquireTime = DateTime.Now + ReacquireDelay;
|
|
|
|
ChangeAIType(AI);
|
|
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null )
|
|
speechType.OnConstruct( this );
|
|
|
|
GenerateLoot( true );
|
|
}
|
|
|
|
public override void OnAfterSpawn()
|
|
{
|
|
if ( this.Region.IsPartOf( "Zoo" ) )
|
|
{
|
|
this.Fame = 0;
|
|
this.Karma = 0;
|
|
this.Invulnerable = true;
|
|
AI = AIType.AI_Animal;
|
|
}
|
|
|
|
if ( m_SeaCreature && Utility.RandomMinMax( 1, 1000 ) <= MyLevel( this ) && (SlayerGroup.GetEntryByName( SlayerName.SeaSlaughter )).Slays(this) )
|
|
PackItem( new SpecialFishingNet() );
|
|
|
|
if ( m_SeaCreature && Utility.RandomMinMax( 1, 1000 ) <= MyLevel( this ) && (SlayerGroup.GetEntryByName( SlayerName.SeaSlaughter )).Slays(this) )
|
|
{
|
|
int pwr = Utility.RandomMinMax( 1, MyLevel( this ) );
|
|
int lvl = 1;
|
|
|
|
if ( pwr > 750 )
|
|
lvl = 4;
|
|
else if ( pwr > 500 )
|
|
lvl = 3;
|
|
else if ( pwr > 250 )
|
|
lvl = 2;
|
|
|
|
PackItem( new MessageInABottle( lvl ) );
|
|
}
|
|
|
|
if ( Map == Map.Underworld && Clan != Clan.Citizen )
|
|
Clan = Clan.Monster;
|
|
|
|
if ( Server.Misc.Settings.MonstersSearch() )
|
|
{
|
|
double searching = (double)(MyLevel( this ) + 10);
|
|
if ( this.Skills[SkillName.Searching].Value > 10 ){} // DON'T MODIFY THOSE THAT ALREADY HAVE THE SKILL
|
|
else { this.SetSkill( SkillName.Searching, searching ); }
|
|
}
|
|
|
|
if ( !Server.Misc.Settings.MonstersConcentrate() && !(this is BaseVendor) )
|
|
{
|
|
this.SetSkill( SkillName.Concentration, 0.0 );
|
|
}
|
|
|
|
WontWalk = CantWalk;
|
|
WillSwim = CanSwim;
|
|
|
|
base.OnAfterSpawn();
|
|
}
|
|
|
|
public BaseCreature( Serial serial ) : base( serial )
|
|
{
|
|
m_arSpellAttack = new List<Type>();
|
|
m_arSpellDefense = new List<Type>();
|
|
|
|
m_bDebugAI = false;
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 17 ); // version
|
|
|
|
writer.Write( (int)m_CurrentAI );
|
|
writer.Write( (int)m_DefaultAI );
|
|
writer.Write( (int)m_iRangePerception );
|
|
writer.Write( (int)m_iRangeFight );
|
|
writer.Write( (int)m_iTeam );
|
|
writer.Write( (double)m_dActiveSpeed );
|
|
writer.Write( (double)m_dPassiveSpeed );
|
|
writer.Write( (double)m_dCurrentSpeed );
|
|
writer.Write( (int) m_pHome.X );
|
|
writer.Write( (int) m_pHome.Y );
|
|
writer.Write( (int) m_pHome.Z );
|
|
writer.Write( (int) m_iRangeHome );
|
|
|
|
int i=0;
|
|
|
|
writer.Write( (int) m_arSpellAttack.Count );
|
|
for ( i=0; i< m_arSpellAttack.Count; i++ )
|
|
{
|
|
writer.Write( m_arSpellAttack[i].ToString() );
|
|
}
|
|
|
|
writer.Write( (int) m_arSpellDefense.Count );
|
|
for ( i=0; i< m_arSpellDefense.Count; i++ )
|
|
{
|
|
writer.Write( m_arSpellDefense[i].ToString() );
|
|
}
|
|
|
|
writer.Write( (int) m_FightMode );
|
|
writer.Write( (int) m_Clan );
|
|
writer.Write( (bool) m_bControlled );
|
|
writer.Write( (Mobile) m_ControlMaster );
|
|
writer.Write( (Mobile) m_ControlTarget );
|
|
writer.Write( (int) m_ControlOrder );
|
|
writer.Write( (bool) m_bSummoned );
|
|
|
|
if ( m_bSummoned )
|
|
writer.WriteDeltaTime( m_SummonEnd );
|
|
|
|
writer.Write( (int) m_iControlSlots );
|
|
writer.Write( m_CurrentWayPoint );
|
|
writer.Write( m_SummonMaster );
|
|
writer.Write( (int) m_HitsMax );
|
|
writer.Write( (int) m_StamMax );
|
|
writer.Write( (int) m_ManaMax );
|
|
writer.Write( (int) m_DamageMin );
|
|
writer.Write( (int) m_DamageMax );
|
|
writer.Write( m_Owners, true );
|
|
writer.Write( (bool) m_IsDeadPet );
|
|
writer.Write( (bool) m_IsBonded );
|
|
writer.Write( (DateTime) m_BondingBegin );
|
|
writer.Write( (DateTime) m_OwnerAbandonTime );
|
|
writer.Write( (bool) m_HasGeneratedLoot );
|
|
writer.Write( (bool) ( m_Friends != null && m_Friends.Count > 0 ) );
|
|
|
|
if ( m_Friends != null && m_Friends.Count > 0 )
|
|
writer.Write( m_Friends, true );
|
|
|
|
writer.Write( (bool)m_RemoveIfUntamed );
|
|
writer.Write( (int)m_RemoveStep );
|
|
|
|
if ( IsStabled || ( Controlled && ControlMaster != null ) )
|
|
writer.Write( TimeSpan.Zero );
|
|
else
|
|
writer.Write( DeleteTimeLeft );
|
|
|
|
writer.Write( (bool) m_SeaCreature );
|
|
}
|
|
|
|
private static double[] m_StandardActiveSpeeds = new double[]
|
|
{
|
|
0.175, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.8
|
|
};
|
|
|
|
private static double[] m_StandardPassiveSpeeds = new double[]
|
|
{
|
|
0.350, 0.2, 0.4, 0.5, 0.6, 0.8, 1.0, 1.2, 1.6, 2.0
|
|
};
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
m_CurrentAI = (AIType)reader.ReadInt();
|
|
m_DefaultAI = (AIType)reader.ReadInt();
|
|
m_iRangePerception = reader.ReadInt();
|
|
m_iRangeFight = reader.ReadInt();
|
|
m_iTeam = reader.ReadInt();
|
|
m_dActiveSpeed = reader.ReadDouble();
|
|
m_dPassiveSpeed = reader.ReadDouble();
|
|
m_dCurrentSpeed = reader.ReadDouble();
|
|
|
|
if ( m_iRangePerception == OldRangePerception )
|
|
m_iRangePerception = DefaultRangePerception;
|
|
|
|
m_pHome.X = reader.ReadInt();
|
|
m_pHome.Y = reader.ReadInt();
|
|
m_pHome.Z = reader.ReadInt();
|
|
m_iRangeHome = reader.ReadInt();
|
|
|
|
int i, iCount;
|
|
|
|
iCount = reader.ReadInt();
|
|
for ( i=0; i< iCount; i++ )
|
|
{
|
|
string str = reader.ReadString();
|
|
Type type = Type.GetType( str );
|
|
|
|
if ( type != null )
|
|
{
|
|
m_arSpellAttack.Add( type );
|
|
}
|
|
}
|
|
|
|
iCount = reader.ReadInt();
|
|
for ( i=0; i< iCount; i++ )
|
|
{
|
|
string str = reader.ReadString();
|
|
Type type = Type.GetType( str );
|
|
|
|
if ( type != null )
|
|
{
|
|
m_arSpellDefense.Add( type );
|
|
}
|
|
}
|
|
|
|
m_FightMode = ( FightMode )reader.ReadInt();
|
|
m_Clan = ( Clan )reader.ReadInt();
|
|
m_bControlled = reader.ReadBool();
|
|
m_ControlMaster = reader.ReadMobile();
|
|
m_ControlTarget = reader.ReadMobile();
|
|
m_ControlOrder = (OrderType) reader.ReadInt();
|
|
m_bSummoned = reader.ReadBool();
|
|
|
|
if ( m_bSummoned )
|
|
{
|
|
m_SummonEnd = reader.ReadDeltaTime();
|
|
new UnsummonTimer( m_ControlMaster, this, m_SummonEnd - DateTime.Now ).Start();
|
|
}
|
|
|
|
m_iControlSlots = reader.ReadInt();
|
|
m_CurrentWayPoint = reader.ReadItem() as WayPoint;
|
|
m_SummonMaster = reader.ReadMobile();
|
|
m_HitsMax = reader.ReadInt();
|
|
m_StamMax = reader.ReadInt();
|
|
m_ManaMax = reader.ReadInt();
|
|
m_DamageMin = reader.ReadInt();
|
|
m_DamageMax = reader.ReadInt();
|
|
m_Owners = reader.ReadStrongMobileList();
|
|
m_IsDeadPet = reader.ReadBool();
|
|
m_IsBonded = reader.ReadBool();
|
|
m_BondingBegin = reader.ReadDateTime();
|
|
m_OwnerAbandonTime = reader.ReadDateTime();
|
|
m_HasGeneratedLoot = reader.ReadBool();
|
|
|
|
if ( reader.ReadBool() )
|
|
m_Friends = reader.ReadStrongMobileList();
|
|
|
|
double activeSpeed = m_dActiveSpeed;
|
|
double passiveSpeed = m_dPassiveSpeed;
|
|
|
|
SpeedInfo.GetSpeeds( this, ref activeSpeed, ref passiveSpeed );
|
|
|
|
bool isStandardActive = false;
|
|
for ( int i2 = 0; !isStandardActive && i2 < m_StandardActiveSpeeds.Length; ++i2 )
|
|
isStandardActive = ( m_dActiveSpeed == m_StandardActiveSpeeds[i2] );
|
|
|
|
bool isStandardPassive = false;
|
|
for ( int i3 = 0; !isStandardPassive && i3 < m_StandardPassiveSpeeds.Length; ++i3 )
|
|
isStandardPassive = ( m_dPassiveSpeed == m_StandardPassiveSpeeds[i3] );
|
|
|
|
if ( isStandardActive && m_dCurrentSpeed == m_dActiveSpeed )
|
|
m_dCurrentSpeed = activeSpeed;
|
|
else if ( isStandardPassive && m_dCurrentSpeed == m_dPassiveSpeed )
|
|
m_dCurrentSpeed = passiveSpeed;
|
|
|
|
if ( isStandardActive )
|
|
m_dActiveSpeed = activeSpeed;
|
|
|
|
if ( isStandardPassive )
|
|
m_dPassiveSpeed = passiveSpeed;
|
|
|
|
m_RemoveIfUntamed = reader.ReadBool();
|
|
m_RemoveStep = reader.ReadInt();
|
|
|
|
TimeSpan deleteTime = reader.ReadTimeSpan();
|
|
|
|
if ( deleteTime > TimeSpan.Zero || LastOwner != null && !Controlled && !IsStabled )
|
|
{
|
|
if ( deleteTime == TimeSpan.Zero )
|
|
deleteTime = TimeSpan.FromDays( 3.0 );
|
|
|
|
m_DeleteTimer = new DeleteTimer( this, deleteTime );
|
|
m_DeleteTimer.Start();
|
|
}
|
|
|
|
m_SeaCreature = reader.ReadBool();
|
|
|
|
CheckStatTimers();
|
|
|
|
ChangeAIType(m_CurrentAI);
|
|
|
|
AddFollowers();
|
|
}
|
|
|
|
public virtual bool IsHumanInTown()
|
|
{
|
|
return ( Body.IsHuman && Region.IsPartOf( typeof( Regions.TownRegion ) ) );
|
|
}
|
|
|
|
public virtual bool CheckGold( Mobile from, Item dropped )
|
|
{
|
|
if ( dropped is Gold )
|
|
return OnGoldGiven( from, (Gold)dropped );
|
|
|
|
return false;
|
|
}
|
|
|
|
public virtual bool OnGoldGiven( Mobile from, Gold dropped )
|
|
{
|
|
if ( CheckTeachingMatch( from ) )
|
|
{
|
|
if ( Teach( m_Teaching, from, dropped.Amount, true ) )
|
|
{
|
|
dropped.Delete();
|
|
return true;
|
|
}
|
|
}
|
|
else if ( IsHumanInTown() )
|
|
{
|
|
Direction = GetDirectionTo( from );
|
|
|
|
int oldSpeechHue = this.SpeechHue;
|
|
|
|
this.SpeechHue = 0x23F;
|
|
SayTo( from, "Thou art giving me gold?" );
|
|
|
|
if ( dropped.Amount >= 400 )
|
|
SayTo( from, "'Tis a noble gift." );
|
|
else
|
|
SayTo( from, "Money is always welcome." );
|
|
|
|
this.SpeechHue = 0x3B2;
|
|
SayTo( from, 501548 ); // I thank thee.
|
|
|
|
this.SpeechHue = oldSpeechHue;
|
|
|
|
dropped.Delete();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override bool ShouldCheckStatTimers{ get{ return false; } }
|
|
|
|
public virtual bool OverrideBondingReqs()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#region OnAction[...]
|
|
|
|
public virtual void OnActionWander()
|
|
{
|
|
}
|
|
|
|
public virtual void OnActionCombat()
|
|
{
|
|
}
|
|
|
|
public virtual void OnActionGuard()
|
|
{
|
|
}
|
|
|
|
public virtual void OnActionFlee()
|
|
{
|
|
}
|
|
|
|
public virtual void OnActionInteract()
|
|
{
|
|
}
|
|
|
|
public virtual void OnActionBackoff()
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override bool OnDragDrop( Mobile from, Item dropped )
|
|
{
|
|
if ( CheckGold( from, dropped ) )
|
|
return true;
|
|
|
|
return base.OnDragDrop( from, dropped );
|
|
}
|
|
|
|
protected virtual BaseAI ForcedAI { get { return null; } }
|
|
|
|
public void ChangeAIType( AIType NewAI )
|
|
{
|
|
if ( m_AI != null )
|
|
m_AI.m_Timer.Stop();
|
|
|
|
if( ForcedAI != null )
|
|
{
|
|
m_AI = ForcedAI;
|
|
return;
|
|
}
|
|
|
|
m_AI = null;
|
|
|
|
switch ( NewAI )
|
|
{
|
|
case AIType.AI_Melee:
|
|
m_AI = new MeleeAI(this);
|
|
break;
|
|
case AIType.AI_Animal:
|
|
m_AI = new AnimalAI(this);
|
|
break;
|
|
case AIType.AI_Berserk:
|
|
m_AI = new BerserkAI(this);
|
|
break;
|
|
case AIType.AI_Archer:
|
|
m_AI = new ArcherAI(this);
|
|
break;
|
|
case AIType.AI_Healer:
|
|
m_AI = new HealerAI(this);
|
|
break;
|
|
case AIType.AI_Vendor:
|
|
m_AI = new VendorAI(this);
|
|
break;
|
|
case AIType.AI_Mage:
|
|
m_AI = new MageAI(this);
|
|
break;
|
|
case AIType.AI_Predator:
|
|
m_AI = new MeleeAI(this);
|
|
break;
|
|
case AIType.AI_Thief:
|
|
m_AI = new ThiefAI(this);
|
|
break;
|
|
case AIType.AI_Citizen:
|
|
m_AI = new CitizenAI(this);
|
|
break;
|
|
case AIType.AI_Timid:
|
|
m_AI = new TimidAI(this);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void ChangeAIToDefault()
|
|
{
|
|
ChangeAIType(m_DefaultAI);
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public AIType AI
|
|
{
|
|
get
|
|
{
|
|
return m_CurrentAI;
|
|
}
|
|
set
|
|
{
|
|
m_CurrentAI = value;
|
|
|
|
if (m_CurrentAI == AIType.AI_Use_Default)
|
|
{
|
|
m_CurrentAI = m_DefaultAI;
|
|
}
|
|
|
|
ChangeAIType(m_CurrentAI);
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.Administrator )]
|
|
public bool Debug
|
|
{
|
|
get
|
|
{
|
|
return m_bDebugAI;
|
|
}
|
|
set
|
|
{
|
|
m_bDebugAI = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int Team
|
|
{
|
|
get
|
|
{
|
|
return m_iTeam;
|
|
}
|
|
set
|
|
{
|
|
m_iTeam = value;
|
|
|
|
OnTeamChange();
|
|
}
|
|
}
|
|
|
|
public virtual void OnTeamChange()
|
|
{
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile FocusMob
|
|
{
|
|
get
|
|
{
|
|
return m_FocusMob;
|
|
}
|
|
set
|
|
{
|
|
m_FocusMob = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public FightMode FightMode
|
|
{
|
|
get
|
|
{
|
|
return m_FightMode;
|
|
}
|
|
set
|
|
{
|
|
m_FightMode = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Clan Clan
|
|
{
|
|
get
|
|
{
|
|
return m_Clan;
|
|
}
|
|
set
|
|
{
|
|
m_Clan = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int RangePerception
|
|
{
|
|
get
|
|
{
|
|
return m_iRangePerception;
|
|
}
|
|
set
|
|
{
|
|
m_iRangePerception = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int RangeFight
|
|
{
|
|
get
|
|
{
|
|
return m_iRangeFight;
|
|
}
|
|
set
|
|
{
|
|
m_iRangeFight = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int RangeHome
|
|
{
|
|
get
|
|
{
|
|
return m_iRangeHome;
|
|
}
|
|
set
|
|
{
|
|
m_iRangeHome = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public double ActiveSpeed
|
|
{
|
|
get
|
|
{
|
|
return m_dActiveSpeed;
|
|
}
|
|
set
|
|
{
|
|
m_dActiveSpeed = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public double PassiveSpeed
|
|
{
|
|
get
|
|
{
|
|
return m_dPassiveSpeed;
|
|
}
|
|
set
|
|
{
|
|
m_dPassiveSpeed = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public double CurrentSpeed
|
|
{
|
|
get
|
|
{
|
|
if ( m_TargetLocation != null )
|
|
return 0.3;
|
|
|
|
return m_dCurrentSpeed;
|
|
}
|
|
set
|
|
{
|
|
if ( m_dCurrentSpeed != value )
|
|
{
|
|
m_dCurrentSpeed = value;
|
|
|
|
if (m_AI != null)
|
|
m_AI.OnCurrentSpeedChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Point3D Home
|
|
{
|
|
get
|
|
{
|
|
return m_pHome;
|
|
}
|
|
set
|
|
{
|
|
m_pHome = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool Controlled
|
|
{
|
|
get
|
|
{
|
|
return m_bControlled;
|
|
}
|
|
set
|
|
{
|
|
if ( m_bControlled == value )
|
|
return;
|
|
|
|
m_bControlled = value;
|
|
Delta( MobileDelta.Noto );
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public override void RevealingAction()
|
|
{
|
|
Spells.Sixth.InvisibilitySpell.RemoveTimer( this );
|
|
|
|
base.RevealingAction();
|
|
}
|
|
|
|
public void RemoveFollowers()
|
|
{
|
|
if ( m_ControlMaster != null )
|
|
{
|
|
m_ControlMaster.Followers -= ControlSlots;
|
|
if( m_ControlMaster is PlayerMobile )
|
|
{
|
|
((PlayerMobile)m_ControlMaster).AllFollowers.Remove( this );
|
|
}
|
|
}
|
|
else if ( m_SummonMaster != null )
|
|
{
|
|
m_SummonMaster.Followers -= ControlSlots;
|
|
if( m_SummonMaster is PlayerMobile )
|
|
{
|
|
((PlayerMobile)m_SummonMaster).AllFollowers.Remove( this );
|
|
}
|
|
}
|
|
|
|
if ( m_ControlMaster != null && m_ControlMaster.Followers < 0 )
|
|
m_ControlMaster.Followers = 0;
|
|
|
|
if ( m_SummonMaster != null && m_SummonMaster.Followers < 0 )
|
|
m_SummonMaster.Followers = 0;
|
|
}
|
|
|
|
public void AddFollowers()
|
|
{
|
|
if ( m_ControlMaster != null )
|
|
{
|
|
m_ControlMaster.Followers += ControlSlots;
|
|
if( m_ControlMaster is PlayerMobile )
|
|
{
|
|
((PlayerMobile)m_ControlMaster).AllFollowers.Add( this );
|
|
}
|
|
}
|
|
else if ( m_SummonMaster != null )
|
|
{
|
|
m_SummonMaster.Followers += ControlSlots;
|
|
if( m_SummonMaster is PlayerMobile )
|
|
{
|
|
((PlayerMobile)m_SummonMaster).AllFollowers.Add( this );
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile ControlMaster
|
|
{
|
|
get
|
|
{
|
|
return m_ControlMaster;
|
|
}
|
|
set
|
|
{
|
|
if ( m_ControlMaster == value || this == value )
|
|
return;
|
|
|
|
RemoveFollowers();
|
|
m_ControlMaster = value;
|
|
AddFollowers();
|
|
if ( m_ControlMaster != null )
|
|
StopDeleteTimer();
|
|
|
|
Delta( MobileDelta.Noto );
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile SummonMaster
|
|
{
|
|
get
|
|
{
|
|
return m_SummonMaster;
|
|
}
|
|
set
|
|
{
|
|
if ( m_SummonMaster == value || this == value )
|
|
return;
|
|
|
|
RemoveFollowers();
|
|
m_SummonMaster = value;
|
|
AddFollowers();
|
|
|
|
Delta( MobileDelta.Noto );
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile ControlTarget
|
|
{
|
|
get
|
|
{
|
|
return m_ControlTarget;
|
|
}
|
|
set
|
|
{
|
|
m_ControlTarget = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public OrderType ControlOrder
|
|
{
|
|
get
|
|
{
|
|
return m_ControlOrder;
|
|
}
|
|
set
|
|
{
|
|
m_ControlOrder = value;
|
|
|
|
if ( m_AI != null )
|
|
m_AI.OnCurrentOrderChanged();
|
|
|
|
InvalidateProperties();
|
|
|
|
if ( m_ControlMaster != null )
|
|
m_ControlMaster.InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool BardProvoked
|
|
{
|
|
get
|
|
{
|
|
return m_bBardProvoked;
|
|
}
|
|
set
|
|
{
|
|
m_bBardProvoked = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool BardPacified
|
|
{
|
|
get
|
|
{
|
|
return m_bBardPacified;
|
|
}
|
|
set
|
|
{
|
|
m_bBardPacified = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile BardMaster
|
|
{
|
|
get
|
|
{
|
|
return m_bBardMaster;
|
|
}
|
|
set
|
|
{
|
|
m_bBardMaster = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile BardTarget
|
|
{
|
|
get
|
|
{
|
|
return m_bBardTarget;
|
|
}
|
|
set
|
|
{
|
|
m_bBardTarget = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public DateTime BardEndTime
|
|
{
|
|
get
|
|
{
|
|
return m_timeBardEnd;
|
|
}
|
|
set
|
|
{
|
|
m_timeBardEnd = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.Administrator )]
|
|
public bool Summoned
|
|
{
|
|
get
|
|
{
|
|
return m_bSummoned;
|
|
}
|
|
set
|
|
{
|
|
if ( m_bSummoned == value )
|
|
return;
|
|
|
|
m_NextReacquireTime = DateTime.Now;
|
|
|
|
m_bSummoned = value;
|
|
Delta( MobileDelta.Noto );
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.Administrator )]
|
|
public int ControlSlots
|
|
{
|
|
get
|
|
{
|
|
return m_iControlSlots;
|
|
}
|
|
set
|
|
{
|
|
m_iControlSlots = value;
|
|
}
|
|
}
|
|
|
|
public virtual bool NoHouseRestrictions{ get{ return false; } }
|
|
public virtual bool IsHouseSummonable{ get{ return false; } }
|
|
|
|
#region Corpse Resources
|
|
public virtual int Feathers{ get{ return 0; } }
|
|
public virtual int Wool{ get{ return 0; } }
|
|
|
|
public virtual MeatType MeatType{ get{ return MeatType.Ribs; } }
|
|
public virtual int Meat{ get{ return 0; } }
|
|
|
|
public virtual int Hides{ get{ return 0; } }
|
|
#endregion
|
|
|
|
public virtual bool AutoDispel{ get{ return false; } }
|
|
public virtual double AutoDispelChance{ get { return 1.0; } }
|
|
|
|
public virtual bool CanRummageCorpses{ get{ return false; } }
|
|
|
|
public bool CanCastDispel()
|
|
{
|
|
if ( Mana < 20 )
|
|
return false;
|
|
|
|
if ( this.Skills[SkillName.Magery].Value < 52.0 )
|
|
return false;
|
|
|
|
if ( this.Skills[SkillName.Magery].Value < Utility.RandomMinMax( 1, 100 ) )
|
|
return false;
|
|
|
|
Mana = Mana - 20;
|
|
return true;
|
|
}
|
|
|
|
public virtual void OnGotMeleeAttack( Mobile attacker )
|
|
{
|
|
if ( AutoDispel && CanCastDispel() && attacker is BaseCreature && ((BaseCreature)attacker).IsDispellable && AutoDispelChance > Utility.RandomDouble() )
|
|
Dispel( attacker );
|
|
}
|
|
|
|
public virtual void Dispel( Mobile m )
|
|
{
|
|
Effects.SendLocationParticles( EffectItem.Create( m.Location, m.Map, EffectItem.DefaultDuration ), 0x3728, 8, 20, 5042 );
|
|
Effects.PlaySound( m, m.Map, 0x201 );
|
|
|
|
m.Delete();
|
|
}
|
|
|
|
public virtual bool DeleteOnRelease{ get{ return m_bSummoned; } }
|
|
|
|
public virtual void OnGaveMeleeAttack( Mobile defender )
|
|
{
|
|
Poison p = HitPoison;
|
|
|
|
if ( p != null && HitPoisonChance >= Utility.RandomDouble() ) {
|
|
defender.ApplyPoison( this, p );
|
|
if ( this.Controlled )
|
|
this.CheckSkill(SkillName.Poisoning, 0, this.Skills[SkillName.Poisoning].Cap);
|
|
}
|
|
|
|
if( AutoDispel && CanCastDispel() && defender is BaseCreature && ((BaseCreature)defender).IsDispellable && AutoDispelChance > Utility.RandomDouble() )
|
|
Dispel( defender );
|
|
}
|
|
|
|
public override void OnAfterDelete()
|
|
{
|
|
if ( m_AI != null )
|
|
{
|
|
if ( m_AI.m_Timer != null )
|
|
m_AI.m_Timer.Stop();
|
|
|
|
m_AI = null;
|
|
}
|
|
|
|
if ( m_DeleteTimer != null )
|
|
{
|
|
m_DeleteTimer.Stop();
|
|
m_DeleteTimer = null;
|
|
}
|
|
|
|
FocusMob = null;
|
|
|
|
base.OnAfterDelete();
|
|
}
|
|
|
|
public void DebugSay( string text )
|
|
{
|
|
if ( m_bDebugAI )
|
|
this.PublicOverheadMessage( MessageType.Regular, 41, false, text );
|
|
}
|
|
|
|
public void DebugSay( string format, params object[] args )
|
|
{
|
|
if ( m_bDebugAI )
|
|
this.PublicOverheadMessage( MessageType.Regular, 41, false, String.Format( format, args ) );
|
|
}
|
|
|
|
/*
|
|
* This function can be overriden.. so a "Strongest" mobile, can have a different definition depending
|
|
* on who check for value
|
|
* -Could add a FightMode.Prefered
|
|
*
|
|
*/
|
|
|
|
public virtual double GetFightModeRanking( Mobile m, FightMode acqType, bool bPlayerOnly )
|
|
{
|
|
if ( ( bPlayerOnly && m.Player ) || !bPlayerOnly )
|
|
{
|
|
switch( acqType )
|
|
{
|
|
case FightMode.Strongest :
|
|
return (m.Skills[SkillName.Tactics].Value + m.Str); //returns strongest mobile
|
|
|
|
case FightMode.Weakest :
|
|
return -m.Hits; // returns weakest mobile
|
|
|
|
default :
|
|
return -GetDistanceToSqrt( m ); // returns closest mobile
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return double.MinValue;
|
|
}
|
|
}
|
|
|
|
// Turn, - for left, + for right
|
|
// Basic for now, needs work
|
|
public virtual void Turn(int iTurnSteps)
|
|
{
|
|
int v = (int)Direction;
|
|
|
|
Direction = (Direction)((((v & 0x7) + iTurnSteps) & 0x7) | (v & 0x80));
|
|
}
|
|
|
|
public virtual void TurnInternal(int iTurnSteps)
|
|
{
|
|
int v = (int)Direction;
|
|
|
|
SetDirection( (Direction)((((v & 0x7) + iTurnSteps) & 0x7) | (v & 0x80)) );
|
|
}
|
|
|
|
public bool IsHurt()
|
|
{
|
|
return ( Hits != HitsMax );
|
|
}
|
|
|
|
public double GetHomeDistance()
|
|
{
|
|
return GetDistanceToSqrt( m_pHome );
|
|
}
|
|
|
|
public virtual int GetTeamSize(int iRange)
|
|
{
|
|
int iCount = 0;
|
|
|
|
foreach ( Mobile m in this.GetMobilesInRange( iRange ) )
|
|
{
|
|
if (m is BaseCreature)
|
|
{
|
|
if ( ((BaseCreature)m).Team == Team )
|
|
{
|
|
if ( !m.Deleted )
|
|
{
|
|
if ( m != this )
|
|
{
|
|
if ( CanSee( m ) )
|
|
{
|
|
iCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return iCount;
|
|
}
|
|
|
|
#region Teaching
|
|
public virtual bool CanTeach{ get{ return false; } }
|
|
|
|
public virtual bool CheckTeach( SkillName skill, Mobile from )
|
|
{
|
|
if ( !CanTeach )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public enum TeachResult
|
|
{
|
|
Success,
|
|
Failure,
|
|
KnowsMoreThanMe,
|
|
KnowsWhatIKnow,
|
|
SkillNotRaisable,
|
|
NotEnoughFreePoints
|
|
}
|
|
|
|
public virtual TeachResult CheckTeachSkills( SkillName skill, Mobile m, int maxPointsToLearn, ref int pointsToLearn, bool doTeach )
|
|
{
|
|
if ( !CheckTeach( skill, m ) || !m.CheckAlive() )
|
|
return TeachResult.Failure;
|
|
|
|
Skill ourSkill = Skills[skill];
|
|
Skill theirSkill = m.Skills[skill];
|
|
|
|
if ( ourSkill == null || theirSkill == null )
|
|
return TeachResult.Failure;
|
|
|
|
int baseToSet = ourSkill.BaseFixedPoint / 3;
|
|
|
|
if ( baseToSet > 420 )
|
|
baseToSet = 420;
|
|
else if ( baseToSet < 200 )
|
|
return TeachResult.Failure;
|
|
|
|
if ( baseToSet > theirSkill.CapFixedPoint )
|
|
baseToSet = theirSkill.CapFixedPoint;
|
|
|
|
pointsToLearn = baseToSet - theirSkill.BaseFixedPoint;
|
|
|
|
if ( maxPointsToLearn > 0 && pointsToLearn > maxPointsToLearn )
|
|
{
|
|
pointsToLearn = maxPointsToLearn;
|
|
baseToSet = theirSkill.BaseFixedPoint + pointsToLearn;
|
|
}
|
|
|
|
if ( pointsToLearn < 0 )
|
|
return TeachResult.KnowsMoreThanMe;
|
|
|
|
if ( pointsToLearn == 0 )
|
|
return TeachResult.KnowsWhatIKnow;
|
|
|
|
if ( theirSkill.Lock != SkillLock.Up )
|
|
return TeachResult.SkillNotRaisable;
|
|
|
|
int freePoints = m.Skills.Cap - m.Skills.Total;
|
|
int freeablePoints = 0;
|
|
|
|
if ( freePoints < 0 )
|
|
freePoints = 0;
|
|
|
|
for ( int i = 0; (freePoints + freeablePoints) < pointsToLearn && i < m.Skills.Length; ++i )
|
|
{
|
|
Skill sk = m.Skills[i];
|
|
|
|
if ( sk == theirSkill || sk.Lock != SkillLock.Down )
|
|
continue;
|
|
|
|
freeablePoints += sk.BaseFixedPoint;
|
|
}
|
|
|
|
if ( (freePoints + freeablePoints) == 0 )
|
|
return TeachResult.NotEnoughFreePoints;
|
|
|
|
if ( (freePoints + freeablePoints) < pointsToLearn )
|
|
{
|
|
pointsToLearn = freePoints + freeablePoints;
|
|
baseToSet = theirSkill.BaseFixedPoint + pointsToLearn;
|
|
}
|
|
|
|
if ( doTeach )
|
|
{
|
|
int need = pointsToLearn - freePoints;
|
|
|
|
for ( int i = 0; need > 0 && i < m.Skills.Length; ++i )
|
|
{
|
|
Skill sk = m.Skills[i];
|
|
|
|
if ( sk == theirSkill || sk.Lock != SkillLock.Down )
|
|
continue;
|
|
|
|
if ( sk.BaseFixedPoint < need )
|
|
{
|
|
need -= sk.BaseFixedPoint;
|
|
sk.BaseFixedPoint = 0;
|
|
}
|
|
else
|
|
{
|
|
sk.BaseFixedPoint -= need;
|
|
need = 0;
|
|
}
|
|
}
|
|
|
|
/* Sanity check */
|
|
if ( baseToSet > theirSkill.CapFixedPoint || (m.Skills.Total - theirSkill.BaseFixedPoint + baseToSet) > m.Skills.Cap )
|
|
return TeachResult.NotEnoughFreePoints;
|
|
|
|
theirSkill.BaseFixedPoint = baseToSet;
|
|
}
|
|
|
|
return TeachResult.Success;
|
|
}
|
|
|
|
public virtual bool CheckTeachingMatch( Mobile m )
|
|
{
|
|
if ( m_Teaching == (SkillName)(-1) )
|
|
return false;
|
|
|
|
if ( m is PlayerMobile )
|
|
return ( ((PlayerMobile)m).Learning == m_Teaching );
|
|
|
|
return true;
|
|
}
|
|
|
|
private SkillName m_Teaching = (SkillName)(-1);
|
|
|
|
public virtual bool Teach( SkillName skill, Mobile m, int maxPointsToLearn, bool doTeach )
|
|
{
|
|
int pointsToLearn = 0;
|
|
TeachResult res = CheckTeachSkills( skill, m, maxPointsToLearn, ref pointsToLearn, doTeach );
|
|
|
|
switch ( res )
|
|
{
|
|
case TeachResult.KnowsMoreThanMe:
|
|
{
|
|
Say( 501508 ); // I cannot teach thee, for thou knowest more than I!
|
|
break;
|
|
}
|
|
case TeachResult.KnowsWhatIKnow:
|
|
{
|
|
Say( 501509 ); // I cannot teach thee, for thou knowest all I can teach!
|
|
break;
|
|
}
|
|
case TeachResult.NotEnoughFreePoints:
|
|
case TeachResult.SkillNotRaisable:
|
|
{
|
|
// Make sure this skill is marked to raise. If you are near the skill cap (700 points) you may need to lose some points in another skill first.
|
|
m.SendLocalizedMessage( 501510, "", 0x22 );
|
|
break;
|
|
}
|
|
case TeachResult.Success:
|
|
{
|
|
if ( doTeach )
|
|
{
|
|
Say( 501539 ); // Let me show thee something of how this is done.
|
|
m.SendLocalizedMessage( 501540 ); // Your skill level increases.
|
|
|
|
m_Teaching = (SkillName)(-1);
|
|
|
|
if ( m is PlayerMobile )
|
|
((PlayerMobile)m).Learning = (SkillName)(-1);
|
|
}
|
|
else
|
|
{
|
|
// I will teach thee all I know, if paid the amount in full. The price is:
|
|
Say( 1019077, AffixType.Append, String.Format( " {0}", pointsToLearn ), "" );
|
|
Say( 1043108 ); // For less I shall teach thee less.
|
|
|
|
m_Teaching = skill;
|
|
|
|
if ( m is PlayerMobile )
|
|
((PlayerMobile)m).Learning = skill;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void AggressiveAction( Mobile aggressor, bool criminal )
|
|
{
|
|
base.AggressiveAction( aggressor, criminal );
|
|
|
|
if ( this.ControlMaster != null )
|
|
if ( NotorietyHandlers.CheckAggressor( this.ControlMaster.Aggressors, aggressor ) )
|
|
aggressor.Aggressors.Add( AggressorInfo.Create( this, aggressor, true ) );
|
|
|
|
OrderType ct = m_ControlOrder;
|
|
|
|
if ( m_AI != null )
|
|
{
|
|
if( ct != OrderType.Follow && ct != OrderType.Stop )
|
|
{
|
|
m_AI.OnAggressiveAction( aggressor );
|
|
}
|
|
else
|
|
{
|
|
DebugSay( "I'm being attacked but my master told me not to fight." );
|
|
Warmode = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
StopFlee();
|
|
|
|
ForceReacquire();
|
|
|
|
if ( aggressor.ChangingCombatant && (m_bControlled || m_bSummoned) && (ct == OrderType.Come || ( ct == OrderType.Stay ) || ct == OrderType.Stop || ct == OrderType.None || ct == OrderType.Follow) )
|
|
{
|
|
ControlTarget = aggressor;
|
|
ControlOrder = OrderType.Attack;
|
|
}
|
|
else if ( Combatant == null && !m_bBardPacified )
|
|
{
|
|
Warmode = true;
|
|
Combatant = aggressor;
|
|
}
|
|
}
|
|
|
|
public override bool OnMoveOver( Mobile m )
|
|
{
|
|
if ( m is BaseCreature && !((BaseCreature)m).Controlled )
|
|
return ( !Alive || !m.Alive || IsDeadBondedPet || m.IsDeadBondedPet ) || ( Hidden && m.AccessLevel > AccessLevel.Player );
|
|
|
|
return base.OnMoveOver( m );
|
|
}
|
|
|
|
public virtual void AddCustomContextEntries( Mobile from, List<ContextMenuEntry> list )
|
|
{
|
|
}
|
|
|
|
public virtual bool CanDrop { get { return IsBonded; } }
|
|
|
|
public override void GetContextMenuEntries( Mobile from, List<ContextMenuEntry> list )
|
|
{
|
|
base.GetContextMenuEntries( from, list );
|
|
|
|
if ( m_AI != null && Commandable )
|
|
m_AI.GetContextMenuEntries( from, list );
|
|
|
|
AddCustomContextEntries( from, list );
|
|
|
|
if ( CanTeach && from.Alive )
|
|
{
|
|
Skills ourSkills = this.Skills;
|
|
Skills theirSkills = from.Skills;
|
|
|
|
for ( int i = 0; i < ourSkills.Length && i < theirSkills.Length; ++i )
|
|
{
|
|
Skill skill = ourSkills[i];
|
|
Skill theirSkill = theirSkills[i];
|
|
|
|
if ( skill != null && theirSkill != null && skill.Base >= 60.0 && CheckTeach( skill.SkillName, from ) )
|
|
{
|
|
double toTeach = skill.Base / 3.0;
|
|
|
|
if ( toTeach > 42.0 )
|
|
toTeach = 42.0;
|
|
|
|
list.Add( new TeachEntry( (SkillName)i, this, from, ( toTeach > theirSkill.Base ) ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override bool HandlesOnSpeech( Mobile from )
|
|
{
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null && (speechType.Flags & IHSFlags.OnSpeech) != 0 && from.InRange( this, 3 ) )
|
|
return true;
|
|
|
|
return ( m_AI != null && m_AI.HandlesOnSpeech( from ) && from.InRange( this, m_iRangePerception ) );
|
|
}
|
|
|
|
public override void OnSpeech( SpeechEventArgs e )
|
|
{
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null && speechType.OnSpeech( this, e.Mobile, e.Speech ) )
|
|
e.Handled = true;
|
|
else if ( !e.Handled && m_AI != null && e.Mobile.InRange( this, m_iRangePerception ) )
|
|
m_AI.OnSpeech( e );
|
|
}
|
|
|
|
public static int MyLevel( Mobile m )
|
|
{
|
|
int level = 0;
|
|
|
|
if ( m is BaseCreature )
|
|
{
|
|
BaseCreature bc = (BaseCreature)m;
|
|
|
|
int psn = 0;
|
|
|
|
if ( bc.HitPoison == Poison.Lesser )
|
|
psn = 5;
|
|
else if ( bc.HitPoison == Poison.Regular )
|
|
psn = 10;
|
|
else if ( bc.HitPoison == Poison.Greater )
|
|
psn = 15;
|
|
else if ( bc.HitPoison == Poison.Deadly )
|
|
psn = 20;
|
|
else if ( bc.HitPoison == Poison.Lethal )
|
|
psn = 25;
|
|
|
|
if ( bc.PoisonImmune == Poison.Lesser )
|
|
psn = psn + 5;
|
|
else if ( bc.PoisonImmune == Poison.Regular )
|
|
psn = psn + 10;
|
|
else if ( bc.PoisonImmune == Poison.Greater )
|
|
psn = psn + 15;
|
|
else if ( bc.PoisonImmune == Poison.Deadly )
|
|
psn = psn + 20;
|
|
else if ( bc.PoisonImmune == Poison.Lethal )
|
|
psn = psn + 25;
|
|
|
|
int bard = 0;
|
|
|
|
if ( bc.BardImmune )
|
|
bard = 50;
|
|
else
|
|
{
|
|
if ( bc.Unprovokable )
|
|
bard = bard + 25;
|
|
|
|
if ( bc.Uncalmable )
|
|
bard = bard + 25;
|
|
}
|
|
|
|
int dmg = (int)( bc.DamageMax * 8 );
|
|
int sts = (int)( m.RawStatTotal / 6 );
|
|
int fam = (int)( m.Fame / 120 );
|
|
int arm = (int)( m.VirtualArmor * 2.5 );
|
|
|
|
level = psn + dmg + sts + fam + arm + bard;
|
|
|
|
if ( level > 1000 ){ level = 1000; }
|
|
else if ( level < 1 ){ level = 1; }
|
|
}
|
|
|
|
return level;
|
|
}
|
|
|
|
public override bool IsHarmfulCriminal( Mobile target )
|
|
{
|
|
if ( (Controlled && target == m_ControlMaster) || (Summoned && target == m_SummonMaster) )
|
|
return false;
|
|
|
|
if ( target is BaseCreature && ((BaseCreature)target).InitialInnocent && !((BaseCreature)target).Controlled )
|
|
return false;
|
|
|
|
if ( target is PlayerMobile && ((PlayerMobile)target).PermaFlags.Count > 0 )
|
|
return false;
|
|
|
|
return base.IsHarmfulCriminal( target );
|
|
}
|
|
|
|
public override void CriminalAction( bool message )
|
|
{
|
|
base.CriminalAction( message );
|
|
|
|
if ( Controlled || Summoned )
|
|
{
|
|
if ( m_ControlMaster != null && m_ControlMaster.Player )
|
|
m_ControlMaster.CriminalAction( false );
|
|
else if ( m_SummonMaster != null && m_SummonMaster.Player )
|
|
m_SummonMaster.CriminalAction( false );
|
|
}
|
|
}
|
|
|
|
public override void DoHarmful( Mobile target, bool indirect )
|
|
{
|
|
base.DoHarmful( target, indirect );
|
|
|
|
if ( target == this || target == m_ControlMaster || target == m_SummonMaster || (!Controlled && !Summoned) )
|
|
return;
|
|
|
|
List<AggressorInfo> list = this.Aggressors;
|
|
|
|
for ( int i = 0; i < list.Count; ++i )
|
|
{
|
|
AggressorInfo ai = list[i];
|
|
|
|
if ( ai.Attacker == target )
|
|
return;
|
|
}
|
|
|
|
list = this.Aggressed;
|
|
|
|
for ( int i = 0; i < list.Count; ++i )
|
|
{
|
|
AggressorInfo ai = list[i];
|
|
|
|
if ( ai.Defender == target )
|
|
{
|
|
if ( m_ControlMaster != null && m_ControlMaster.Player && m_ControlMaster.CanBeHarmful( target, false ) )
|
|
m_ControlMaster.DoHarmful( target, true );
|
|
else if ( m_SummonMaster != null && m_SummonMaster.Player && m_SummonMaster.CanBeHarmful( target, false ) )
|
|
m_SummonMaster.DoHarmful( target, true );
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private DateTime m_IdleReleaseTime;
|
|
|
|
public virtual bool CheckIdle()
|
|
{
|
|
if ( Combatant != null )
|
|
return false; // in combat.. not idling
|
|
|
|
if ( m_IdleReleaseTime > DateTime.MinValue )
|
|
{
|
|
// idling...
|
|
|
|
if ( DateTime.Now >= m_IdleReleaseTime )
|
|
{
|
|
m_IdleReleaseTime = DateTime.MinValue;
|
|
return false; // idle is over
|
|
}
|
|
|
|
return true; // still idling
|
|
}
|
|
|
|
if ( 95 > Utility.Random( 100 ) )
|
|
return false; // not idling, but don't want to enter idle state
|
|
|
|
m_IdleReleaseTime = DateTime.Now + TimeSpan.FromSeconds( Utility.RandomMinMax( 15, 25 ) );
|
|
|
|
if ( Body.IsHuman && !Mounted )
|
|
{
|
|
switch ( Utility.Random( 2 ) )
|
|
{
|
|
case 0: Animate( 5, 5, 1, true, true, 1 ); break;
|
|
case 1: Animate( 6, 5, 1, true, false, 1 ); break;
|
|
}
|
|
}
|
|
else if ( Body.IsAnimal )
|
|
{
|
|
switch ( Utility.Random( 3 ) )
|
|
{
|
|
case 0: Animate( 3, 3, 1, true, false, 1 ); break;
|
|
case 1: Animate( 9, 5, 1, true, false, 1 ); break;
|
|
case 2: Animate( 10, 5, 1, true, false, 1 ); break;
|
|
}
|
|
}
|
|
else if ( Body.IsMonster )
|
|
{
|
|
switch ( Utility.Random( 2 ) )
|
|
{
|
|
case 0: Animate( 17, 5, 1, true, false, 1 ); break;
|
|
case 1: Animate( 18, 5, 1, true, false, 1 ); break;
|
|
}
|
|
}
|
|
|
|
PlaySound( GetIdleSound() );
|
|
return true; // entered idle state
|
|
}
|
|
|
|
protected override void OnLocationChange( Point3D oldLocation )
|
|
{
|
|
Map map = this.Map;
|
|
|
|
if ( PlayerRangeSensitive && m_AI != null && map != null && map.GetSector( this.Location ).Active )
|
|
m_AI.Activate();
|
|
|
|
base.OnLocationChange( oldLocation );
|
|
}
|
|
|
|
public override void OnMovement( Mobile m, Point3D oldLocation )
|
|
{
|
|
base.OnMovement( m, oldLocation );
|
|
|
|
if ( ReacquireOnMovement )
|
|
ForceReacquire();
|
|
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null )
|
|
speechType.OnMovement( this, m, oldLocation );
|
|
|
|
/* Begin notice sound */
|
|
if ( (!m.Hidden || m.AccessLevel == AccessLevel.Player) && m.Player && m_FightMode != FightMode.Aggressor && m_FightMode != FightMode.None && Combatant == null && !Controlled && !Summoned )
|
|
{
|
|
// If this creature defends itself but doesn't actively attack (animal) or
|
|
// doesn't fight at all (vendor) then no notice sounds are played..
|
|
// So, players are only notified of aggressive monsters
|
|
|
|
// Monsters that are currently fighting are ignored
|
|
|
|
// Controlled or summoned creatures are ignored
|
|
|
|
if ( InRange( m.Location, 18 ) && !InRange( oldLocation, 18 ) )
|
|
{
|
|
if ( Body.IsMonster )
|
|
Animate( 11, 5, 1, true, false, 1 );
|
|
|
|
PlaySound( GetAngerSound() );
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddSpellAttack( Type type )
|
|
{
|
|
m_arSpellAttack.Add ( type );
|
|
}
|
|
|
|
public void AddSpellDefense( Type type )
|
|
{
|
|
m_arSpellDefense.Add ( type );
|
|
}
|
|
|
|
public Spell GetAttackSpellRandom()
|
|
{
|
|
if ( m_arSpellAttack.Count > 0 )
|
|
{
|
|
Type type = m_arSpellAttack[Utility.Random(m_arSpellAttack.Count)];
|
|
|
|
object[] args = {this, null};
|
|
return Activator.CreateInstance( type, args ) as Spell;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Spell GetDefenseSpellRandom()
|
|
{
|
|
if ( m_arSpellDefense.Count > 0 )
|
|
{
|
|
Type type = m_arSpellDefense[Utility.Random(m_arSpellDefense.Count)];
|
|
|
|
object[] args = {this, null};
|
|
return Activator.CreateInstance( type, args ) as Spell;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Spell GetSpellSpecific( Type type )
|
|
{
|
|
int i;
|
|
|
|
for( i=0; i< m_arSpellAttack.Count; i++ )
|
|
{
|
|
if( m_arSpellAttack[i] == type )
|
|
{
|
|
object[] args = { this, null };
|
|
return Activator.CreateInstance( type, args ) as Spell;
|
|
}
|
|
}
|
|
|
|
for ( i=0; i< m_arSpellDefense.Count; i++ )
|
|
{
|
|
if ( m_arSpellDefense[i] == type )
|
|
{
|
|
object[] args = {this, null};
|
|
return Activator.CreateInstance( type, args ) as Spell;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#region Set[...]
|
|
|
|
public void SetDamage( int val )
|
|
{
|
|
m_DamageMin = val;
|
|
m_DamageMax = val;
|
|
}
|
|
|
|
public void SetDamage( int min, int max )
|
|
{
|
|
m_DamageMin = min;
|
|
m_DamageMax = max;
|
|
}
|
|
|
|
public void SetHits( int val )
|
|
{
|
|
if ( val < 1000 )
|
|
val = (val * 100) / 60;
|
|
|
|
m_HitsMax = val;
|
|
Hits = HitsMax;
|
|
}
|
|
|
|
public void SetHits( int min, int max )
|
|
{
|
|
if ( min < 1000 )
|
|
{
|
|
min = (min * 100) / 60;
|
|
max = (max * 100) / 60;
|
|
}
|
|
|
|
m_HitsMax = Utility.RandomMinMax( min, max );
|
|
Hits = HitsMax;
|
|
}
|
|
|
|
public void SetStam( int val )
|
|
{
|
|
m_StamMax = val;
|
|
Stam = StamMax;
|
|
}
|
|
|
|
public void SetStam( int min, int max )
|
|
{
|
|
m_StamMax = Utility.RandomMinMax( min, max );
|
|
Stam = StamMax;
|
|
}
|
|
|
|
public void SetMana( int val )
|
|
{
|
|
m_ManaMax = val;
|
|
Mana = ManaMax;
|
|
}
|
|
|
|
public void SetMana( int min, int max )
|
|
{
|
|
m_ManaMax = Utility.RandomMinMax( min, max );
|
|
Mana = ManaMax;
|
|
}
|
|
|
|
public void SetStr( int val )
|
|
{
|
|
RawStr = val;
|
|
Hits = HitsMax;
|
|
}
|
|
|
|
public void SetStr( int min, int max )
|
|
{
|
|
RawStr = Utility.RandomMinMax( min, max );
|
|
Hits = HitsMax;
|
|
}
|
|
|
|
public void SetDex( int val )
|
|
{
|
|
RawDex = val;
|
|
Stam = StamMax;
|
|
}
|
|
|
|
public void SetDex( int min, int max )
|
|
{
|
|
RawDex = Utility.RandomMinMax( min, max );
|
|
Stam = StamMax;
|
|
}
|
|
|
|
public void SetInt( int val )
|
|
{
|
|
RawInt = val;
|
|
Mana = ManaMax;
|
|
}
|
|
|
|
public void SetInt( int min, int max )
|
|
{
|
|
RawInt = Utility.RandomMinMax( min, max );
|
|
Mana = ManaMax;
|
|
}
|
|
|
|
public void SetSkill( SkillName name, double val )
|
|
{
|
|
Skills[name].BaseFixedPoint = (int)(val * 10);
|
|
|
|
if ( Skills[name].Base > Skills[name].Cap )
|
|
{
|
|
Skills[name].Cap = Skills[name].Base;
|
|
}
|
|
}
|
|
|
|
public void SetSkill( SkillName name, double min, double max )
|
|
{
|
|
int minFixed = (int)(min * 10);
|
|
int maxFixed = (int)(max * 10);
|
|
|
|
Skills[name].BaseFixedPoint = Utility.RandomMinMax( minFixed, maxFixed );
|
|
|
|
if ( Skills[name].Base > Skills[name].Cap )
|
|
{
|
|
Skills[name].Cap = Skills[name].Base;
|
|
}
|
|
}
|
|
|
|
public void SetFameLevel( int level )
|
|
{
|
|
switch ( level )
|
|
{
|
|
case 1: Fame = Utility.RandomMinMax( 0, 1249 ); break;
|
|
case 2: Fame = Utility.RandomMinMax( 1250, 2499 ); break;
|
|
case 3: Fame = Utility.RandomMinMax( 2500, 4999 ); break;
|
|
case 4: Fame = Utility.RandomMinMax( 5000, 9999 ); break;
|
|
case 5: Fame = Utility.RandomMinMax( 10000, 10000 ); break;
|
|
}
|
|
}
|
|
|
|
public void SetKarmaLevel( int level )
|
|
{
|
|
switch ( level )
|
|
{
|
|
case 0: Karma = -Utility.RandomMinMax( 0, 624 ); break;
|
|
case 1: Karma = -Utility.RandomMinMax( 625, 1249 ); break;
|
|
case 2: Karma = -Utility.RandomMinMax( 1250, 2499 ); break;
|
|
case 3: Karma = -Utility.RandomMinMax( 2500, 4999 ); break;
|
|
case 4: Karma = -Utility.RandomMinMax( 5000, 9999 ); break;
|
|
case 5: Karma = -Utility.RandomMinMax( 10000, 10000 ); break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public static void Cap( ref int val, int min, int max )
|
|
{
|
|
if ( val < min )
|
|
val = min;
|
|
else if ( val > max )
|
|
val = max;
|
|
}
|
|
|
|
#region Pack & Loot
|
|
|
|
public virtual void DropBackpack()
|
|
{
|
|
if ( Backpack != null )
|
|
{
|
|
if( Backpack.Items.Count > 0 )
|
|
{
|
|
Backpack b = new CreatureBackpack( Name );
|
|
|
|
List<Item> list = new List<Item>( Backpack.Items );
|
|
foreach ( Item item in list )
|
|
{
|
|
b.DropItem( item );
|
|
}
|
|
|
|
BaseHouse house = BaseHouse.FindHouseAt( this );
|
|
if ( house != null )
|
|
b.MoveToWorld( house.BanLocation, house.Map );
|
|
else
|
|
b.MoveToWorld( Location, Map );
|
|
}
|
|
}
|
|
}
|
|
|
|
protected bool m_Spawning;
|
|
|
|
public virtual void GenerateLoot( bool spawning )
|
|
{
|
|
m_Spawning = spawning;
|
|
|
|
GenerateLoot();
|
|
|
|
m_Spawning = false;
|
|
}
|
|
|
|
public virtual void GenerateLoot()
|
|
{
|
|
}
|
|
|
|
public virtual void AddLoot( LootPack pack, int amount )
|
|
{
|
|
for ( int i = 0; i < amount; ++i )
|
|
AddLoot( pack );
|
|
}
|
|
|
|
public virtual void AddLoot( LootPack pack )
|
|
{
|
|
if ( Summoned )
|
|
return;
|
|
|
|
Container backpack = Backpack;
|
|
|
|
if ( backpack == null )
|
|
{
|
|
backpack = new Backpack();
|
|
|
|
backpack.Movable = false;
|
|
|
|
AddItem( backpack );
|
|
}
|
|
|
|
pack.Generate( this, backpack, m_Spawning );
|
|
}
|
|
|
|
public void PackGold( int amount )
|
|
{
|
|
if ( amount > 0 )
|
|
PackItem( new Gold( amount ) );
|
|
}
|
|
|
|
public void PackGold( int min, int max )
|
|
{
|
|
PackGold( Utility.RandomMinMax( min, max ) );
|
|
}
|
|
|
|
public void PackWood( int amount )
|
|
{
|
|
if ( amount > 0 )
|
|
{
|
|
Item wood = new WoodBoard();
|
|
wood.ItemID = 0x1BE0;
|
|
wood.Weight = 2.0;
|
|
wood.Amount = amount;
|
|
PackItem( wood );
|
|
}
|
|
}
|
|
|
|
public void PackReg( int min, int max )
|
|
{
|
|
PackReg( Utility.RandomMinMax( min, max ) );
|
|
}
|
|
|
|
public void PackReg( int amount )
|
|
{
|
|
if ( amount <= 0 )
|
|
return;
|
|
|
|
Item reg = Loot.RandomReagent();
|
|
|
|
reg.Amount = amount;
|
|
|
|
PackItem( reg );
|
|
}
|
|
|
|
public void PackItem( Item item )
|
|
{
|
|
if ( Summoned || item == null )
|
|
{
|
|
if ( item != null )
|
|
item.Delete();
|
|
|
|
return;
|
|
}
|
|
|
|
Container pack = Backpack;
|
|
|
|
if ( pack == null )
|
|
{
|
|
pack = new Backpack();
|
|
|
|
pack.Movable = false;
|
|
|
|
AddItem( pack );
|
|
}
|
|
|
|
if ( !item.Stackable || !pack.TryDropItem( this, item, false ) ) // try stack
|
|
pack.DropItem( item ); // failed, drop it anyway
|
|
}
|
|
|
|
#endregion
|
|
|
|
public override void OnDoubleClick( Mobile from )
|
|
{
|
|
if ( from.AccessLevel >= AccessLevel.GameMaster && !Body.IsHuman )
|
|
{
|
|
Container pack = this.Backpack;
|
|
|
|
if ( pack != null )
|
|
pack.DisplayTo( from );
|
|
}
|
|
|
|
base.OnDoubleClick( from );
|
|
}
|
|
|
|
public override void AddNameProperties( ObjectPropertyList list )
|
|
{
|
|
base.AddNameProperties( list );
|
|
/*
|
|
if ( Summoned )
|
|
list.Add( 1049646 ); // (summoned)
|
|
else if ( Controlled && Commandable )
|
|
{
|
|
if ( IsBonded ) //Intentional difference (showing ONLY bonded when bonded instead of bonded & tame)
|
|
list.Add( 1049608 ); // (bonded)
|
|
else
|
|
list.Add( 502006 ); // (tame)
|
|
}
|
|
*/
|
|
}
|
|
|
|
public override void OnSingleClick( Mobile from )
|
|
{
|
|
/*
|
|
if ( Controlled && Commandable )
|
|
{
|
|
int number;
|
|
|
|
if ( Summoned )
|
|
number = 1049646; // (summoned)
|
|
else if ( IsBonded )
|
|
number = 1049608; // (bonded)
|
|
else
|
|
number = 502006; // (tame)
|
|
|
|
PrivateOverheadMessage( MessageType.Regular, 0x3B2, number, from.NetState );
|
|
}
|
|
*/
|
|
|
|
base.OnSingleClick( from );
|
|
}
|
|
|
|
public virtual double TreasureMapChance{ get{ return TreasureMap.LootChance; } }
|
|
public virtual int TreasureMapLevel
|
|
{
|
|
get
|
|
{
|
|
int level = MyLevel( this );
|
|
|
|
int map = -1;
|
|
|
|
if ( Fame >= 2000 && AI != AIType.AI_Animal )
|
|
{
|
|
if ( level >= 143 && level <= 285 ){ map = 1; }
|
|
else if ( level >= 286 && level <= 428 ){ map = 2; }
|
|
else if ( level >= 429 && level <= 571 ){ map = 3; }
|
|
else if ( level >= 572 && level <= 714 ){ map = 4; }
|
|
else if ( level >= 715 && level <= 857 ){ map = 5; }
|
|
else if ( level >= 858 && level <= 1000 ){ map = 6; }
|
|
}
|
|
|
|
if ( Utility.RandomMinMax( 0, 6 ) > map && Utility.RandomBool() )
|
|
map = -1;
|
|
|
|
return map;
|
|
}
|
|
}
|
|
|
|
public override bool OnBeforeDeath()
|
|
{
|
|
int treasureLevel = TreasureMapLevel;
|
|
|
|
if ( !Summoned && !NoKillAwards && !IsBonded && treasureLevel > 0 )
|
|
{
|
|
if ( TreasureMap.LootChance >= Utility.RandomDouble() )
|
|
PackItem( new TreasureMap( treasureLevel, Map ) );
|
|
}
|
|
|
|
if ( !Summoned && !NoKillAwards && !m_HasGeneratedLoot )
|
|
{
|
|
m_HasGeneratedLoot = true;
|
|
GenerateLoot( false );
|
|
}
|
|
|
|
InhumanSpeech speechType = this.SpeechType;
|
|
|
|
if ( speechType != null )
|
|
speechType.OnDeath( this );
|
|
|
|
return base.OnBeforeDeath();
|
|
}
|
|
|
|
private bool m_NoKillAwards;
|
|
|
|
public bool NoKillAwards
|
|
{
|
|
get{ return m_NoKillAwards; }
|
|
set{ m_NoKillAwards = value; }
|
|
}
|
|
|
|
public int ComputeBonusDamage( List<DamageEntry> list, Mobile m )
|
|
{
|
|
int bonus = 0;
|
|
|
|
for ( int i = list.Count - 1; i >= 0; --i )
|
|
{
|
|
DamageEntry de = list[i];
|
|
|
|
if ( de.Damager == m || !(de.Damager is BaseCreature) )
|
|
continue;
|
|
|
|
BaseCreature bc = (BaseCreature)de.Damager;
|
|
Mobile master = null;
|
|
|
|
master = bc.GetMaster();
|
|
|
|
if ( master == m )
|
|
bonus += de.DamageGiven;
|
|
}
|
|
|
|
return bonus;
|
|
}
|
|
|
|
public Mobile GetMaster()
|
|
{
|
|
if ( Controlled && ControlMaster != null )
|
|
return ControlMaster;
|
|
else if ( Summoned && SummonMaster != null )
|
|
return SummonMaster;
|
|
|
|
return null;
|
|
}
|
|
|
|
private class FKEntry
|
|
{
|
|
public Mobile m_Mobile;
|
|
public int m_Damage;
|
|
|
|
public FKEntry( Mobile m, int damage )
|
|
{
|
|
m_Mobile = m;
|
|
m_Damage = damage;
|
|
}
|
|
}
|
|
|
|
public static List<DamageStore> GetLootingRights( List<DamageEntry> damageEntries, int hitsMax )
|
|
{
|
|
List<DamageStore> rights = new List<DamageStore>();
|
|
|
|
for ( int i = damageEntries.Count - 1; i >= 0; --i )
|
|
{
|
|
if ( i >= damageEntries.Count )
|
|
continue;
|
|
|
|
DamageEntry de = damageEntries[i];
|
|
|
|
if ( de.HasExpired )
|
|
{
|
|
damageEntries.RemoveAt( i );
|
|
continue;
|
|
}
|
|
|
|
int damage = de.DamageGiven;
|
|
|
|
List<DamageEntry> respList = de.Responsible;
|
|
|
|
if ( respList != null )
|
|
{
|
|
for ( int j = 0; j < respList.Count; ++j )
|
|
{
|
|
DamageEntry subEntry = respList[j];
|
|
Mobile master = subEntry.Damager;
|
|
|
|
if ( master == null || master.Deleted || !master.Player )
|
|
continue;
|
|
|
|
bool needNewSubEntry = true;
|
|
|
|
for ( int k = 0; needNewSubEntry && k < rights.Count; ++k )
|
|
{
|
|
DamageStore ds = rights[k];
|
|
|
|
if ( ds.m_Mobile == master )
|
|
{
|
|
ds.m_Damage += subEntry.DamageGiven;
|
|
needNewSubEntry = false;
|
|
}
|
|
}
|
|
|
|
if ( needNewSubEntry )
|
|
rights.Add( new DamageStore( master, subEntry.DamageGiven ) );
|
|
|
|
damage -= subEntry.DamageGiven;
|
|
}
|
|
}
|
|
|
|
Mobile m = de.Damager;
|
|
|
|
if ( m == null || m.Deleted || !m.Player )
|
|
continue;
|
|
|
|
if ( damage <= 0 )
|
|
continue;
|
|
|
|
bool needNewEntry = true;
|
|
|
|
for ( int j = 0; needNewEntry && j < rights.Count; ++j )
|
|
{
|
|
DamageStore ds = rights[j];
|
|
|
|
if ( ds.m_Mobile == m )
|
|
{
|
|
ds.m_Damage += damage;
|
|
needNewEntry = false;
|
|
}
|
|
}
|
|
|
|
if ( needNewEntry )
|
|
rights.Add( new DamageStore( m, damage ) );
|
|
}
|
|
|
|
if ( rights.Count > 0 )
|
|
{
|
|
rights[0].m_Damage = (int)(rights[0].m_Damage * 1.25); //This would be the first valid person attacking it. Gets a 25% bonus. Per 1/19/07 Five on Friday
|
|
|
|
if ( rights.Count > 1 )
|
|
rights.Sort(); //Sort by damage
|
|
|
|
int topDamage = rights[0].m_Damage;
|
|
int minDamage;
|
|
|
|
if ( hitsMax >= 3000 )
|
|
minDamage = topDamage / 16;
|
|
else if ( hitsMax >= 1000 )
|
|
minDamage = topDamage / 8;
|
|
else if ( hitsMax >= 200 )
|
|
minDamage = topDamage / 4;
|
|
else
|
|
minDamage = topDamage / 2;
|
|
|
|
for ( int i = 0; i < rights.Count; ++i )
|
|
{
|
|
DamageStore ds = rights[i];
|
|
|
|
ds.m_HasRight = ( ds.m_Damage >= minDamage );
|
|
}
|
|
}
|
|
|
|
return rights;
|
|
}
|
|
|
|
public virtual void OnKilledBy( Mobile mob )
|
|
{
|
|
}
|
|
|
|
public override void OnDeath( Container c )
|
|
{
|
|
if ( IsBonded )
|
|
{
|
|
int sound = this.GetDeathSound();
|
|
|
|
if ( sound >= 0 )
|
|
Effects.PlaySound( this, this.Map, sound );
|
|
|
|
Warmode = false;
|
|
|
|
Poison = null;
|
|
Combatant = null;
|
|
|
|
Hits = 0;
|
|
Stam = 0;
|
|
Mana = 0;
|
|
|
|
IsDeadPet = true;
|
|
ControlTarget = ControlMaster;
|
|
ControlOrder = OrderType.Follow;
|
|
|
|
ProcessDeltaQueue();
|
|
SendIncomingPacket();
|
|
SendIncomingPacket();
|
|
|
|
List<AggressorInfo> aggressors = this.Aggressors;
|
|
|
|
for ( int i = 0; i < aggressors.Count; ++i )
|
|
{
|
|
AggressorInfo info = aggressors[i];
|
|
|
|
if ( info.Attacker.Combatant == this )
|
|
info.Attacker.Combatant = null;
|
|
}
|
|
|
|
List<AggressorInfo> aggressed = this.Aggressed;
|
|
|
|
for ( int i = 0; i < aggressed.Count; ++i )
|
|
{
|
|
AggressorInfo info = aggressed[i];
|
|
|
|
if ( info.Defender.Combatant == this )
|
|
info.Defender.Combatant = null;
|
|
}
|
|
|
|
Mobile owner = this.ControlMaster;
|
|
|
|
if ( owner == null || owner.Deleted || owner.Map != this.Map || !owner.InRange( this, 12 ) || !this.CanSee( owner ) || !this.InLOS( owner ) )
|
|
{
|
|
if ( this.OwnerAbandonTime == DateTime.MinValue )
|
|
this.OwnerAbandonTime = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
this.OwnerAbandonTime = DateTime.MinValue;
|
|
}
|
|
|
|
CheckStatTimers();
|
|
}
|
|
else
|
|
{
|
|
if ( !Summoned && !m_NoKillAwards )
|
|
{
|
|
int totalFame = Fame / 100;
|
|
int totalKarma = -Karma / 100;
|
|
|
|
List<DamageStore> list = GetLootingRights( this.DamageEntries, this.HitsMax );
|
|
List<Mobile> titles = new List<Mobile>();
|
|
List<int> fame = new List<int>();
|
|
List<int> karma = new List<int>();
|
|
|
|
for ( int i = 0; i < list.Count; ++i )
|
|
{
|
|
DamageStore ds = list[i];
|
|
|
|
if ( !ds.m_HasRight )
|
|
continue;
|
|
|
|
Party party = Engines.PartySystem.Party.Get( ds.m_Mobile );
|
|
|
|
if ( party != null )
|
|
{
|
|
int divedFame = totalFame / party.Members.Count;
|
|
int divedKarma = totalKarma / party.Members.Count;
|
|
|
|
for ( int j = 0; j < party.Members.Count; ++j )
|
|
{
|
|
PartyMemberInfo info = party.Members[ j ] as PartyMemberInfo;
|
|
|
|
if ( info != null && info.Mobile != null )
|
|
{
|
|
int index = titles.IndexOf( info.Mobile );
|
|
|
|
if ( index == -1 )
|
|
{
|
|
titles.Add( info.Mobile );
|
|
fame.Add( divedFame );
|
|
karma.Add( divedKarma );
|
|
}
|
|
else
|
|
{
|
|
fame[ index ] += divedFame;
|
|
karma[ index ] += divedKarma;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
titles.Add( ds.m_Mobile );
|
|
fame.Add( totalFame );
|
|
karma.Add( totalKarma );
|
|
}
|
|
|
|
OnKilledBy( ds.m_Mobile );
|
|
}
|
|
for ( int i = 0; i < titles.Count; ++i )
|
|
{
|
|
Titles.AwardFame( titles[ i ], fame[ i ], true );
|
|
Titles.AwardKarma( titles[ i ], karma[ i ], true );
|
|
}
|
|
}
|
|
|
|
base.OnDeath( c );
|
|
|
|
if ( DeleteCorpseOnDeath )
|
|
c.Delete();
|
|
}
|
|
}
|
|
|
|
/* To save on cpu usage, RunUO creatures only reacquire creatures under the following circumstances:
|
|
* - 10 seconds have elapsed since the last time it tried
|
|
* - The creature was attacked
|
|
* - Some creatures, like dragons, will reacquire when they see someone move
|
|
*
|
|
* This functionality appears to be implemented on OSI as well
|
|
*/
|
|
|
|
private DateTime m_NextReacquireTime;
|
|
|
|
public DateTime NextReacquireTime{ get{ return m_NextReacquireTime; } set{ m_NextReacquireTime = value; } }
|
|
|
|
public virtual TimeSpan ReacquireDelay{ get{ return TimeSpan.FromSeconds( 10.0 ); } }
|
|
public virtual bool ReacquireOnMovement{ get{ return false; } }
|
|
|
|
public void ForceReacquire()
|
|
{
|
|
m_NextReacquireTime = DateTime.MinValue;
|
|
}
|
|
|
|
public override void OnDelete()
|
|
{
|
|
Server.Mobiles.PremiumSpawner.DropSpawner( this );
|
|
|
|
Mobile m = m_ControlMaster;
|
|
|
|
SetControlMaster( null );
|
|
SummonMaster = null;
|
|
|
|
if ( this.Mounted )
|
|
{
|
|
Mobiles.IMount mt = this.Mount;
|
|
Mobile animal = (Mobile)mt;
|
|
animal.Delete();
|
|
}
|
|
|
|
base.OnDelete();
|
|
|
|
if ( m != null )
|
|
m.InvalidateProperties();
|
|
}
|
|
|
|
public override bool CanBeHarmful( Mobile target, bool message, bool ignoreOurBlessedness )
|
|
{
|
|
if ( target.Invulnerable || target is PlayerVendor || target is TownCrier )
|
|
{
|
|
if ( message )
|
|
{
|
|
if ( target.Title == null )
|
|
SendMessage( "{0} the vendor cannot be harmed.", target.Name );
|
|
else
|
|
SendMessage( "{0} {1} cannot be harmed.", target.Name, target.Title );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return base.CanBeHarmful( target, message, ignoreOurBlessedness );
|
|
}
|
|
|
|
public override bool CanBeRenamedBy( Mobile from )
|
|
{
|
|
bool ret = base.CanBeRenamedBy( from );
|
|
|
|
if ( Controlled && from == ControlMaster )
|
|
ret = true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
public bool SetControlMaster( Mobile m )
|
|
{
|
|
if ( m == null )
|
|
{
|
|
ControlMaster = null;
|
|
Controlled = false;
|
|
ControlTarget = null;
|
|
ControlOrder = OrderType.None;
|
|
Guild = null;
|
|
|
|
Delta( MobileDelta.Noto );
|
|
}
|
|
else
|
|
{
|
|
ISpawner se = this.Spawner;
|
|
if ( se != null && se.UnlinkOnTaming )
|
|
{
|
|
this.Spawner.Remove( this );
|
|
this.Spawner = null;
|
|
}
|
|
|
|
if ( m.Followers + ControlSlots > m.FollowersMax )
|
|
{
|
|
m.SendLocalizedMessage( 1049607 ); // You have too many followers to control that creature.
|
|
return false;
|
|
}
|
|
|
|
CurrentWayPoint = null;//so tamed animals don't try to go back
|
|
|
|
ControlMaster = m;
|
|
Controlled = true;
|
|
ControlTarget = null;
|
|
ControlOrder = OrderType.Come;
|
|
Guild = null;
|
|
|
|
if ( m_DeleteTimer != null )
|
|
{
|
|
m_DeleteTimer.Stop();
|
|
m_DeleteTimer = null;
|
|
}
|
|
|
|
Delta( MobileDelta.Noto );
|
|
}
|
|
|
|
InvalidateProperties();
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void OnRegionChange( Region Old, Region New )
|
|
{
|
|
base.OnRegionChange( Old, New );
|
|
|
|
if ( this.Controlled )
|
|
{
|
|
SpawnEntry se = this.Spawner as SpawnEntry;
|
|
|
|
if ( se != null && !se.UnlinkOnTaming && ( New == null || !New.AcceptsSpawnsFrom( se.Region ) ) )
|
|
{
|
|
this.Spawner.Remove( this );
|
|
this.Spawner = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool m_Summoning;
|
|
|
|
public static bool Summoning
|
|
{
|
|
get{ return m_Summoning; }
|
|
set{ m_Summoning = value; }
|
|
}
|
|
|
|
public static bool Summon( BaseCreature creature, Mobile caster, Point3D p, int sound, TimeSpan duration )
|
|
{
|
|
return Summon( creature, true, caster, p, sound, duration );
|
|
}
|
|
|
|
public static bool Summon( BaseCreature creature, bool controlled, Mobile caster, Point3D p, int sound, TimeSpan duration )
|
|
{
|
|
if ( caster.Followers + creature.ControlSlots > caster.FollowersMax )
|
|
{
|
|
caster.SendLocalizedMessage( 1049645 ); // You have too many followers to summon that creature.
|
|
creature.Delete();
|
|
return false;
|
|
}
|
|
|
|
m_Summoning = true;
|
|
|
|
if ( controlled )
|
|
creature.SetControlMaster( caster );
|
|
|
|
creature.RangeHome = 10;
|
|
creature.Summoned = true;
|
|
|
|
creature.SummonMaster = caster;
|
|
|
|
Container pack = creature.Backpack;
|
|
|
|
if ( pack != null )
|
|
{
|
|
for ( int i = pack.Items.Count - 1; i >= 0; --i )
|
|
{
|
|
if ( i >= pack.Items.Count )
|
|
continue;
|
|
|
|
pack.Items[i].Delete();
|
|
}
|
|
}
|
|
|
|
new UnsummonTimer( caster, creature, duration ).Start();
|
|
creature.m_SummonEnd = DateTime.Now + duration;
|
|
|
|
creature.MoveToWorld( p, caster.Map );
|
|
|
|
Effects.PlaySound( p, creature.Map, sound );
|
|
|
|
m_Summoning = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool EnableRummaging = true;
|
|
|
|
private const double ChanceToRummage = 0.5; // 50%
|
|
|
|
private const double MinutesToNextRummageMin = 1.0;
|
|
private const double MinutesToNextRummageMax = 4.0;
|
|
|
|
private const double MinutesToNextChanceMin = 0.25;
|
|
private const double MinutesToNextChanceMax = 0.75;
|
|
|
|
private DateTime m_NextRummageTime;
|
|
|
|
public virtual bool CanBreath { get { return HasBreath && !Summoned; } }
|
|
public virtual bool IsDispellable { get { return Summoned; } }
|
|
|
|
#region Healing
|
|
public virtual bool CanHeal { get { return false; } }
|
|
public virtual bool CanHealOwner { get { return false; } }
|
|
public virtual double HealScalar { get { return 1.0; } }
|
|
|
|
public virtual int HealSound { get { return 0x57; } }
|
|
public virtual int HealStartRange { get { return 2; } }
|
|
public virtual int HealEndRange { get { return RangePerception; } }
|
|
public virtual double HealTrigger { get { return 0.78; } }
|
|
public virtual double HealDelay { get { return 6.5; } }
|
|
public virtual double HealInterval { get { return 0.0; } }
|
|
public virtual bool HealFully { get { return true; } }
|
|
public virtual double HealOwnerTrigger { get { return 0.78; } }
|
|
public virtual double HealOwnerDelay { get { return 6.5; } }
|
|
public virtual double HealOwnerInterval { get { return 30.0; } }
|
|
public virtual bool HealOwnerFully { get { return false; } }
|
|
|
|
private DateTime m_NextHealTime = DateTime.Now;
|
|
private DateTime m_NextHealOwnerTime = DateTime.Now;
|
|
private Timer m_HealTimer = null;
|
|
|
|
public bool IsHealing { get { return ( m_HealTimer != null ); } }
|
|
|
|
public virtual void HealStart( Mobile patient )
|
|
{
|
|
bool onSelf = ( patient == this );
|
|
|
|
//DoBeneficial( patient );
|
|
|
|
RevealingAction();
|
|
|
|
if ( !onSelf )
|
|
{
|
|
patient.RevealingAction();
|
|
patient.SendLocalizedMessage( 1008078, false, Name ); // : Attempting to heal you.
|
|
}
|
|
|
|
double seconds = ( onSelf ? HealDelay : HealOwnerDelay ) + ( patient.Alive ? 0.0 : 5.0 );
|
|
|
|
m_HealTimer = Timer.DelayCall( TimeSpan.FromSeconds( seconds ), new TimerStateCallback( Heal_Callback ), patient );
|
|
}
|
|
|
|
private void Heal_Callback( object state )
|
|
{
|
|
if ( state is Mobile )
|
|
Heal( (Mobile)state );
|
|
}
|
|
|
|
public virtual void Heal( Mobile patient )
|
|
{
|
|
if ( !Alive || this.Map == Map.Internal || !CanBeBeneficial( patient, true, true ) || patient.Map != this.Map || !InRange( patient, HealEndRange ) )
|
|
{
|
|
StopHeal();
|
|
return;
|
|
}
|
|
|
|
bool onSelf = ( patient == this );
|
|
|
|
if ( !patient.Alive )
|
|
{
|
|
}
|
|
else if ( patient.Poisoned )
|
|
{
|
|
int poisonLevel = patient.Poison.Level;
|
|
|
|
double healing = Skills.Healing.Value;
|
|
double chance = ( healing - 30.0 ) / 50.0 - poisonLevel * 0.1;
|
|
|
|
if ( ( healing >= 60.0 ) && chance > Utility.RandomDouble() )
|
|
{
|
|
if ( patient.CurePoison( this ) )
|
|
{
|
|
patient.SendLocalizedMessage( 1010059 ); // You have been cured of all poisons.
|
|
|
|
CheckSkill( SkillName.Healing, 0.0, 60.0 + poisonLevel * 10.0 ); // TODO: Verify formula
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double healing = Skills.Healing.Value;
|
|
double chance = ( healing + 10.0 ) / 100.0;
|
|
|
|
if ( chance > Utility.RandomDouble() )
|
|
{
|
|
double min, max;
|
|
|
|
min = ( healing / 10.0 ) + ( healing / 6.0 ) + 4.0;
|
|
max = ( healing / 8.0 ) + ( healing / 3.0 ) + 4.0;
|
|
|
|
if ( onSelf )
|
|
max += 10;
|
|
|
|
double toHeal = min + ( Utility.RandomDouble() * ( max - min ) );
|
|
|
|
toHeal *= HealScalar;
|
|
|
|
patient.Heal( (int)toHeal );
|
|
|
|
CheckSkill( SkillName.Healing, 0.0, 90.0 );
|
|
}
|
|
}
|
|
|
|
HealEffect( patient );
|
|
|
|
StopHeal();
|
|
|
|
if ( ( onSelf && HealFully && Hits >= HealTrigger * HitsMax && Hits < HitsMax ) || ( !onSelf && HealOwnerFully && patient.Hits >= HealOwnerTrigger * patient.HitsMax && patient.Hits < patient.HitsMax ) )
|
|
HealStart( patient );
|
|
}
|
|
|
|
public virtual void StopHeal()
|
|
{
|
|
if ( m_HealTimer != null )
|
|
m_HealTimer.Stop();
|
|
|
|
m_HealTimer = null;
|
|
}
|
|
|
|
public virtual void HealEffect( Mobile patient )
|
|
{
|
|
patient.PlaySound( HealSound );
|
|
}
|
|
|
|
#endregion
|
|
|
|
public virtual void OnThink()
|
|
{
|
|
if ( EnableRummaging && CanRummageCorpses && !Summoned && !Controlled && DateTime.Now >= m_NextRummageTime )
|
|
{
|
|
double min, max;
|
|
|
|
if ( ChanceToRummage > Utility.RandomDouble() && Rummage() )
|
|
{
|
|
min = MinutesToNextRummageMin;
|
|
max = MinutesToNextRummageMax;
|
|
}
|
|
else
|
|
{
|
|
min = MinutesToNextChanceMin;
|
|
max = MinutesToNextChanceMax;
|
|
}
|
|
|
|
double delay = min + (Utility.RandomDouble() * (max - min));
|
|
m_NextRummageTime = DateTime.Now + TimeSpan.FromMinutes( delay );
|
|
}
|
|
|
|
if ( CanBreath && DateTime.Now >= m_NextBreathTime ) // tested: controlled dragons do breath fire, what about summoned skeletal dragons?
|
|
{
|
|
Mobile target = this.Combatant;
|
|
|
|
if( target != null && target.Alive && !target.IsDeadBondedPet && CanBeHarmful( target ) && target.Map == this.Map && !IsDeadBondedPet && target.InRange( this, BreathRange ) && InLOS( target ) && !BardPacified )
|
|
{
|
|
if( ( DateTime.Now - m_NextBreathTime ) < TimeSpan.FromSeconds( 30 ) )
|
|
{
|
|
BreathStart( target );
|
|
}
|
|
|
|
m_NextBreathTime = DateTime.Now + TimeSpan.FromSeconds( BreathMinDelay + ( Utility.RandomDouble() * BreathMaxDelay ) );
|
|
}
|
|
}
|
|
|
|
if ( ( CanHeal || CanHealOwner ) && Alive && !IsHealing && !BardPacified )
|
|
{
|
|
Mobile owner = this.ControlMaster;
|
|
|
|
if ( owner != null && CanHealOwner && DateTime.Now >= m_NextHealOwnerTime && CanBeBeneficial( owner, true, true ) && owner.Map == this.Map && InRange( owner, HealStartRange ) && InLOS( owner ) && owner.Hits < HealOwnerTrigger * owner.HitsMax )
|
|
{
|
|
HealStart( owner );
|
|
|
|
m_NextHealOwnerTime = DateTime.Now + TimeSpan.FromSeconds( HealOwnerInterval );
|
|
}
|
|
else if ( CanHeal && DateTime.Now >= m_NextHealTime && CanBeBeneficial( this ) && ( Hits < HealTrigger * HitsMax || Poisoned ) )
|
|
{
|
|
HealStart( this );
|
|
|
|
m_NextHealTime = DateTime.Now + TimeSpan.FromSeconds( HealInterval );
|
|
}
|
|
}
|
|
|
|
if ( Server.Misc.Settings.MonstersSurprise() && Combatant == null && ( Region is GraveRegion || Region is DungeonRegion ) ) // MOVE FROM PLAYER SIGHT WHEN NOT SEEN
|
|
{
|
|
bool seeing = true;
|
|
bool swims = (SlayerGroup.GetEntryByName( SlayerName.SeaSlaughter )).Slays(this);
|
|
|
|
foreach ( NetState state in NetState.Instances )
|
|
{
|
|
Mobile m = state.Mobile;
|
|
|
|
if ( ( m is PlayerMobile || (m is BaseCreature && ((BaseCreature)m).Controlled) ) && InLOS( m ) && m.Alive && m.Map == this.Map )
|
|
seeing = false;
|
|
}
|
|
|
|
if ( seeing )
|
|
{
|
|
Warmode = false;
|
|
CantWalk = true;
|
|
CanSwim = false;
|
|
Hidden = true;
|
|
}
|
|
else if ( Hidden )
|
|
{
|
|
CantWalk = WontWalk;
|
|
CanSwim = WillSwim;
|
|
Hidden = false;
|
|
}
|
|
}
|
|
else if ( SeaCreature && !WontWalk && Combatant == null && !Hidden ) // DIVE UNDER WATER AND WAIT FOR VICTIM IF YOU CAN ALSO WALK ON LAND
|
|
{
|
|
bool dive = true;
|
|
|
|
foreach ( NetState state in NetState.Instances )
|
|
{
|
|
Mobile m = state.Mobile;
|
|
|
|
if ( m is PlayerMobile && m.InRange( this.Location, 20 ) && m.Alive && m.Map == this.Map )
|
|
dive = false;
|
|
}
|
|
|
|
if ( dive )
|
|
{
|
|
Point3D loc = BaseRegion.GetBoatWater( this.X, this.Y, this.Map, 8 );
|
|
|
|
this.Location = loc;
|
|
this.PlaySound( 0x026 );
|
|
Effects.SendLocationEffect( this.Location, this.Map, 0x35B2, 16 );
|
|
|
|
this.Warmode = false;
|
|
this.CantWalk = true;
|
|
this.CanSwim = false;
|
|
this.Hidden = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual bool Rummage()
|
|
{
|
|
Corpse toRummage = null;
|
|
|
|
foreach ( Item item in this.GetItemsInRange( 2 ) )
|
|
{
|
|
if ( item is Corpse && item.Items.Count > 0 )
|
|
{
|
|
toRummage = (Corpse)item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( toRummage == null )
|
|
return false;
|
|
|
|
Container pack = this.Backpack;
|
|
|
|
if ( pack == null )
|
|
return false;
|
|
|
|
List<Item> items = toRummage.Items;
|
|
|
|
bool rejected;
|
|
LRReason reason;
|
|
|
|
for ( int i = 0; i < items.Count; ++i )
|
|
{
|
|
Item item = items[Utility.Random( items.Count )];
|
|
|
|
Lift( item, item.Amount, out rejected, out reason );
|
|
|
|
if ( !rejected && Drop( this, new Point3D( -1, -1, 0 ) ) )
|
|
{
|
|
// *rummages through a corpse and takes an item*
|
|
PublicOverheadMessage( MessageType.Emote, 0x3B2, 1008086 );
|
|
//TODO: Instancing of Rummaged stuff.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void Pacify( Mobile master, DateTime endtime )
|
|
{
|
|
BardPacified = true;
|
|
BardEndTime = endtime;
|
|
}
|
|
|
|
public override Mobile GetDamageMaster( Mobile damagee )
|
|
{
|
|
if ( m_bBardProvoked && damagee == m_bBardTarget )
|
|
return m_bBardMaster;
|
|
else if ( m_bControlled && m_ControlMaster != null )
|
|
return m_ControlMaster;
|
|
else if ( m_bSummoned && m_SummonMaster != null )
|
|
return m_SummonMaster;
|
|
|
|
return base.GetDamageMaster( damagee );
|
|
}
|
|
|
|
public void Provoke( Mobile master, Mobile target, bool bSuccess )
|
|
{
|
|
BardProvoked = true;
|
|
|
|
this.PublicOverheadMessage( MessageType.Emote, EmoteHue, false, "*looks furious*" );
|
|
|
|
if ( bSuccess )
|
|
{
|
|
PlaySound( GetIdleSound() );
|
|
|
|
BardMaster = master;
|
|
BardTarget = target;
|
|
Combatant = target;
|
|
BardEndTime = DateTime.Now + TimeSpan.FromSeconds( 30.0 );
|
|
|
|
if ( target is BaseCreature )
|
|
{
|
|
BaseCreature t = (BaseCreature)target;
|
|
|
|
if ( t.Unprovokable )
|
|
return;
|
|
|
|
t.BardProvoked = true;
|
|
|
|
t.BardMaster = master;
|
|
t.BardTarget = this;
|
|
t.Combatant = this;
|
|
t.BardEndTime = DateTime.Now + TimeSpan.FromSeconds( 30.0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PlaySound( GetAngerSound() );
|
|
|
|
BardMaster = master;
|
|
BardTarget = target;
|
|
}
|
|
}
|
|
|
|
public bool FindMyName( string str, bool bWithAll )
|
|
{
|
|
int i, j;
|
|
|
|
string name = this.Name;
|
|
|
|
if( name == null || str.Length < name.Length )
|
|
return false;
|
|
|
|
string[] wordsString = str.Split(' ');
|
|
string[] wordsName = name.Split(' ');
|
|
|
|
for ( j=0 ; j < wordsName.Length; j++ )
|
|
{
|
|
string wordName = wordsName[j];
|
|
|
|
bool bFound = false;
|
|
for ( i=0 ; i < wordsString.Length; i++ )
|
|
{
|
|
string word = wordsString[i];
|
|
|
|
if ( Insensitive.Equals( word, wordName ) )
|
|
bFound = true;
|
|
|
|
if ( bWithAll && Insensitive.Equals( word, "all" ) )
|
|
return true;
|
|
}
|
|
|
|
if ( !bFound )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void TeleportPets( Mobile master, Point3D loc, Map map )
|
|
{
|
|
TeleportPets( master, loc, map, false );
|
|
}
|
|
|
|
public static void TeleportPets( Mobile master, Point3D loc, Map map, bool onlyBonded )
|
|
{
|
|
List<Mobile> move = new List<Mobile>();
|
|
|
|
foreach ( Mobile m in master.GetMobilesInRange( 3 ) )
|
|
{
|
|
if ( m is BaseCreature )
|
|
{
|
|
BaseCreature pet = (BaseCreature)m;
|
|
|
|
if ( pet.Controlled && pet.ControlMaster == master )
|
|
{
|
|
if ( !onlyBonded || pet.IsBonded )
|
|
{
|
|
if ( pet.ControlOrder == OrderType.Guard || pet.ControlOrder == OrderType.Follow || pet.ControlOrder == OrderType.Come )
|
|
move.Add( pet );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ( Mobile m in move )
|
|
m.MoveToWorld( loc, map );
|
|
}
|
|
|
|
public static void DeletePets( Mobile master )
|
|
{
|
|
List<Mobile> move = new List<Mobile>();
|
|
|
|
foreach ( Mobile m in master.GetMobilesInRange( 10 ) )
|
|
{
|
|
if ( m is BaseCreature )
|
|
{
|
|
BaseCreature pet = (BaseCreature)m;
|
|
|
|
if ( pet.Controlled && pet.ControlMaster == master )
|
|
{
|
|
move.Add( pet );
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ( Mobile m in move )
|
|
m.Delete();
|
|
}
|
|
|
|
public virtual void ResurrectPet()
|
|
{
|
|
if ( !IsDeadPet )
|
|
return;
|
|
|
|
OnBeforeResurrect();
|
|
|
|
Poison = null;
|
|
|
|
Warmode = false;
|
|
|
|
Hits = 10;
|
|
Stam = StamMax;
|
|
Mana = 0;
|
|
|
|
ProcessDeltaQueue();
|
|
|
|
IsDeadPet = false;
|
|
|
|
Effects.SendPacket( Location, Map, new BondedStatus( 0, this.Serial, 0 ) );
|
|
|
|
this.SendIncomingPacket();
|
|
this.SendIncomingPacket();
|
|
|
|
OnAfterResurrect();
|
|
|
|
Mobile owner = this.ControlMaster;
|
|
|
|
if ( owner == null || owner.Deleted || owner.Map != this.Map || !owner.InRange( this, 12 ) || !this.CanSee( owner ) || !this.InLOS( owner ) )
|
|
{
|
|
if ( this.OwnerAbandonTime == DateTime.MinValue )
|
|
this.OwnerAbandonTime = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
this.OwnerAbandonTime = DateTime.MinValue;
|
|
}
|
|
|
|
CheckStatTimers();
|
|
}
|
|
|
|
public override bool CanBeDamaged()
|
|
{
|
|
if ( IsDeadPet )
|
|
return false;
|
|
|
|
return base.CanBeDamaged();
|
|
}
|
|
|
|
public virtual bool PlayerRangeSensitive{ get{ return (this.CurrentWayPoint == null); } } //If they are following a waypoint, they'll continue to follow it even if players aren't around
|
|
|
|
public override void OnSectorDeactivate()
|
|
{
|
|
if ( PlayerRangeSensitive && m_AI != null )
|
|
m_AI.Deactivate();
|
|
|
|
base.OnSectorDeactivate();
|
|
}
|
|
|
|
public override void OnSectorActivate()
|
|
{
|
|
if ( PlayerRangeSensitive && m_AI != null )
|
|
m_AI.Activate();
|
|
|
|
base.OnSectorActivate();
|
|
}
|
|
|
|
private bool m_RemoveIfUntamed;
|
|
|
|
// used for deleting untamed creatures [in houses]
|
|
private int m_RemoveStep;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool RemoveIfUntamed{ get{ return m_RemoveIfUntamed; } set{ m_RemoveIfUntamed = value; } }
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int RemoveStep { get { return m_RemoveStep; } set { m_RemoveStep = value; } }
|
|
}
|
|
} |