4952 lines
150 KiB
C#
4952 lines
150 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Metrics;
|
|
using ModernUO.CodeGeneratedEvents;
|
|
using Server.Accounting;
|
|
using Server.Collections;
|
|
using Server.ContextMenus;
|
|
using Server.Engines.BulkOrders;
|
|
using Server.Engines.CannedEvil;
|
|
using Server.Engines.ConPVP;
|
|
using Server.Engines.Craft;
|
|
using Server.Engines.Help;
|
|
using Server.Engines.MLQuests;
|
|
using Server.Engines.MLQuests.Gumps;
|
|
using Server.Engines.PartySystem;
|
|
using Server.Engines.PlayerMurderSystem;
|
|
using Server.Engines.Quests;
|
|
using Server.Engines.Virtues;
|
|
using Server.Ethics;
|
|
using Server.Factions;
|
|
using Server.Guilds;
|
|
using Server.Gumps;
|
|
using Server.Items;
|
|
using Server.Misc;
|
|
using Server.Movement;
|
|
using Server.Multis;
|
|
using Server.Network;
|
|
using Server.Regions;
|
|
using Server.SkillHandlers;
|
|
using Server.Spells;
|
|
using Server.Spells.Bushido;
|
|
using Server.Spells.Fifth;
|
|
using Server.Spells.First;
|
|
using Server.Spells.Fourth;
|
|
using Server.Spells.Mysticism;
|
|
using Server.Spells.Necromancy;
|
|
using Server.Spells.Ninjitsu;
|
|
using Server.Spells.Second;
|
|
using Server.Spells.Sixth;
|
|
using Server.Spells.Spellweaving;
|
|
using Server.Targeting;
|
|
using BaseQuestGump = Server.Engines.MLQuests.Gumps.BaseQuestGump;
|
|
using CalcMoves = Server.Movement.Movement;
|
|
using QuestOfferGump = Server.Engines.MLQuests.Gumps.QuestOfferGump;
|
|
using RankDefinition = Server.Guilds.RankDefinition;
|
|
|
|
namespace Server.Mobiles
|
|
{
|
|
[Flags]
|
|
public enum PlayerFlag // First 16 bits are reserved for default-distro use, start custom flags at 0x00010000
|
|
{
|
|
None = 0x00000000,
|
|
Glassblowing = 0x00000001,
|
|
Masonry = 0x00000002,
|
|
SandMining = 0x00000004,
|
|
StoneMining = 0x00000008,
|
|
ToggleMiningStone = 0x00000010,
|
|
KarmaLocked = 0x00000020,
|
|
AutoRenewInsurance = 0x00000040,
|
|
UseOwnFilter = 0x00000080,
|
|
PagingSquelched = 0x00000200,
|
|
Young = 0x00000400,
|
|
AcceptGuildInvites = 0x00000800,
|
|
DisplayChampionTitle = 0x00001000,
|
|
HasStatReward = 0x00002000,
|
|
RefuseTrades = 0x00004000
|
|
}
|
|
|
|
public enum NpcGuild
|
|
{
|
|
None,
|
|
MagesGuild,
|
|
WarriorsGuild,
|
|
ThievesGuild,
|
|
RangersGuild,
|
|
HealersGuild,
|
|
MinersGuild,
|
|
MerchantsGuild,
|
|
TinkersGuild,
|
|
TailorsGuild,
|
|
FishermensGuild,
|
|
BardsGuild,
|
|
BlacksmithsGuild
|
|
}
|
|
|
|
public enum SolenFriendship
|
|
{
|
|
None,
|
|
Red,
|
|
Black
|
|
}
|
|
|
|
public enum BlockMountType
|
|
{
|
|
None = -1,
|
|
Dazed = 1040024, // You are still too dazed from being knocked off your mount to ride!
|
|
BolaRecovery = 1062910, // You cannot mount while recovering from a bola throw.
|
|
DismountRecovery = 1070859 // You cannot mount while recovering from a dismount special maneuver.
|
|
}
|
|
|
|
public partial class PlayerMobile : Mobile, IHonorTarget, IHasSteps
|
|
{
|
|
public static readonly Counter<long> playerDeathCounter = Telemetry.MobilesMeter.CreateCounter<long>("player_death_count");
|
|
private static bool m_NoRecursion;
|
|
|
|
private static readonly Point3D[] m_TrammelDeathDestinations =
|
|
{
|
|
new(1481, 1612, 20),
|
|
new(2708, 2153, 0),
|
|
new(2249, 1230, 0),
|
|
new(5197, 3994, 37),
|
|
new(1412, 3793, 0),
|
|
new(3688, 2232, 20),
|
|
new(2578, 604, 0),
|
|
new(4397, 1089, 0),
|
|
new(5741, 3218, -2),
|
|
new(2996, 3441, 15),
|
|
new(624, 2225, 0),
|
|
new(1916, 2814, 0),
|
|
new(2929, 854, 0),
|
|
new(545, 967, 0),
|
|
new(3665, 2587, 0)
|
|
};
|
|
|
|
private static readonly Point3D[] m_IlshenarDeathDestinations =
|
|
{
|
|
new(1216, 468, -13),
|
|
new(723, 1367, -60),
|
|
new(745, 725, -28),
|
|
new(281, 1017, 0),
|
|
new(986, 1011, -32),
|
|
new(1175, 1287, -30),
|
|
new(1533, 1341, -3),
|
|
new(529, 217, -44),
|
|
new(1722, 219, 96)
|
|
};
|
|
|
|
private static readonly Point3D[] m_MalasDeathDestinations =
|
|
{
|
|
new(2079, 1376, -70),
|
|
new(944, 519, -71)
|
|
};
|
|
|
|
private static readonly Point3D[] m_TokunoDeathDestinations =
|
|
{
|
|
new(1166, 801, 27),
|
|
new(782, 1228, 25),
|
|
new(268, 624, 15)
|
|
};
|
|
|
|
private HashSet<int> _acquiredRecipes;
|
|
|
|
private HashSet<Mobile> _allFollowers;
|
|
private int m_BeardModID = -1, m_BeardModHue;
|
|
|
|
// TODO: Pool BuffInfo objects
|
|
private Dictionary<BuffIcon, BuffInfo> m_BuffTable;
|
|
|
|
private DuelPlayer m_DuelPlayer;
|
|
|
|
private Type m_EnemyOfOneType;
|
|
private TimeSpan m_GameTime;
|
|
|
|
/*
|
|
* a value of zero means, that the mobile is not executing the spell. Otherwise,
|
|
* the value should match the BaseMana required
|
|
*/
|
|
|
|
private RankDefinition m_GuildRank;
|
|
|
|
private int m_HairModID = -1, m_HairModHue;
|
|
|
|
public DateTime _honorTime;
|
|
|
|
private Mobile m_InsuranceAward;
|
|
private int m_InsuranceBonus;
|
|
|
|
private int m_LastGlobalLight = -1, m_LastPersonalLight = -1;
|
|
|
|
private bool m_LastProtectedMessage;
|
|
|
|
private DateTime m_LastYoungHeal = DateTime.MinValue;
|
|
|
|
private DateTime m_LastYoungMessage = DateTime.MinValue;
|
|
|
|
private MountBlock _mountBlock;
|
|
|
|
private DateTime m_NextJustAward;
|
|
|
|
private int m_NextProtectionCheck = 10;
|
|
private DateTime m_NextSmithBulkOrder;
|
|
private DateTime m_NextTailorBulkOrder;
|
|
|
|
private bool m_NoDeltaRecursion;
|
|
|
|
// number of items that could not be automatically reinsured because gold in bank was not enough
|
|
private int m_NonAutoreinsuredItems;
|
|
|
|
private DateTime m_SavagePaintExpiration;
|
|
|
|
private DateTime[] m_StuckMenuUses;
|
|
|
|
private QuestArrow m_QuestArrow;
|
|
|
|
public PlayerMobile()
|
|
{
|
|
VisibilityList = new List<Mobile>();
|
|
PermaFlags = new List<Mobile>();
|
|
|
|
BOBFilter = new BOBFilter();
|
|
|
|
m_GameTime = TimeSpan.Zero;
|
|
m_GuildRank = RankDefinition.Lowest;
|
|
}
|
|
|
|
public PlayerMobile(Serial s) : base(s)
|
|
{
|
|
VisibilityList = new List<Mobile>();
|
|
}
|
|
|
|
public int StepsMax => 16;
|
|
|
|
public int StepsGainedPerIdleTime => 1;
|
|
|
|
public TimeSpan IdleTimePerStepsGain => TimeSpan.FromSeconds(1);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime AnkhNextUse { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan DisguiseTimeLeft => DisguisePersistence.TimeRemaining(this);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime PeacedUntil { get; set; }
|
|
|
|
public DesignContext DesignContext { get; set; }
|
|
|
|
public BlockMountType MountBlockReason => _mountBlock?.MountBlockReason ?? BlockMountType.None;
|
|
|
|
public override int MaxWeight => (Core.ML && Race == Race.Human ? 100 : 40) + (int)(3.5 * Str);
|
|
|
|
public override double ArmorRating
|
|
{
|
|
get
|
|
{
|
|
// BaseArmor ar;
|
|
var rating = 0.0;
|
|
|
|
AddArmorRating(ref rating, NeckArmor);
|
|
AddArmorRating(ref rating, HandArmor);
|
|
AddArmorRating(ref rating, HeadArmor);
|
|
AddArmorRating(ref rating, ArmsArmor);
|
|
AddArmorRating(ref rating, LegsArmor);
|
|
AddArmorRating(ref rating, ChestArmor);
|
|
AddArmorRating(ref rating, ShieldArmor);
|
|
|
|
return VirtualArmor + VirtualArmorMod + rating;
|
|
}
|
|
}
|
|
|
|
public SkillName[] AnimalFormRestrictedSkills { get; } =
|
|
{
|
|
SkillName.ArmsLore, SkillName.Begging, SkillName.Discordance, SkillName.Forensics,
|
|
SkillName.Inscribe, SkillName.ItemID, SkillName.Meditation, SkillName.Peacemaking,
|
|
SkillName.Provocation, SkillName.RemoveTrap, SkillName.SpiritSpeak, SkillName.Stealing,
|
|
SkillName.TasteID
|
|
};
|
|
|
|
public override double RacialSkillBonus
|
|
{
|
|
get
|
|
{
|
|
if (Core.ML && Race == Race.Human)
|
|
{
|
|
return 20.0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public List<Item> EquipSnapshot { get; private set; }
|
|
|
|
public SkillName Learning { get; set; } = (SkillName)(-1);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan SavagePaintExpiration
|
|
{
|
|
get => Utility.Max(m_SavagePaintExpiration - Core.Now, TimeSpan.Zero);
|
|
set => m_SavagePaintExpiration = Core.Now + value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan NextSmithBulkOrder
|
|
{
|
|
get => Utility.Max(m_NextSmithBulkOrder - Core.Now, TimeSpan.Zero);
|
|
set
|
|
{
|
|
try
|
|
{
|
|
m_NextSmithBulkOrder = Core.Now + value;
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan NextTailorBulkOrder
|
|
{
|
|
get => Utility.Max(m_NextTailorBulkOrder - Core.Now, TimeSpan.Zero);
|
|
set
|
|
{
|
|
try
|
|
{
|
|
m_NextTailorBulkOrder = Core.Now + value;
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime LastEscortTime { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime LastPetBallTime { get; set; }
|
|
|
|
public List<Mobile> VisibilityList { get; }
|
|
|
|
public List<Mobile> PermaFlags { get; private set; }
|
|
|
|
public override int Luck => AosAttributes.GetValue(this, AosAttribute.Luck);
|
|
|
|
public BOBFilter BOBFilter { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime SessionStart { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan GameTime
|
|
{
|
|
get
|
|
{
|
|
if (NetState != null)
|
|
{
|
|
return m_GameTime + (Core.Now - SessionStart);
|
|
}
|
|
|
|
return m_GameTime;
|
|
}
|
|
}
|
|
|
|
public override bool NewGuildDisplay => Guilds.Guild.NewGuildSystem;
|
|
|
|
public bool BedrollLogout { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override bool Paralyzed
|
|
{
|
|
get => base.Paralyzed;
|
|
set
|
|
{
|
|
base.Paralyzed = value;
|
|
|
|
if (value)
|
|
{
|
|
AddBuff(new BuffInfo(BuffIcon.Paralyze, 1075827)); // Paralyze/You are frozen and can not move
|
|
}
|
|
else
|
|
{
|
|
RemoveBuff(BuffIcon.Paralyze);
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public Player EthicPlayer { get; set; }
|
|
|
|
public PlayerState FactionPlayerState { get; set; }
|
|
|
|
// WARNING - This can be null!!
|
|
public HashSet<Mobile> Stabled { get; private set; }
|
|
|
|
// WARNING - This can be null!!
|
|
public HashSet<Mobile> AutoStabled { get; private set; }
|
|
|
|
public bool NinjaWepCooldown { get; set; }
|
|
|
|
// WARNING - This can be null!!
|
|
public HashSet<Mobile> AllFollowers => _allFollowers;
|
|
|
|
public RankDefinition GuildRank
|
|
{
|
|
get => AccessLevel >= AccessLevel.GameMaster ? RankDefinition.Leader : m_GuildRank;
|
|
set => m_GuildRank = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int GuildMessageHue { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int AllianceMessageHue { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Profession { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool IsStealthing // IsStealthing should be moved to Server.Mobiles
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public NpcGuild NpcGuild { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime NpcGuildJoinTime { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime NextBODTurnInTime { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime LastOnline { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public long LastMoved => LastMoveTime;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public TimeSpan NpcGuildGameTime { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ToTItemsTurnedIn { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ToTTotalMonsterFame { get; set; }
|
|
|
|
public int ExecutesLightningStrike { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ToothAche
|
|
{
|
|
get => CandyCane.GetToothAche(this);
|
|
set => CandyCane.SetToothAche(this, value);
|
|
}
|
|
|
|
public PlayerFlag Flags { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool PagingSquelched
|
|
{
|
|
get => GetFlag(PlayerFlag.PagingSquelched);
|
|
set => SetFlag(PlayerFlag.PagingSquelched, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Glassblowing
|
|
{
|
|
get => GetFlag(PlayerFlag.Glassblowing);
|
|
set => SetFlag(PlayerFlag.Glassblowing, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Masonry
|
|
{
|
|
get => GetFlag(PlayerFlag.Masonry);
|
|
set => SetFlag(PlayerFlag.Masonry, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool SandMining
|
|
{
|
|
get => GetFlag(PlayerFlag.SandMining);
|
|
set => SetFlag(PlayerFlag.SandMining, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool StoneMining
|
|
{
|
|
get => GetFlag(PlayerFlag.StoneMining);
|
|
set => SetFlag(PlayerFlag.StoneMining, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool ToggleMiningStone
|
|
{
|
|
get => GetFlag(PlayerFlag.ToggleMiningStone);
|
|
set => SetFlag(PlayerFlag.ToggleMiningStone, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool KarmaLocked
|
|
{
|
|
get => GetFlag(PlayerFlag.KarmaLocked);
|
|
set => SetFlag(PlayerFlag.KarmaLocked, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool AutoRenewInsurance
|
|
{
|
|
get => GetFlag(PlayerFlag.AutoRenewInsurance);
|
|
set => SetFlag(PlayerFlag.AutoRenewInsurance, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool UseOwnFilter
|
|
{
|
|
get => GetFlag(PlayerFlag.UseOwnFilter);
|
|
set => SetFlag(PlayerFlag.UseOwnFilter, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool AcceptGuildInvites
|
|
{
|
|
get => GetFlag(PlayerFlag.AcceptGuildInvites);
|
|
set => SetFlag(PlayerFlag.AcceptGuildInvites, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool HasStatReward
|
|
{
|
|
get => GetFlag(PlayerFlag.HasStatReward);
|
|
set => SetFlag(PlayerFlag.HasStatReward, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool RefuseTrades
|
|
{
|
|
get => GetFlag(PlayerFlag.RefuseTrades);
|
|
set => SetFlag(PlayerFlag.RefuseTrades, value);
|
|
}
|
|
|
|
public Dictionary<Type, int> RecoverableAmmo { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime AcceleratedStart { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SkillName AcceleratedSkill { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int HitsMax
|
|
{
|
|
get
|
|
{
|
|
int strBase;
|
|
var strOffs = GetStatOffset(StatType.Str);
|
|
|
|
if (Core.AOS)
|
|
{
|
|
strBase = Str; // this.Str already includes GetStatOffset/str
|
|
strOffs = AosAttributes.GetValue(this, AosAttribute.BonusHits);
|
|
|
|
if (Core.ML && strOffs > 25 && AccessLevel <= AccessLevel.Player)
|
|
{
|
|
strOffs = 25;
|
|
}
|
|
|
|
if (AnimalForm.UnderTransformation(this, typeof(BakeKitsune)) ||
|
|
AnimalForm.UnderTransformation(this, typeof(GreyWolf)))
|
|
{
|
|
strOffs += 20;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strBase = RawStr;
|
|
}
|
|
|
|
return strBase / 2 + 50 + strOffs;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int StamMax => base.StamMax + AosAttributes.GetValue(this, AosAttribute.BonusStam);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int ManaMax => base.ManaMax + AosAttributes.GetValue(this, AosAttribute.BonusMana) +
|
|
(Core.ML && Race == Race.Elf ? 20 : 0);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int Str
|
|
{
|
|
get
|
|
{
|
|
if (Core.ML && AccessLevel == AccessLevel.Player)
|
|
{
|
|
return Math.Min(base.Str, 150);
|
|
}
|
|
|
|
return base.Str;
|
|
}
|
|
set => base.Str = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int Int
|
|
{
|
|
get
|
|
{
|
|
if (Core.ML && AccessLevel == AccessLevel.Player)
|
|
{
|
|
return Math.Min(base.Int, 150);
|
|
}
|
|
|
|
return base.Int;
|
|
}
|
|
set => base.Int = value;
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public override int Dex
|
|
{
|
|
get
|
|
{
|
|
if (Core.ML && AccessLevel == AccessLevel.Player)
|
|
{
|
|
return Math.Min(base.Dex, 150);
|
|
}
|
|
|
|
return base.Dex;
|
|
}
|
|
set => base.Dex = value;
|
|
}
|
|
|
|
public DuelContext DuelContext { get; private set; }
|
|
|
|
public DuelPlayer DuelPlayer
|
|
{
|
|
get => m_DuelPlayer;
|
|
set
|
|
{
|
|
var wasInTourney = DuelContext?.Finished == false && DuelContext.m_Tournament != null;
|
|
|
|
m_DuelPlayer = value;
|
|
|
|
DuelContext = m_DuelPlayer?.Participant.Context;
|
|
|
|
var isInTourney = DuelContext?.Finished == false && DuelContext.m_Tournament != null;
|
|
|
|
if (wasInTourney != isInTourney)
|
|
{
|
|
SendEverything();
|
|
}
|
|
}
|
|
}
|
|
|
|
public QuestSystem Quest { get; set; }
|
|
|
|
public List<QuestRestartInfo> DoneQuests { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public SolenFriendship SolenFriendship { get; set; }
|
|
|
|
public Type EnemyOfOneType
|
|
{
|
|
get => m_EnemyOfOneType;
|
|
set
|
|
{
|
|
var oldType = m_EnemyOfOneType;
|
|
var newType = value;
|
|
|
|
if (oldType == newType)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_EnemyOfOneType = value;
|
|
|
|
DeltaEnemies(oldType, newType);
|
|
}
|
|
}
|
|
|
|
public bool WaitingForEnemy { get; set; }
|
|
|
|
public HonorContext SentHonorContext { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Young
|
|
{
|
|
get => GetFlag(PlayerFlag.Young);
|
|
set
|
|
{
|
|
SetFlag(PlayerFlag.Young, value);
|
|
InvalidateProperties();
|
|
}
|
|
}
|
|
|
|
public SpeechLog SpeechLog { get; private set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool DisplayChampionTitle
|
|
{
|
|
get => GetFlag(PlayerFlag.DisplayChampionTitle);
|
|
set => SetFlag(PlayerFlag.DisplayChampionTitle, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster, canModify: true)]
|
|
public ChampionTitleContext ChampionTitles => ChampionTitleSystem.GetOrCreateChampionTitleContext(this);
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ShortTermMurders
|
|
{
|
|
get => PlayerMurderSystem.GetMurderContext(this, out var context) ? context.ShortTermMurders : 0;
|
|
set => PlayerMurderSystem.ManuallySetShortTermMurders(this, value);
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime ShortTermMurderExpiration
|
|
=> PlayerMurderSystem.GetMurderContext(this, out var context) && context.ShortTermMurders > 0
|
|
? Core.Now + (context.ShortTermElapse - GameTime)
|
|
: DateTime.MinValue;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public DateTime LongTermMurderExpiration
|
|
=> Kills > 0 && PlayerMurderSystem.GetMurderContext(this, out var context)
|
|
? Core.Now + (context.LongTermElapse - GameTime)
|
|
: DateTime.MinValue;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int KnownRecipes => _acquiredRecipes?.Count ?? 0;
|
|
|
|
[CommandProperty(AccessLevel.Counselor, canModify: true)]
|
|
public VirtueContext Virtues => VirtueSystem.GetOrCreateVirtues(this);
|
|
|
|
public HonorContext ReceivedHonorContext { get; set; }
|
|
|
|
public QuestArrow QuestArrow
|
|
{
|
|
get => m_QuestArrow;
|
|
set
|
|
{
|
|
if (m_QuestArrow != value)
|
|
{
|
|
m_QuestArrow?.Stop();
|
|
|
|
m_QuestArrow = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearQuestArrow() => m_QuestArrow = null;
|
|
|
|
public override void ToggleFlying()
|
|
{
|
|
if (Race != Race.Gargoyle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Flying)
|
|
{
|
|
Freeze(TimeSpan.FromSeconds(1));
|
|
Animate(61, 10, 1, true, false, 0);
|
|
Flying = false;
|
|
BuffInfo.RemoveBuff(this, BuffIcon.Fly);
|
|
SendMessage("You have landed.");
|
|
|
|
BaseMount.Dismount(this);
|
|
return;
|
|
}
|
|
|
|
var type = MountBlockReason;
|
|
|
|
if (!Alive)
|
|
{
|
|
SendLocalizedMessage(1113082); // You may not fly while dead.
|
|
}
|
|
else if (IsBodyMod && !(BodyMod == 666 || BodyMod == 667))
|
|
{
|
|
SendLocalizedMessage(1112453); // You can't fly in your current form!
|
|
}
|
|
else if (type != BlockMountType.None)
|
|
{
|
|
switch (type)
|
|
{
|
|
case BlockMountType.Dazed:
|
|
{
|
|
SendLocalizedMessage(1112457); // You are still too dazed to fly.
|
|
break;
|
|
}
|
|
case BlockMountType.BolaRecovery:
|
|
{
|
|
SendLocalizedMessage(1112455); // You cannot fly while recovering from a bola throw.
|
|
break;
|
|
}
|
|
case BlockMountType.DismountRecovery:
|
|
{
|
|
// You cannot fly while recovering from a dismount maneuver.
|
|
SendLocalizedMessage(1112456);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (Hits < 25) // TODO confirm
|
|
{
|
|
SendLocalizedMessage(1112454); // You must heal before flying.
|
|
}
|
|
else
|
|
{
|
|
if (!Flying)
|
|
{
|
|
// No message?
|
|
if (Spell is FlySpell spell)
|
|
{
|
|
spell.Stop();
|
|
}
|
|
|
|
new FlySpell(this).Cast();
|
|
}
|
|
else
|
|
{
|
|
Flying = false;
|
|
BuffInfo.RemoveBuff(this, BuffIcon.Fly);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Direction GetDirection4(Point3D from, Point3D to)
|
|
{
|
|
var dx = from.X - to.X;
|
|
var dy = from.Y - to.Y;
|
|
|
|
var rx = dx - dy;
|
|
var ry = dx + dy;
|
|
|
|
Direction ret;
|
|
|
|
if (rx >= 0 && ry >= 0)
|
|
{
|
|
ret = Direction.West;
|
|
}
|
|
else if (rx >= 0 && ry < 0)
|
|
{
|
|
ret = Direction.South;
|
|
}
|
|
else if (rx < 0 && ry < 0)
|
|
{
|
|
ret = Direction.East;
|
|
}
|
|
else
|
|
{
|
|
ret = Direction.North;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
public override bool OnDroppedItemToWorld(Item item, Point3D location)
|
|
{
|
|
if (!base.OnDroppedItemToWorld(item, location))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Core.AOS)
|
|
{
|
|
foreach (Mobile m in Map.GetMobilesAt(location))
|
|
{
|
|
if (m.Z >= location.Z && m.Z < location.Z + 16 && (!m.Hidden || m.AccessLevel == AccessLevel.Player))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
var bi = item.GetBounce();
|
|
|
|
if (bi == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var type = item.GetType();
|
|
|
|
if (type.IsDefined(typeof(FurnitureAttribute), true) ||
|
|
type.IsDefined(typeof(DynamicFlippingAttribute), true))
|
|
{
|
|
var objs = type.GetCustomAttributes(typeof(FlippableAttribute), true);
|
|
|
|
if (objs.Length > 0)
|
|
{
|
|
if (objs[0] is FlippableAttribute fp)
|
|
{
|
|
var itemIDs = fp.ItemIDs;
|
|
|
|
var oldWorldLoc = bi.WorldLoc;
|
|
var newWorldLoc = location;
|
|
|
|
if (oldWorldLoc.X != newWorldLoc.X || oldWorldLoc.Y != newWorldLoc.Y)
|
|
{
|
|
var dir = GetDirection4(oldWorldLoc, newWorldLoc);
|
|
|
|
item.ItemID = itemIDs.Length switch
|
|
{
|
|
2 => dir switch
|
|
{
|
|
Direction.North => itemIDs[0],
|
|
Direction.South => itemIDs[0],
|
|
Direction.East => itemIDs[1],
|
|
Direction.West => itemIDs[1],
|
|
_ => item.ItemID
|
|
},
|
|
4 => dir switch
|
|
{
|
|
Direction.South => itemIDs[0],
|
|
Direction.East => itemIDs[1],
|
|
Direction.North => itemIDs[2],
|
|
Direction.West => itemIDs[3],
|
|
_ => item.ItemID
|
|
},
|
|
_ => item.ItemID
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool GetFlag(PlayerFlag flag) => (Flags & flag) != 0;
|
|
|
|
public void SetFlag(PlayerFlag flag, bool value)
|
|
{
|
|
if (value)
|
|
{
|
|
Flags |= flag;
|
|
}
|
|
else
|
|
{
|
|
Flags &= ~flag;
|
|
}
|
|
}
|
|
|
|
public static void Initialize()
|
|
{
|
|
EventSink.Logout += OnLogout;
|
|
EventSink.Connected += EventSink_Connected;
|
|
EventSink.Disconnected += EventSink_Disconnected;
|
|
|
|
if (Core.SE)
|
|
{
|
|
Timer.StartTimer(CheckPets);
|
|
}
|
|
|
|
var stableMigrations = StableMigrations;
|
|
if (stableMigrations?.Count > 0)
|
|
{
|
|
foreach (var (player, stabled) in stableMigrations)
|
|
{
|
|
if (player is PlayerMobile pm)
|
|
{
|
|
pm.Stabled = stabled;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void TargetedSkillUse(Mobile from, IEntity target, int skillId)
|
|
{
|
|
if (from == null || target == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
from.TargetLocked = true;
|
|
|
|
if (skillId == 35)
|
|
{
|
|
AnimalTaming.DisableMessage = true;
|
|
}
|
|
// AnimalTaming.DeferredTarget = false;
|
|
|
|
if (from.UseSkill(skillId))
|
|
{
|
|
from.Target?.Invoke(from, target);
|
|
}
|
|
|
|
if (skillId == 35)
|
|
// AnimalTaming.DeferredTarget = true;
|
|
{
|
|
AnimalTaming.DisableMessage = false;
|
|
}
|
|
|
|
from.TargetLocked = false;
|
|
}
|
|
|
|
public static void EquipMacro(Mobile m, List<Serial> list)
|
|
{
|
|
if (m is PlayerMobile { Alive: true } pm && pm.Backpack != null)
|
|
{
|
|
var pack = pm.Backpack;
|
|
|
|
foreach (var serial in list)
|
|
{
|
|
Item item = null;
|
|
foreach (var i in pack.Items)
|
|
{
|
|
if (i.Serial == serial)
|
|
{
|
|
item = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (item == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var toMove = pm.FindItemOnLayer(item.Layer);
|
|
|
|
if (toMove != null)
|
|
{
|
|
// pack.DropItem(toMove);
|
|
toMove.Internalize();
|
|
|
|
if (!pm.EquipItem(item))
|
|
{
|
|
pm.EquipItem(toMove);
|
|
}
|
|
else
|
|
{
|
|
pack.DropItem(toMove);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pm.EquipItem(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void UnequipMacro(Mobile m, List<Layer> layers)
|
|
{
|
|
if (m is PlayerMobile { Alive: true } pm && pm.Backpack != null)
|
|
{
|
|
var pack = pm.Backpack;
|
|
var eq = m.Items;
|
|
|
|
for (var i = eq.Count - 1; i >= 0; i--)
|
|
{
|
|
var item = eq[i];
|
|
if (layers.Contains(item.Layer))
|
|
{
|
|
pack.TryDropItem(pm, item, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void CheckPets()
|
|
{
|
|
foreach (var m in World.Mobiles.Values)
|
|
{
|
|
if (m is not PlayerMobile pm || pm.AllFollowers == null || pm.AllFollowers.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var autoStabledCount = pm.AutoStabled?.Count ?? 0;
|
|
if (pm.Mounted && pm.Mount is not EtherealMount)
|
|
{
|
|
autoStabledCount++;
|
|
}
|
|
|
|
if (pm.AllFollowers.Count > autoStabledCount)
|
|
{
|
|
pm.AutoStablePets();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetMountBlock(BlockMountType type, bool dismount) =>
|
|
SetMountBlock(type, TimeSpan.MaxValue, dismount);
|
|
|
|
public void SetMountBlock(BlockMountType type, TimeSpan duration, bool dismount)
|
|
{
|
|
if (dismount)
|
|
{
|
|
if (Mount != null)
|
|
{
|
|
Mount.Rider = null;
|
|
}
|
|
else if (AnimalForm.UnderTransformation(this))
|
|
{
|
|
AnimalForm.RemoveContext(this);
|
|
}
|
|
}
|
|
|
|
if (_mountBlock == null || !_mountBlock.CheckBlock() || _mountBlock.Expiration < Core.Now + duration)
|
|
{
|
|
_mountBlock?.RemoveBlock(this);
|
|
_mountBlock = new MountBlock(duration, type, this);
|
|
}
|
|
}
|
|
|
|
public override void OnSkillInvalidated(Skill skill)
|
|
{
|
|
if (Core.AOS && skill.SkillName == SkillName.MagicResist)
|
|
{
|
|
UpdateResistances();
|
|
}
|
|
}
|
|
|
|
public override int GetMaxResistance(ResistanceType type)
|
|
{
|
|
if (AccessLevel > AccessLevel.Player)
|
|
{
|
|
return 100;
|
|
}
|
|
|
|
var max = base.GetMaxResistance(type);
|
|
|
|
if (type != ResistanceType.Physical && CurseSpell.UnderEffect(this))
|
|
{
|
|
max -= 10;
|
|
}
|
|
|
|
if (Core.ML && Race == Race.Elf && type == ResistanceType.Energy)
|
|
{
|
|
max += 5; // Intended to go after the 60 max from curse
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
protected override void OnRaceChange(Race oldRace)
|
|
{
|
|
ValidateEquipment();
|
|
UpdateResistances();
|
|
}
|
|
|
|
public override void OnNetStateChanged()
|
|
{
|
|
m_LastGlobalLight = -1;
|
|
m_LastPersonalLight = -1;
|
|
}
|
|
|
|
public override void ComputeBaseLightLevels(out int global, out int personal)
|
|
{
|
|
global = LightCycle.ComputeLevelFor(this);
|
|
|
|
var racialNightSight = Core.ML && Race == Race.Elf;
|
|
|
|
if (LightLevel < 21 && (AosAttributes.GetValue(this, AosAttribute.NightSight) > 0 || racialNightSight))
|
|
{
|
|
personal = 21;
|
|
}
|
|
else
|
|
{
|
|
personal = LightLevel;
|
|
}
|
|
}
|
|
|
|
public override void CheckLightLevels(bool forceResend)
|
|
{
|
|
var ns = NetState;
|
|
|
|
if (ns == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ComputeLightLevels(out var global, out var personal);
|
|
|
|
if (!forceResend)
|
|
{
|
|
forceResend = global != m_LastGlobalLight || personal != m_LastPersonalLight;
|
|
}
|
|
|
|
if (!forceResend)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_LastGlobalLight = global;
|
|
m_LastPersonalLight = personal;
|
|
|
|
ns.SendGlobalLightLevel(global);
|
|
ns.SendPersonalLightLevel(Serial, personal);
|
|
}
|
|
|
|
public override int GetMinResistance(ResistanceType type)
|
|
{
|
|
var magicResist = (int)(Skills.MagicResist.Value * 10);
|
|
int min;
|
|
|
|
if (magicResist >= 1000)
|
|
{
|
|
min = 40 + (magicResist - 1000) / 50;
|
|
}
|
|
else if (magicResist >= 400)
|
|
{
|
|
min = (magicResist - 400) / 15;
|
|
}
|
|
else
|
|
{
|
|
min = int.MinValue;
|
|
}
|
|
|
|
return Math.Clamp(min, base.GetMinResistance(type), MaxPlayerResistance);
|
|
}
|
|
|
|
public override void OnManaChange(int oldValue)
|
|
{
|
|
base.OnManaChange(oldValue);
|
|
if (ExecutesLightningStrike > 0)
|
|
{
|
|
if (Mana < ExecutesLightningStrike)
|
|
{
|
|
SpecialMove.ClearCurrentMove(this);
|
|
}
|
|
}
|
|
|
|
if (!Meditating)
|
|
{
|
|
BuffInfo.RemoveBuff(this, BuffIcon.ActiveMeditation);
|
|
}
|
|
}
|
|
|
|
public static void OnLogin(PlayerMobile from)
|
|
{
|
|
if (AccountHandler.LockdownLevel > AccessLevel.Player)
|
|
{
|
|
string notice;
|
|
|
|
if (from.Account is not Account acct || !acct.HasAccess(from.NetState))
|
|
{
|
|
if (from.AccessLevel == AccessLevel.Player)
|
|
{
|
|
notice = "The server is currently under lockdown. No players are allowed to log in at this time.";
|
|
}
|
|
else
|
|
{
|
|
notice =
|
|
"The server is currently under lockdown. You do not have sufficient access level to connect.";
|
|
}
|
|
|
|
if (from.NetState != null)
|
|
{
|
|
Timer.StartTimer(TimeSpan.FromSeconds(1.0), () => from.NetState.Disconnect("Server is locked down"));
|
|
}
|
|
}
|
|
else if (from.AccessLevel >= AccessLevel.Administrator)
|
|
{
|
|
notice =
|
|
"The server is currently under lockdown. As you are an administrator, you may change this from the [Admin gump.";
|
|
}
|
|
else
|
|
{
|
|
notice = "The server is currently under lockdown. You have sufficient access level to connect.";
|
|
}
|
|
|
|
from.SendGump(new ServerLockdownNoticeGump(notice));
|
|
return;
|
|
}
|
|
|
|
VirtueSystem.CheckAtrophies(from);
|
|
from.ClaimAutoStabledPets();
|
|
AnimalForm.GetContext(from)?.Timer.Start();
|
|
}
|
|
|
|
private class ServerLockdownNoticeGump : StaticNoticeGump<ServerLockdownNoticeGump>
|
|
{
|
|
public override int Width => 300;
|
|
public override int Height => 140;
|
|
public override string Content { get; }
|
|
|
|
public ServerLockdownNoticeGump(string content) => Content = content;
|
|
}
|
|
|
|
public void ValidateEquipment()
|
|
{
|
|
if (m_NoDeltaRecursion || Map == null || Map == Map.Internal)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Items == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_NoDeltaRecursion = true;
|
|
Timer.StartTimer(ValidateEquipment_Sandbox);
|
|
}
|
|
|
|
private void ValidateEquipment_Sandbox()
|
|
{
|
|
try
|
|
{
|
|
if (Map == null || Map == Map.Internal)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var items = Items;
|
|
|
|
if (items == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var moved = false;
|
|
|
|
var str = Str;
|
|
var dex = Dex;
|
|
var intel = Int;
|
|
|
|
var factionItemCount = 0;
|
|
|
|
Mobile from = this;
|
|
|
|
var ethic = Ethic.Find(from);
|
|
|
|
for (var i = items.Count - 1; i >= 0; --i)
|
|
{
|
|
if (i >= items.Count)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var item = items[i];
|
|
var itemEthic = Ethic.Find(item);
|
|
|
|
if (itemEthic != null && itemEthic != ethic)
|
|
{
|
|
from.AddToBackpack(item);
|
|
moved = true;
|
|
continue;
|
|
}
|
|
|
|
if (item is BaseWeapon weapon)
|
|
{
|
|
var drop = false;
|
|
|
|
if (dex < weapon.DexRequirement)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (str < AOS.Scale(weapon.StrRequirement, 100 - weapon.GetLowerStatReq()))
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (intel < weapon.IntRequirement)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (!weapon.CheckRace(Race))
|
|
{
|
|
drop = true;
|
|
}
|
|
|
|
if (drop)
|
|
{
|
|
// You can no longer wield your ~1_WEAPON~
|
|
from.SendLocalizedMessage(1062001, weapon.Name ?? $"#{weapon.LabelNumber}");
|
|
from.AddToBackpack(weapon);
|
|
moved = true;
|
|
}
|
|
}
|
|
else if (item is BaseArmor armor)
|
|
{
|
|
var drop = false;
|
|
|
|
if (!armor.AllowMaleWearer && !from.Female && from.AccessLevel < AccessLevel.GameMaster)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (!armor.AllowFemaleWearer && from.Female && from.AccessLevel < AccessLevel.GameMaster)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (!armor.CheckRace(Race))
|
|
{
|
|
drop = true;
|
|
}
|
|
else
|
|
{
|
|
int strBonus = armor.ComputeStatBonus(StatType.Str), strReq = armor.ComputeStatReq(StatType.Str);
|
|
int dexBonus = armor.ComputeStatBonus(StatType.Dex), dexReq = armor.ComputeStatReq(StatType.Dex);
|
|
int intBonus = armor.ComputeStatBonus(StatType.Int), intReq = armor.ComputeStatReq(StatType.Int);
|
|
|
|
if (dex < dexReq || dex + dexBonus < 1)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (str < strReq || str + strBonus < 1)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (intel < intReq || intel + intBonus < 1)
|
|
{
|
|
drop = true;
|
|
}
|
|
}
|
|
|
|
if (drop)
|
|
{
|
|
var name = armor.Name ?? $"#{armor.LabelNumber}";
|
|
|
|
if (armor is BaseShield)
|
|
{
|
|
from.SendLocalizedMessage(1062003, name); // You can no longer equip your ~1_SHIELD~
|
|
}
|
|
else
|
|
{
|
|
from.SendLocalizedMessage(1062002, name); // You can no longer wear your ~1_ARMOR~
|
|
}
|
|
|
|
from.AddToBackpack(armor);
|
|
moved = true;
|
|
}
|
|
}
|
|
else if (item is BaseClothing clothing)
|
|
{
|
|
var drop = false;
|
|
|
|
if (!clothing.AllowMaleWearer && !from.Female && from.AccessLevel < AccessLevel.GameMaster)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (!clothing.AllowFemaleWearer && from.Female && from.AccessLevel < AccessLevel.GameMaster)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (clothing.RequiredRace != null && clothing.RequiredRace != Race)
|
|
{
|
|
drop = true;
|
|
}
|
|
else
|
|
{
|
|
var strBonus = clothing.ComputeStatBonus(StatType.Str);
|
|
var strReq = clothing.ComputeStatReq(StatType.Str);
|
|
|
|
if (str < strReq || str + strBonus < 1)
|
|
{
|
|
drop = true;
|
|
}
|
|
}
|
|
|
|
if (drop)
|
|
{
|
|
// You can no longer wear your ~1_ARMOR~
|
|
from.SendLocalizedMessage(1062002, clothing.Name ?? $"#{clothing.LabelNumber}");
|
|
|
|
from.AddToBackpack(clothing);
|
|
moved = true;
|
|
}
|
|
}
|
|
|
|
var factionItem = FactionItem.Find(item);
|
|
|
|
if (factionItem != null)
|
|
{
|
|
var drop = false;
|
|
|
|
var ourFaction = Faction.Find(this);
|
|
|
|
if (ourFaction == null || ourFaction != factionItem.Faction)
|
|
{
|
|
drop = true;
|
|
}
|
|
else if (++factionItemCount > FactionItem.GetMaxWearables(this))
|
|
{
|
|
drop = true;
|
|
}
|
|
|
|
if (drop)
|
|
{
|
|
from.AddToBackpack(item);
|
|
moved = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (moved)
|
|
{
|
|
from.SendLocalizedMessage(500647); // Some equipment has been moved to your backpack.
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e);
|
|
}
|
|
finally
|
|
{
|
|
m_NoDeltaRecursion = false;
|
|
}
|
|
}
|
|
|
|
public override void Delta(MobileDelta flag)
|
|
{
|
|
base.Delta(flag);
|
|
|
|
if ((flag & MobileDelta.Stat) != 0)
|
|
{
|
|
ValidateEquipment();
|
|
}
|
|
}
|
|
|
|
private static void OnLogout(Mobile m)
|
|
{
|
|
(m as PlayerMobile)?.AutoStablePets();
|
|
}
|
|
|
|
private static void EventSink_Connected(Mobile m)
|
|
{
|
|
if (m is PlayerMobile pm)
|
|
{
|
|
pm.SessionStart = Core.Now;
|
|
|
|
pm.Quest?.StartTimer();
|
|
|
|
pm.BedrollLogout = false;
|
|
pm.LastOnline = Core.Now;
|
|
}
|
|
|
|
DisguisePersistence.StartTimer(m);
|
|
|
|
Timer.StartTimer(() => SpecialMove.ClearAllMoves(m));
|
|
}
|
|
|
|
private static void EventSink_Disconnected(Mobile from)
|
|
{
|
|
var context = DesignContext.Find(from);
|
|
|
|
if (context != null)
|
|
{
|
|
/* Client disconnected
|
|
* - Remove design context
|
|
* - Eject all from house
|
|
* - Restore relocated entities
|
|
*/
|
|
|
|
// Remove design context
|
|
DesignContext.Remove(from);
|
|
|
|
// Eject all from house
|
|
from.RevealingAction();
|
|
|
|
foreach (var item in context.Foundation.GetItems())
|
|
{
|
|
item.Location = context.Foundation.BanLocation;
|
|
}
|
|
|
|
foreach (var mobile in context.Foundation.GetMobiles())
|
|
{
|
|
mobile.Location = context.Foundation.BanLocation;
|
|
}
|
|
|
|
// Restore relocated entities
|
|
context.Foundation.RestoreRelocatedEntities();
|
|
}
|
|
|
|
if (from is PlayerMobile pm)
|
|
{
|
|
pm.m_GameTime += Core.Now - pm.SessionStart;
|
|
|
|
pm.Quest?.StopTimer();
|
|
|
|
pm.SpeechLog = null;
|
|
pm.ClearQuestArrow();
|
|
pm.LastOnline = Core.Now;
|
|
}
|
|
|
|
DisguisePersistence.StopTimer(from);
|
|
}
|
|
|
|
public override void RevealingAction()
|
|
{
|
|
if (DesignContext != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InvisibilitySpell.StopTimer(this);
|
|
|
|
base.RevealingAction();
|
|
|
|
IsStealthing = false; // IsStealthing should be moved to Server.Mobiles
|
|
}
|
|
|
|
public override void OnHiddenChanged()
|
|
{
|
|
base.OnHiddenChanged();
|
|
|
|
RemoveBuff(
|
|
BuffIcon
|
|
.Invisibility
|
|
); // Always remove, default to the hiding icon EXCEPT in the invis spell where it's explicitly set
|
|
|
|
if (!Hidden)
|
|
{
|
|
RemoveBuff(BuffIcon.HidingAndOrStealth);
|
|
}
|
|
else // if (!InvisibilitySpell.HasTimer( this ))
|
|
{
|
|
BuffInfo.AddBuff(
|
|
this,
|
|
new BuffInfo(BuffIcon.HidingAndOrStealth, 1075655)
|
|
); // Hidden/Stealthing & You Are Hidden
|
|
}
|
|
}
|
|
|
|
public override void OnSubItemAdded(Item item)
|
|
{
|
|
if (AccessLevel < AccessLevel.GameMaster && item.IsChildOf(Backpack))
|
|
{
|
|
var maxWeight = StaminaSystem.GetMaxWeight(this);
|
|
var curWeight = BodyWeight + TotalWeight;
|
|
|
|
if (curWeight > maxWeight)
|
|
{
|
|
SendLocalizedMessage(1019035, true, $" : {curWeight} / {maxWeight}");
|
|
}
|
|
}
|
|
|
|
base.OnSubItemAdded(item);
|
|
}
|
|
|
|
public override bool CanBeHarmful(Mobile target, bool message, bool ignoreOurBlessedness)
|
|
{
|
|
if (DesignContext != null || target is PlayerMobile mobile && mobile.DesignContext != null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (target is BaseCreature creature && creature.IsInvulnerable || target is PlayerVendor or TownCrier)
|
|
{
|
|
if (message)
|
|
{
|
|
if (target.Title == null)
|
|
{
|
|
SendMessage($"{target.Name} cannot be harmed.");
|
|
}
|
|
else
|
|
{
|
|
SendMessage($"{target.Name} {target.Title} cannot be harmed.");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return base.CanBeHarmful(target, message, ignoreOurBlessedness);
|
|
}
|
|
|
|
public override bool CanBeBeneficial(Mobile target, bool message, bool allowDead)
|
|
{
|
|
if (DesignContext != null || target is PlayerMobile mobile && mobile.DesignContext != null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return base.CanBeBeneficial(target, message, allowDead);
|
|
}
|
|
|
|
public override bool CheckContextMenuDisplay(IEntity target) => DesignContext == null;
|
|
|
|
public override void OnItemAdded(Item item)
|
|
{
|
|
base.OnItemAdded(item);
|
|
|
|
if (item is BaseArmor or BaseWeapon)
|
|
{
|
|
CheckStatTimers();
|
|
}
|
|
|
|
if (NetState != null)
|
|
{
|
|
CheckLightLevels(false);
|
|
}
|
|
}
|
|
|
|
public override void OnItemRemoved(Item item)
|
|
{
|
|
base.OnItemRemoved(item);
|
|
|
|
if (item is BaseArmor or BaseWeapon)
|
|
{
|
|
CheckStatTimers();
|
|
}
|
|
|
|
if (NetState != null)
|
|
{
|
|
CheckLightLevels(false);
|
|
}
|
|
}
|
|
|
|
private void AddArmorRating(ref double rating, Item armor)
|
|
{
|
|
if (armor is BaseArmor ar && (!Core.AOS || ar.ArmorAttributes.MageArmor == 0))
|
|
{
|
|
rating += ar.ArmorRatingScaled;
|
|
}
|
|
}
|
|
|
|
public override bool Move(Direction d)
|
|
{
|
|
if (NetState != null)
|
|
{
|
|
var gumps = NetState.GetGumps();
|
|
|
|
if (Alive)
|
|
{
|
|
gumps.Close<ResurrectGump>();
|
|
}
|
|
else if (gumps.Has<ResurrectGump>())
|
|
{
|
|
SendLocalizedMessage(500111); // You are frozen and cannot move.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// var speed = ComputeMovementSpeed(d);
|
|
|
|
if (!Alive)
|
|
{
|
|
MovementImpl.IgnoreMovableImpassables = true;
|
|
}
|
|
|
|
var res = base.Move(d);
|
|
MovementImpl.IgnoreMovableImpassables = false;
|
|
return res;
|
|
}
|
|
|
|
public override bool CheckMovement(Direction d, out int newZ)
|
|
{
|
|
var context = DesignContext;
|
|
|
|
if (context == null)
|
|
{
|
|
return base.CheckMovement(d, out newZ);
|
|
}
|
|
|
|
var foundation = context.Foundation;
|
|
|
|
newZ = foundation.Z + HouseFoundation.GetLevelZ(context.Level, context.Foundation);
|
|
|
|
var newX = X;
|
|
var newY = Y;
|
|
CalcMoves.Offset(d, ref newX, ref newY);
|
|
|
|
var startX = foundation.X + foundation.Components.Min.X + 1;
|
|
var startY = foundation.Y + foundation.Components.Min.Y + 1;
|
|
var endX = startX + foundation.Components.Width - 1;
|
|
var endY = startY + foundation.Components.Height - 2;
|
|
|
|
return newX >= startX && newY >= startY && newX < endX && newY < endY && Map == foundation.Map;
|
|
}
|
|
|
|
public override bool AllowItemUse(Item item)
|
|
{
|
|
if (DuelContext?.AllowItemUse(this, item) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return DesignContext.Check(this);
|
|
}
|
|
|
|
public override bool AllowSkillUse(SkillName skill)
|
|
{
|
|
if (AnimalForm.UnderTransformation(this))
|
|
{
|
|
for (var i = 0; i < AnimalFormRestrictedSkills.Length; i++)
|
|
{
|
|
if (AnimalFormRestrictedSkills[i] == skill)
|
|
{
|
|
SendLocalizedMessage(1070771); // You cannot use that skill in this form.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return DuelContext?.AllowSkillUse(this, skill) != false && DesignContext.Check(this);
|
|
}
|
|
|
|
public virtual void RecheckTownProtection()
|
|
{
|
|
m_NextProtectionCheck = 10;
|
|
|
|
var reg = Region.GetRegion<GuardedRegion>();
|
|
var isProtected = reg?.IsDisabled() == false;
|
|
|
|
if (isProtected != m_LastProtectedMessage)
|
|
{
|
|
if (isProtected)
|
|
{
|
|
SendLocalizedMessage(500112); // You are now under the protection of the town guards.
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(500113); // You have left the protection of the town guards.
|
|
}
|
|
|
|
m_LastProtectedMessage = isProtected;
|
|
}
|
|
}
|
|
|
|
public override void MoveToWorld(Point3D loc, Map map)
|
|
{
|
|
base.MoveToWorld(loc, map);
|
|
|
|
RecheckTownProtection();
|
|
}
|
|
|
|
public override void SetLocation(Point3D loc, bool isTeleport)
|
|
{
|
|
if (!isTeleport && AccessLevel == AccessLevel.Player)
|
|
{
|
|
// moving, not teleporting
|
|
var zDrop = Location.Z - loc.Z;
|
|
|
|
if (zDrop > 20) // we fell more than one story
|
|
{
|
|
Hits -= zDrop / 20 * 10 - 5; // deal some damage; does not kill, disrupt, etc
|
|
}
|
|
}
|
|
|
|
base.SetLocation(loc, isTeleport);
|
|
|
|
if (isTeleport || --m_NextProtectionCheck == 0)
|
|
{
|
|
RecheckTownProtection();
|
|
}
|
|
}
|
|
|
|
public override void GetContextMenuEntries(Mobile from, ref PooledRefList<ContextMenuEntry> list)
|
|
{
|
|
base.GetContextMenuEntries(from, ref list);
|
|
|
|
if (from == this)
|
|
{
|
|
if (Alive && Backpack != null && CanSee(Backpack))
|
|
{
|
|
list.Add(new OpenBackpackEntry());
|
|
}
|
|
|
|
Quest?.GetContextMenuEntries(ref list);
|
|
|
|
if (Alive)
|
|
{
|
|
if (InsuranceEnabled)
|
|
{
|
|
if (Core.SA)
|
|
{
|
|
list.Add(new CallbackEntry(1114299, OpenItemInsuranceMenu)); // Open Item Insurance Menu
|
|
}
|
|
|
|
list.Add(new CallbackEntry(6201, ToggleItemInsurance)); // Toggle Item Insurance
|
|
|
|
if (!Core.SA)
|
|
{
|
|
if (AutoRenewInsurance)
|
|
{
|
|
// Cancel Renewing Inventory Insurance
|
|
list.Add(new CallbackEntry(6202, CancelRenewInventoryInsurance));
|
|
}
|
|
else
|
|
{
|
|
// Auto Renew Inventory Insurance
|
|
list.Add(new CallbackEntry(6200, AutoRenewInventoryInsurance));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MLQuestSystem.Enabled)
|
|
{
|
|
list.Add(new CallbackEntry(6169, ToggleQuestItem)); // Toggle Quest Item
|
|
}
|
|
}
|
|
|
|
var house = BaseHouse.FindHouseAt(this);
|
|
|
|
if (house != null)
|
|
{
|
|
if (Alive && house.InternalizedVendors.Count > 0 && house.IsOwner(this))
|
|
{
|
|
list.Add(new CallbackEntry(6204, GetVendor));
|
|
}
|
|
|
|
if (house.IsAosRules && !Region.IsPartOf<SafeZone>()) // Dueling
|
|
{
|
|
list.Add(new CallbackEntry(6207, LeaveHouse));
|
|
}
|
|
}
|
|
|
|
if (JusticeVirtue.IsProtected(this))
|
|
{
|
|
list.Add(new CallbackEntry(6157, CancelProtection));
|
|
}
|
|
|
|
if (Alive)
|
|
{
|
|
list.Add(new CallbackEntry(6210, ToggleChampionTitleDisplay));
|
|
}
|
|
|
|
if (Core.HS)
|
|
{
|
|
var ns = from.NetState;
|
|
|
|
if (ns?.ExtendedStatus == true)
|
|
{
|
|
// Allow Trades / Refuse Trades
|
|
list.Add(new CallbackEntry(RefuseTrades ? 1154112 : 1154113, ToggleTrades));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Core.TOL && from.InRange(this, 2))
|
|
{
|
|
list.Add(new CallbackEntry(1077728, () => OpenTrade(from))); // Trade
|
|
}
|
|
|
|
if (Alive && Core.Expansion >= Expansion.AOS)
|
|
{
|
|
var theirParty = from.Party as Party;
|
|
var ourParty = Party as Party;
|
|
|
|
if (theirParty == null && ourParty == null)
|
|
{
|
|
list.Add(new AddToPartyEntry());
|
|
}
|
|
else if (theirParty != null && theirParty.Leader == from)
|
|
{
|
|
if (ourParty == null)
|
|
{
|
|
list.Add(new AddToPartyEntry());
|
|
}
|
|
else if (ourParty == theirParty)
|
|
{
|
|
list.Add(new RemoveFromPartyEntry());
|
|
}
|
|
}
|
|
}
|
|
|
|
var curhouse = BaseHouse.FindHouseAt(this);
|
|
|
|
if (curhouse != null && Alive && Core.Expansion >= Expansion.AOS && curhouse.IsAosRules &&
|
|
curhouse.IsFriend(from))
|
|
{
|
|
list.Add(new EjectPlayerEntry());
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CancelProtection()
|
|
{
|
|
if (JusticeVirtue.CancelProtection(this, out var prot))
|
|
{
|
|
var args = $"{Name}\t{prot.Name}";
|
|
|
|
// The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended.
|
|
prot.SendLocalizedMessage(1049371, args);
|
|
|
|
// The protective relationship between ~1_PLAYER1~ and ~2_PLAYER2~ has been ended.
|
|
SendLocalizedMessage(1049371, args);
|
|
}
|
|
}
|
|
|
|
private void ToggleTrades()
|
|
{
|
|
RefuseTrades = !RefuseTrades;
|
|
}
|
|
|
|
private void GetVendor()
|
|
{
|
|
var house = BaseHouse.FindHouseAt(this);
|
|
|
|
if (CheckAlive() && house?.IsOwner(this) == true && house.InternalizedVendors.Count > 0 && NetState is NetState { } ns)
|
|
{
|
|
ns.SendGump(new ReclaimVendorGump(house));
|
|
}
|
|
}
|
|
|
|
private void LeaveHouse()
|
|
{
|
|
var house = BaseHouse.FindHouseAt(this);
|
|
|
|
if (house != null)
|
|
{
|
|
Location = house.BanLocation;
|
|
}
|
|
}
|
|
|
|
public override void DisruptiveAction()
|
|
{
|
|
if (Meditating)
|
|
{
|
|
RemoveBuff(BuffIcon.ActiveMeditation);
|
|
}
|
|
|
|
base.DisruptiveAction();
|
|
}
|
|
|
|
public override void OnDoubleClick(Mobile from)
|
|
{
|
|
if (this == from && !Warmode)
|
|
{
|
|
var mount = Mount;
|
|
|
|
if (mount != null && !DesignContext.Check(this))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
base.OnDoubleClick(from);
|
|
}
|
|
|
|
public override void DisplayPaperdollTo(Mobile to)
|
|
{
|
|
if (DesignContext.Check(this))
|
|
{
|
|
base.DisplayPaperdollTo(to);
|
|
}
|
|
}
|
|
|
|
public override bool CheckEquip(Item item)
|
|
{
|
|
if (!base.CheckEquip(item))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (DuelContext?.AllowItemEquip(this, item) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var factionItem = FactionItem.Find(item);
|
|
|
|
if (factionItem != null)
|
|
{
|
|
var faction = Faction.Find(this);
|
|
|
|
if (faction == null)
|
|
{
|
|
SendLocalizedMessage(1010371); // You cannot equip a faction item!
|
|
return false;
|
|
}
|
|
|
|
if (faction != factionItem.Faction)
|
|
{
|
|
SendLocalizedMessage(1010372); // You cannot equip an opposing faction's item!
|
|
return false;
|
|
}
|
|
|
|
var maxWearables = FactionItem.GetMaxWearables(this);
|
|
|
|
for (var i = 0; i < Items.Count; ++i)
|
|
{
|
|
var equipped = Items[i];
|
|
|
|
if (item != equipped && FactionItem.Find(equipped) != null)
|
|
{
|
|
if (--maxWearables == 0)
|
|
{
|
|
SendLocalizedMessage(1010373); // You do not have enough rank to equip more faction items!
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (AccessLevel < AccessLevel.GameMaster && item.Layer != Layer.Mount && HasTrade)
|
|
{
|
|
var bounce = item.GetBounce();
|
|
|
|
if (bounce != null)
|
|
{
|
|
if (bounce.Parent is Item parent)
|
|
{
|
|
if (parent == Backpack || parent.IsChildOf(Backpack))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else if (bounce.Parent == this)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
SendLocalizedMessage(
|
|
1004042
|
|
); // You can only equip what you are already carrying while you have a trade pending.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override bool CheckTrade(
|
|
Mobile to, Item item, SecureTradeContainer cont, bool message, bool checkItems,
|
|
int plusItems, int plusWeight
|
|
)
|
|
{
|
|
var msgNum = 0;
|
|
|
|
if (cont == null)
|
|
{
|
|
if (to.Holding != null)
|
|
{
|
|
msgNum = 1062727; // You cannot trade with someone who is dragging something.
|
|
}
|
|
else if (HasTrade)
|
|
{
|
|
msgNum = 1062781; // You are already trading with someone else!
|
|
}
|
|
else if (to.HasTrade)
|
|
{
|
|
msgNum = 1062779; // That person is already involved in a trade
|
|
}
|
|
else if (to is PlayerMobile mobile && mobile.RefuseTrades)
|
|
{
|
|
msgNum = 1154111; // ~1_NAME~ is refusing all trades.
|
|
}
|
|
}
|
|
|
|
if (msgNum == 0 && item != null)
|
|
{
|
|
if (cont != null)
|
|
{
|
|
plusItems += cont.TotalItems;
|
|
plusWeight += cont.TotalWeight;
|
|
}
|
|
|
|
if (Backpack?.CheckHold(this, item, false, checkItems, plusItems, plusWeight) != true)
|
|
{
|
|
msgNum = 1004040; // You would not be able to hold this if the trade failed.
|
|
}
|
|
else if (to.Backpack?.CheckHold(to, item, false, checkItems, plusItems, plusWeight) != true)
|
|
{
|
|
msgNum = 1004039; // The recipient of this trade would not be able to carry this.
|
|
}
|
|
else
|
|
{
|
|
msgNum = CheckContentForTrade(item);
|
|
}
|
|
}
|
|
|
|
if (msgNum != 0)
|
|
{
|
|
if (message)
|
|
{
|
|
if (msgNum == 1154111)
|
|
{
|
|
SendLocalizedMessage(msgNum, to.Name);
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(msgNum);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static int CheckContentForTrade(Item item)
|
|
{
|
|
if (item is TrappableContainer container && container.TrapType != TrapType.None)
|
|
{
|
|
return 1004044; // You may not trade trapped items.
|
|
}
|
|
|
|
if (StolenItem.IsStolen(item))
|
|
{
|
|
return 1004043; // You may not trade recently stolen items.
|
|
}
|
|
|
|
if (item is Container)
|
|
{
|
|
foreach (var subItem in item.Items)
|
|
{
|
|
var msg = CheckContentForTrade(subItem);
|
|
|
|
if (msg != 0)
|
|
{
|
|
return msg;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
public override bool CheckNonlocalDrop(Mobile from, Item item, Item target)
|
|
{
|
|
if (!base.CheckNonlocalDrop(from, item, target))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (from.AccessLevel >= AccessLevel.GameMaster)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var pack = Backpack;
|
|
if (from == this && HasTrade && (target == pack || target.IsChildOf(pack)))
|
|
{
|
|
var bounce = item.GetBounce();
|
|
|
|
if (bounce?.Parent is Item parent && (parent == pack || parent.IsChildOf(pack)))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
SendLocalizedMessage(1004041); // You can't do that while you have a trade pending.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override void OnLocationChange(Point3D oldLocation)
|
|
{
|
|
CheckLightLevels(false);
|
|
|
|
DuelContext?.OnLocationChanged(this);
|
|
|
|
var context = DesignContext;
|
|
|
|
if (context == null || m_NoRecursion)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_NoRecursion = true;
|
|
|
|
var foundation = context.Foundation;
|
|
|
|
int newX = X, newY = Y;
|
|
var newZ = foundation.Z + HouseFoundation.GetLevelZ(context.Level, context.Foundation);
|
|
|
|
var startX = foundation.X + foundation.Components.Min.X + 1;
|
|
var startY = foundation.Y + foundation.Components.Min.Y + 1;
|
|
var endX = startX + foundation.Components.Width - 1;
|
|
var endY = startY + foundation.Components.Height - 2;
|
|
|
|
if (newX >= startX && newY >= startY && newX < endX && newY < endY && Map == foundation.Map)
|
|
{
|
|
if (Z != newZ)
|
|
{
|
|
Location = new Point3D(X, Y, newZ);
|
|
}
|
|
|
|
m_NoRecursion = false;
|
|
return;
|
|
}
|
|
|
|
Location = new Point3D(foundation.X, foundation.Y, newZ);
|
|
Map = foundation.Map;
|
|
|
|
m_NoRecursion = false;
|
|
}
|
|
|
|
public override bool OnMoveOver(Mobile m) =>
|
|
m is BaseCreature creature && !creature.Controlled
|
|
? !Alive || !creature.Alive || IsDeadBondedPet || creature.IsDeadBondedPet ||
|
|
Hidden && AccessLevel > AccessLevel.Player
|
|
: Region.IsPartOf<SafeZone>() && m is PlayerMobile pm &&
|
|
(pm.DuelContext == null || pm.DuelPlayer == null || !pm.DuelContext.Started || pm.DuelContext.Finished ||
|
|
pm.DuelPlayer.Eliminated) || base.OnMoveOver(m);
|
|
|
|
public override bool CheckShove(Mobile shoved) =>
|
|
IgnoreMobiles || shoved.IgnoreMobiles || TransformationSpellHelper.UnderTransformation(shoved, typeof(WraithFormSpell)) ||
|
|
base.CheckShove(shoved);
|
|
|
|
protected override void OnMapChange(Map oldMap)
|
|
{
|
|
if (Map != Faction.Facet && oldMap == Faction.Facet || Map == Faction.Facet && oldMap != Faction.Facet)
|
|
{
|
|
InvalidateProperties();
|
|
}
|
|
|
|
DuelContext?.OnMapChanged(this);
|
|
|
|
var context = DesignContext;
|
|
|
|
if (context == null || m_NoRecursion)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_NoRecursion = true;
|
|
|
|
var foundation = context.Foundation;
|
|
|
|
if (Map != foundation.Map)
|
|
{
|
|
Map = foundation.Map;
|
|
}
|
|
|
|
m_NoRecursion = false;
|
|
}
|
|
|
|
public override void OnBeneficialAction(Mobile target, bool isCriminal)
|
|
{
|
|
SentHonorContext?.OnSourceBeneficialAction(target);
|
|
|
|
base.OnBeneficialAction(target, isCriminal);
|
|
}
|
|
|
|
public override void OnDamage(int amount, Mobile from, bool willKill)
|
|
{
|
|
int disruptThreshold;
|
|
|
|
if (!Core.AOS)
|
|
{
|
|
disruptThreshold = 0;
|
|
}
|
|
else if (from?.Player == true)
|
|
{
|
|
disruptThreshold = 18;
|
|
}
|
|
else
|
|
{
|
|
disruptThreshold = 25;
|
|
}
|
|
|
|
if (amount > disruptThreshold)
|
|
{
|
|
var c = BandageContext.GetContext(this);
|
|
|
|
c?.Slip();
|
|
}
|
|
|
|
Confidence.StopRegenerating(this);
|
|
|
|
StaminaSystem.FatigueOnDamage(this, amount);
|
|
|
|
ReceivedHonorContext?.OnTargetDamaged(from, amount);
|
|
SentHonorContext?.OnSourceDamaged(from, amount);
|
|
|
|
if (willKill && from is PlayerMobile mobile)
|
|
{
|
|
Timer.StartTimer(TimeSpan.FromSeconds(10), mobile.RecoverAmmo);
|
|
}
|
|
|
|
base.OnDamage(amount, from, willKill);
|
|
}
|
|
|
|
public override void Resurrect()
|
|
{
|
|
var wasAlive = Alive;
|
|
|
|
base.Resurrect();
|
|
|
|
if (Alive && !wasAlive)
|
|
{
|
|
Item deathRobe = new DeathRobe();
|
|
|
|
if (!EquipItem(deathRobe))
|
|
{
|
|
deathRobe.Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnWarmodeChanged()
|
|
{
|
|
if (!Warmode)
|
|
{
|
|
Timer.StartTimer(TimeSpan.FromSeconds(10), RecoverAmmo);
|
|
}
|
|
}
|
|
|
|
private bool FindItems_Callback(Item item) =>
|
|
!item.Deleted && (item.LootType == LootType.Blessed || item.Insured) &&
|
|
Backpack != item.Parent;
|
|
|
|
public override bool OnBeforeDeath()
|
|
{
|
|
var state = NetState;
|
|
|
|
state?.CancelAllTrades();
|
|
|
|
DropHolding();
|
|
|
|
// During AOS+, insured/blessed items are moved out of their child containers and put directly into the backpack.
|
|
// This fixes a "bug" where players put blessed items in nested bags and they were dropped on death
|
|
if (Core.AOS && Backpack?.Deleted == false)
|
|
{
|
|
foreach (var item in Backpack.EnumerateItems(true, FindItems_Callback))
|
|
{
|
|
Backpack.AddItem(item);
|
|
}
|
|
}
|
|
|
|
EquipSnapshot = new List<Item>(Items);
|
|
|
|
m_NonAutoreinsuredItems = 0;
|
|
m_InsuranceAward = FindMostRecentDamager(false);
|
|
|
|
if (m_InsuranceAward is BaseCreature creature)
|
|
{
|
|
var master = creature.GetMaster();
|
|
|
|
if (master != null)
|
|
{
|
|
m_InsuranceAward = master;
|
|
}
|
|
}
|
|
|
|
if (m_InsuranceAward != null && (!m_InsuranceAward.Player || m_InsuranceAward == this))
|
|
{
|
|
m_InsuranceAward = null;
|
|
}
|
|
|
|
if (m_InsuranceAward is PlayerMobile mobile)
|
|
{
|
|
mobile.m_InsuranceBonus = 0;
|
|
}
|
|
|
|
ReceivedHonorContext?.OnTargetKilled();
|
|
SentHonorContext?.OnSourceKilled();
|
|
|
|
RecoverAmmo();
|
|
|
|
return base.OnBeforeDeath();
|
|
}
|
|
|
|
private bool CheckInsuranceOnDeath(Item item)
|
|
{
|
|
if (!InsuranceEnabled || !item.Insured)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (DuelContext?.Registered == true && DuelContext.Started &&
|
|
m_DuelPlayer?.Eliminated != true)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (AutoRenewInsurance)
|
|
{
|
|
var cost = GetInsuranceCost(item);
|
|
|
|
if (m_InsuranceAward != null)
|
|
{
|
|
cost /= 2;
|
|
}
|
|
|
|
if (Banker.Withdraw(this, cost))
|
|
{
|
|
item.PaidInsurance = true;
|
|
// ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
|
SendLocalizedMessage(1060398, cost.ToString());
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance
|
|
item.PaidInsurance = false;
|
|
item.Insured = false;
|
|
m_NonAutoreinsuredItems++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item.PaidInsurance = false;
|
|
item.Insured = false;
|
|
}
|
|
|
|
if (m_InsuranceAward is PlayerMobile insurancePm && Banker.Deposit(m_InsuranceAward, 300))
|
|
{
|
|
insurancePm.m_InsuranceBonus += 300;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override DeathMoveResult GetParentMoveResultFor(Item item)
|
|
{
|
|
// It seems all items are unmarked on death, even blessed/insured ones
|
|
if (item.QuestItem)
|
|
{
|
|
item.QuestItem = false;
|
|
}
|
|
|
|
if (CheckInsuranceOnDeath(item))
|
|
{
|
|
return DeathMoveResult.MoveToBackpack;
|
|
}
|
|
|
|
var res = base.GetParentMoveResultFor(item);
|
|
|
|
if (res == DeathMoveResult.MoveToCorpse && item.Movable && Young)
|
|
{
|
|
res = DeathMoveResult.MoveToBackpack;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
public override DeathMoveResult GetInventoryMoveResultFor(Item item)
|
|
{
|
|
// It seems all items are unmarked on death, even blessed/insured ones
|
|
if (item.QuestItem)
|
|
{
|
|
item.QuestItem = false;
|
|
}
|
|
|
|
if (CheckInsuranceOnDeath(item))
|
|
{
|
|
return DeathMoveResult.MoveToBackpack;
|
|
}
|
|
|
|
var res = base.GetInventoryMoveResultFor(item);
|
|
|
|
if (res == DeathMoveResult.MoveToCorpse && item.Movable && Young)
|
|
{
|
|
res = DeathMoveResult.MoveToBackpack;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
[GeneratedEvent(nameof(PlayerDeathEvent))]
|
|
public static partial void PlayerDeathEvent(PlayerMobile m);
|
|
|
|
public override void OnDeath(Container c)
|
|
{
|
|
if (m_NonAutoreinsuredItems > 0)
|
|
{
|
|
SendLocalizedMessage(1061115);
|
|
}
|
|
|
|
base.OnDeath(c);
|
|
|
|
playerDeathCounter.Add(1, new KeyValuePair<string, object>[]
|
|
{
|
|
new("Map", Map?.Name),
|
|
new("Region", Region?.Name),
|
|
});
|
|
|
|
EquipSnapshot = null;
|
|
|
|
HueMod = -1;
|
|
NameMod = null;
|
|
SavagePaintExpiration = TimeSpan.Zero;
|
|
SetHairMods(-1, -1);
|
|
|
|
if (Flying)
|
|
{
|
|
Flying = false;
|
|
BuffInfo.RemoveBuff(this, BuffIcon.Fly);
|
|
}
|
|
|
|
if (PermaFlags.Count > 0)
|
|
{
|
|
PermaFlags.Clear();
|
|
|
|
if (c is Corpse corpse)
|
|
{
|
|
corpse.Criminal = true;
|
|
}
|
|
|
|
if (Stealing.ClassicMode)
|
|
{
|
|
Criminal = true;
|
|
}
|
|
}
|
|
|
|
if (Kills >= 5 && Core.Now >= m_NextJustAward)
|
|
{
|
|
var m = FindMostRecentDamager(false);
|
|
|
|
if (m is BaseCreature bc)
|
|
{
|
|
m = bc.GetMaster();
|
|
}
|
|
|
|
if (m != this && m is PlayerMobile pm)
|
|
{
|
|
var gainedPath = false;
|
|
|
|
var pointsToGain = 0;
|
|
|
|
pointsToGain += (int)Math.Sqrt(GameTime.TotalSeconds * 4);
|
|
pointsToGain *= 5;
|
|
pointsToGain += (int)Math.Pow(Skills.Total / 250.0, 2);
|
|
|
|
if (VirtueSystem.Award(pm, VirtueName.Justice, pointsToGain, ref gainedPath))
|
|
{
|
|
if (gainedPath)
|
|
{
|
|
m.SendLocalizedMessage(1049367); // You have gained a path in Justice!
|
|
}
|
|
else
|
|
{
|
|
m.SendLocalizedMessage(1049363); // You have gained in Justice.
|
|
}
|
|
|
|
m.FixedParticles(0x375A, 9, 20, 5027, EffectLayer.Waist);
|
|
m.PlaySound(0x1F7);
|
|
|
|
m_NextJustAward = Core.Now + TimeSpan.FromMinutes(pointsToGain / 3.0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_InsuranceAward is PlayerMobile insurancePm && insurancePm.m_InsuranceBonus > 0)
|
|
{
|
|
// ~1_AMOUNT~ gold has been deposited into your bank box.
|
|
insurancePm.SendLocalizedMessage(1060397, insurancePm.m_InsuranceBonus.ToString());
|
|
}
|
|
|
|
var killer = FindMostRecentDamager(true);
|
|
|
|
if (killer is BaseCreature bcKiller)
|
|
{
|
|
var master = bcKiller.GetMaster();
|
|
if (master != null)
|
|
{
|
|
killer = master;
|
|
}
|
|
}
|
|
|
|
if (Young && DuelContext == null)
|
|
{
|
|
if (YoungDeathTeleport())
|
|
{
|
|
Timer.StartTimer(TimeSpan.FromSeconds(2.5), SendYoungDeathNotice);
|
|
}
|
|
}
|
|
|
|
if (DuelContext?.Registered != true || !DuelContext.Started || m_DuelPlayer?.Eliminated != false)
|
|
{
|
|
Faction.HandleDeath(this, killer);
|
|
}
|
|
|
|
Guilds.Guild.HandleDeath(this, killer);
|
|
|
|
if (m_BuffTable != null)
|
|
{
|
|
using var queue = PooledRefQueue<BuffInfo>.Create();
|
|
|
|
foreach (var buff in m_BuffTable.Values)
|
|
{
|
|
if (!buff.RetainThroughDeath)
|
|
{
|
|
queue.Enqueue(buff);
|
|
}
|
|
}
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
RemoveBuff(queue.Dequeue());
|
|
}
|
|
}
|
|
|
|
PlayerDeathEvent(this);
|
|
}
|
|
|
|
public override bool MutateSpeech(List<Mobile> hears, ref string text, ref object context)
|
|
{
|
|
if (Alive)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Core.ML && Skills.SpiritSpeak.Value >= 100.0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Core.AOS)
|
|
{
|
|
for (var i = 0; i < hears.Count; ++i)
|
|
{
|
|
var m = hears[i];
|
|
|
|
if (m != this && m.Skills.SpiritSpeak.Value >= 100.0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return base.MutateSpeech(hears, ref text, ref context);
|
|
}
|
|
|
|
public override void DoSpeech(string text, int[] keywords, MessageType type, int hue)
|
|
{
|
|
if (Guilds.Guild.NewGuildSystem && type is MessageType.Guild or MessageType.Alliance)
|
|
{
|
|
if (Guild is not Guild g)
|
|
{
|
|
SendLocalizedMessage(1063142); // You are not in a guild!
|
|
}
|
|
else if (type == MessageType.Alliance)
|
|
{
|
|
if (g.Alliance?.IsMember(g) == true)
|
|
{
|
|
// g.Alliance.AllianceTextMessage( hue, "[Alliance][{0}]: {1}", this.Name, text );
|
|
g.Alliance.AllianceChat(this, text);
|
|
SendToStaffMessage(this, $"[Alliance]: {text}");
|
|
|
|
AllianceMessageHue = hue;
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(1071020); // You are not in an alliance!
|
|
}
|
|
}
|
|
else // Type == MessageType.Guild
|
|
{
|
|
GuildMessageHue = hue;
|
|
|
|
g.GuildChat(this, text);
|
|
SendToStaffMessage(this, $"[Guild]: {text}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
base.DoSpeech(text, keywords, type, hue);
|
|
}
|
|
}
|
|
|
|
private static void SendToStaffMessage(Mobile from, string text)
|
|
{
|
|
Span<byte> buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)].InitializePacket();
|
|
|
|
foreach (var ns in from.GetClientsInRange(8))
|
|
{
|
|
var mob = ns.Mobile;
|
|
|
|
if (mob?.AccessLevel >= AccessLevel.GameMaster && mob.AccessLevel > from.AccessLevel)
|
|
{
|
|
var length = OutgoingMessagePackets.CreateMessage(
|
|
buffer,
|
|
from.Serial,
|
|
from.Body,
|
|
MessageType.Regular,
|
|
from.SpeechHue,
|
|
3,
|
|
false,
|
|
from.Language,
|
|
from.Name,
|
|
text
|
|
);
|
|
|
|
if (length != buffer.Length)
|
|
{
|
|
buffer = buffer[..length]; // Adjust to the actual size
|
|
}
|
|
|
|
ns.Send(buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Damage(int amount, Mobile from = null, bool informMount = true, bool ignoreEvilOmen = false)
|
|
{
|
|
var damageBonus = 1.0;
|
|
|
|
if (EvilOmenSpell.EndEffect(this) && !ignoreEvilOmen)
|
|
{
|
|
damageBonus += 0.25;
|
|
}
|
|
|
|
var hasBloodOath = false;
|
|
|
|
if (from != null)
|
|
{
|
|
if (Talisman is BaseTalisman talisman &&
|
|
talisman.Protection?.Type?.IsInstanceOfType(from) == true)
|
|
{
|
|
damageBonus -= talisman.Protection.Amount / 100.0;
|
|
}
|
|
|
|
// Is the attacker attacking the blood oath caster?
|
|
if (BloodOathSpell.GetBloodOath(from) == this)
|
|
{
|
|
hasBloodOath = true;
|
|
damageBonus += 0.2;
|
|
}
|
|
}
|
|
|
|
base.Damage((int)(amount * damageBonus), from, informMount);
|
|
|
|
// If the blood oath caster will die then damage is not reflected back to the attacker
|
|
if (hasBloodOath && Alive && !Deleted && !IsDeadBondedPet)
|
|
{
|
|
// In some expansions resisting spells reduces reflect dmg from monster blood oath
|
|
var resistReflectedDamage = !from.Player && Core.ML && !Core.HS
|
|
? (from.Skills.MagicResist.Value * 0.5 + 10) / 100
|
|
: 0;
|
|
|
|
// Reflect damage to the attacker
|
|
from.Damage((int)(amount * (1.0 - resistReflectedDamage)), this);
|
|
}
|
|
}
|
|
|
|
public override bool IsHarmfulCriminal(Mobile target)
|
|
{
|
|
if (Stealing.ClassicMode && target is PlayerMobile mobile && mobile.PermaFlags.Count > 0)
|
|
{
|
|
if (Notoriety.Compute(this, mobile) == Notoriety.Innocent)
|
|
{
|
|
mobile.Delta(MobileDelta.Noto);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
var bc = target as BaseCreature;
|
|
|
|
if (bc?.InitialInnocent == true && !bc.Controlled)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Core.ML && bc?.Controlled == true && this == bc.ControlMaster)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return base.IsHarmfulCriminal(target);
|
|
}
|
|
|
|
private void RevertHair()
|
|
{
|
|
SetHairMods(-1, -1);
|
|
}
|
|
|
|
public override void Deserialize(IGenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
var version = reader.ReadInt();
|
|
|
|
VirtueContext virtues = version < 32 ? Virtues : null;
|
|
|
|
switch (version)
|
|
{
|
|
case 34: // Acquired Recipes is now a Set
|
|
case 33: // Removes champion title
|
|
case 32: // Removes virtue properties
|
|
case 31: // Removed Short/Long Term Elapse
|
|
case 30:
|
|
{
|
|
Stabled = reader.ReadEntitySet<Mobile>(true);
|
|
goto case 29;
|
|
}
|
|
case 29:
|
|
{
|
|
if (reader.ReadBool())
|
|
{
|
|
m_StuckMenuUses = new DateTime[reader.ReadInt()];
|
|
|
|
for (var i = 0; i < m_StuckMenuUses.Length; ++i)
|
|
{
|
|
m_StuckMenuUses[i] = reader.ReadDateTime();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_StuckMenuUses = null;
|
|
}
|
|
|
|
goto case 28;
|
|
}
|
|
case 28:
|
|
{
|
|
PeacedUntil = reader.ReadDateTime();
|
|
|
|
goto case 27;
|
|
}
|
|
case 27:
|
|
{
|
|
AnkhNextUse = reader.ReadDateTime();
|
|
|
|
goto case 26;
|
|
}
|
|
case 26:
|
|
{
|
|
AutoStabled = reader.ReadEntitySet<Mobile>(true);
|
|
|
|
goto case 25;
|
|
}
|
|
case 25:
|
|
{
|
|
var recipeCount = reader.ReadInt();
|
|
|
|
if (recipeCount > 0)
|
|
{
|
|
_acquiredRecipes = new HashSet<int>();
|
|
|
|
for (var i = 0; i < recipeCount; i++)
|
|
{
|
|
var r = reader.ReadInt();
|
|
if (version > 33 || reader.ReadBool()) // Don't add in recipes which we haven't gotten or have been removed
|
|
{
|
|
_acquiredRecipes.Add(r);
|
|
}
|
|
}
|
|
}
|
|
|
|
goto case 24;
|
|
}
|
|
case 24:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
reader.ReadDeltaTime(); // LastHonorLoss - Not even used
|
|
}
|
|
goto case 23;
|
|
}
|
|
case 23:
|
|
{
|
|
if (version < 33)
|
|
{
|
|
ChampionTitles.Deserialize(reader);
|
|
}
|
|
|
|
goto case 22;
|
|
}
|
|
case 22:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
virtues.LastValorLoss = reader.ReadDateTime();
|
|
}
|
|
|
|
goto case 21;
|
|
}
|
|
case 21:
|
|
{
|
|
ToTItemsTurnedIn = reader.ReadEncodedInt();
|
|
ToTTotalMonsterFame = reader.ReadInt();
|
|
goto case 20;
|
|
}
|
|
case 20:
|
|
{
|
|
AllianceMessageHue = reader.ReadEncodedInt();
|
|
GuildMessageHue = reader.ReadEncodedInt();
|
|
|
|
goto case 19;
|
|
}
|
|
case 19:
|
|
{
|
|
var rank = reader.ReadEncodedInt();
|
|
var maxRank = RankDefinition.Ranks.Length - 1;
|
|
if (rank > maxRank)
|
|
{
|
|
rank = maxRank;
|
|
}
|
|
|
|
m_GuildRank = RankDefinition.Ranks[rank];
|
|
LastOnline = reader.ReadDateTime();
|
|
goto case 18;
|
|
}
|
|
case 18:
|
|
{
|
|
SolenFriendship = (SolenFriendship)reader.ReadEncodedInt();
|
|
|
|
goto case 17;
|
|
}
|
|
case 17: // changed how DoneQuests is serialized
|
|
case 16:
|
|
{
|
|
Quest = QuestSerializer.DeserializeQuest(reader);
|
|
|
|
if (Quest != null)
|
|
{
|
|
Quest.From = this;
|
|
}
|
|
|
|
var count = reader.ReadEncodedInt();
|
|
|
|
if (count > 0)
|
|
{
|
|
DoneQuests = new List<QuestRestartInfo>();
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var questType = QuestSerializer.ReadType(QuestSystem.QuestTypes, reader);
|
|
DateTime restartTime;
|
|
|
|
if (version < 17)
|
|
{
|
|
restartTime = DateTime.MaxValue;
|
|
}
|
|
else
|
|
{
|
|
restartTime = reader.ReadDateTime();
|
|
}
|
|
|
|
DoneQuests.Add(new QuestRestartInfo(questType, restartTime));
|
|
}
|
|
}
|
|
|
|
Profession = reader.ReadEncodedInt();
|
|
goto case 15;
|
|
}
|
|
case 15:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
virtues.LastCompassionLoss = reader.ReadDeltaTime();
|
|
}
|
|
goto case 14;
|
|
}
|
|
case 14:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
virtues.CompassionGains = reader.ReadEncodedInt();
|
|
if (virtues.CompassionGains > 0)
|
|
{
|
|
virtues.NextCompassionDay = reader.ReadDeltaTime();
|
|
}
|
|
}
|
|
|
|
goto case 13;
|
|
}
|
|
case 13: // just removed m_PaidInsurance list
|
|
case 12:
|
|
{
|
|
BOBFilter = new BOBFilter();
|
|
BOBFilter.Deserialize(reader);
|
|
goto case 11;
|
|
}
|
|
case 11:
|
|
{
|
|
if (version < 13)
|
|
{
|
|
var paid = reader.ReadEntityList<Item>();
|
|
|
|
for (var i = 0; i < paid.Count; ++i)
|
|
{
|
|
paid[i].PaidInsurance = true;
|
|
}
|
|
}
|
|
|
|
goto case 10;
|
|
}
|
|
case 10:
|
|
{
|
|
if (reader.ReadBool())
|
|
{
|
|
m_HairModID = reader.ReadInt();
|
|
m_HairModHue = reader.ReadInt();
|
|
m_BeardModID = reader.ReadInt();
|
|
m_BeardModHue = reader.ReadInt();
|
|
}
|
|
|
|
goto case 9;
|
|
}
|
|
case 9:
|
|
{
|
|
SavagePaintExpiration = reader.ReadTimeSpan();
|
|
|
|
if (SavagePaintExpiration > TimeSpan.Zero)
|
|
{
|
|
BodyMod = Female ? 184 : 183;
|
|
HueMod = 0;
|
|
}
|
|
|
|
goto case 8;
|
|
}
|
|
case 8:
|
|
{
|
|
NpcGuild = (NpcGuild)reader.ReadInt();
|
|
NpcGuildJoinTime = reader.ReadDateTime();
|
|
NpcGuildGameTime = reader.ReadTimeSpan();
|
|
goto case 7;
|
|
}
|
|
case 7:
|
|
{
|
|
PermaFlags = reader.ReadEntityList<Mobile>();
|
|
goto case 6;
|
|
}
|
|
case 6:
|
|
{
|
|
NextTailorBulkOrder = reader.ReadTimeSpan();
|
|
goto case 5;
|
|
}
|
|
case 5:
|
|
{
|
|
NextSmithBulkOrder = reader.ReadTimeSpan();
|
|
goto case 4;
|
|
}
|
|
case 4:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
virtues.LastJusticeLoss = reader.ReadDeltaTime();
|
|
var protectors = reader.ReadEntityList<PlayerMobile>(); // Always a list of 0, or 1
|
|
if (protectors.Count > 0)
|
|
{
|
|
var protector = protectors[0];
|
|
if (protector != null)
|
|
{
|
|
JusticeVirtue.AddProtection(protector, this);
|
|
}
|
|
}
|
|
}
|
|
|
|
goto case 3;
|
|
}
|
|
case 3:
|
|
{
|
|
if (version < 32)
|
|
{
|
|
virtues.LastSacrificeGain = reader.ReadDeltaTime();
|
|
virtues.LastSacrificeLoss = reader.ReadDeltaTime();
|
|
virtues.AvailableResurrects = reader.ReadInt();
|
|
}
|
|
|
|
goto case 2;
|
|
}
|
|
case 2:
|
|
{
|
|
Flags = (PlayerFlag)reader.ReadInt();
|
|
goto case 1;
|
|
}
|
|
case 1:
|
|
{
|
|
if (version < 31)
|
|
{
|
|
var longTermElapse = reader.ReadTimeSpan();
|
|
var shortTermElapse = reader.ReadTimeSpan();
|
|
|
|
PlayerMurderSystem.MigrateContext(this, shortTermElapse, longTermElapse);
|
|
}
|
|
|
|
m_GameTime = reader.ReadTimeSpan();
|
|
goto case 0;
|
|
}
|
|
case 0:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ProfessionInfo.VerifyProfession(Profession))
|
|
{
|
|
Profession = 0;
|
|
}
|
|
|
|
PermaFlags ??= new List<Mobile>();
|
|
BOBFilter ??= new BOBFilter();
|
|
|
|
// Default to member if going from older version to new version (only time it should be null)
|
|
m_GuildRank ??= RankDefinition.Member;
|
|
|
|
if (LastOnline == DateTime.MinValue && Account != null)
|
|
{
|
|
LastOnline = ((Account)Account).LastLogin;
|
|
}
|
|
|
|
if (AccessLevel > AccessLevel.Player)
|
|
{
|
|
IgnoreMobiles = true;
|
|
}
|
|
|
|
if (Stabled != null)
|
|
{
|
|
foreach (var stabled in Stabled)
|
|
{
|
|
if (stabled is BaseCreature bc)
|
|
{
|
|
bc.IsStabled = true;
|
|
bc.StabledBy = this;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Hidden) // Hiding is the only buff where it has an effect that's serialized.
|
|
{
|
|
AddBuff(new BuffInfo(BuffIcon.HidingAndOrStealth, 1075655));
|
|
}
|
|
}
|
|
|
|
public override void Serialize(IGenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
|
|
writer.Write(34); // version
|
|
|
|
if (Stabled == null)
|
|
{
|
|
writer.Write(0);
|
|
}
|
|
else
|
|
{
|
|
Stabled.Tidy();
|
|
writer.Write(Stabled);
|
|
}
|
|
|
|
if (m_StuckMenuUses != null)
|
|
{
|
|
writer.Write(true);
|
|
|
|
writer.Write(m_StuckMenuUses.Length);
|
|
|
|
for (var i = 0; i < m_StuckMenuUses.Length; ++i)
|
|
{
|
|
writer.Write(m_StuckMenuUses[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
writer.Write(false);
|
|
}
|
|
|
|
writer.Write(PeacedUntil);
|
|
writer.Write(AnkhNextUse);
|
|
if (AutoStabled == null)
|
|
{
|
|
writer.Write(0);
|
|
}
|
|
else
|
|
{
|
|
AutoStabled.Tidy();
|
|
writer.Write(AutoStabled);
|
|
}
|
|
|
|
if (_acquiredRecipes == null)
|
|
{
|
|
writer.Write(0);
|
|
}
|
|
else
|
|
{
|
|
writer.Write(_acquiredRecipes.Count);
|
|
|
|
foreach (var recipeId in _acquiredRecipes)
|
|
{
|
|
writer.Write(recipeId);
|
|
}
|
|
}
|
|
|
|
writer.WriteEncodedInt(ToTItemsTurnedIn);
|
|
writer.Write(ToTTotalMonsterFame); // This ain't going to be a small #.
|
|
|
|
writer.WriteEncodedInt(AllianceMessageHue);
|
|
writer.WriteEncodedInt(GuildMessageHue);
|
|
|
|
writer.WriteEncodedInt(m_GuildRank.Rank);
|
|
writer.Write(LastOnline);
|
|
|
|
writer.WriteEncodedInt((int)SolenFriendship);
|
|
|
|
QuestSerializer.Serialize(Quest, writer);
|
|
|
|
if (DoneQuests == null)
|
|
{
|
|
writer.WriteEncodedInt(0);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteEncodedInt(DoneQuests.Count);
|
|
|
|
for (var i = 0; i < DoneQuests.Count; ++i)
|
|
{
|
|
var restartInfo = DoneQuests[i];
|
|
|
|
QuestSerializer.Write(restartInfo.QuestType, QuestSystem.QuestTypes, writer);
|
|
writer.Write(restartInfo.RestartTime);
|
|
}
|
|
}
|
|
|
|
writer.WriteEncodedInt(Profession);
|
|
|
|
BOBFilter.Serialize(writer);
|
|
|
|
var useMods = m_HairModID != -1 || m_BeardModID != -1;
|
|
|
|
writer.Write(useMods);
|
|
|
|
if (useMods)
|
|
{
|
|
writer.Write(m_HairModID);
|
|
writer.Write(m_HairModHue);
|
|
writer.Write(m_BeardModID);
|
|
writer.Write(m_BeardModHue);
|
|
}
|
|
|
|
writer.Write(SavagePaintExpiration);
|
|
|
|
writer.Write((int)NpcGuild);
|
|
writer.Write(NpcGuildJoinTime);
|
|
writer.Write(NpcGuildGameTime);
|
|
|
|
PermaFlags.Tidy();
|
|
writer.Write(PermaFlags);
|
|
|
|
writer.Write(NextTailorBulkOrder);
|
|
|
|
writer.Write(NextSmithBulkOrder);
|
|
|
|
writer.Write((int)Flags);
|
|
|
|
writer.Write(GameTime);
|
|
}
|
|
|
|
public override bool CanSee(Mobile m)
|
|
{
|
|
if (m is CharacterStatue statue)
|
|
{
|
|
statue.OnRequestedAnimation(this);
|
|
}
|
|
|
|
if (m is PlayerMobile mobile && mobile.VisibilityList.Contains(this))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (DuelContext?.Finished == false && DuelContext.m_Tournament != null && m_DuelPlayer?.Eliminated == false)
|
|
{
|
|
var owner = m;
|
|
|
|
if (owner is BaseCreature bc)
|
|
{
|
|
var master = bc.GetMaster();
|
|
|
|
if (master != null)
|
|
{
|
|
owner = master;
|
|
}
|
|
}
|
|
|
|
if (m.AccessLevel == AccessLevel.Player && owner is PlayerMobile pm && pm.DuelContext != DuelContext)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return base.CanSee(m);
|
|
}
|
|
|
|
public virtual void CheckedAnimate(int action, int frameCount, int repeatCount, bool forward, bool repeat, int delay)
|
|
{
|
|
if (!Mounted)
|
|
{
|
|
Animate(action, frameCount, repeatCount, forward, repeat, delay);
|
|
}
|
|
}
|
|
|
|
public override bool CanSee(Item item) =>
|
|
DesignContext?.Foundation.IsHiddenToCustomizer(item) != true && base.CanSee(item);
|
|
|
|
public override void OnAfterDelete()
|
|
{
|
|
base.OnAfterDelete();
|
|
|
|
var faction = Faction.Find(this);
|
|
faction?.RemoveMember(this);
|
|
|
|
MLQuestSystem.HandleDeletion(this);
|
|
BaseHouse.HandleDeletion(this);
|
|
DisguisePersistence.RemoveTimer(this);
|
|
|
|
StaminaSystem.OnPlayerDeleted(this);
|
|
JusticeVirtue.OnPlayerDeleted(this);
|
|
PlayerMurderSystem.OnPlayerDeleted(this);
|
|
ChampionTitleSystem.OnPlayerDeleted(this);
|
|
|
|
// Spells
|
|
MagicReflectSpell.EndReflect(this);
|
|
ReactiveArmorSpell.EndArmor(this);
|
|
ProtectionSpell.EndProtection(this);
|
|
StoneFormSpell.RemoveEffects(this);
|
|
AnimateDeadSpell.RemoveEffects(this);
|
|
SummonFamiliarSpell.RemoveEffects(this);
|
|
AnimalForm.RemoveLastAnimalForm(this);
|
|
}
|
|
|
|
public override void GetProperties(IPropertyList list)
|
|
{
|
|
base.GetProperties(list);
|
|
|
|
if (Map == Faction.Facet)
|
|
{
|
|
var pl = PlayerState.Find(this);
|
|
|
|
if (pl != null)
|
|
{
|
|
var faction = pl.Faction;
|
|
|
|
if (faction.Commander == this)
|
|
{
|
|
// Commanding Lord of the ~1_FACTION_NAME~
|
|
list.Add(1042733, $"{faction.Definition.PropName}");
|
|
}
|
|
else if (pl.Sheriff != null)
|
|
{
|
|
list.Add(
|
|
1042734, // The Sheriff of ~1_CITY~, ~2_FACTION_NAME~
|
|
$"{pl.Sheriff.Definition.FriendlyName}\t{faction.Definition.PropName}"
|
|
);
|
|
}
|
|
else if (pl.Finance != null)
|
|
{
|
|
list.Add(
|
|
1042735, // The Finance Minister of ~1_CITY~, ~2_FACTION_NAME~
|
|
$"{pl.Finance.Definition.FriendlyName}\t{faction.Definition.PropName}"
|
|
);
|
|
}
|
|
else if (pl.MerchantTitle != MerchantTitle.None)
|
|
{
|
|
list.Add(
|
|
1060776, // ~1_val~, ~2_val~
|
|
$"{MerchantTitles.GetInfo(pl.MerchantTitle).Title}\t{faction.Definition.PropName}"
|
|
);
|
|
}
|
|
else
|
|
{
|
|
list.Add(1060776, $"{pl.Rank.Title}\t{faction.Definition.PropName}"); // ~1_val~, ~2_val~
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Add the Titles Menu for later Eras:
|
|
// https://uo.com/wiki/ultima-online-wiki/player/skill-titles-order/
|
|
if (DisplayChampionTitle)
|
|
{
|
|
var titleLabel = ChampionTitleSystem.GetChampionTitleLabel(this);
|
|
if (titleLabel > 0)
|
|
{
|
|
list.Add(titleLabel);
|
|
}
|
|
}
|
|
|
|
if (Core.ML && AllFollowers != null)
|
|
{
|
|
foreach (var follower in AllFollowers)
|
|
{
|
|
if (follower is BaseCreature { ControlOrder: OrderType.Guard })
|
|
{
|
|
list.Add(501129); // guarded
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnSingleClick(Mobile from)
|
|
{
|
|
if (Map == Faction.Facet)
|
|
{
|
|
var pl = PlayerState.Find(this);
|
|
|
|
if (pl != null)
|
|
{
|
|
string text;
|
|
var ascii = false;
|
|
|
|
var faction = pl.Faction;
|
|
|
|
if (faction.Commander == this)
|
|
{
|
|
text =
|
|
$"{(Female ? "(Commanding Lady of the " : "(Commanding Lord of the ")}{faction.Definition.FriendlyName})";
|
|
}
|
|
else if (pl.Sheriff != null)
|
|
{
|
|
text = $"(The Sheriff of {pl.Sheriff.Definition.FriendlyName}, {faction.Definition.FriendlyName})";
|
|
}
|
|
else if (pl.Finance != null)
|
|
{
|
|
text =
|
|
$"(The Finance Minister of {pl.Finance.Definition.FriendlyName}, {faction.Definition.FriendlyName})";
|
|
}
|
|
else
|
|
{
|
|
ascii = true;
|
|
|
|
var title = MerchantTitles.GetInfo(pl.MerchantTitle)?.Title?.String ?? pl.Rank.Title.String;
|
|
text = $"({title}, {faction.Definition.FriendlyName})";
|
|
}
|
|
|
|
var hue = Faction.Find(from) == faction ? 98 : 38;
|
|
|
|
PrivateOverheadMessage(MessageType.Label, hue, ascii, text, from.NetState);
|
|
}
|
|
}
|
|
|
|
base.OnSingleClick(from);
|
|
}
|
|
|
|
protected override bool OnMove(Direction d)
|
|
{
|
|
if (!Core.SE)
|
|
{
|
|
return base.OnMove(d);
|
|
}
|
|
|
|
if (AccessLevel != AccessLevel.Player)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (Hidden && DesignContext.Find(this) == null) // Hidden & NOT customizing a house
|
|
{
|
|
if (!Mounted && Skills.Stealth.Value >= 25.0)
|
|
{
|
|
var running = (d & Direction.Running) != 0;
|
|
|
|
if (running)
|
|
{
|
|
if ((AllowedStealthSteps -= 2) <= 0)
|
|
{
|
|
RevealingAction();
|
|
}
|
|
}
|
|
else if (AllowedStealthSteps-- <= 0)
|
|
{
|
|
Stealth.OnUse(this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RevealingAction();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void AddFollower(Mobile m)
|
|
{
|
|
_allFollowers ??= new HashSet<Mobile>();
|
|
_allFollowers.Add(m);
|
|
}
|
|
|
|
public void AddStabled(Mobile m)
|
|
{
|
|
Stabled ??= new HashSet<Mobile>();
|
|
Stabled.Add(m);
|
|
}
|
|
|
|
public bool RemoveStabled(Mobile m)
|
|
{
|
|
if (Stabled?.Remove(m) == true)
|
|
{
|
|
if (Stabled.Count == 0)
|
|
{
|
|
Stabled = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool RemoveFollower(Mobile m)
|
|
{
|
|
if (_allFollowers?.Remove(m) == true)
|
|
{
|
|
if (_allFollowers.Count == 0)
|
|
{
|
|
_allFollowers = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void AutoStablePets()
|
|
{
|
|
var allFollowers = _allFollowers;
|
|
|
|
if (!Core.SE || !(allFollowers?.Count > 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var follower in allFollowers)
|
|
{
|
|
if (follower is not BaseCreature pet || pet.ControlMaster == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pet.Summoned)
|
|
{
|
|
if (pet.Map != Map)
|
|
{
|
|
pet.PlaySound(pet.GetAngerSound());
|
|
Timer.StartTimer(pet.Delete);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if ((pet as IMount)?.Rider != null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pet is PackLlama or PackHorse or Beetle && pet.Backpack?.Items.Count > 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pet is BaseEscortable)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pet.ControlTarget = null;
|
|
pet.ControlOrder = OrderType.Stay;
|
|
pet.Internalize();
|
|
|
|
pet.SetControlMaster(null);
|
|
pet.SummonMaster = null;
|
|
|
|
pet.IsStabled = true;
|
|
pet.StabledBy = this;
|
|
|
|
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully happy
|
|
|
|
Stabled ??= new HashSet<Mobile>();
|
|
Stabled.Add(pet);
|
|
|
|
AutoStabled ??= new HashSet<Mobile>();
|
|
AutoStabled.Add(pet);
|
|
}
|
|
}
|
|
|
|
public void ClaimAutoStabledPets()
|
|
{
|
|
if (!Core.SE || !(AutoStabled?.Count > 0))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Alive)
|
|
{
|
|
// Your pet was unable to join you while you are a ghost. Please re-login once you have ressurected to claim your pets.
|
|
SendLocalizedMessage(1076251);
|
|
return;
|
|
}
|
|
|
|
foreach (var stabled in AutoStabled)
|
|
{
|
|
if (stabled is not BaseCreature pet)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (pet.Deleted)
|
|
{
|
|
pet.IsStabled = false;
|
|
pet.StabledBy = null;
|
|
|
|
Stabled?.Remove(pet);
|
|
continue;
|
|
}
|
|
|
|
if (Followers + pet.ControlSlots <= FollowersMax)
|
|
{
|
|
pet.SetControlMaster(this);
|
|
|
|
if (pet.Summoned)
|
|
{
|
|
pet.SummonMaster = this;
|
|
}
|
|
|
|
pet.ControlTarget = this;
|
|
pet.ControlOrder = OrderType.Follow;
|
|
|
|
pet.MoveToWorld(Location, Map);
|
|
|
|
pet.IsStabled = false;
|
|
pet.StabledBy = null;
|
|
|
|
pet.Loyalty = BaseCreature.MaxLoyalty; // Wonderfully Happy
|
|
|
|
Stabled?.Remove(pet);
|
|
}
|
|
else
|
|
{
|
|
// ~1_NAME~ remained in the stables because you have too many followers.
|
|
SendLocalizedMessage(1049612, pet.Name);
|
|
}
|
|
}
|
|
|
|
AutoStabled = null;
|
|
}
|
|
|
|
public void RecoverAmmo()
|
|
{
|
|
if (!Core.SE || !Alive || RecoverableAmmo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var kvp in RecoverableAmmo)
|
|
{
|
|
if (kvp.Value > 0)
|
|
{
|
|
Item ammo = null;
|
|
|
|
try
|
|
{
|
|
ammo = kvp.Key.CreateInstance<Item>();
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
|
|
if (ammo == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ammo.Amount = kvp.Value;
|
|
|
|
var name = ammo.Name ?? ammo switch
|
|
{
|
|
Arrow _ => $"arrow{(ammo.Amount != 1 ? "s" : "")}",
|
|
Bolt _ => $"bolt{(ammo.Amount != 1 ? "s" : "")}",
|
|
_ => $"#{ammo.LabelNumber}"
|
|
};
|
|
|
|
PlaceInBackpack(ammo);
|
|
SendLocalizedMessage(1073504, $"{ammo.Amount}\t{name}"); // You recover ~1_NUM~ ~2_AMMO~.
|
|
}
|
|
}
|
|
|
|
RecoverableAmmo.Clear();
|
|
}
|
|
|
|
private static int GetInsuranceCost(Item item) => 600;
|
|
|
|
private void ToggleItemInsurance()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback);
|
|
SendLocalizedMessage(1060868); // Target the item you wish to toggle insurance status on <ESC> to cancel
|
|
}
|
|
|
|
private bool CanInsure(Item item)
|
|
{
|
|
if (item is Container && item is not BaseQuiver || item is BagOfSending or KeyRing or PotionKeg or Sigil)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (item.Stackable)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (item.LootType == LootType.Cursed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (item.ItemID == 0x204E) // death shroud
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (item.Layer == Layer.Mount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return item.LootType != LootType.Blessed && item.LootType != LootType.Newbied && item.BlessedFor != this;
|
|
}
|
|
|
|
private void ToggleItemInsurance_Callback(Mobile from, object obj)
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ToggleItemInsurance_Callback(from, obj as Item, true);
|
|
}
|
|
|
|
private void ToggleItemInsurance_Callback(Mobile from, Item item, bool target)
|
|
{
|
|
if (item?.IsChildOf(this) != true)
|
|
{
|
|
if (target)
|
|
{
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback);
|
|
}
|
|
|
|
SendLocalizedMessage(
|
|
1060871,
|
|
"",
|
|
0x23
|
|
); // You can only insure items that you have equipped or that are in your backpack
|
|
}
|
|
else if (item.Insured)
|
|
{
|
|
item.Insured = false;
|
|
|
|
SendLocalizedMessage(1060874, "", 0x35); // You cancel the insurance on the item
|
|
|
|
if (target)
|
|
{
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback);
|
|
SendLocalizedMessage(
|
|
1060868,
|
|
"",
|
|
0x23
|
|
); // Target the item you wish to toggle insurance status on <ESC> to cancel
|
|
}
|
|
}
|
|
else if (!CanInsure(item))
|
|
{
|
|
if (target)
|
|
{
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback);
|
|
}
|
|
|
|
SendLocalizedMessage(1060869, "", 0x23); // You cannot insure that
|
|
}
|
|
else
|
|
{
|
|
if (!item.PaidInsurance)
|
|
{
|
|
var cost = GetInsuranceCost(item);
|
|
|
|
if (Banker.Withdraw(from, cost))
|
|
{
|
|
SendLocalizedMessage(
|
|
1060398,
|
|
cost.ToString()
|
|
); // ~1_AMOUNT~ gold has been withdrawn from your bank box.
|
|
item.PaidInsurance = true;
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(1061079, "", 0x23); // You lack the funds to purchase the insurance
|
|
return;
|
|
}
|
|
}
|
|
|
|
item.Insured = true;
|
|
|
|
SendLocalizedMessage(1060873, "", 0x23); // You have insured the item
|
|
|
|
if (target)
|
|
{
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleItemInsurance_Callback);
|
|
SendLocalizedMessage(
|
|
1060868,
|
|
"",
|
|
0x23
|
|
); // Target the item you wish to toggle insurance status on <ESC> to cancel
|
|
}
|
|
}
|
|
}
|
|
|
|
private void AutoRenewInventoryInsurance()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
SendLocalizedMessage(
|
|
1060881,
|
|
"",
|
|
0x23
|
|
); // You have selected to automatically reinsure all insured items upon death
|
|
AutoRenewInsurance = true;
|
|
}
|
|
|
|
private void CancelRenewInventoryInsurance()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Core.SE && NetState is { } ns)
|
|
{
|
|
if (!ns.HasGump<CancelRenewInventoryInsuranceGump>())
|
|
{
|
|
ns.SendGump(new CancelRenewInventoryInsuranceGump(this, null));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendLocalizedMessage(
|
|
1061075,
|
|
"",
|
|
0x23
|
|
); // You have cancelled automatically reinsuring all insured items upon death
|
|
AutoRenewInsurance = false;
|
|
}
|
|
}
|
|
|
|
private void OpenItemInsuranceMenu()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var queue = PooledRefQueue<Item>.Create(128);
|
|
|
|
foreach (var item in Items)
|
|
{
|
|
if (DisplayInItemInsuranceGump(item))
|
|
{
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
|
|
var pack = Backpack;
|
|
|
|
if (pack != null)
|
|
{
|
|
foreach (var item in pack.FindItems())
|
|
{
|
|
if (DisplayInItemInsuranceGump(item))
|
|
{
|
|
queue.Enqueue(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Investigate item sorting
|
|
|
|
if (NetState is { } ns)
|
|
{
|
|
ns.CloseGump<ItemInsuranceMenuGump>();
|
|
|
|
if (queue.Count == 0)
|
|
{
|
|
SendLocalizedMessage(1114915, "", 0x35); // None of your current items meet the requirements for insurance.
|
|
}
|
|
else
|
|
{
|
|
ns.SendGump(new ItemInsuranceMenuGump(this, queue.ToArray()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool DisplayInItemInsuranceGump(Item item) => (item.Visible || AccessLevel >= AccessLevel.GameMaster) &&
|
|
(item.Insured || CanInsure(item));
|
|
|
|
private void ToggleQuestItem()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ToggleQuestItemTarget();
|
|
}
|
|
|
|
private void ToggleQuestItemTarget()
|
|
{
|
|
if (NetState != null)
|
|
{
|
|
BaseQuestGump.CloseOtherGumps(this);
|
|
var gumps = this.GetGumps();
|
|
gumps.Close<QuestLogDetailedGump>();
|
|
gumps.Close<QuestLogGump>();
|
|
gumps.Close<QuestOfferGump>();
|
|
}
|
|
|
|
BeginTarget(-1, false, TargetFlags.None, ToggleQuestItem_Callback);
|
|
SendLocalizedMessage(1072352); // Target the item you wish to toggle Quest Item status on <ESC> to cancel
|
|
}
|
|
|
|
private void ToggleQuestItem_Callback(Mobile from, object obj)
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (obj is not Item item)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (from.Backpack == null || item.Parent != from.Backpack)
|
|
{
|
|
// An item must be in your backpack (and not in a container within) to be toggled as a quest item.
|
|
SendLocalizedMessage(1074769);
|
|
}
|
|
else if (item.QuestItem)
|
|
{
|
|
item.QuestItem = false;
|
|
SendLocalizedMessage(1072354); // You remove Quest Item status from the item
|
|
}
|
|
else if (MLQuestSystem.MarkQuestItem(this, item))
|
|
{
|
|
SendLocalizedMessage(1072353); // You set the item to Quest Item status
|
|
}
|
|
else
|
|
{
|
|
// That item does not match any of your quest criteria
|
|
SendLocalizedMessage(1072355, "", 0x23);
|
|
}
|
|
|
|
ToggleQuestItemTarget();
|
|
}
|
|
|
|
public bool CanUseStuckMenu()
|
|
{
|
|
if (m_StuckMenuUses == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (var i = 0; i < m_StuckMenuUses.Length; ++i)
|
|
{
|
|
if (Core.Now - m_StuckMenuUses[i] > TimeSpan.FromDays(1.0))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void UsedStuckMenu()
|
|
{
|
|
if (m_StuckMenuUses == null)
|
|
{
|
|
m_StuckMenuUses = new DateTime[2];
|
|
}
|
|
|
|
for (var i = 0; i < m_StuckMenuUses.Length; ++i)
|
|
{
|
|
if (Core.Now - m_StuckMenuUses[i] > TimeSpan.FromDays(1.0))
|
|
{
|
|
m_StuckMenuUses[i] = Core.Now;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override ApplyPoisonResult ApplyPoison(Mobile from, Poison poison)
|
|
{
|
|
if (!Alive)
|
|
{
|
|
return ApplyPoisonResult.Immune;
|
|
}
|
|
|
|
if (EvilOmenSpell.EndEffect(this))
|
|
{
|
|
poison = PoisonImpl.IncreaseLevel(poison);
|
|
}
|
|
|
|
var result = base.ApplyPoison(from, poison);
|
|
|
|
if (from != null && result == ApplyPoisonResult.Poisoned && PoisonTimer is PoisonImpl.PoisonTimer timer)
|
|
{
|
|
timer.From = from;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public override bool CheckPoisonImmunity(Mobile from, Poison poison) =>
|
|
Young && (DuelContext?.Started != true || DuelContext.Finished) || base.CheckPoisonImmunity(from, poison);
|
|
|
|
public override void OnPoisonImmunity(Mobile from, Poison poison)
|
|
{
|
|
if (Young && (DuelContext?.Started != true || DuelContext.Finished))
|
|
{
|
|
// You would have been poisoned, were you not new to the land of Britannia.
|
|
// Be careful in the future.
|
|
SendLocalizedMessage(502808);
|
|
}
|
|
else
|
|
{
|
|
base.OnPoisonImmunity(from, poison);
|
|
}
|
|
}
|
|
|
|
public override void OnKillsChange(int oldValue)
|
|
{
|
|
if (Young && Kills > oldValue)
|
|
{
|
|
((Account)Account)?.RemoveYoungStatus(0);
|
|
}
|
|
}
|
|
|
|
public override void OnGenderChanged(bool oldFemale)
|
|
{
|
|
}
|
|
|
|
public override void OnGuildChange(BaseGuild oldGuild)
|
|
{
|
|
}
|
|
|
|
public override void OnGuildTitleChange(string oldTitle)
|
|
{
|
|
}
|
|
|
|
public override void OnKarmaChange(int oldValue)
|
|
{
|
|
}
|
|
|
|
public override void OnFameChange(int oldValue)
|
|
{
|
|
}
|
|
|
|
public override void OnSkillChange(SkillName skill, double oldBase)
|
|
{
|
|
if (Young && SkillsTotal >= 4500)
|
|
{
|
|
// You have successfully obtained a respectable skill level, and have outgrown your status as a young player!
|
|
((Account)Account)?.RemoveYoungStatus(1019036);
|
|
}
|
|
|
|
if (MLQuestSystem.Enabled)
|
|
{
|
|
MLQuestSystem.HandleSkillGain(this, skill);
|
|
}
|
|
}
|
|
|
|
public override void OnAccessLevelChanged(AccessLevel oldLevel)
|
|
{
|
|
IgnoreMobiles = AccessLevel != AccessLevel.Player;
|
|
}
|
|
|
|
public override void OnRawStatChange(StatType stat, int oldValue)
|
|
{
|
|
}
|
|
|
|
public override void OnDelete()
|
|
{
|
|
ReceivedHonorContext?.Cancel();
|
|
SentHonorContext?.Cancel();
|
|
|
|
if (Stabled != null)
|
|
{
|
|
foreach (var stabled in Stabled)
|
|
{
|
|
stabled.Delete();
|
|
}
|
|
|
|
Stabled = null;
|
|
}
|
|
}
|
|
|
|
public override int ComputeMovementSpeed(Direction dir, bool checkTurning = true)
|
|
{
|
|
if (checkTurning && (dir & Direction.Mask) != (Direction & Direction.Mask))
|
|
{
|
|
return CalcMoves.TurnDelay; // We are NOT actually moving (just a direction change)
|
|
}
|
|
|
|
var context = TransformationSpellHelper.GetContext(this);
|
|
|
|
if (context?.Type == typeof(ReaperFormSpell))
|
|
{
|
|
return CalcMoves.WalkFootDelay;
|
|
}
|
|
|
|
var running = (dir & Direction.Running) != 0;
|
|
|
|
var onHorse = Mount != null;
|
|
|
|
if (onHorse || AnimalForm.GetContext(this)?.SpeedBoost == true)
|
|
{
|
|
return running ? CalcMoves.RunMountDelay : CalcMoves.WalkMountDelay;
|
|
}
|
|
|
|
return running ? CalcMoves.RunFootDelay : CalcMoves.WalkFootDelay;
|
|
}
|
|
|
|
private void DeltaEnemies(Type oldType, Type newType)
|
|
{
|
|
foreach (var m in GetMobilesInRange(18))
|
|
{
|
|
var t = m.GetType();
|
|
|
|
if (t == oldType || t == newType)
|
|
{
|
|
m.NetState.SendMobileMoving(this, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetHairMods(int hairID, int beardID)
|
|
{
|
|
if (hairID == -1)
|
|
{
|
|
InternalRestoreHair(true, ref m_HairModID, ref m_HairModHue);
|
|
}
|
|
else if (hairID != -2)
|
|
{
|
|
InternalChangeHair(true, hairID, ref m_HairModID, ref m_HairModHue);
|
|
}
|
|
|
|
if (beardID == -1)
|
|
{
|
|
InternalRestoreHair(false, ref m_BeardModID, ref m_BeardModHue);
|
|
}
|
|
else if (beardID != -2)
|
|
{
|
|
InternalChangeHair(false, beardID, ref m_BeardModID, ref m_BeardModHue);
|
|
}
|
|
}
|
|
|
|
private void CreateHair(bool hair, int id, int hue)
|
|
{
|
|
if (hair)
|
|
{
|
|
// TODO Verification?
|
|
HairItemID = id;
|
|
HairHue = hue;
|
|
}
|
|
else
|
|
{
|
|
FacialHairItemID = id;
|
|
FacialHairHue = hue;
|
|
}
|
|
}
|
|
|
|
private void InternalRestoreHair(bool hair, ref int id, ref int hue)
|
|
{
|
|
if (id == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (hair)
|
|
{
|
|
HairItemID = 0;
|
|
}
|
|
else
|
|
{
|
|
FacialHairItemID = 0;
|
|
}
|
|
|
|
// if (id != 0)
|
|
CreateHair(hair, id, hue);
|
|
|
|
id = -1;
|
|
hue = 0;
|
|
}
|
|
|
|
private void InternalChangeHair(bool hair, int id, ref int storeID, ref int storeHue)
|
|
{
|
|
if (storeID == -1)
|
|
{
|
|
storeID = hair ? HairItemID : FacialHairItemID;
|
|
storeHue = hair ? HairHue : FacialHairHue;
|
|
}
|
|
|
|
CreateHair(hair, id, 0);
|
|
}
|
|
|
|
public override string ApplyNameSuffix(string suffix)
|
|
{
|
|
if (Young)
|
|
{
|
|
suffix = suffix.Length == 0 ? "(Young)" : $"{suffix} (Young)";
|
|
}
|
|
|
|
if (EthicPlayer != null)
|
|
{
|
|
if (suffix.Length == 0)
|
|
{
|
|
suffix = EthicPlayer.Ethic.Definition.Adjunct.String;
|
|
}
|
|
else
|
|
{
|
|
suffix = $"{suffix} {EthicPlayer.Ethic.Definition.Adjunct.String}";
|
|
}
|
|
}
|
|
|
|
if (Core.ML && Map == Faction.Facet)
|
|
{
|
|
var faction = Faction.Find(this);
|
|
|
|
if (faction != null)
|
|
{
|
|
var adjunct = $"[{faction.Definition.Abbreviation}]";
|
|
suffix = suffix.Length == 0 ? adjunct : $"{suffix} {adjunct}";
|
|
}
|
|
}
|
|
|
|
return base.ApplyNameSuffix(suffix);
|
|
}
|
|
|
|
public override TimeSpan GetLogoutDelay()
|
|
{
|
|
if (Young || BedrollLogout || TestCenter.Enabled)
|
|
{
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
return base.GetLogoutDelay();
|
|
}
|
|
|
|
public bool CheckYoungProtection(Mobile from)
|
|
{
|
|
if (!Young)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Region is BaseRegion region && !region.YoungProtected)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (from is BaseCreature creature && creature.IgnoreYoungProtection)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Quest?.IgnoreYoungProtection(from) == true)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Core.Now - m_LastYoungMessage > TimeSpan.FromMinutes(1.0))
|
|
{
|
|
m_LastYoungMessage = Core.Now;
|
|
// A monster looks at you menacingly but does not attack.
|
|
// You would be under attack now if not for your status as a new citizen of Britannia.
|
|
SendLocalizedMessage(1019067);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public bool CheckYoungHealTime()
|
|
{
|
|
if (Core.Now - m_LastYoungHeal > TimeSpan.FromMinutes(5.0))
|
|
{
|
|
m_LastYoungHeal = Core.Now;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool YoungDeathTeleport()
|
|
{
|
|
if (Region.IsPartOf<JailRegion>()
|
|
|| Region.IsPartOf("Samurai start location")
|
|
|| Region.IsPartOf("Ninja start location")
|
|
|| Region.IsPartOf("Ninja cave"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Point3D loc;
|
|
Map map;
|
|
|
|
var dungeon = Region.GetRegion<DungeonRegion>();
|
|
if (dungeon != null && dungeon.Entrance != Point3D.Zero)
|
|
{
|
|
loc = dungeon.Entrance;
|
|
map = dungeon.Map;
|
|
}
|
|
else
|
|
{
|
|
loc = Location;
|
|
map = Map;
|
|
}
|
|
|
|
Point3D[] list;
|
|
|
|
if (map == Map.Trammel)
|
|
{
|
|
list = m_TrammelDeathDestinations;
|
|
}
|
|
else if (map == Map.Ilshenar)
|
|
{
|
|
list = m_IlshenarDeathDestinations;
|
|
}
|
|
else if (map == Map.Malas)
|
|
{
|
|
list = m_MalasDeathDestinations;
|
|
}
|
|
else if (map == Map.Tokuno)
|
|
{
|
|
list = m_TokunoDeathDestinations;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var dest = Point3D.Zero;
|
|
var sqDistance = int.MaxValue;
|
|
|
|
for (var i = 0; i < list.Length; i++)
|
|
{
|
|
var curDest = list[i];
|
|
|
|
var width = loc.X - curDest.X;
|
|
var height = loc.Y - curDest.Y;
|
|
var curSqDistance = width * width + height * height;
|
|
|
|
if (curSqDistance < sqDistance)
|
|
{
|
|
dest = curDest;
|
|
sqDistance = curSqDistance;
|
|
}
|
|
}
|
|
|
|
MoveToWorld(dest, map);
|
|
return true;
|
|
}
|
|
|
|
private void SendYoungDeathNotice()
|
|
{
|
|
if (NetState is { } ns)
|
|
{
|
|
ns.SendGump(new YoungDeathNoticeGump());
|
|
}
|
|
}
|
|
|
|
public override void OnSpeech(SpeechEventArgs e)
|
|
{
|
|
if (SpeechLog.Enabled && NetState != null)
|
|
{
|
|
if (SpeechLog == null)
|
|
{
|
|
SpeechLog = new SpeechLog();
|
|
}
|
|
|
|
SpeechLog.Add(e.Mobile, e.Speech);
|
|
}
|
|
}
|
|
|
|
private void ToggleChampionTitleDisplay()
|
|
{
|
|
if (!CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (DisplayChampionTitle)
|
|
{
|
|
// You have chosen to hide your monster kill title.
|
|
SendLocalizedMessage(1062419, "", 0x23);
|
|
}
|
|
else
|
|
{
|
|
// You have chosen to display your monster kill title.
|
|
SendLocalizedMessage(1062418, "", 0x23);
|
|
}
|
|
|
|
DisplayChampionTitle = !DisplayChampionTitle;
|
|
InvalidateProperties();
|
|
}
|
|
|
|
public bool HasRecipe(Recipe r) => r != null && HasRecipe(r.ID);
|
|
|
|
public bool HasRecipe(int recipeID) => _acquiredRecipes?.Contains(recipeID) == true;
|
|
|
|
public void AcquireRecipe(Recipe r)
|
|
{
|
|
if (r != null)
|
|
{
|
|
AcquireRecipe(r.ID);
|
|
}
|
|
}
|
|
|
|
public void AcquireRecipe(int recipeID)
|
|
{
|
|
_acquiredRecipes ??= new HashSet<int>();
|
|
_acquiredRecipes.Add(recipeID);
|
|
}
|
|
|
|
public void RemoveRecipe(int recipeID) => _acquiredRecipes?.Remove(recipeID);
|
|
|
|
public void ResetRecipes() => _acquiredRecipes = null;
|
|
|
|
public void SendAddBuffPacket(BuffInfo buffInfo)
|
|
{
|
|
if (buffInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
NetState.SendAddBuffPacket(
|
|
Serial,
|
|
buffInfo.ID,
|
|
buffInfo.TitleCliloc,
|
|
buffInfo.SecondaryCliloc,
|
|
buffInfo.Args,
|
|
buffInfo.TimeStart == 0
|
|
? 0
|
|
: Math.Max(buffInfo.TimeStart + (long)buffInfo.TimeLength.TotalMilliseconds - Core.TickCount, 0)
|
|
);
|
|
}
|
|
|
|
public void ResendBuffs()
|
|
{
|
|
if (BuffInfo.Enabled && m_BuffTable != null && NetState?.BuffIcon == true)
|
|
{
|
|
foreach (var info in m_BuffTable.Values)
|
|
{
|
|
SendAddBuffPacket(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void AddBuff(BuffInfo b)
|
|
{
|
|
if (!BuffInfo.Enabled || b == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RemoveBuff(b); // Check & subsequently remove the old one.
|
|
|
|
m_BuffTable ??= new Dictionary<BuffIcon, BuffInfo>();
|
|
|
|
m_BuffTable.Add(b.ID, b);
|
|
|
|
if (NetState?.BuffIcon == true)
|
|
{
|
|
// Synchronize the buff icon as close to _on the second_ as we can.
|
|
var msecs = b.TimeLength.Milliseconds;
|
|
if (msecs >= 8)
|
|
{
|
|
Timer.DelayCall(TimeSpan.FromMilliseconds(msecs), (buffInfo, pm) =>
|
|
{
|
|
// They are still online, we still have the buff icon in the table, and it is the same buff icon
|
|
if (pm.NetState != null && pm.m_BuffTable?.GetValueOrDefault(buffInfo.ID) == buffInfo)
|
|
{
|
|
pm.SendAddBuffPacket(buffInfo);
|
|
}
|
|
}, b, this);
|
|
}
|
|
else
|
|
{
|
|
SendAddBuffPacket(b);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveBuff(BuffInfo b)
|
|
{
|
|
if (b == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RemoveBuff(b.ID);
|
|
}
|
|
|
|
public void RemoveBuff(BuffIcon b)
|
|
{
|
|
if (m_BuffTable?.Remove(b) != true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NetState?.BuffIcon == true)
|
|
{
|
|
NetState.SendRemoveBuffPacket(Serial, b);
|
|
}
|
|
|
|
if (m_BuffTable.Count <= 0)
|
|
{
|
|
m_BuffTable = null;
|
|
}
|
|
}
|
|
|
|
private class MountBlock
|
|
{
|
|
private TimerExecutionToken _timerToken;
|
|
private BlockMountType _type;
|
|
|
|
public MountBlock(TimeSpan duration, BlockMountType type, Mobile mobile)
|
|
{
|
|
_type = type;
|
|
|
|
if (duration < TimeSpan.MaxValue)
|
|
{
|
|
Timer.StartTimer(duration, () => RemoveBlock(mobile), out _timerToken);
|
|
}
|
|
}
|
|
|
|
public DateTime Expiration => _timerToken.Next;
|
|
|
|
public BlockMountType MountBlockReason => CheckBlock() ? _type : BlockMountType.None;
|
|
|
|
public bool CheckBlock() => _timerToken.Next == DateTime.MinValue || _timerToken.Running;
|
|
|
|
public void RemoveBlock(Mobile mobile)
|
|
{
|
|
if (mobile is PlayerMobile pm)
|
|
{
|
|
pm._mountBlock = null;
|
|
}
|
|
|
|
_timerToken.Cancel();
|
|
}
|
|
}
|
|
|
|
private delegate void ContextCallback();
|
|
|
|
private class CallbackEntry : ContextMenuEntry
|
|
{
|
|
private readonly ContextCallback m_Callback;
|
|
|
|
public CallbackEntry(int number, ContextCallback callback) : this(number, -1, callback)
|
|
{
|
|
}
|
|
|
|
public CallbackEntry(int number, int range, ContextCallback callback) : base(number, range) =>
|
|
m_Callback = callback;
|
|
|
|
public override void OnClick(Mobile from, IEntity target)
|
|
{
|
|
m_Callback?.Invoke();
|
|
}
|
|
}
|
|
|
|
private class CancelRenewInventoryInsuranceGump : Gump
|
|
{
|
|
private readonly ItemInsuranceMenuGump m_InsuranceGump;
|
|
private readonly PlayerMobile m_Player;
|
|
|
|
public CancelRenewInventoryInsuranceGump(PlayerMobile player, ItemInsuranceMenuGump insuranceGump) : base(
|
|
250,
|
|
200
|
|
)
|
|
{
|
|
m_Player = player;
|
|
m_InsuranceGump = insuranceGump;
|
|
|
|
AddBackground(0, 0, 240, 142, 0x13BE);
|
|
AddImageTiled(6, 6, 228, 100, 0xA40);
|
|
AddImageTiled(6, 116, 228, 20, 0xA40);
|
|
AddAlphaRegion(6, 6, 228, 142);
|
|
|
|
AddHtmlLocalized(
|
|
8,
|
|
8,
|
|
228,
|
|
100,
|
|
1071021,
|
|
0x7FFF
|
|
); // You are about to disable inventory insurance auto-renewal.
|
|
|
|
AddButton(6, 116, 0xFB1, 0xFB2, 0);
|
|
AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL
|
|
|
|
AddButton(114, 116, 0xFA5, 0xFA7, 1);
|
|
AddHtmlLocalized(148, 118, 450, 20, 1071022, 0x7FFF); // DISABLE IT!
|
|
}
|
|
|
|
public override void OnResponse(NetState sender, in RelayInfo info)
|
|
{
|
|
if (!m_Player.CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (info.ButtonID == 1)
|
|
{
|
|
// You have cancelled automatically reinsuring all insured items upon death
|
|
m_Player.SendLocalizedMessage(1061075, "", 0x23);
|
|
m_Player.AutoRenewInsurance = false;
|
|
}
|
|
else
|
|
{
|
|
m_Player.SendLocalizedMessage(1042021); // Cancelled.
|
|
}
|
|
|
|
if (m_InsuranceGump != null)
|
|
{
|
|
m_Player.SendGump(m_InsuranceGump.NewInstance());
|
|
}
|
|
}
|
|
}
|
|
|
|
private class ItemInsuranceMenuGump : Gump
|
|
{
|
|
private readonly PlayerMobile m_From;
|
|
private readonly bool[] m_Insure;
|
|
private readonly Item[] m_Items;
|
|
private readonly int m_Page;
|
|
|
|
public ItemInsuranceMenuGump(PlayerMobile from, Item[] items, bool[] insure = null, int page = 0)
|
|
: base(25, 50)
|
|
{
|
|
m_From = from;
|
|
m_Items = items;
|
|
|
|
if (insure == null)
|
|
{
|
|
insure = new bool[items.Length];
|
|
|
|
for (var i = 0; i < items.Length; ++i)
|
|
{
|
|
insure[i] = items[i].Insured;
|
|
}
|
|
}
|
|
|
|
m_Insure = insure;
|
|
m_Page = page;
|
|
|
|
AddPage(0);
|
|
|
|
AddBackground(0, 0, 520, 510, 0x13BE);
|
|
AddImageTiled(10, 10, 500, 30, 0xA40);
|
|
AddImageTiled(10, 50, 500, 355, 0xA40);
|
|
AddImageTiled(10, 415, 500, 80, 0xA40);
|
|
AddAlphaRegion(10, 10, 500, 485);
|
|
|
|
AddButton(15, 470, 0xFB1, 0xFB2, 0);
|
|
AddHtmlLocalized(50, 472, 80, 20, 1011012, 0x7FFF); // CANCEL
|
|
|
|
if (from.AutoRenewInsurance)
|
|
{
|
|
AddButton(360, 10, 9723, 9724, 1);
|
|
}
|
|
else
|
|
{
|
|
AddButton(360, 10, 9720, 9722, 1);
|
|
}
|
|
|
|
AddHtmlLocalized(395, 14, 105, 20, 1114122, 0x7FFF); // AUTO REINSURE
|
|
|
|
AddButton(395, 470, 0xFA5, 0xFA6, 2);
|
|
AddHtmlLocalized(430, 472, 50, 20, 1006044, 0x7FFF); // OK
|
|
|
|
AddHtmlLocalized(10, 14, 150, 20, 1114121, 0x7FFF); // <CENTER>ITEM INSURANCE MENU</CENTER>
|
|
|
|
AddHtmlLocalized(45, 54, 70, 20, 1062214, 0x7FFF); // Item
|
|
AddHtmlLocalized(250, 54, 70, 20, 1061038, 0x7FFF); // Cost
|
|
AddHtmlLocalized(400, 54, 70, 20, 1114311, 0x7FFF); // Insured
|
|
|
|
var balance = Banker.GetBalance(from);
|
|
var cost = 0;
|
|
|
|
for (var i = 0; i < items.Length; ++i)
|
|
{
|
|
if (insure[i])
|
|
{
|
|
cost += GetInsuranceCost(items[i]);
|
|
}
|
|
}
|
|
|
|
AddHtmlLocalized(15, 420, 300, 20, 1114310, 0x7FFF); // GOLD AVAILABLE:
|
|
AddLabel(215, 420, 0x481, balance.ToString());
|
|
AddHtmlLocalized(15, 435, 300, 20, 1114123, 0x7FFF); // TOTAL COST OF INSURANCE:
|
|
AddLabel(215, 435, 0x481, cost.ToString());
|
|
|
|
if (cost != 0)
|
|
{
|
|
AddHtmlLocalized(15, 450, 300, 20, 1114125, 0x7FFF); // NUMBER OF DEATHS PAYABLE:
|
|
AddLabel(215, 450, 0x481, (balance / cost).ToString());
|
|
}
|
|
|
|
for (int i = page * 4, y = 72; i < (page + 1) * 4 && i < items.Length; ++i, y += 75)
|
|
{
|
|
var item = items[i];
|
|
var b = ItemBounds.Table[item.ItemID];
|
|
|
|
AddImageTiledButton(
|
|
40,
|
|
y,
|
|
0x918,
|
|
0x918,
|
|
0,
|
|
GumpButtonType.Page,
|
|
0,
|
|
item.ItemID,
|
|
item.Hue,
|
|
40 - b.Width / 2 - b.X,
|
|
30 - b.Height / 2 - b.Y
|
|
);
|
|
AddItemProperty(item.Serial);
|
|
|
|
if (insure[i])
|
|
{
|
|
AddButton(400, y, 9723, 9724, 100 + i);
|
|
AddLabel(250, y, 0x481, GetInsuranceCost(item).ToString());
|
|
}
|
|
else
|
|
{
|
|
AddButton(400, y, 9720, 9722, 100 + i);
|
|
AddLabel(250, y, 0x66C, GetInsuranceCost(item).ToString());
|
|
}
|
|
}
|
|
|
|
if (page >= 1)
|
|
{
|
|
AddButton(15, 380, 0xFAE, 0xFAF, 3);
|
|
AddHtmlLocalized(50, 380, 450, 20, 1044044, 0x7FFF); // PREV PAGE
|
|
}
|
|
|
|
if ((page + 1) * 4 < items.Length)
|
|
{
|
|
AddButton(400, 380, 0xFA5, 0xFA7, 4);
|
|
AddHtmlLocalized(435, 380, 70, 20, 1044045, 0x7FFF); // NEXT PAGE
|
|
}
|
|
}
|
|
|
|
public ItemInsuranceMenuGump NewInstance() => new(m_From, m_Items, m_Insure, m_Page);
|
|
|
|
public override void OnResponse(NetState sender, in RelayInfo info)
|
|
{
|
|
if (info.ButtonID == 0 || !m_From.CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (info.ButtonID)
|
|
{
|
|
case 1: // Auto Reinsure
|
|
{
|
|
if (m_From.AutoRenewInsurance)
|
|
{
|
|
if (!m_From.HasGump<CancelRenewInventoryInsuranceGump>())
|
|
{
|
|
m_From.SendGump(new CancelRenewInventoryInsuranceGump(m_From, this));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_From.AutoRenewInventoryInsurance();
|
|
m_From.SendGump(new ItemInsuranceMenuGump(m_From, m_Items, m_Insure, m_Page));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 2: // OK
|
|
{
|
|
m_From.SendGump(new ItemInsuranceMenuConfirmGump(m_From, m_Items, m_Insure, m_Page));
|
|
|
|
break;
|
|
}
|
|
case 3: // Prev
|
|
{
|
|
if (m_Page >= 1)
|
|
{
|
|
m_From.SendGump(new ItemInsuranceMenuGump(m_From, m_Items, m_Insure, m_Page - 1));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 4: // Next
|
|
{
|
|
if ((m_Page + 1) * 4 < m_Items.Length)
|
|
{
|
|
m_From.SendGump(new ItemInsuranceMenuGump(m_From, m_Items, m_Insure, m_Page + 1));
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
var idx = info.ButtonID - 100;
|
|
|
|
if (idx >= 0 && idx < m_Items.Length)
|
|
{
|
|
m_Insure[idx] = !m_Insure[idx];
|
|
}
|
|
|
|
m_From.SendGump(new ItemInsuranceMenuGump(m_From, m_Items, m_Insure, m_Page));
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class ItemInsuranceMenuConfirmGump : Gump
|
|
{
|
|
private readonly PlayerMobile m_From;
|
|
private readonly bool[] m_Insure;
|
|
private readonly Item[] m_Items;
|
|
private readonly int m_Page;
|
|
|
|
public ItemInsuranceMenuConfirmGump(PlayerMobile from, Item[] items, bool[] insure, int page)
|
|
: base(250, 200)
|
|
{
|
|
m_From = from;
|
|
m_Items = items;
|
|
m_Insure = insure;
|
|
m_Page = page;
|
|
|
|
AddBackground(0, 0, 240, 142, 0x13BE);
|
|
AddImageTiled(6, 6, 228, 100, 0xA40);
|
|
AddImageTiled(6, 116, 228, 20, 0xA40);
|
|
AddAlphaRegion(6, 6, 228, 142);
|
|
|
|
AddHtmlLocalized(8, 8, 228, 100, 1114300, 0x7FFF); // Do you wish to insure all newly selected items?
|
|
|
|
AddButton(6, 116, 0xFB1, 0xFB2, 0);
|
|
AddHtmlLocalized(40, 118, 450, 20, 1060051, 0x7FFF); // CANCEL
|
|
|
|
AddButton(114, 116, 0xFA5, 0xFA7, 1);
|
|
AddHtmlLocalized(148, 118, 450, 20, 1073996, 0x7FFF); // ACCEPT
|
|
}
|
|
|
|
public override void OnResponse(NetState sender, in RelayInfo info)
|
|
{
|
|
if (!m_From.CheckAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (info.ButtonID == 1)
|
|
{
|
|
for (var i = 0; i < m_Items.Length; ++i)
|
|
{
|
|
var item = m_Items[i];
|
|
|
|
if (item.Insured != m_Insure[i])
|
|
{
|
|
m_From.ToggleItemInsurance_Callback(m_From, item, false);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_From.SendLocalizedMessage(1042021); // Cancelled.
|
|
m_From.SendGump(new ItemInsuranceMenuGump(m_From, m_Items, m_Insure, m_Page));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|