Convert movement, actions (lift/use), combat, and spells to use Core.TickCount and avoid DateTime caveats (performance, system time dependency, etc) Refactor DateTime.Now to DateTime.UtcNow Add LOS check for Iron Maiden addon
587 lines
No EOL
14 KiB
C#
587 lines
No EOL
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using Server;
|
|
using Server.Network;
|
|
using Server.Mobiles;
|
|
using Server.Targeting;
|
|
using Server.Engines.Craft;
|
|
|
|
namespace Server.Items
|
|
{
|
|
public delegate void InstrumentPickedCallback( Mobile from, BaseInstrument instrument );
|
|
|
|
public enum InstrumentQuality
|
|
{
|
|
Low,
|
|
Regular,
|
|
Exceptional
|
|
}
|
|
|
|
public abstract class BaseInstrument : Item, ICraftable, ISlayer
|
|
{
|
|
private int m_WellSound, m_BadlySound;
|
|
private SlayerName m_Slayer, m_Slayer2;
|
|
private InstrumentQuality m_Quality;
|
|
private Mobile m_Crafter;
|
|
private int m_UsesRemaining;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int SuccessSound
|
|
{
|
|
get{ return m_WellSound; }
|
|
set{ m_WellSound = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int FailureSound
|
|
{
|
|
get{ return m_BadlySound; }
|
|
set{ m_BadlySound = value; }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public SlayerName Slayer
|
|
{
|
|
get{ return m_Slayer; }
|
|
set{ m_Slayer = value; InvalidateProperties(); }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public SlayerName Slayer2
|
|
{
|
|
get{ return m_Slayer2; }
|
|
set{ m_Slayer2 = value; InvalidateProperties(); }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public InstrumentQuality Quality
|
|
{
|
|
get{ return m_Quality; }
|
|
set{ UnscaleUses(); m_Quality = value; InvalidateProperties(); ScaleUses(); }
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public Mobile Crafter
|
|
{
|
|
get{ return m_Crafter; }
|
|
set{ m_Crafter = value; InvalidateProperties(); }
|
|
}
|
|
|
|
public virtual int InitMinUses{ get{ return 350; } }
|
|
public virtual int InitMaxUses{ get{ return 450; } }
|
|
|
|
public virtual TimeSpan ChargeReplenishRate { get { return TimeSpan.FromMinutes( 5.0 ); } }
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int UsesRemaining
|
|
{
|
|
get{ CheckReplenishUses(); return m_UsesRemaining; }
|
|
set{ m_UsesRemaining = value; InvalidateProperties(); }
|
|
}
|
|
|
|
private DateTime m_LastReplenished;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public DateTime LastReplenished
|
|
{
|
|
get { return m_LastReplenished; }
|
|
set { m_LastReplenished = value; CheckReplenishUses(); }
|
|
}
|
|
|
|
private bool m_ReplenishesCharges;
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public bool ReplenishesCharges
|
|
{
|
|
get { return m_ReplenishesCharges; }
|
|
set
|
|
{
|
|
if( value != m_ReplenishesCharges && value )
|
|
m_LastReplenished = DateTime.UtcNow;
|
|
|
|
m_ReplenishesCharges = value;
|
|
}
|
|
}
|
|
|
|
public void CheckReplenishUses()
|
|
{
|
|
CheckReplenishUses( true );
|
|
}
|
|
|
|
public void CheckReplenishUses( bool invalidate )
|
|
{
|
|
if( !m_ReplenishesCharges || m_UsesRemaining >= InitMaxUses )
|
|
return;
|
|
|
|
if( m_LastReplenished + ChargeReplenishRate < DateTime.UtcNow )
|
|
{
|
|
TimeSpan timeDifference = DateTime.UtcNow - m_LastReplenished;
|
|
|
|
m_UsesRemaining = Math.Min( m_UsesRemaining + (int)( timeDifference.Ticks / ChargeReplenishRate.Ticks), InitMaxUses ); //How rude of TimeSpan to not allow timespan division.
|
|
m_LastReplenished = DateTime.UtcNow;
|
|
|
|
if( invalidate )
|
|
InvalidateProperties();
|
|
|
|
}
|
|
}
|
|
|
|
public void ScaleUses()
|
|
{
|
|
UsesRemaining = (UsesRemaining * GetUsesScalar()) / 100;
|
|
//InvalidateProperties();
|
|
}
|
|
|
|
public void UnscaleUses()
|
|
{
|
|
UsesRemaining = (UsesRemaining * 100) / GetUsesScalar();
|
|
}
|
|
|
|
public int GetUsesScalar()
|
|
{
|
|
if ( m_Quality == InstrumentQuality.Exceptional )
|
|
return 200;
|
|
|
|
return 100;
|
|
}
|
|
|
|
public void ConsumeUse( Mobile from )
|
|
{
|
|
// TODO: Confirm what must happen here?
|
|
|
|
if ( UsesRemaining > 1 )
|
|
{
|
|
--UsesRemaining;
|
|
}
|
|
else
|
|
{
|
|
if ( from != null )
|
|
from.SendLocalizedMessage( 502079 ); // The instrument played its last tune.
|
|
|
|
Delete();
|
|
}
|
|
}
|
|
|
|
private static Hashtable m_Instruments = new Hashtable();
|
|
|
|
public static BaseInstrument GetInstrument( Mobile from )
|
|
{
|
|
BaseInstrument item = m_Instruments[from] as BaseInstrument;
|
|
|
|
if ( item == null )
|
|
return null;
|
|
|
|
if ( !item.IsChildOf( from.Backpack ) )
|
|
{
|
|
m_Instruments.Remove( from );
|
|
return null;
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
public static int GetBardRange( Mobile bard, SkillName skill )
|
|
{
|
|
return 8 + (int)(bard.Skills[skill].Value / 15);
|
|
}
|
|
|
|
public static void PickInstrument( Mobile from, InstrumentPickedCallback callback )
|
|
{
|
|
BaseInstrument instrument = GetInstrument( from );
|
|
|
|
if ( instrument != null )
|
|
{
|
|
if ( callback != null )
|
|
callback( from, instrument );
|
|
}
|
|
else
|
|
{
|
|
from.SendLocalizedMessage( 500617 ); // What instrument shall you play?
|
|
from.BeginTarget( 1, false, TargetFlags.None, new TargetStateCallback( OnPickedInstrument ), callback );
|
|
}
|
|
}
|
|
|
|
public static void OnPickedInstrument( Mobile from, object targeted, object state )
|
|
{
|
|
BaseInstrument instrument = targeted as BaseInstrument;
|
|
|
|
if ( instrument == null )
|
|
{
|
|
from.SendLocalizedMessage( 500619 ); // That is not a musical instrument.
|
|
}
|
|
else
|
|
{
|
|
SetInstrument( from, instrument );
|
|
|
|
InstrumentPickedCallback callback = state as InstrumentPickedCallback;
|
|
|
|
if ( callback != null )
|
|
callback( from, instrument );
|
|
}
|
|
}
|
|
|
|
public static bool IsMageryCreature( BaseCreature bc )
|
|
{
|
|
return ( bc != null && bc.AI == AIType.AI_Mage && bc.Skills[SkillName.Magery].Base > 5.0 );
|
|
}
|
|
|
|
public static bool IsFireBreathingCreature( BaseCreature bc )
|
|
{
|
|
if ( bc == null )
|
|
return false;
|
|
|
|
return bc.HasBreath;
|
|
}
|
|
|
|
public static bool IsPoisonImmune( BaseCreature bc )
|
|
{
|
|
return ( bc != null && bc.PoisonImmune != null );
|
|
}
|
|
|
|
public static int GetPoisonLevel( BaseCreature bc )
|
|
{
|
|
if ( bc == null )
|
|
return 0;
|
|
|
|
Poison p = bc.HitPoison;
|
|
|
|
if ( p == null )
|
|
return 0;
|
|
|
|
return p.Level + 1;
|
|
}
|
|
|
|
public static double GetBaseDifficulty( Mobile targ )
|
|
{
|
|
/* Difficulty TODO: Add another 100 points for each of the following abilities:
|
|
- Radiation or Aura Damage (Heat, Cold etc.)
|
|
- Summoning Undead
|
|
*/
|
|
|
|
double val = (targ.HitsMax * 1.6) + targ.StamMax + targ.ManaMax;
|
|
|
|
val += targ.SkillsTotal / 10;
|
|
|
|
if ( val > 700 )
|
|
val = 700 + (int)((val - 700) * (3.0 / 11));
|
|
|
|
BaseCreature bc = targ as BaseCreature;
|
|
|
|
if ( IsMageryCreature( bc ) )
|
|
val += 100;
|
|
|
|
if ( IsFireBreathingCreature( bc ) )
|
|
val += 100;
|
|
|
|
if ( IsPoisonImmune( bc ) )
|
|
val += 100;
|
|
|
|
if ( targ is VampireBat || targ is VampireBatFamiliar )
|
|
val += 100;
|
|
|
|
val += GetPoisonLevel( bc ) * 20;
|
|
|
|
val /= 10;
|
|
|
|
if ( bc != null && bc.IsParagon )
|
|
val += 40.0;
|
|
|
|
if ( Core.SE && val > 160.0 )
|
|
val = 160.0;
|
|
|
|
return val;
|
|
}
|
|
|
|
public double GetDifficultyFor( Mobile targ )
|
|
{
|
|
double val = GetBaseDifficulty( targ );
|
|
|
|
if ( m_Quality == InstrumentQuality.Exceptional )
|
|
val -= 5.0; // 10%
|
|
|
|
if ( m_Slayer != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer );
|
|
|
|
if ( entry != null )
|
|
{
|
|
if ( entry.Slays( targ ) )
|
|
val -= 10.0; // 20%
|
|
else if ( entry.Group.OppositionSuperSlays( targ ) )
|
|
val += 10.0; // -20%
|
|
}
|
|
}
|
|
|
|
if ( m_Slayer2 != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer2 );
|
|
|
|
if ( entry != null )
|
|
{
|
|
if ( entry.Slays( targ ) )
|
|
val -= 10.0; // 20%
|
|
else if ( entry.Group.OppositionSuperSlays( targ ) )
|
|
val += 10.0; // -20%
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
public static void SetInstrument( Mobile from, BaseInstrument item )
|
|
{
|
|
m_Instruments[from] = item;
|
|
}
|
|
|
|
public BaseInstrument( int itemID, int wellSound, int badlySound ) : base( itemID )
|
|
{
|
|
m_WellSound = wellSound;
|
|
m_BadlySound = badlySound;
|
|
UsesRemaining = Utility.RandomMinMax( InitMinUses, InitMaxUses );
|
|
}
|
|
|
|
public override void GetProperties( ObjectPropertyList list )
|
|
{
|
|
int oldUses = m_UsesRemaining;
|
|
CheckReplenishUses( false );
|
|
|
|
base.GetProperties( list );
|
|
|
|
if ( m_Crafter != null )
|
|
list.Add( 1050043, m_Crafter.Name ); // crafted by ~1_NAME~
|
|
|
|
if ( m_Quality == InstrumentQuality.Exceptional )
|
|
list.Add( 1060636 ); // exceptional
|
|
|
|
list.Add( 1060584, m_UsesRemaining.ToString() ); // uses remaining: ~1_val~
|
|
|
|
if( m_ReplenishesCharges )
|
|
list.Add( 1070928 ); // Replenish Charges
|
|
|
|
if( m_Slayer != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer );
|
|
if( entry != null )
|
|
list.Add( entry.Title );
|
|
}
|
|
|
|
if( m_Slayer2 != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer2 );
|
|
if( entry != null )
|
|
list.Add( entry.Title );
|
|
}
|
|
|
|
if( m_UsesRemaining != oldUses )
|
|
Timer.DelayCall( TimeSpan.Zero, new TimerCallback( InvalidateProperties ) );
|
|
}
|
|
|
|
public override void OnSingleClick( Mobile from )
|
|
{
|
|
ArrayList attrs = new ArrayList();
|
|
|
|
if ( DisplayLootType )
|
|
{
|
|
if ( LootType == LootType.Blessed )
|
|
attrs.Add( new EquipInfoAttribute( 1038021 ) ); // blessed
|
|
else if ( LootType == LootType.Cursed )
|
|
attrs.Add( new EquipInfoAttribute( 1049643 ) ); // cursed
|
|
}
|
|
|
|
if ( m_Quality == InstrumentQuality.Exceptional )
|
|
attrs.Add( new EquipInfoAttribute( 1018305 - (int)m_Quality ) );
|
|
|
|
if( m_ReplenishesCharges )
|
|
attrs.Add( new EquipInfoAttribute( 1070928 ) ); // Replenish Charges
|
|
|
|
// TODO: Must this support item identification?
|
|
if( m_Slayer != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer );
|
|
if( entry != null )
|
|
attrs.Add( new EquipInfoAttribute( entry.Title ) );
|
|
}
|
|
|
|
if( m_Slayer2 != SlayerName.None )
|
|
{
|
|
SlayerEntry entry = SlayerGroup.GetEntryByName( m_Slayer2 );
|
|
if( entry != null )
|
|
attrs.Add( new EquipInfoAttribute( entry.Title ) );
|
|
}
|
|
|
|
int number;
|
|
|
|
if ( Name == null )
|
|
{
|
|
number = LabelNumber;
|
|
}
|
|
else
|
|
{
|
|
this.LabelTo( from, Name );
|
|
number = 1041000;
|
|
}
|
|
|
|
if ( attrs.Count == 0 && Crafter == null && Name != null )
|
|
return;
|
|
|
|
EquipmentInfo eqInfo = new EquipmentInfo( number, m_Crafter, false, (EquipInfoAttribute[])attrs.ToArray( typeof( EquipInfoAttribute ) ) );
|
|
|
|
from.Send( new DisplayEquipmentInfo( this, eqInfo ) );
|
|
}
|
|
|
|
public BaseInstrument( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.Write( (int) 3 ); // version
|
|
|
|
writer.Write( m_ReplenishesCharges );
|
|
if( m_ReplenishesCharges )
|
|
writer.Write( m_LastReplenished );
|
|
|
|
|
|
writer.Write( m_Crafter );
|
|
|
|
writer.WriteEncodedInt( (int) m_Quality );
|
|
writer.WriteEncodedInt( (int) m_Slayer );
|
|
writer.WriteEncodedInt( (int) m_Slayer2 );
|
|
|
|
writer.WriteEncodedInt( (int)UsesRemaining );
|
|
|
|
writer.WriteEncodedInt( (int) m_WellSound );
|
|
writer.WriteEncodedInt( (int) m_BadlySound );
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
switch ( version )
|
|
{
|
|
case 3:
|
|
{
|
|
m_ReplenishesCharges = reader.ReadBool();
|
|
|
|
if( m_ReplenishesCharges )
|
|
m_LastReplenished = reader.ReadDateTime();
|
|
|
|
goto case 2;
|
|
}
|
|
case 2:
|
|
{
|
|
m_Crafter = reader.ReadMobile();
|
|
|
|
m_Quality = (InstrumentQuality)reader.ReadEncodedInt();
|
|
m_Slayer = (SlayerName)reader.ReadEncodedInt();
|
|
m_Slayer2 = (SlayerName)reader.ReadEncodedInt();
|
|
|
|
UsesRemaining = reader.ReadEncodedInt();
|
|
|
|
m_WellSound = reader.ReadEncodedInt();
|
|
m_BadlySound = reader.ReadEncodedInt();
|
|
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
m_Crafter = reader.ReadMobile();
|
|
|
|
m_Quality = (InstrumentQuality)reader.ReadEncodedInt();
|
|
m_Slayer = (SlayerName)reader.ReadEncodedInt();
|
|
|
|
UsesRemaining = reader.ReadEncodedInt();
|
|
|
|
m_WellSound = reader.ReadEncodedInt();
|
|
m_BadlySound = reader.ReadEncodedInt();
|
|
|
|
break;
|
|
}
|
|
case 0:
|
|
{
|
|
m_WellSound = reader.ReadInt();
|
|
m_BadlySound = reader.ReadInt();
|
|
UsesRemaining = Utility.RandomMinMax( InitMinUses, InitMaxUses );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
CheckReplenishUses();
|
|
}
|
|
|
|
public override void OnDoubleClick( Mobile from )
|
|
{
|
|
if ( !from.InRange( GetWorldLocation(), 1 ) )
|
|
{
|
|
from.SendLocalizedMessage( 500446 ); // That is too far away.
|
|
}
|
|
else if ( from.BeginAction( typeof( BaseInstrument ) ) )
|
|
{
|
|
SetInstrument( from, this );
|
|
|
|
// Delay of 7 second before beign able to play another instrument again
|
|
new InternalTimer( from ).Start();
|
|
|
|
if ( CheckMusicianship( from ) )
|
|
PlayInstrumentWell( from );
|
|
else
|
|
PlayInstrumentBadly( from );
|
|
}
|
|
else
|
|
{
|
|
from.SendLocalizedMessage( 500119 ); // You must wait to perform another action
|
|
}
|
|
}
|
|
|
|
public static bool CheckMusicianship( Mobile m )
|
|
{
|
|
m.CheckSkill( SkillName.Musicianship, 0.0, 120.0 );
|
|
|
|
return ( (m.Skills[SkillName.Musicianship].Value / 100) > Utility.RandomDouble() );
|
|
}
|
|
|
|
public void PlayInstrumentWell( Mobile from )
|
|
{
|
|
from.PlaySound( m_WellSound );
|
|
}
|
|
|
|
public void PlayInstrumentBadly( Mobile from )
|
|
{
|
|
from.PlaySound( m_BadlySound );
|
|
}
|
|
|
|
private class InternalTimer : Timer
|
|
{
|
|
private Mobile m_From;
|
|
|
|
public InternalTimer( Mobile from ) : base( TimeSpan.FromSeconds( 6.0 ) )
|
|
{
|
|
m_From = from;
|
|
Priority = TimerPriority.TwoFiftyMS;
|
|
}
|
|
|
|
protected override void OnTick()
|
|
{
|
|
m_From.EndAction( typeof( BaseInstrument ) );
|
|
}
|
|
}
|
|
#region ICraftable Members
|
|
|
|
public int OnCraft( int quality, bool makersMark, Mobile from, CraftSystem craftSystem, Type typeRes, BaseTool tool, CraftItem craftItem, int resHue )
|
|
{
|
|
Quality = (InstrumentQuality)quality;
|
|
|
|
if ( makersMark )
|
|
Crafter = from;
|
|
|
|
return quality;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |