461 lines
12 KiB
C#
461 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Server.Mobiles;
|
|
using Server.Targeting;
|
|
using Server.ContextMenus;
|
|
using Server.Network;
|
|
using Server.Regions;
|
|
using Server.Spells;
|
|
using Server.Spells.Ninjitsu;
|
|
|
|
namespace Server.Items
|
|
{
|
|
public class BallOfSummoning : Item, TranslocationItem
|
|
{
|
|
private int m_Charges;
|
|
private int m_Recharges;
|
|
private BaseCreature m_Pet;
|
|
private string m_PetName;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int Charges
|
|
{
|
|
get => m_Charges;
|
|
set
|
|
{
|
|
if ( value > MaxCharges )
|
|
m_Charges = MaxCharges;
|
|
else if ( value < 0 )
|
|
m_Charges = 0;
|
|
else
|
|
m_Charges = value;
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int Recharges
|
|
{
|
|
get => m_Recharges;
|
|
set
|
|
{
|
|
if ( value > MaxRecharges )
|
|
m_Recharges = MaxRecharges;
|
|
else if ( value < 0 )
|
|
m_Recharges = 0;
|
|
else
|
|
m_Recharges = value;
|
|
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int MaxCharges => 20;
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public int MaxRecharges => 255;
|
|
|
|
public string TranslocationItemName => "crystal ball of pet summoning";
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public BaseCreature Pet
|
|
{
|
|
get
|
|
{
|
|
if ( m_Pet != null && m_Pet.Deleted )
|
|
{
|
|
m_Pet = null;
|
|
InternalUpdatePetName();
|
|
}
|
|
|
|
return m_Pet;
|
|
}
|
|
set
|
|
{
|
|
m_Pet = value;
|
|
InternalUpdatePetName();
|
|
}
|
|
}
|
|
|
|
[CommandProperty( AccessLevel.GameMaster )]
|
|
public string PetName => m_PetName;
|
|
|
|
[Constructible]
|
|
public BallOfSummoning() : base( 0xE2E )
|
|
{
|
|
Weight = 10.0;
|
|
Light = LightType.Circle150;
|
|
|
|
m_Charges = Utility.RandomMinMax( 3, 9 );
|
|
|
|
m_PetName = "";
|
|
}
|
|
|
|
public override void AddNameProperty( ObjectPropertyList list )
|
|
{
|
|
list.Add( 1054131, m_Charges.ToString() + ( m_PetName.Length == 0 ? "\t " : "\t" + m_PetName ) ); // a crystal ball of pet summoning: [charges: ~1_charges~] : [linked pet: ~2_petName~]
|
|
}
|
|
|
|
public override void OnSingleClick( Mobile from )
|
|
{
|
|
LabelTo( from, 1054131, m_Charges.ToString() + ( m_PetName.Length == 0 ? "\t " : "\t" + m_PetName ) ); // a crystal ball of pet summoning: [charges: ~1_charges~] : [linked pet: ~2_petName~]
|
|
}
|
|
|
|
private delegate void BallCallback( Mobile from );
|
|
|
|
public override void GetContextMenuEntries( Mobile from, List<ContextMenuEntry> list )
|
|
{
|
|
base.GetContextMenuEntries( from, list );
|
|
|
|
if ( from.Alive && RootParent == from )
|
|
{
|
|
if ( Pet == null )
|
|
{
|
|
list.Add( new BallEntry( new BallCallback( LinkPet ), 6180 ) );
|
|
}
|
|
else
|
|
{
|
|
list.Add(new BallEntry(new BallCallback( CastSummonPet ), 6181 ));
|
|
list.Add( new BallEntry( new BallCallback( UpdatePetName ), 6183 ) );
|
|
list.Add( new BallEntry( new BallCallback( UnlinkPet ), 6182 ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
private class BallEntry : ContextMenuEntry
|
|
{
|
|
private BallCallback m_Callback;
|
|
|
|
public BallEntry( BallCallback callback, int number ) : base( number, 2 )
|
|
{
|
|
m_Callback = callback;
|
|
}
|
|
|
|
public override void OnClick()
|
|
{
|
|
Mobile from = Owner.From;
|
|
|
|
if ( from.CheckAlive() )
|
|
m_Callback( from );
|
|
}
|
|
}
|
|
|
|
public override void OnDoubleClick( Mobile from )
|
|
{
|
|
if ( RootParent != from ) // TODO: Previous implementation allowed use on ground, without house protection checks. What is the correct behavior?
|
|
{
|
|
from.LocalOverheadMessage( MessageType.Regular, 0x3B2, 1042001 ); // That must be in your pack for you to use it.
|
|
return;
|
|
}
|
|
|
|
AnimalFormContext animalContext = AnimalForm.GetContext( from );
|
|
|
|
if ( Core.ML && animalContext != null )
|
|
{
|
|
from.LocalOverheadMessage( MessageType.Regular, 0x3B2, 1080073 ); // You cannot use a Crystal Ball of Pet Summoning while in animal form.
|
|
return;
|
|
}
|
|
|
|
if ( Pet == null )
|
|
{
|
|
LinkPet( from );
|
|
}
|
|
else
|
|
{
|
|
CastSummonPet( from );
|
|
}
|
|
}
|
|
|
|
public void LinkPet( Mobile from )
|
|
{
|
|
BaseCreature pet = Pet;
|
|
|
|
if ( Deleted || pet != null || RootParent != from )
|
|
return;
|
|
|
|
from.SendLocalizedMessage( 1054114 ); // Target your pet that you wish to link to this Crystal Ball of Pet Summoning.
|
|
from.Target = new PetLinkTarget( this );
|
|
}
|
|
|
|
private class PetLinkTarget : Target
|
|
{
|
|
private BallOfSummoning m_Ball;
|
|
|
|
public PetLinkTarget( BallOfSummoning ball ) : base( -1, false, TargetFlags.None )
|
|
{
|
|
m_Ball = ball;
|
|
}
|
|
|
|
protected override void OnTarget( Mobile from, object targeted )
|
|
{
|
|
if ( m_Ball.Deleted || m_Ball.Pet != null )
|
|
return;
|
|
|
|
if ( m_Ball.RootParent != from )
|
|
{
|
|
from.LocalOverheadMessage( MessageType.Regular, 0x3B2, 1042001 ); // That must be in your pack for you to use it.
|
|
}
|
|
else if ( targeted is BaseCreature creature )
|
|
{
|
|
if ( !creature.Controlled || creature.ControlMaster != from )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( m_Ball, from, 1054117, 0x59 ); // You may only link your own pets to a Crystal Ball of Pet Summoning.
|
|
}
|
|
else if ( !creature.IsBonded )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( m_Ball, from, 1054118, 0x59 ); // You must bond with your pet before it can be linked to a Crystal Ball of Pet Summoning.
|
|
}
|
|
else
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( m_Ball, from, 1054119, 0x59 ); // Your pet is now linked to this Crystal Ball of Pet Summoning.
|
|
|
|
m_Ball.Pet = creature;
|
|
}
|
|
}
|
|
else if ( targeted == m_Ball )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( m_Ball, from, 1054115, 0x59 ); // The Crystal Ball of Pet Summoning cannot summon itself.
|
|
}
|
|
else
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( m_Ball, from, 1054116, 0x59 ); // Only pets can be linked to this Crystal Ball of Pet Summoning.
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CastSummonPet( Mobile from )
|
|
{
|
|
BaseCreature pet = Pet;
|
|
|
|
if ( Deleted || pet == null || RootParent != from )
|
|
return;
|
|
|
|
if ( Charges == 0 )
|
|
{
|
|
SendLocalizedMessageTo( from, 1054122 ); // The Crystal Ball darkens. It must be charged before it can be used again.
|
|
}
|
|
else if ( pet is BaseMount mount && mount.Rider == from )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( this, from, 1054124, 0x36 ); // The Crystal Ball fills with a yellow mist. Why would you summon your pet while riding it?
|
|
}
|
|
else if ( pet.Map == Map.Internal && ( !pet.IsStabled || (from.Followers + pet.ControlSlots) > from.FollowersMax ) )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( this, from, 1054125, 0x5 ); // The Crystal Ball fills with a blue mist. Your pet is not responding to the summons.
|
|
}
|
|
else if ( ( !pet.Controlled || pet.ControlMaster != from ) && !from.Stabled.Contains( pet ) )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( this, from, 1054126, 0x8FD ); // The Crystal Ball fills with a grey mist. You are not the owner of the pet you are attempting to summon.
|
|
}
|
|
else if ( !pet.IsBonded )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( this, from, 1054127, 0x22 ); // The Crystal Ball fills with a red mist. You appear to have let your bond to your pet deteriorate.
|
|
}
|
|
else if ( from.Map == Map.Ilshenar || from.Region.IsPartOf( typeof( DungeonRegion ) ) || from.Region.IsPartOf( typeof( Jail ) ) || from.Region.IsPartOf( typeof( Engines.ConPVP.SafeZone ) ) )
|
|
{
|
|
from.Send( new AsciiMessage( Serial, ItemID, MessageType.Regular, 0x22, 3, "", "You cannot summon your pet to this location." ) );
|
|
}
|
|
else if ( Core.ML && from is PlayerMobile mobile && DateTime.UtcNow < mobile.LastPetBallTime.AddSeconds( 15.0 ) )
|
|
{
|
|
MessageHelper.SendLocalizedMessageTo( this, mobile, 1080072, 0x22 ); // You must wait a few seconds before you can summon your pet.
|
|
}
|
|
else
|
|
{
|
|
if ( Core.ML )
|
|
new PetSummoningSpell( this, from ).Cast();
|
|
else
|
|
SummonPet( from );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public void SummonPet( Mobile from )
|
|
{
|
|
BaseCreature pet = Pet;
|
|
|
|
if ( pet == null )
|
|
return;
|
|
|
|
Charges--;
|
|
|
|
if ( pet.IsStabled )
|
|
{
|
|
pet.SetControlMaster( from );
|
|
|
|
if ( pet.Summoned )
|
|
pet.SummonMaster = from;
|
|
|
|
pet.ControlTarget = from;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
|
|
pet.IsStabled = false;
|
|
pet.StabledBy = null;
|
|
from.Stabled.Remove( pet );
|
|
|
|
if ( from is PlayerMobile mobile )
|
|
mobile.AutoStabled.Remove( pet );
|
|
}
|
|
|
|
pet.MoveToWorld( from.Location, from.Map );
|
|
|
|
MessageHelper.SendLocalizedMessageTo( this, from, 1054128, 0x43 ); // The Crystal Ball fills with a green mist. Your pet has been summoned.
|
|
|
|
if ( from is PlayerMobile playerMobile )
|
|
{
|
|
playerMobile.LastPetBallTime = DateTime.UtcNow;
|
|
}
|
|
}
|
|
|
|
public void UnlinkPet( Mobile from )
|
|
{
|
|
if ( !Deleted && Pet != null && RootParent == from )
|
|
{
|
|
Pet = null;
|
|
|
|
SendLocalizedMessageTo( from, 1054120 ); // This crystal ball is no longer linked to a pet.
|
|
}
|
|
}
|
|
|
|
public void UpdatePetName( Mobile from )
|
|
{
|
|
InternalUpdatePetName();
|
|
}
|
|
|
|
private void InternalUpdatePetName()
|
|
{
|
|
BaseCreature pet = Pet;
|
|
|
|
if ( pet == null )
|
|
m_PetName = "";
|
|
else
|
|
m_PetName = pet.Name;
|
|
|
|
InvalidateProperties();
|
|
}
|
|
|
|
public BallOfSummoning( Serial serial ) : base( serial )
|
|
{
|
|
}
|
|
|
|
public override void Serialize( GenericWriter writer )
|
|
{
|
|
base.Serialize( writer );
|
|
|
|
writer.WriteEncodedInt( (int) 1 ); // version
|
|
|
|
writer.WriteEncodedInt( (int) m_Recharges );
|
|
|
|
writer.WriteEncodedInt( (int) m_Charges );
|
|
writer.Write( (Mobile) Pet );
|
|
writer.Write( (string) m_PetName );
|
|
}
|
|
|
|
public override void Deserialize( GenericReader reader )
|
|
{
|
|
base.Deserialize( reader );
|
|
|
|
int version = reader.ReadEncodedInt();
|
|
|
|
switch ( version )
|
|
{
|
|
case 1:
|
|
{
|
|
m_Recharges = reader.ReadEncodedInt();
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
m_Charges = Math.Min( reader.ReadEncodedInt(), MaxCharges );
|
|
Pet = (BaseCreature) reader.ReadMobile();
|
|
m_PetName = reader.ReadString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class PetSummoningSpell : Spell
|
|
{
|
|
private static SpellInfo m_Info = new SpellInfo( "Ball Of Summoning", "", 230 );
|
|
|
|
private BallOfSummoning m_Ball;
|
|
private Mobile m_Caster;
|
|
|
|
public PetSummoningSpell( BallOfSummoning ball, Mobile caster )
|
|
: base( caster, null, m_Info )
|
|
{
|
|
m_Caster = caster;
|
|
m_Ball = ball;
|
|
}
|
|
|
|
public override bool ClearHandsOnCast => false;
|
|
public override bool RevealOnCast => true;
|
|
|
|
public override TimeSpan GetCastRecovery()
|
|
{
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
public override double CastDelayFastScalar => 0;
|
|
|
|
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds( 2.0 );
|
|
|
|
public override int GetMana()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public override bool ConsumeReagents()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public override bool CheckFizzle()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
private bool m_Stop;
|
|
|
|
public void Stop()
|
|
{
|
|
m_Stop = true;
|
|
Disturb( DisturbType.Hurt, false, false );
|
|
}
|
|
|
|
public override bool CheckDisturb( DisturbType type, bool checkFirst, bool resistable )
|
|
{
|
|
if ( type == DisturbType.EquipRequest || type == DisturbType.UseRequest/* || type == DisturbType.Hurt*/ )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void DoHurtFizzle()
|
|
{
|
|
if ( !m_Stop )
|
|
base.DoHurtFizzle();
|
|
}
|
|
|
|
public override void DoFizzle()
|
|
{
|
|
if ( !m_Stop )
|
|
base.DoFizzle();
|
|
}
|
|
|
|
public override void OnDisturb( DisturbType type, bool message )
|
|
{
|
|
if ( message && !m_Stop )
|
|
Caster.SendLocalizedMessage( 1080074 ); // You have been disrupted while attempting to summon your pet!
|
|
}
|
|
|
|
public override void OnCast()
|
|
{
|
|
m_Ball.SummonPet( m_Caster );
|
|
|
|
FinishSequence();
|
|
}
|
|
}
|
|
}
|
|
}
|